Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e3e2d0b18 | |||
| 6d52ad13c5 | |||
| 7c406cfd1c | |||
| c5ccfb6ead | |||
| 5aa5393ada | |||
| 15d171f94e | |||
| 7751c8fce0 | |||
| 9d1ad833f9 | |||
| e6f7354ce7 | |||
| a2c3033f5a | |||
| 03cda07c9d | |||
| fb999b4ee8 | |||
| e3ebfcf721 | |||
| 33071deb53 | |||
| 9b3b8d4540 | |||
| 98bf124078 | |||
| abc73a6525 | |||
| 2276175354 | |||
| 7f24735677 | |||
| 4d1544864d | |||
| 4aadee7ca0 | |||
| 6aba1354d5 | |||
| 63b229143d | |||
| f861950c50 | |||
| 97d1694bfa | |||
| 2d28d9b409 | |||
| 560c41acbe | |||
| e3c33bf649 | |||
| 216db63551 | |||
| 05d8a6bf85 | |||
| 1e6c2b9598 | |||
| f006323f54 | |||
| f3bfe9bb9a | |||
| ffbab9682a | |||
| 123813dc90 | |||
| 7bc7468cf3 | |||
| 12addde548 | |||
| e54cca19fa | |||
| a67ec6f58e | |||
| f913591af0 | |||
| 38f7c131a2 | |||
| 7c0478d7f4 | |||
| b26aa8d53c | |||
| fb12a27d62 | |||
| 82c7b43f06 | |||
| 05880ed3b3 | |||
| 1b13fd6839 | |||
| 44fd5f767b | |||
| 9135033dfd | |||
| b1a61584b1 | |||
| b2a0ccfe02 | |||
| 590f6871af | |||
| 282a24b8fc | |||
| af1d34a762 | |||
| 69da810426 | |||
| 8f98050964 | |||
| 1c14c1ce9c | |||
| f2ccba3cd2 | |||
| 5ad8d61002 | |||
| f7e40023c4 | |||
| 975150420c | |||
| 9c3db8cc2b | |||
| 55b6272204 | |||
| 371eb9ece8 | |||
| e883a8e153 | |||
| f608f0bba0 | |||
| 4c73fa6d58 | |||
| d13cac69c6 | |||
| 13990b68d2 | |||
| 4068096fce | |||
| a20a926332 | |||
| 50ef6cd225 | |||
| dd7e81c557 | |||
| 58fd5f17cf | |||
| 13117843ec | |||
| 60b0b3c878 | |||
| 825a8d9fd9 | |||
| 58105be433 | |||
| 98db6d2445 | |||
| cd3d4e6451 | |||
| 1e3d1b9ed3 | |||
| a794cace54 | |||
| 6520a8dc9c | |||
| 8ccd60cb74 | |||
| c9d89540d3 | |||
| c2d33d2a1e | |||
| fe4fa9b9e6 | |||
| 1d23f5a1df | |||
| 349d490a65 | |||
| 11326d7cc1 | |||
| d2827d013b | |||
| f239574e41 | |||
| bc05aed51f | |||
| ff791d0a27 | |||
| 319e3b1eba | |||
| 12a24ec617 | |||
| 92c742987e | |||
| 4dabe656c9 | |||
| 03fff53260 | |||
| f65cb2ca06 | |||
| 36938aee41 | |||
| d82af6f9bd | |||
| 6b785c3404 | |||
| df1a271efa | |||
| bd9bd8590c | |||
| d186875ab7 | |||
| 3f7657c080 | |||
| a5f4c46066 | |||
| 596dc8a884 | |||
| 6c97ad8871 | |||
| 5b77dded66 | |||
| 73cf4d5b7e | |||
| 1991091444 | |||
| f69c74b09c | |||
| 118baf12df | |||
| fc410c9a8d | |||
| d873c86ef8 | |||
| 855a838599 | |||
| 354378e038 | |||
| 8b431f4da8 | |||
| 0a08e9f834 | |||
| a10950499b | |||
| bac2580be7 | |||
| d21deda218 | |||
| 6ad2a7af70 | |||
| 59d2c652e6 | |||
| 1c0c5f61c6 | |||
| 97d3b10e2f | |||
| d50695067e | |||
| 5bfd60176f | |||
| 9dd63f83da | |||
| 045f368c27 | |||
| a1e9ef00a1 | |||
| 11215b96ae | |||
| 1535338e0b | |||
| f6be2dd12e | |||
| 66c3f142a7 | |||
| e197a7081b | |||
| 875c660fb2 | |||
| f233f1c6b6 | |||
| 1c4a927e0d | |||
| ea6ceac2f2 | |||
| 99d3cc5c6d | |||
| 95855092fd | |||
| b1d3cfab1c | |||
| 4af00b560f | |||
| 1ccbbf14dc | |||
| 917c2aa734 | |||
| 17fe80bd9b | |||
| f0a7481d72 | |||
| 4b1654e3eb | |||
| 6304f4b263 | |||
| ba225aedc7 | |||
| e4e692abdd | |||
| b3c26a2af2 | |||
| bf0df2f625 | |||
| 7af80ae577 | |||
| 1a0c01c092 | |||
| 0e82b0442a | |||
| 11970cb514 | |||
| 928fc228d4 | |||
| 840a28e072 | |||
| 29283ce83b | |||
| 3fd2cf05bb | |||
| 1ac7bce6b4 | |||
| 4865072f55 | |||
| 7faa28a89d | |||
| f4b1b09362 | |||
| c97467da3c | |||
| cde8eb100e | |||
| 20520552c2 | |||
| e380cb888f | |||
| 24a3e7b0da | |||
| b6c16e1bdf |
@@ -3,6 +3,7 @@ dist/
|
|||||||
release/
|
release/
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
|
db/
|
||||||
sui
|
sui
|
||||||
web/html
|
web/html
|
||||||
main
|
main
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
buy_me_a_coffee: alireza7
|
github: alireza0
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "gomod"
|
- package-ecosystem: "gomod"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
@@ -6,12 +6,39 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
frontend-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
- name: Install dependencies and build frontend
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
- name: Upload frontend build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: frontend-dist
|
||||||
|
path: frontend/dist/
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: frontend-build
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
- name: Download frontend build artifact
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
name: frontend-dist
|
||||||
|
path: frontend_dist
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
@@ -23,31 +50,39 @@ jobs:
|
|||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=ref,event=tag
|
type=ref,event=tag
|
||||||
type=pep440,pattern={{version}}
|
type=pep440,pattern={{version}}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache
|
||||||
|
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-buildx-
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
file: Dockerfile.frontend-artifact
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
platforms: linux/amd64, linux/386, linux/arm64/v8, linux/arm/v7, linux/arm/v6
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache,mode=max
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
name: Release S-UI
|
name: Release S-UI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/release.yml'
|
||||||
|
- 'frontend/**'
|
||||||
|
- '**.sh'
|
||||||
|
- '**.go'
|
||||||
|
- 'go.mod'
|
||||||
|
- 'go.sum'
|
||||||
|
- 's-ui.service'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-linux:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
@@ -18,39 +28,25 @@ jobs:
|
|||||||
- armv5
|
- armv5
|
||||||
- 386
|
- 386
|
||||||
- s390x
|
- s390x
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
go-version-file: backend/go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v5
|
||||||
with:
|
with:
|
||||||
node-version: '22'
|
node-version: '22'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabi
|
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
|
||||||
sudo apt install gcc-i686-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
|
||||||
sudo apt install gcc-s390x-linux-gnu
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: |
|
run: |
|
||||||
@@ -58,40 +54,42 @@ jobs:
|
|||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
cd ..
|
cd ..
|
||||||
mv frontend/dist backend/web/html
|
mv frontend/dist web/html
|
||||||
|
rm -fr frontend
|
||||||
|
|
||||||
- name: Build s-ui
|
- name: Build s-ui
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
export GOARCH=${{ matrix.platform }}
|
export GOARCH=${{ matrix.platform }}
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
||||||
export GOARCH=arm64
|
case "${{ matrix.platform }}" in
|
||||||
export CC=aarch64-linux-gnu-gcc
|
amd64) BOOTLIN_ARCH="x86-64" ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
arm64) BOOTLIN_ARCH="aarch64" ;;
|
||||||
export GOARCH=arm
|
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
||||||
export GOARM=7
|
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
386) BOOTLIN_ARCH="x86-i686" ;;
|
||||||
export GOARCH=arm
|
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
||||||
export GOARM=6
|
esac
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
|
||||||
export GOARCH=arm
|
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
|
||||||
export GOARM=5
|
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
|
||||||
export CC=arm-linux-gnueabi-gcc
|
echo "Downloading: $TARBALL_URL"
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
cd /tmp
|
||||||
export GOARCH=386
|
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
|
||||||
export CC=i686-linux-gnu-gcc
|
tar -xf "$(basename "$TARBALL_URL")"
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
|
||||||
export GOARCH=s390x
|
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
|
||||||
export CC=s390x-linux-gnu-gcc
|
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
|
||||||
fi
|
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
|
||||||
|
cd -
|
||||||
|
|
||||||
### Build s-ui
|
### Build s-ui
|
||||||
cd backend
|
go build -ldflags="-w -s -linkmode external -extldflags '-static'" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui main.go
|
||||||
go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o ../sui main.go
|
file sui
|
||||||
cd ..
|
ldd sui || echo "Static binary confirmed"
|
||||||
|
|
||||||
mkdir s-ui
|
mkdir s-ui
|
||||||
cp sui s-ui/
|
cp sui s-ui/
|
||||||
@@ -101,8 +99,16 @@ jobs:
|
|||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: s-ui-linux-${{ matrix.platform }}
|
||||||
|
path: ./s-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload to Release
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
name: Build S-UI for Windows
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/windows.yml'
|
||||||
|
- 'frontend/**'
|
||||||
|
- '**.go'
|
||||||
|
- 'go.mod'
|
||||||
|
- 'go.sum'
|
||||||
|
- 'windows/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
cache: false
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install zip for Windows
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
# Install Chocolatey if not available
|
||||||
|
if (!(Get-Command choco -ErrorAction SilentlyContinue)) {
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||||
|
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
|
||||||
|
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
|
||||||
|
}
|
||||||
|
# Install zip
|
||||||
|
choco install zip -y
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
|
mv frontend/dist web/html
|
||||||
|
rm -fr frontend
|
||||||
|
|
||||||
|
- name: Build s-ui
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOOS=windows
|
||||||
|
export GOARCH=amd64
|
||||||
|
|
||||||
|
echo "Building for Windows amd64"
|
||||||
|
go version
|
||||||
|
|
||||||
|
### Build s-ui
|
||||||
|
go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui.exe main.go
|
||||||
|
file sui.exe
|
||||||
|
|
||||||
|
mkdir s-ui-windows
|
||||||
|
cp sui.exe s-ui-windows/
|
||||||
|
cp -r windows/* s-ui-windows/
|
||||||
|
|
||||||
|
- name: Package
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
zip -r "s-ui-windows-amd64.zip" s-ui-windows
|
||||||
|
|
||||||
|
- name: Upload files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: s-ui-windows-amd64
|
||||||
|
path: ./s-ui-windows-amd64.zip
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload to Release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file: s-ui-windows-amd64.zip
|
||||||
|
asset_name: s-ui-windows-amd64.zip
|
||||||
|
prerelease: true
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
build-windows-arm64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5.0.0
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
cache: false
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
run: |
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
|
mv frontend/dist web/html
|
||||||
|
rm -fr frontend
|
||||||
|
|
||||||
|
- name: Build s-ui for ARM64
|
||||||
|
run: |
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
export GOOS=windows
|
||||||
|
export GOARCH=arm64
|
||||||
|
|
||||||
|
echo "Building for Windows ARM64 (32-bit)"
|
||||||
|
go version
|
||||||
|
go env GOOS GOARCH
|
||||||
|
|
||||||
|
### Build s-ui without CGO for ARM64
|
||||||
|
go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui.exe main.go
|
||||||
|
file sui.exe
|
||||||
|
|
||||||
|
mkdir s-ui-windows
|
||||||
|
cp sui.exe s-ui-windows/
|
||||||
|
cp -r windows/* s-ui-windows/
|
||||||
|
|
||||||
|
- name: Package ARM64
|
||||||
|
run: |
|
||||||
|
zip -r "s-ui-windows-arm64.zip" s-ui-windows
|
||||||
|
|
||||||
|
- name: Upload ARM64 files to Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: s-ui-windows-arm64
|
||||||
|
path: ./s-ui-windows-arm64.zip
|
||||||
|
retention-days: 30
|
||||||
|
|
||||||
|
- name: Upload ARM64 to Release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
file: s-ui-windows-arm64.zip
|
||||||
|
asset_name: s-ui-windows-arm64.zip
|
||||||
|
prerelease: true
|
||||||
|
overwrite: true
|
||||||
+10
@@ -5,10 +5,12 @@ backup/
|
|||||||
bin/
|
bin/
|
||||||
db/
|
db/
|
||||||
sui
|
sui
|
||||||
|
web/html
|
||||||
main
|
main
|
||||||
tmp
|
tmp
|
||||||
.sync*
|
.sync*
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
frontend/
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
@@ -18,6 +20,14 @@ tmp
|
|||||||
*.log*
|
*.log*
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
|
# Windows build artifacts
|
||||||
|
*.exe
|
||||||
|
*.zip
|
||||||
|
s-ui-windows/
|
||||||
|
sui-*.exe
|
||||||
|
sui-*.zip
|
||||||
|
windows/sui-*.exe
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "frontend"]
|
||||||
|
path = frontend
|
||||||
|
url = https://github.com/alireza0/s-ui-frontend
|
||||||
|
branch = main
|
||||||
+23
-8
@@ -3,23 +3,38 @@ WORKDIR /app
|
|||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
RUN npm install && npm run build
|
RUN npm install && npm run build
|
||||||
|
|
||||||
FROM golang:1.23-alpine AS backend-builder
|
FROM golang:1.25-alpine AS backend-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
ENV GOARCH=$TARGETARCH
|
ENV GOARCH=$TARGETARCH
|
||||||
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
|
|
||||||
COPY backend/ ./
|
RUN apk update && apk add --no-cache \
|
||||||
COPY --from=front-builder /app/dist/ /app/web/html/
|
gcc \
|
||||||
RUN go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go
|
musl-dev \
|
||||||
|
libc-dev \
|
||||||
|
make \
|
||||||
|
git \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
|
bash
|
||||||
|
|
||||||
|
ENV CC=gcc
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
COPY --from=front-builder /app/dist/ /app/web/html/
|
||||||
|
|
||||||
|
RUN go build -ldflags="-w -s" \
|
||||||
|
-tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" \
|
||||||
|
-o sui main.go
|
||||||
|
|
||||||
FROM --platform=$TARGETPLATFORM alpine
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||||
ENV TZ=Asia/Tehran
|
ENV TZ=Asia/Tehran
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apk add --no-cache --update ca-certificates tzdata
|
RUN apk add --no-cache --update ca-certificates tzdata
|
||||||
COPY --from=backend-builder /app/sui /app/
|
COPY --from=backend-builder /app/sui /app/
|
||||||
COPY entrypoint.sh /app/
|
COPY entrypoint.sh /app/
|
||||||
VOLUME [ "s-ui" ]
|
VOLUME [ "s-ui" ]
|
||||||
ENTRYPOINT [ "./entrypoint.sh" ]
|
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
FROM golang:1.25-alpine AS backend-builder
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETARCH
|
||||||
|
ENV CGO_ENABLED=1
|
||||||
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
|
ENV GOARCH=$TARGETARCH
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache \
|
||||||
|
gcc \
|
||||||
|
musl-dev \
|
||||||
|
libc-dev \
|
||||||
|
make \
|
||||||
|
git \
|
||||||
|
wget \
|
||||||
|
unzip \
|
||||||
|
bash
|
||||||
|
|
||||||
|
ENV CC=gcc
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
# Copy pre-built frontend files from a known location (provided by workflow artifact)
|
||||||
|
COPY frontend_dist/ /app/web/html/
|
||||||
|
|
||||||
|
RUN go build -ldflags="-w -s" \
|
||||||
|
-tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" \
|
||||||
|
-o sui main.go
|
||||||
|
|
||||||
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
|
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||||
|
ENV TZ=Asia/Tehran
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache --update ca-certificates tzdata
|
||||||
|
COPY --from=backend-builder /app/sui /app/
|
||||||
|
COPY entrypoint.sh /app/
|
||||||
|
VOLUME [ "s-ui" ]
|
||||||
|
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||

|

|
||||||

|

|
||||||

|
[](https://goreportcard.com/report/github.com/alireza0/s-ui)
|
||||||
[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
|
[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
@@ -13,7 +13,9 @@
|
|||||||
|
|
||||||
[](https://www.buymeacoffee.com/alireza7)
|
[](https://www.buymeacoffee.com/alireza7)
|
||||||
|
|
||||||
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
<a href="https://nowpayments.io/donation/alireza7" target="_blank" rel="noreferrer noopener">
|
||||||
|
<img src="https://nowpayments.io/images/embeds/donation-button-white.svg" alt="Crypto donation button by NOWPayments">
|
||||||
|
</a>
|
||||||
|
|
||||||
## Quick Overview
|
## Quick Overview
|
||||||
| Features | Enable? |
|
| Features | Enable? |
|
||||||
@@ -25,7 +27,24 @@
|
|||||||
| Client & Traffic & System Status | :heavy_check_mark: |
|
| Client & Traffic & System Status | :heavy_check_mark: |
|
||||||
| Subscription Service (link/json + info)| :heavy_check_mark: |
|
| Subscription Service (link/json + info)| :heavy_check_mark: |
|
||||||
| Dark/Light Theme | :heavy_check_mark: |
|
| Dark/Light Theme | :heavy_check_mark: |
|
||||||
|
| API Interface | :heavy_check_mark: |
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
| Platform | Architecture | Status |
|
||||||
|
|----------|--------------|---------|
|
||||||
|
| Linux | amd64, arm64, armv7, armv6, armv5, 386, s390x | ✅ Supported |
|
||||||
|
| Windows | amd64, 386, arm64 | ✅ Supported |
|
||||||
|
| macOS | amd64, arm64 | 🚧 Experimental |
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[Other UI Screenshots](https://github.com/alireza0/s-ui-frontend/blob/main/screenshots.md)
|
||||||
|
|
||||||
|
## API Documentation
|
||||||
|
|
||||||
|
[API-Documentation Wiki](https://github.com/alireza0/s-ui/wiki/API-Documentation)
|
||||||
|
|
||||||
## Default Installation Information
|
## Default Installation Information
|
||||||
- Panel Port: 2095
|
- Panel Port: 2095
|
||||||
@@ -36,10 +55,17 @@
|
|||||||
|
|
||||||
## Install & Upgrade to Latest Version
|
## Install & Upgrade to Latest Version
|
||||||
|
|
||||||
|
### Linux/macOS
|
||||||
```sh
|
```sh
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
1. Download the latest Windows release from [GitHub Releases](https://github.com/alireza0/s-ui/releases/latest)
|
||||||
|
2. Extract the ZIP file
|
||||||
|
3. Run `install-windows.bat` as Administrator
|
||||||
|
4. Follow the installation wizard
|
||||||
|
|
||||||
## Install legacy Version
|
## Install legacy Version
|
||||||
|
|
||||||
**Step 1:** To install your desired legacy version, add the version to the end of the installation command. e.g., ver `1.0.0`:
|
**Step 1:** To install your desired legacy version, add the version to the end of the installation command. e.g., ver `1.0.0`:
|
||||||
@@ -50,6 +76,7 @@ VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui
|
|||||||
|
|
||||||
## Manual installation
|
## Manual installation
|
||||||
|
|
||||||
|
### Linux/macOS
|
||||||
1. Get the latest version of S-UI based on your OS/Architecture from GitHub: [https://github.com/alireza0/s-ui/releases/latest](https://github.com/alireza0/s-ui/releases/latest)
|
1. Get the latest version of S-UI based on your OS/Architecture from GitHub: [https://github.com/alireza0/s-ui/releases/latest](https://github.com/alireza0/s-ui/releases/latest)
|
||||||
2. **OPTIONAL** Get the latest version of `s-ui.sh` [https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh](https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh)
|
2. **OPTIONAL** Get the latest version of `s-ui.sh` [https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh](https://raw.githubusercontent.com/alireza0/s-ui/master/s-ui.sh)
|
||||||
3. **OPTIONAL** Copy `s-ui.sh` to /usr/bin/ and run `chmod +x /usr/bin/s-ui`.
|
3. **OPTIONAL** Copy `s-ui.sh` to /usr/bin/ and run `chmod +x /usr/bin/s-ui`.
|
||||||
@@ -58,21 +85,26 @@ VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui
|
|||||||
6. Enable autostart and start S-UI service using `systemctl enable s-ui --now`
|
6. Enable autostart and start S-UI service using `systemctl enable s-ui --now`
|
||||||
7. Start sing-box service using `systemctl enable sing-box --now`
|
7. Start sing-box service using `systemctl enable sing-box --now`
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
1. Get the latest Windows version from GitHub: [https://github.com/alireza0/s-ui/releases/latest](https://github.com/alireza0/s-ui/releases/latest)
|
||||||
|
2. Download the appropriate Windows package (e.g., `s-ui-windows-amd64.zip`)
|
||||||
|
3. Extract the ZIP file to a directory of your choice
|
||||||
|
4. Run `install-windows.bat` as Administrator
|
||||||
|
5. Follow the installation wizard
|
||||||
|
6. Access the panel at http://localhost:2095/app
|
||||||
|
|
||||||
## Uninstall S-UI
|
## Uninstall S-UI
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo -i
|
sudo -i
|
||||||
|
|
||||||
systemctl disable sing-box --now
|
|
||||||
systemctl disable s-ui --now
|
systemctl disable s-ui --now
|
||||||
|
|
||||||
rm -f /etc/systemd/system/s-ui.service
|
|
||||||
rm -f /etc/systemd/system/sing-box.service
|
rm -f /etc/systemd/system/sing-box.service
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
rm -fr /usr/local/s-ui
|
rm -fr /usr/local/s-ui
|
||||||
rm /usr/bin/s-ui
|
rm /usr/bin/s-ui
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install using Docker
|
## Install using Docker
|
||||||
@@ -98,13 +130,13 @@ wget -q https://raw.githubusercontent.com/alireza0/s-ui/master/docker-compose.ym
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
> Use docker for s-ui only
|
> Use docker
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir s-ui && cd s-ui
|
mkdir s-ui && cd s-ui
|
||||||
docker run -itd \
|
docker run -itd \
|
||||||
-p 2095:2095 -p 2096:2096 -p 443:443 -p 80:80 \
|
-p 2095:2095 -p 2096:2096 -p 443:443 -p 80:80 \
|
||||||
-v $PWD/db/:/usr/local/s-ui/db/ \
|
-v $PWD/db/:/app/db/ \
|
||||||
-v $PWD/cert/:/root/cert/ \
|
-v $PWD/cert/:/root/cert/ \
|
||||||
--name s-ui --restart=unless-stopped \
|
--name s-ui --restart=unless-stopped \
|
||||||
alireza7/s-ui:latest
|
alireza7/s-ui:latest
|
||||||
@@ -113,6 +145,8 @@ docker run -itd \
|
|||||||
> Build your own image
|
> Build your own image
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
git clone https://github.com/alireza0/s-ui
|
||||||
|
git submodule update --init --recursive
|
||||||
docker build -t s-ui .
|
docker build -t s-ui .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -128,37 +162,30 @@ docker build -t s-ui .
|
|||||||
./runSUI.sh
|
./runSUI.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Clone the repository
|
||||||
|
```shell
|
||||||
|
# clone repository
|
||||||
|
git clone https://github.com/alireza0/s-ui
|
||||||
|
# clone submodules
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### - Frontend
|
### - Frontend
|
||||||
|
|
||||||
Frontend codes are in `frontend` folder in the root of repository.
|
Visit [s-ui-frontend](https://github.com/alireza0/s-ui-frontend) for frontend code
|
||||||
|
|
||||||
To run it locally for instant development you can use (apply automatic changes on file save):
|
|
||||||
```shell
|
|
||||||
cd frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
> By this command it will run a `vite` web server on separate port `3000`, with backend proxy to `http://localhost:2095`. You can change it in `frontend/vite.config.mts`.
|
|
||||||
|
|
||||||
To build frontend:
|
|
||||||
```shell
|
|
||||||
cd frontend
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### - Backend
|
### - Backend
|
||||||
Backend codes are in `backend` folder in the root of repository.
|
|
||||||
> Please build frontend once before!
|
> Please build frontend once before!
|
||||||
|
|
||||||
To build backend:
|
To build backend:
|
||||||
```shell
|
```shell
|
||||||
cd backend
|
|
||||||
|
|
||||||
# remove old frontend compiled files
|
# remove old frontend compiled files
|
||||||
rm -fr web/html/*
|
rm -fr web/html/*
|
||||||
# apply new frontend compiled files
|
# apply new frontend compiled files
|
||||||
cp -R ../frontend/dist/ web/html/
|
cp -R frontend/dist/ web/html/
|
||||||
# build
|
# build
|
||||||
go build -o ../sui main.go
|
go build -o sui main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
To run backend (from root folder of repository):
|
To run backend (from root folder of repository):
|
||||||
@@ -182,7 +209,7 @@ To run backend (from root folder of repository):
|
|||||||
- Supported protocols:
|
- Supported protocols:
|
||||||
- General: Mixed, SOCKS, HTTP, HTTPS, Direct, Redirect, TProxy
|
- General: Mixed, SOCKS, HTTP, HTTPS, Direct, Redirect, TProxy
|
||||||
- V2Ray based: VLESS, VMess, Trojan, Shadowsocks
|
- V2Ray based: VLESS, VMess, Trojan, Shadowsocks
|
||||||
- Other protocols: ShadowTLS, Hysteria, Hysteri2, Naive, TUIC
|
- Other protocols: ShadowTLS, Hysteria, Hysteria2, Naive, TUIC
|
||||||
- Supports XTLS protocols
|
- Supports XTLS protocols
|
||||||
- An advanced interface for routing traffic, incorporating PROXY Protocol, External, and Transparent Proxy, SSL Certificate, and Port
|
- An advanced interface for routing traffic, incorporating PROXY Protocol, External, and Transparent Proxy, SSL Certificate, and Port
|
||||||
- An advanced interface for inbound and outbound configuration
|
- An advanced interface for inbound and outbound configuration
|
||||||
@@ -192,21 +219,6 @@ To run backend (from root folder of repository):
|
|||||||
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
||||||
- Dark/Light theme
|
- Dark/Light theme
|
||||||
|
|
||||||
## Recommended OS
|
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
|
||||||
- Debian 11+
|
|
||||||
- CentOS 8+
|
|
||||||
- Fedora 36+
|
|
||||||
- Arch Linux
|
|
||||||
- Parch Linux
|
|
||||||
- Manjaro
|
|
||||||
- Armbian
|
|
||||||
- AlmaLinux 9+
|
|
||||||
- Rocky Linux 9+
|
|
||||||
- Oracle Linux 8+
|
|
||||||
- OpenSUSE Tubleweed
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIHandler struct {
|
||||||
|
ApiService
|
||||||
|
apiv2 *APIv2Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIHandler(g *gin.RouterGroup, a2 *APIv2Handler) {
|
||||||
|
a := &APIHandler{
|
||||||
|
apiv2: a2,
|
||||||
|
}
|
||||||
|
a.initRouter(g)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
||||||
|
g.Use(func(c *gin.Context) {
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
|
||||||
|
checkLogin(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.POST("/:postAction", a.postHandler)
|
||||||
|
g.GET("/:getAction", a.getHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) postHandler(c *gin.Context) {
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
action := c.Param("postAction")
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "login":
|
||||||
|
a.ApiService.Login(c)
|
||||||
|
case "changePass":
|
||||||
|
a.ApiService.ChangePass(c)
|
||||||
|
case "save":
|
||||||
|
a.ApiService.Save(c, loginUser)
|
||||||
|
case "restartApp":
|
||||||
|
a.ApiService.RestartApp(c)
|
||||||
|
case "restartSb":
|
||||||
|
a.ApiService.RestartSb(c)
|
||||||
|
case "linkConvert":
|
||||||
|
a.ApiService.LinkConvert(c)
|
||||||
|
case "importdb":
|
||||||
|
a.ApiService.ImportDb(c)
|
||||||
|
case "addToken":
|
||||||
|
a.ApiService.AddToken(c)
|
||||||
|
a.apiv2.ReloadTokens()
|
||||||
|
case "deleteToken":
|
||||||
|
a.ApiService.DeleteToken(c)
|
||||||
|
a.apiv2.ReloadTokens()
|
||||||
|
default:
|
||||||
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) getHandler(c *gin.Context) {
|
||||||
|
action := c.Param("getAction")
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "logout":
|
||||||
|
a.ApiService.Logout(c)
|
||||||
|
case "load":
|
||||||
|
a.ApiService.LoadData(c)
|
||||||
|
case "inbounds", "outbounds", "endpoints", "services", "tls", "clients", "config":
|
||||||
|
err := a.ApiService.LoadPartialData(c, []string{action})
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, action, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case "users":
|
||||||
|
a.ApiService.GetUsers(c)
|
||||||
|
case "settings":
|
||||||
|
a.ApiService.GetSettings(c)
|
||||||
|
case "stats":
|
||||||
|
a.ApiService.GetStats(c)
|
||||||
|
case "status":
|
||||||
|
a.ApiService.GetStatus(c)
|
||||||
|
case "onlines":
|
||||||
|
a.ApiService.GetOnlines(c)
|
||||||
|
case "logs":
|
||||||
|
a.ApiService.GetLogs(c)
|
||||||
|
case "changes":
|
||||||
|
a.ApiService.CheckChanges(c)
|
||||||
|
case "keypairs":
|
||||||
|
a.ApiService.GetKeypairs(c)
|
||||||
|
case "getdb":
|
||||||
|
a.ApiService.GetDb(c)
|
||||||
|
case "tokens":
|
||||||
|
a.ApiService.GetTokens(c)
|
||||||
|
default:
|
||||||
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/database"
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
"github.com/alireza0/s-ui/service"
|
||||||
|
"github.com/alireza0/s-ui/util"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiService struct {
|
||||||
|
service.SettingService
|
||||||
|
service.UserService
|
||||||
|
service.ConfigService
|
||||||
|
service.ClientService
|
||||||
|
service.TlsService
|
||||||
|
service.InboundService
|
||||||
|
service.OutboundService
|
||||||
|
service.EndpointService
|
||||||
|
service.ServicesService
|
||||||
|
service.PanelService
|
||||||
|
service.StatsService
|
||||||
|
service.ServerService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) LoadData(c *gin.Context) {
|
||||||
|
data, err := a.getData(c)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) getData(c *gin.Context) (interface{}, error) {
|
||||||
|
data := make(map[string]interface{}, 0)
|
||||||
|
lu := c.Query("lu")
|
||||||
|
isUpdated, err := a.ConfigService.CheckChanges(lu)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
onlines, err := a.StatsService.GetOnlines()
|
||||||
|
|
||||||
|
sysInfo := a.ServerService.GetSingboxInfo()
|
||||||
|
if sysInfo["running"] == false {
|
||||||
|
logs := a.ServerService.GetLogs("1", "debug")
|
||||||
|
if len(logs) > 0 {
|
||||||
|
data["lastLog"] = logs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if isUpdated {
|
||||||
|
config, err := a.SettingService.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
clients, err := a.ClientService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tlsConfigs, err := a.TlsService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
inbounds, err := a.InboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
outbounds, err := a.OutboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
endpoints, err := a.EndpointService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
services, err := a.ServicesService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
subURI, err := a.SettingService.GetFinalSubURI(getHostname(c))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
trafficAge, err := a.SettingService.GetTrafficAge()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
data["config"] = json.RawMessage(config)
|
||||||
|
data["clients"] = clients
|
||||||
|
data["tls"] = tlsConfigs
|
||||||
|
data["inbounds"] = inbounds
|
||||||
|
data["outbounds"] = outbounds
|
||||||
|
data["endpoints"] = endpoints
|
||||||
|
data["services"] = services
|
||||||
|
data["subURI"] = subURI
|
||||||
|
data["enableTraffic"] = trafficAge > 0
|
||||||
|
data["onlines"] = onlines
|
||||||
|
} else {
|
||||||
|
data["onlines"] = onlines
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) LoadPartialData(c *gin.Context, objs []string) error {
|
||||||
|
data := make(map[string]interface{}, 0)
|
||||||
|
id := c.Query("id")
|
||||||
|
|
||||||
|
for _, obj := range objs {
|
||||||
|
switch obj {
|
||||||
|
case "inbounds":
|
||||||
|
inbounds, err := a.InboundService.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = inbounds
|
||||||
|
case "outbounds":
|
||||||
|
outbounds, err := a.OutboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = outbounds
|
||||||
|
case "endpoints":
|
||||||
|
endpoints, err := a.EndpointService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = endpoints
|
||||||
|
case "services":
|
||||||
|
services, err := a.ServicesService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = services
|
||||||
|
case "tls":
|
||||||
|
tlsConfigs, err := a.TlsService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = tlsConfigs
|
||||||
|
case "clients":
|
||||||
|
clients, err := a.ClientService.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = clients
|
||||||
|
case "config":
|
||||||
|
config, err := a.SettingService.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = json.RawMessage(config)
|
||||||
|
case "settings":
|
||||||
|
settings, err := a.SettingService.GetAllSetting()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObj(c, data, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetUsers(c *gin.Context) {
|
||||||
|
users, err := a.UserService.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, *users, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetSettings(c *gin.Context) {
|
||||||
|
data, err := a.SettingService.GetAllSetting()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, data, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetStats(c *gin.Context) {
|
||||||
|
resource := c.Query("resource")
|
||||||
|
tag := c.Query("tag")
|
||||||
|
limit, err := strconv.Atoi(c.Query("limit"))
|
||||||
|
if err != nil {
|
||||||
|
limit = 100
|
||||||
|
}
|
||||||
|
data, err := a.StatsService.GetStats(resource, tag, limit)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, data, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetStatus(c *gin.Context) {
|
||||||
|
request := c.Query("r")
|
||||||
|
result := a.ServerService.GetStatus(request)
|
||||||
|
jsonObj(c, result, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetOnlines(c *gin.Context) {
|
||||||
|
onlines, err := a.StatsService.GetOnlines()
|
||||||
|
jsonObj(c, onlines, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetLogs(c *gin.Context) {
|
||||||
|
count := c.Query("c")
|
||||||
|
level := c.Query("l")
|
||||||
|
logs := a.ServerService.GetLogs(count, level)
|
||||||
|
jsonObj(c, logs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) CheckChanges(c *gin.Context) {
|
||||||
|
actor := c.Query("a")
|
||||||
|
chngKey := c.Query("k")
|
||||||
|
count := c.Query("c")
|
||||||
|
changes := a.ConfigService.GetChanges(actor, chngKey, count)
|
||||||
|
jsonObj(c, changes, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetKeypairs(c *gin.Context) {
|
||||||
|
kType := c.Query("k")
|
||||||
|
options := c.Query("o")
|
||||||
|
keypair := a.ServerService.GenKeypair(kType, options)
|
||||||
|
jsonObj(c, keypair, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetDb(c *gin.Context) {
|
||||||
|
exclude := c.Query("exclude")
|
||||||
|
db, err := database.GetDb(exclude)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
|
c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db")
|
||||||
|
c.Writer.Write(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) postActions(c *gin.Context) (string, json.RawMessage, error) {
|
||||||
|
var data map[string]json.RawMessage
|
||||||
|
err := c.ShouldBind(&data)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return string(data["action"]), data["data"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) Login(c *gin.Context) {
|
||||||
|
remoteIP := getRemoteIp(c)
|
||||||
|
loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionMaxAge, err := a.SettingService.GetSessionMaxAge()
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("Unable to get session's max age from DB")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetLoginUser(c, loginUser, sessionMaxAge)
|
||||||
|
if err == nil {
|
||||||
|
logger.Info("user ", loginUser, " login success")
|
||||||
|
} else {
|
||||||
|
logger.Warning("login failed: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMsg(c, "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) ChangePass(c *gin.Context) {
|
||||||
|
id := c.Request.FormValue("id")
|
||||||
|
oldPass := c.Request.FormValue("oldPass")
|
||||||
|
newUsername := c.Request.FormValue("newUsername")
|
||||||
|
newPass := c.Request.FormValue("newPass")
|
||||||
|
err := a.UserService.ChangePass(id, oldPass, newUsername, newPass)
|
||||||
|
if err == nil {
|
||||||
|
logger.Info("change user credentials success")
|
||||||
|
jsonMsg(c, "save", nil)
|
||||||
|
} else {
|
||||||
|
logger.Warning("change user credentials failed:", err)
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) Save(c *gin.Context, loginUser string) {
|
||||||
|
hostname := getHostname(c)
|
||||||
|
obj := c.Request.FormValue("object")
|
||||||
|
act := c.Request.FormValue("action")
|
||||||
|
data := c.Request.FormValue("data")
|
||||||
|
initUsers := c.Request.FormValue("initUsers")
|
||||||
|
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "save", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = a.LoadPartialData(c, objs)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, obj, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) RestartApp(c *gin.Context) {
|
||||||
|
err := a.PanelService.RestartPanel(3)
|
||||||
|
jsonMsg(c, "restartApp", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) RestartSb(c *gin.Context) {
|
||||||
|
err := a.ConfigService.RestartCore()
|
||||||
|
jsonMsg(c, "restartSb", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) LinkConvert(c *gin.Context) {
|
||||||
|
link := c.Request.FormValue("link")
|
||||||
|
result, _, err := util.GetOutbound(link, 0)
|
||||||
|
jsonObj(c, result, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) ImportDb(c *gin.Context) {
|
||||||
|
file, _, err := c.Request.FormFile("db")
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
err = database.ImportDB(file)
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) Logout(c *gin.Context) {
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
if loginUser != "" {
|
||||||
|
logger.Infof("user %s logout", loginUser)
|
||||||
|
}
|
||||||
|
ClearSession(c)
|
||||||
|
jsonMsg(c, "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) LoadTokens() ([]byte, error) {
|
||||||
|
return a.UserService.LoadTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) GetTokens(c *gin.Context) {
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
tokens, err := a.UserService.GetUserTokens(loginUser)
|
||||||
|
jsonObj(c, tokens, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) AddToken(c *gin.Context) {
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
expiry := c.Request.FormValue("expiry")
|
||||||
|
expiryInt, err := strconv.ParseInt(expiry, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
desc := c.Request.FormValue("desc")
|
||||||
|
token, err := a.UserService.AddToken(loginUser, expiryInt, desc)
|
||||||
|
jsonObj(c, token, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApiService) DeleteToken(c *gin.Context) {
|
||||||
|
tokenId := c.Request.FormValue("id")
|
||||||
|
err := a.UserService.DeleteToken(tokenId)
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
"github.com/alireza0/s-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenInMemory struct {
|
||||||
|
Token string
|
||||||
|
Expiry int64
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIv2Handler struct {
|
||||||
|
ApiService
|
||||||
|
tokens *[]TokenInMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIv2Handler(g *gin.RouterGroup) *APIv2Handler {
|
||||||
|
a := &APIv2Handler{}
|
||||||
|
a.ReloadTokens()
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) initRouter(g *gin.RouterGroup) {
|
||||||
|
g.Use(func(c *gin.Context) {
|
||||||
|
a.checkToken(c)
|
||||||
|
})
|
||||||
|
g.POST("/:postAction", a.postHandler)
|
||||||
|
g.GET("/:getAction", a.getHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) postHandler(c *gin.Context) {
|
||||||
|
username := a.findUsername(c)
|
||||||
|
action := c.Param("postAction")
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "save":
|
||||||
|
a.ApiService.Save(c, username)
|
||||||
|
case "restartApp":
|
||||||
|
a.ApiService.RestartApp(c)
|
||||||
|
case "restartSb":
|
||||||
|
a.ApiService.RestartSb(c)
|
||||||
|
case "linkConvert":
|
||||||
|
a.ApiService.LinkConvert(c)
|
||||||
|
case "importdb":
|
||||||
|
a.ApiService.ImportDb(c)
|
||||||
|
default:
|
||||||
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) getHandler(c *gin.Context) {
|
||||||
|
action := c.Param("getAction")
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "load":
|
||||||
|
a.ApiService.LoadData(c)
|
||||||
|
case "inbounds", "outbounds", "endpoints", "services", "tls", "clients", "config":
|
||||||
|
err := a.ApiService.LoadPartialData(c, []string{action})
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, action, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case "users":
|
||||||
|
a.ApiService.GetUsers(c)
|
||||||
|
case "settings":
|
||||||
|
a.ApiService.GetSettings(c)
|
||||||
|
case "stats":
|
||||||
|
a.ApiService.GetStats(c)
|
||||||
|
case "status":
|
||||||
|
a.ApiService.GetStatus(c)
|
||||||
|
case "onlines":
|
||||||
|
a.ApiService.GetOnlines(c)
|
||||||
|
case "logs":
|
||||||
|
a.ApiService.GetLogs(c)
|
||||||
|
case "changes":
|
||||||
|
a.ApiService.CheckChanges(c)
|
||||||
|
case "keypairs":
|
||||||
|
a.ApiService.GetKeypairs(c)
|
||||||
|
case "getdb":
|
||||||
|
a.ApiService.GetDb(c)
|
||||||
|
default:
|
||||||
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) findUsername(c *gin.Context) string {
|
||||||
|
token := c.Request.Header.Get("Token")
|
||||||
|
for index, t := range *a.tokens {
|
||||||
|
if t.Expiry > 0 && t.Expiry < time.Now().Unix() {
|
||||||
|
(*a.tokens) = append((*a.tokens)[:index], (*a.tokens)[index+1:]...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.Token == token {
|
||||||
|
return t.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) checkToken(c *gin.Context) {
|
||||||
|
username := a.findUsername(c)
|
||||||
|
if username != "" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(c, "", common.NewError("invalid token"))
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIv2Handler) ReloadTokens() {
|
||||||
|
tokens, err := a.ApiService.LoadTokens()
|
||||||
|
if err == nil {
|
||||||
|
var newTokens []TokenInMemory
|
||||||
|
err = json.Unmarshal(tokens, &newTokens)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("unable to load tokens: ", err)
|
||||||
|
}
|
||||||
|
a.tokens = &newTokens
|
||||||
|
} else {
|
||||||
|
logger.Error("unable to load tokens: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"s-ui/database/model"
|
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -3,9 +3,10 @@ package api
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"s-ui/logger"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,8 +30,11 @@ func getRemoteIp(c *gin.Context) string {
|
|||||||
|
|
||||||
func getHostname(c *gin.Context) string {
|
func getHostname(c *gin.Context) string {
|
||||||
host := c.Request.Host
|
host := c.Request.Host
|
||||||
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
if strings.Contains(host, ":") {
|
||||||
host, _, _ = net.SplitHostPort(c.Request.Host)
|
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
host = "[" + host + "]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,15 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/core"
|
"github.com/alireza0/s-ui/config"
|
||||||
"s-ui/cronjob"
|
"github.com/alireza0/s-ui/core"
|
||||||
"s-ui/database"
|
"github.com/alireza0/s-ui/cronjob"
|
||||||
"s-ui/logger"
|
"github.com/alireza0/s-ui/database"
|
||||||
"s-ui/service"
|
"github.com/alireza0/s-ui/logger"
|
||||||
"s-ui/sub"
|
"github.com/alireza0/s-ui/service"
|
||||||
"s-ui/web"
|
"github.com/alireza0/s-ui/sub"
|
||||||
|
"github.com/alireza0/s-ui/web"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"s-ui/database"
|
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/service"
|
|
||||||
"s-ui/util"
|
|
||||||
"s-ui/util/common"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type APIHandler struct {
|
|
||||||
service.SettingService
|
|
||||||
service.UserService
|
|
||||||
service.ConfigService
|
|
||||||
service.ClientService
|
|
||||||
service.TlsService
|
|
||||||
service.InboundService
|
|
||||||
service.OutboundService
|
|
||||||
service.EndpointService
|
|
||||||
service.PanelService
|
|
||||||
service.StatsService
|
|
||||||
service.ServerService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAPIHandler(g *gin.RouterGroup) {
|
|
||||||
a := &APIHandler{}
|
|
||||||
a.initRouter(g)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
|
||||||
g.Use(func(c *gin.Context) {
|
|
||||||
path := c.Request.URL.Path
|
|
||||||
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
|
|
||||||
checkLogin(c)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
g.POST("/:postAction", a.postHandler)
|
|
||||||
g.GET("/:getAction", a.getHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) postHandler(c *gin.Context) {
|
|
||||||
var err error
|
|
||||||
action := c.Param("postAction")
|
|
||||||
remoteIP := getRemoteIp(c)
|
|
||||||
loginUser := GetLoginUser(c)
|
|
||||||
hostname := getHostname(c)
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "login":
|
|
||||||
loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionMaxAge, err := a.SettingService.GetSessionMaxAge()
|
|
||||||
if err != nil {
|
|
||||||
logger.Infof("Unable to get session's max age from DB")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SetLoginUser(c, loginUser, sessionMaxAge)
|
|
||||||
if err == nil {
|
|
||||||
logger.Info("user ", loginUser, " login success")
|
|
||||||
} else {
|
|
||||||
logger.Warning("login failed: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonMsg(c, "", nil)
|
|
||||||
case "changePass":
|
|
||||||
id := c.Request.FormValue("id")
|
|
||||||
oldPass := c.Request.FormValue("oldPass")
|
|
||||||
newUsername := c.Request.FormValue("newUsername")
|
|
||||||
newPass := c.Request.FormValue("newPass")
|
|
||||||
err = a.UserService.ChangePass(id, oldPass, newUsername, newPass)
|
|
||||||
if err == nil {
|
|
||||||
logger.Info("change user credentials success")
|
|
||||||
jsonMsg(c, "save", nil)
|
|
||||||
} else {
|
|
||||||
logger.Warning("change user credentials failed:", err)
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
}
|
|
||||||
case "save":
|
|
||||||
obj := c.Request.FormValue("object")
|
|
||||||
act := c.Request.FormValue("action")
|
|
||||||
data := c.Request.FormValue("data")
|
|
||||||
initUsers := c.Request.FormValue("initUsers")
|
|
||||||
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "save", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = a.loadPartialData(c, objs)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, obj, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case "restartApp":
|
|
||||||
err = a.PanelService.RestartPanel(3)
|
|
||||||
jsonMsg(c, "restartApp", err)
|
|
||||||
case "restartSb":
|
|
||||||
err = a.ConfigService.RestartCore()
|
|
||||||
jsonMsg(c, "restartSb", err)
|
|
||||||
case "linkConvert":
|
|
||||||
link := c.Request.FormValue("link")
|
|
||||||
result, _, err := util.GetOutbound(link, 0)
|
|
||||||
jsonObj(c, result, err)
|
|
||||||
case "importdb":
|
|
||||||
file, _, err := c.Request.FormFile("db")
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
err = database.ImportDB(file)
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
default:
|
|
||||||
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) getHandler(c *gin.Context) {
|
|
||||||
action := c.Param("getAction")
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "logout":
|
|
||||||
loginUser := GetLoginUser(c)
|
|
||||||
if loginUser != "" {
|
|
||||||
logger.Infof("user %s logout", loginUser)
|
|
||||||
}
|
|
||||||
ClearSession(c)
|
|
||||||
jsonMsg(c, "", nil)
|
|
||||||
case "load":
|
|
||||||
data, err := a.loadData(c)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, data, nil)
|
|
||||||
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
|
|
||||||
err := a.loadPartialData(c, []string{action})
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, action, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case "users":
|
|
||||||
users, err := a.UserService.GetUsers()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, *users, nil)
|
|
||||||
case "settings":
|
|
||||||
data, err := a.SettingService.GetAllSetting()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, data, err)
|
|
||||||
case "stats":
|
|
||||||
resource := c.Query("resource")
|
|
||||||
tag := c.Query("tag")
|
|
||||||
limit, err := strconv.Atoi(c.Query("limit"))
|
|
||||||
if err != nil {
|
|
||||||
limit = 100
|
|
||||||
}
|
|
||||||
data, err := a.StatsService.GetStats(resource, tag, limit)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, data, err)
|
|
||||||
case "status":
|
|
||||||
request := c.Query("r")
|
|
||||||
result := a.ServerService.GetStatus(request)
|
|
||||||
jsonObj(c, result, nil)
|
|
||||||
case "onlines":
|
|
||||||
onlines, err := a.StatsService.GetOnlines()
|
|
||||||
jsonObj(c, onlines, err)
|
|
||||||
case "logs":
|
|
||||||
count := c.Query("c")
|
|
||||||
level := c.Query("l")
|
|
||||||
logs := a.ServerService.GetLogs(count, level)
|
|
||||||
jsonObj(c, logs, nil)
|
|
||||||
case "changes":
|
|
||||||
actor := c.Query("a")
|
|
||||||
chngKey := c.Query("k")
|
|
||||||
count := c.Query("c")
|
|
||||||
changes := a.ConfigService.GetChanges(actor, chngKey, count)
|
|
||||||
jsonObj(c, changes, nil)
|
|
||||||
case "keypairs":
|
|
||||||
kType := c.Query("k")
|
|
||||||
options := c.Query("o")
|
|
||||||
keypair := a.ServerService.GenKeypair(kType, options)
|
|
||||||
jsonObj(c, keypair, nil)
|
|
||||||
case "getdb":
|
|
||||||
exclude := c.Query("exclude")
|
|
||||||
db, err := database.GetDb(exclude)
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Header("Content-Type", "application/octet-stream")
|
|
||||||
c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db")
|
|
||||||
c.Writer.Write(db)
|
|
||||||
default:
|
|
||||||
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|
||||||
data := make(map[string]interface{}, 0)
|
|
||||||
lu := c.Query("lu")
|
|
||||||
isUpdated, err := a.ConfigService.CheckChanges(lu)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
onlines, err := a.StatsService.GetOnlines()
|
|
||||||
|
|
||||||
sysInfo := a.ServerService.GetSingboxInfo()
|
|
||||||
if sysInfo["running"] == false {
|
|
||||||
logs := a.ServerService.GetLogs("1", "debug")
|
|
||||||
if len(logs) > 0 {
|
|
||||||
data["lastLog"] = logs[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if isUpdated {
|
|
||||||
config, err := a.SettingService.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
clients, err := a.ClientService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
tlsConfigs, err := a.TlsService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
inbounds, err := a.InboundService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
outbounds, err := a.OutboundService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
endpoints, err := a.EndpointService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
data["config"] = json.RawMessage(config)
|
|
||||||
data["clients"] = clients
|
|
||||||
data["tls"] = tlsConfigs
|
|
||||||
data["inbounds"] = inbounds
|
|
||||||
data["outbounds"] = outbounds
|
|
||||||
data["endpoints"] = endpoints
|
|
||||||
data["subURI"] = subURI
|
|
||||||
data["onlines"] = onlines
|
|
||||||
} else {
|
|
||||||
data["onlines"] = onlines
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
|
|
||||||
data := make(map[string]interface{}, 0)
|
|
||||||
id := c.Query("id")
|
|
||||||
|
|
||||||
for _, obj := range objs {
|
|
||||||
switch obj {
|
|
||||||
case "inbounds":
|
|
||||||
inbounds, err := a.InboundService.Get(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = inbounds
|
|
||||||
case "outbounds":
|
|
||||||
outbounds, err := a.OutboundService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = outbounds
|
|
||||||
case "endpoints":
|
|
||||||
endpoints, err := a.EndpointService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = endpoints
|
|
||||||
case "tls":
|
|
||||||
tlsConfigs, err := a.TlsService.GetAll()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = tlsConfigs
|
|
||||||
case "clients":
|
|
||||||
clients, err := a.ClientService.Get(id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = clients
|
|
||||||
case "config":
|
|
||||||
config, err := a.SettingService.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = json.RawMessage(config)
|
|
||||||
case "settings":
|
|
||||||
settings, err := a.SettingService.GetAllSetting()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data[obj] = settings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonObj(c, data, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) {
|
|
||||||
var data map[string]json.RawMessage
|
|
||||||
err := c.ShouldBind(&data)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, err
|
|
||||||
}
|
|
||||||
return string(data["action"]), data["data"], nil
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
1.2.0-beta.3
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package cronjob
|
|
||||||
|
|
||||||
import (
|
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DepleteJob struct {
|
|
||||||
service.ClientService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDepleteJob() *DepleteJob {
|
|
||||||
return new(DepleteJob)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DepleteJob) Run() {
|
|
||||||
err := s.ClientService.DepleteClients()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("Disable depleted users failed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package cronjob
|
|
||||||
|
|
||||||
import (
|
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StatsJob struct {
|
|
||||||
service.StatsService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStatsJob() *StatsJob {
|
|
||||||
return &StatsJob{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StatsJob) Run() {
|
|
||||||
err := s.StatsService.SaveStats()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("Get stats failed: ", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-129
@@ -1,129 +0,0 @@
|
|||||||
module s-ui
|
|
||||||
|
|
||||||
go 1.23.2
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gin-contrib/gzip v1.0.1
|
|
||||||
github.com/gin-gonic/gin v1.10.0
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
|
||||||
github.com/sagernet/sing v0.6.0-beta.12
|
|
||||||
github.com/sagernet/sing-box v1.11.0-beta.24
|
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2
|
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
|
||||||
gorm.io/driver/sqlite v1.5.7
|
|
||||||
gorm.io/gorm v1.25.12
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/ebitengine/purego v0.8.1 // indirect
|
|
||||||
google.golang.org/grpc v1.67.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
|
||||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
|
||||||
github.com/bytedance/sonic v1.12.3 // indirect
|
|
||||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
|
||||||
github.com/caddyserver/certmagic v0.20.0 // indirect
|
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
|
||||||
github.com/cretz/bine v0.2.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
|
||||||
github.com/gin-contrib/sessions v1.0.1
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-chi/chi/v5 v5.1.0 // indirect
|
|
||||||
github.com/go-chi/render v1.0.3 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
|
||||||
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/google/btree v1.1.3 // indirect
|
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
|
||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
|
||||||
github.com/klauspost/compress v1.17.7 // indirect
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/libdns/alidns v1.0.3 // indirect
|
|
||||||
github.com/libdns/cloudflare v0.1.1 // indirect
|
|
||||||
github.com/libdns/libdns v0.2.2 // indirect; indiresct
|
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
|
||||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
|
|
||||||
github.com/mholt/acmez v1.2.0 // indirect
|
|
||||||
github.com/miekg/dns v1.1.62 // 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.10.0 // indirect
|
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
|
|
||||||
github.com/sagernet/cors v1.2.1 // indirect
|
|
||||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
|
||||||
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect
|
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
|
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4 // indirect
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
|
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
|
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.8 // indirect
|
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect
|
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
|
||||||
github.com/sagernet/utls v1.6.7 // indirect
|
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect
|
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12
|
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
|
||||||
golang.org/x/arch v0.11.0 // indirect
|
|
||||||
golang.org/x/crypto v0.31.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
|
||||||
golang.org/x/net v0.31.0 // indirect
|
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
|
||||||
golang.org/x/text v0.21.0 // indirect
|
|
||||||
golang.org/x/time v0.7.0 // indirect
|
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
|
||||||
)
|
|
||||||
-303
@@ -1,303 +0,0 @@
|
|||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
|
||||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
|
||||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
|
||||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
|
||||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
|
||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
|
||||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
|
||||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
|
||||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
|
||||||
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
|
||||||
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
|
||||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
|
||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
|
||||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
|
||||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
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/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
|
||||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
|
||||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
|
||||||
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.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
|
||||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
|
||||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
|
||||||
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
|
||||||
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
|
||||||
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
|
||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
|
||||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
|
||||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
|
||||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
|
||||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
|
||||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
|
||||||
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=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
|
||||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
|
||||||
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
|
|
||||||
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/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
|
||||||
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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
|
|
||||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
|
||||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
|
||||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
|
||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs=
|
|
||||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
|
||||||
github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
|
|
||||||
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
|
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
|
||||||
github.com/sagernet/sing v0.6.0-beta.11 h1:jWCNlZI1Vdj8lQeBrjRZIQfNwlqMk0ZRqMJuPfTJupI=
|
|
||||||
github.com/sagernet/sing v0.6.0-beta.11/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
|
||||||
github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
|
|
||||||
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
|
||||||
github.com/sagernet/sing-box v1.11.0-beta.22 h1:UQrhqbUyJUZ1GvT3yNu4ANdZC8s1YdgN92jtvPd559g=
|
|
||||||
github.com/sagernet/sing-box v1.11.0-beta.22/go.mod h1:CYFqT0KvhmGhs3hp6htI8x6DugWZgdiAde+Fyufxmek=
|
|
||||||
github.com/sagernet/sing-box v1.11.0-beta.24 h1:6rUl8t6Cb0p9ML1eUobWgODL75c5iszxNvVABcWCivU=
|
|
||||||
github.com/sagernet/sing-box v1.11.0-beta.24/go.mod h1:DmL1WKyrfaAEu5z88CtUeQBfELaEdUyQzLS5nzmRg8o=
|
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
|
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
|
||||||
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
|
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
|
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
|
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
|
|
||||||
github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
|
|
||||||
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
|
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
|
||||||
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
|
||||||
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
|
||||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
|
||||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
|
||||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
|
||||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
|
||||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
|
||||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
|
||||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
|
||||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
|
||||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
|
||||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"s-ui/database"
|
|
||||||
"s-ui/database/model"
|
|
||||||
"s-ui/util/common"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TlsService struct {
|
|
||||||
InboundService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TlsService) GetAll() ([]model.Tls, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
tlsConfig := []model.Tls{}
|
|
||||||
err := db.Model(model.Tls{}).Scan(&tlsConfig).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) {
|
|
||||||
var err error
|
|
||||||
var inboundIds []uint
|
|
||||||
|
|
||||||
switch action {
|
|
||||||
case "new", "edit":
|
|
||||||
var tls model.Tls
|
|
||||||
err = json.Unmarshal(data, &tls)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tx.Save(&tls).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return inboundIds, nil
|
|
||||||
case "del":
|
|
||||||
var id uint
|
|
||||||
err = json.Unmarshal(data, &id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var inboundCount int64
|
|
||||||
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inboundCount > 0 {
|
|
||||||
return nil, common.NewError("tls in use")
|
|
||||||
}
|
|
||||||
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,10 @@ npm i
|
|||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
cd backend
|
|
||||||
echo "Backend"
|
echo "Backend"
|
||||||
|
|
||||||
mkdir -p web/html
|
mkdir -p web/html
|
||||||
rm -fr web/html/*
|
rm -fr web/html/*
|
||||||
cp -R ../frontend/dist/* web/html/
|
cp -R frontend/dist/* web/html/
|
||||||
|
|
||||||
go build -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o ../sui main.go
|
go build -ldflags "-w -s" -tags "with_quic,with_grpc,with_utls,with_acme,with_gvisor" -o sui main.go
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database"
|
"github.com/alireza0/s-ui/config"
|
||||||
"s-ui/service"
|
"github.com/alireza0/s-ui/database"
|
||||||
|
"github.com/alireza0/s-ui/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resetAdmin() {
|
func resetAdmin() {
|
||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"s-ui/cmd/migration"
|
"runtime/debug"
|
||||||
"s-ui/config"
|
|
||||||
|
"github.com/alireza0/s-ui/cmd/migration"
|
||||||
|
"github.com/alireza0/s-ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseCmd() {
|
func ParseCmd() {
|
||||||
@@ -52,7 +54,16 @@ func ParseCmd() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if showVersion {
|
if showVersion {
|
||||||
fmt.Println(config.GetVersion())
|
fmt.Println("S-UI Panel\t", config.GetVersion())
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if ok {
|
||||||
|
for _, dep := range info.Deps {
|
||||||
|
if dep.Path == "github.com/sagernet/sing-box" {
|
||||||
|
fmt.Println("Sing-Box\t", dep.Version)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,9 +3,10 @@ package migration
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"s-ui/database/model"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5,7 +5,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"s-ui/database/model"
|
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func migrate_dns(db *gorm.DB) error {
|
||||||
|
var configStr string
|
||||||
|
err := db.Model(model.Setting{}).Select("value").Where("key = ?", "config").First(&configStr).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if configStr == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var config map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(configStr), &config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dnsConfig, ok := config["dns"].(map[string]interface{}); ok {
|
||||||
|
if dnsServers, ok := dnsConfig["servers"].([]interface{}); ok {
|
||||||
|
for index, dnsServer := range dnsServers {
|
||||||
|
if dnsServer, ok := dnsServer.(map[string]interface{}); ok {
|
||||||
|
if addr, ok := dnsServer["address"].(string); ok && addr != "" {
|
||||||
|
switch addr {
|
||||||
|
case "local":
|
||||||
|
delete(dnsServer, "address")
|
||||||
|
dnsServer["type"] = "local"
|
||||||
|
case "fakeip":
|
||||||
|
delete(dnsServer, "address")
|
||||||
|
dnsServer["type"] = "fakeip"
|
||||||
|
default:
|
||||||
|
addrParsed, err := url.Parse(addr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch addrParsed.Scheme {
|
||||||
|
case "":
|
||||||
|
dnsServer["type"] = "udp"
|
||||||
|
dnsServer["server"] = addr
|
||||||
|
case "udp", "tcp", "tls", "quic", "https", "h3":
|
||||||
|
dnsServer["type"] = addrParsed.Scheme
|
||||||
|
dnsServer["server"] = addrParsed.Host
|
||||||
|
case "dhcp":
|
||||||
|
dnsServer["type"] = addrParsed.Scheme
|
||||||
|
if addrParsed.Host != "auto" && addrParsed.Host != "" {
|
||||||
|
dnsServer["interface"] = addrParsed.Host
|
||||||
|
}
|
||||||
|
case "rcode":
|
||||||
|
dnsServer["type"] = "predefined"
|
||||||
|
dnsServer["responses"] = []map[string]string{
|
||||||
|
{
|
||||||
|
"rcode": strings.ToUpper(addrParsed.Host),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(dnsServer, "address")
|
||||||
|
if addrParsed.Port() != "" {
|
||||||
|
port, err := strconv.Atoi(addrParsed.Port())
|
||||||
|
if err == nil {
|
||||||
|
dnsServer["server_port"] = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if address_resolver, ok := dnsServer["address_resolver"].(string); ok && address_resolver != "" {
|
||||||
|
delete(dnsServer, "address_resolver")
|
||||||
|
dnsServer["domain_resolver"] = address_resolver
|
||||||
|
}
|
||||||
|
delete(dnsServer, "strategy")
|
||||||
|
}
|
||||||
|
dnsServers[index] = dnsServer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dnsConfig["servers"] = dnsServers
|
||||||
|
}
|
||||||
|
config["dns"] = dnsConfig
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// save changes
|
||||||
|
configs, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove_outbound_strategy(db *gorm.DB) error {
|
||||||
|
var outbounds []model.Outbound
|
||||||
|
err := db.Find(&outbounds).Where("json_extract(options, '$.domain_strategy') IS NOT NULL").Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(restFields, "domain_strategy")
|
||||||
|
outbound.Options, _ = json.MarshalIndent(restFields, "", " ")
|
||||||
|
db.Save(&outbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func anytls_user_config(db *gorm.DB) error {
|
||||||
|
var clients []model.Client
|
||||||
|
err := db.Model(model.Client{}).Find(&clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for index, client := range clients {
|
||||||
|
var configs map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(client.Config, &configs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if configs["anytls"] != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
configs["anytls"] = configs["trojan"]
|
||||||
|
configJson, err := json.MarshalIndent(configs, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clients[index].Config = configJson
|
||||||
|
db.Save(&clients[index])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func to1_3(db *gorm.DB) error {
|
||||||
|
err := anytls_user_config(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = migrate_dns(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = remove_outbound_strategy(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"s-ui/config"
|
|
||||||
|
"github.com/alireza0/s-ui/config"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -56,10 +57,20 @@ func MigrateDb() {
|
|||||||
log.Fatal("Migration to 1.2 failed: ", err)
|
log.Fatal("Migration to 1.2 failed: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dbVersion = "1.2"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before 1.3
|
||||||
|
if dbVersion[0:3] == "1.2" {
|
||||||
|
err = to1_3(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Migration to 1.3 failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set version
|
// Set version
|
||||||
err = tx.Raw("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
err = tx.Exec("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Update version failed: ", err)
|
log.Fatal("Update version failed: ", err)
|
||||||
return
|
return
|
||||||
@@ -4,11 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database"
|
|
||||||
"s-ui/service"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/config"
|
||||||
|
"github.com/alireza0/s-ui/database"
|
||||||
|
"github.com/alireza0/s-ui/service"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,9 +52,13 @@ func GetDBFolderPath() string {
|
|||||||
if dbFolderPath == "" {
|
if dbFolderPath == "" {
|
||||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dbFolderPath = "/usr/local/s-ui/db"
|
// Cross-platform fallback path
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "C:\\Program Files\\s-ui\\db"
|
||||||
|
}
|
||||||
|
return "/usr/local/s-ui/db"
|
||||||
}
|
}
|
||||||
dbFolderPath = dir + "/db"
|
dbFolderPath = filepath.Join(dir, "db")
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.3.7
|
||||||
@@ -5,16 +5,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"s-ui/util/common"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/util/common"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -28,21 +34,25 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
connection *route.ConnectionManager
|
service *boxService.Manager
|
||||||
router *route.Router
|
dnsTransport *dns.TransportManager
|
||||||
services []adapter.LifecycleService
|
dnsRouter *dns.Router
|
||||||
connTracker *ConnTracker
|
connection *route.ConnectionManager
|
||||||
done chan struct{}
|
router *route.Router
|
||||||
|
internalService []adapter.LifecycleService
|
||||||
|
statsTracker *StatsTracker
|
||||||
|
connTracker *ConnTracker
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -55,6 +65,8 @@ func Context(
|
|||||||
inboundRegistry adapter.InboundRegistry,
|
inboundRegistry adapter.InboundRegistry,
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -71,6 +83,14 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
}
|
}
|
||||||
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
||||||
|
}
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +106,8 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, common.NewError("missing endpoint registry in context")
|
return nil, common.NewError("missing endpoint registry in context")
|
||||||
@@ -96,13 +118,27 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, common.NewError("missing outbound registry in context")
|
return nil, common.NewError("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
|
if dnsTransportRegistry == nil {
|
||||||
|
return nil, common.NewError("missing DNS transport registry in context")
|
||||||
|
}
|
||||||
|
if serviceRegistry == nil {
|
||||||
|
return nil, common.NewError("missing service registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
|
||||||
var needCacheFile bool
|
var needCacheFile bool
|
||||||
|
var needClashAPI bool
|
||||||
|
var needV2RayAPI bool
|
||||||
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
|
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
|
||||||
needCacheFile = true
|
needCacheFile = true
|
||||||
}
|
}
|
||||||
|
if experimentalOptions.ClashAPI != nil {
|
||||||
|
needClashAPI = true
|
||||||
|
}
|
||||||
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
|
needV2RayAPI = true
|
||||||
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -120,13 +156,36 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
factory = logFactory
|
factory = logFactory
|
||||||
|
|
||||||
|
var internalServices []adapter.LifecycleService
|
||||||
|
certificateOptions := sbCommon.PtrValueOrDefault(options.Certificate)
|
||||||
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
|
len(certificateOptions.CertificatePath) > 0 ||
|
||||||
|
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
||||||
|
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
|
internalServices = append(internalServices, certificateStore)
|
||||||
|
}
|
||||||
|
|
||||||
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
||||||
|
dnsOptions := sbCommon.PtrValueOrDefault(options.DNS)
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
|
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
|
|
||||||
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
|
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -135,10 +194,34 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, sbCommon.PtrValueOrDefault(options.DNS))
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
|
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.NewError("initialize router", err)
|
return nil, common.NewError("initialize router", err)
|
||||||
}
|
}
|
||||||
|
for i, transportOptions := range dnsOptions.Servers {
|
||||||
|
var tag string
|
||||||
|
if transportOptions.Tag != "" {
|
||||||
|
tag = transportOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = dnsTransportManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
transportOptions.Type,
|
||||||
|
transportOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize DNS server[", i, "]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize dns router", err)
|
||||||
|
}
|
||||||
for i, endpointOptions := range options.Endpoints {
|
for i, endpointOptions := range options.Endpoints {
|
||||||
var tag string
|
var tag string
|
||||||
if endpointOptions.Tag != "" {
|
if endpointOptions.Tag != "" {
|
||||||
@@ -146,7 +229,8 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = endpointManager.Create(ctx,
|
err = endpointManager.Create(
|
||||||
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -164,7 +248,8 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = inboundManager.Create(ctx,
|
err = inboundManager.Create(
|
||||||
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -201,36 +286,85 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(sbCommon.Must1(
|
for i, serviceOptions := range options.Services {
|
||||||
direct.NewOutbound(
|
var tag string
|
||||||
|
if serviceOptions.Tag != "" {
|
||||||
|
tag = serviceOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = serviceManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
serviceOptions.Type,
|
||||||
|
serviceOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize service["+F.ToString(i)+"]"+tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
|
return direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger("outbound/direct"),
|
logFactory.NewLogger("outbound/direct"),
|
||||||
"direct",
|
"direct",
|
||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
),
|
)
|
||||||
))
|
})
|
||||||
|
dnsTransportManager.Initialize(sbCommon.Must1(
|
||||||
|
local.NewTransport(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger("dns/local"),
|
||||||
|
"local",
|
||||||
|
option.LocalDNSServerOptions{},
|
||||||
|
)))
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.NewError("initialize platform interface", err)
|
return nil, common.NewError("initialize platform interface", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if statsTracker == nil {
|
||||||
|
statsTracker = NewStatsTracker()
|
||||||
|
}
|
||||||
|
router.AppendTracker(statsTracker)
|
||||||
if connTracker == nil {
|
if connTracker == nil {
|
||||||
connTracker = NewConnTracker()
|
connTracker = NewConnTracker()
|
||||||
}
|
}
|
||||||
router.SetTracker(connTracker)
|
router.AppendTracker(connTracker)
|
||||||
|
|
||||||
var services []adapter.LifecycleService
|
|
||||||
|
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
services = append(services, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
|
}
|
||||||
|
if needClashAPI {
|
||||||
|
clashAPIOptions := sbCommon.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
|
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||||
|
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError(err, "create clash-server")
|
||||||
|
}
|
||||||
|
router.AppendTracker(clashServer)
|
||||||
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
|
internalServices = append(internalServices, clashServer)
|
||||||
|
}
|
||||||
|
if needV2RayAPI {
|
||||||
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), sbCommon.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError(err, "create v2ray-server")
|
||||||
|
}
|
||||||
|
if v2rayServer.StatsService() != nil {
|
||||||
|
router.AppendTracker(v2rayServer.StatsService())
|
||||||
|
internalServices = append(internalServices, v2rayServer)
|
||||||
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
|
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
|
||||||
if ntpOptions.Enabled {
|
if ntpOptions.Enabled {
|
||||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.NewError(err, "create NTP service")
|
return nil, common.NewError(err, "create NTP service")
|
||||||
}
|
}
|
||||||
@@ -243,21 +377,25 @@ func NewBox(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
internalServices = append(internalServices, adapter.NewLifecycleService(timeService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
connection: connectionManager,
|
dnsTransport: dnsTransportManager,
|
||||||
router: router,
|
service: serviceManager,
|
||||||
createdAt: createdAt,
|
dnsRouter: dnsRouter,
|
||||||
logFactory: logFactory,
|
connection: connectionManager,
|
||||||
logger: logFactory.Logger(),
|
router: router,
|
||||||
services: services,
|
createdAt: createdAt,
|
||||||
connTracker: connTracker,
|
logFactory: logFactory,
|
||||||
done: make(chan struct{}),
|
logger: logFactory.Logger(),
|
||||||
|
internalService: internalServices,
|
||||||
|
statsTracker: statsTracker,
|
||||||
|
connTracker: connTracker,
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,15 +443,15 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError(err, "start logger")
|
return common.NewError(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -325,31 +463,27 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.inbound.Start(adapter.StartStateStart)
|
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -364,9 +498,9 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := sbCommon.Close(
|
err := sbCommon.Close(
|
||||||
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
|
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.internalService {
|
||||||
err1 := lifecycleService.Close()
|
err1 := lifecycleService.Close()
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
|
s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
|
||||||
@@ -403,6 +537,10 @@ func (s *Box) Endpoint() adapter.EndpointManager {
|
|||||||
return s.endpoint
|
return s.endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) StatsTracker() *StatsTracker {
|
||||||
|
return s.statsTracker
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Box) ConnTracker() *ConnTracker {
|
func (s *Box) ConnTracker() *ConnTracker {
|
||||||
return s.connTracker
|
return s.connTracker
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"s-ui/logger"
|
"github.com/alireza0/s-ui/logger"
|
||||||
"s-ui/util/common"
|
"github.com/alireza0/s-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,13 +14,13 @@ func (c *Core) AddInbound(config []byte) error {
|
|||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
var inbound_config option.Inbound
|
var inbound_config option.Inbound
|
||||||
err = inbound_config.UnmarshalJSONContext(globalCtx, config)
|
err = inbound_config.UnmarshalJSONContext(c.GetCtx(), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = inbound_manager.Create(
|
err = inbound_manager.Create(
|
||||||
globalCtx,
|
c.GetCtx(),
|
||||||
router,
|
router,
|
||||||
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
|
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
|
||||||
inbound_config.Tag,
|
inbound_config.Tag,
|
||||||
@@ -47,13 +48,17 @@ func (c *Core) AddOutbound(config []byte) error {
|
|||||||
var err error
|
var err error
|
||||||
var outbound_config option.Outbound
|
var outbound_config option.Outbound
|
||||||
|
|
||||||
err = outbound_config.UnmarshalJSONContext(globalCtx, config)
|
err = outbound_config.UnmarshalJSONContext(c.GetCtx(), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outboundCtx := adapter.WithContext(c.GetCtx(), &adapter.InboundContext{
|
||||||
|
Outbound: outbound_config.Tag,
|
||||||
|
})
|
||||||
|
|
||||||
err = outbound_manager.Create(
|
err = outbound_manager.Create(
|
||||||
globalCtx,
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
||||||
outbound_config.Tag,
|
outbound_config.Tag,
|
||||||
@@ -81,13 +86,13 @@ func (c *Core) AddEndpoint(config []byte) error {
|
|||||||
var err error
|
var err error
|
||||||
var endpoint_config option.Endpoint
|
var endpoint_config option.Endpoint
|
||||||
|
|
||||||
err = endpoint_config.UnmarshalJSONContext(globalCtx, config)
|
err = endpoint_config.UnmarshalJSONContext(c.GetCtx(), config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = endpoint_manager.Create(
|
err = endpoint_manager.Create(
|
||||||
globalCtx,
|
c.GetCtx(),
|
||||||
router,
|
router,
|
||||||
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
||||||
endpoint_config.Tag,
|
endpoint_config.Tag,
|
||||||
@@ -107,3 +112,36 @@ func (c *Core) RemoveEndpoint(tag string) error {
|
|||||||
logger.Info("remove endpoint: ", tag)
|
logger.Info("remove endpoint: ", tag)
|
||||||
return endpoint_manager.Remove(tag)
|
return endpoint_manager.Remove(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Core) AddService(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var srv_config option.Service
|
||||||
|
|
||||||
|
err = srv_config.UnmarshalJSONContext(c.GetCtx(), config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = service_manager.Create(
|
||||||
|
c.GetCtx(),
|
||||||
|
factory.NewLogger("service/"+srv_config.Type+"["+srv_config.Tag+"]"),
|
||||||
|
srv_config.Tag,
|
||||||
|
srv_config.Type,
|
||||||
|
srv_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveService(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove service: ", tag)
|
||||||
|
return service_manager.Remove(tag)
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
suiLog "s-ui/logger"
|
"time"
|
||||||
|
|
||||||
|
suiLog "github.com/alireza0/s-ui/logger"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
@@ -177,6 +179,10 @@ func (l *observableLogger) Log(ctx context.Context, level log.Level, args []any)
|
|||||||
default:
|
default:
|
||||||
suiLog.Debug(l.tag, msg)
|
suiLog.Debug(l.tag, msg)
|
||||||
}
|
}
|
||||||
|
if (l.filePath != "" || l.writer != os.Stderr) && l.writer != nil {
|
||||||
|
message := l.formatter.Format(ctx, level, l.tag, msg, time.Now())
|
||||||
|
l.writer.Write([]byte(message))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *observableLogger) Trace(args ...any) {
|
func (l *observableLogger) Trace(args ...any) {
|
||||||
@@ -2,7 +2,8 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"s-ui/logger"
|
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
|
||||||
sb "github.com/sagernet/sing-box"
|
sb "github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -19,8 +20,10 @@ var (
|
|||||||
globalCtx context.Context
|
globalCtx context.Context
|
||||||
inbound_manager adapter.InboundManager
|
inbound_manager adapter.InboundManager
|
||||||
outbound_manager adapter.OutboundManager
|
outbound_manager adapter.OutboundManager
|
||||||
|
service_manager adapter.ServiceManager
|
||||||
endpoint_manager adapter.EndpointManager
|
endpoint_manager adapter.EndpointManager
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
|
statsTracker *StatsTracker
|
||||||
connTracker *ConnTracker
|
connTracker *ConnTracker
|
||||||
factory log.Factory
|
factory log.Factory
|
||||||
)
|
)
|
||||||
@@ -32,7 +35,7 @@ type Core struct {
|
|||||||
|
|
||||||
func NewCore() *Core {
|
func NewCore() *Core {
|
||||||
globalCtx = context.Background()
|
globalCtx = context.Background()
|
||||||
globalCtx = sb.Context(globalCtx, inboundRegistry(), outboundRegistry(), EndpointRegistry())
|
globalCtx = sb.Context(globalCtx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
|
||||||
return &Core{
|
return &Core{
|
||||||
isRunning: false,
|
isRunning: false,
|
||||||
instance: nil,
|
instance: nil,
|
||||||
@@ -70,6 +73,7 @@ func (c *Core) Start(sbConfig []byte) error {
|
|||||||
globalCtx = service.ContextWith(globalCtx, c)
|
globalCtx = service.ContextWith(globalCtx, c)
|
||||||
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
|
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
|
||||||
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
|
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
|
||||||
|
service_manager = service.FromContext[adapter.ServiceManager](globalCtx)
|
||||||
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
|
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
|
||||||
router = service.FromContext[adapter.Router](globalCtx)
|
router = service.FromContext[adapter.Router](globalCtx)
|
||||||
|
|
||||||
@@ -4,9 +4,18 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/dhcp"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/fakeip"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/quic"
|
||||||
|
"github.com/sagernet/sing-box/protocol/anytls"
|
||||||
"github.com/sagernet/sing-box/protocol/block"
|
"github.com/sagernet/sing-box/protocol/block"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/protocol/dns"
|
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/protocol/http"
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
"github.com/sagernet/sing-box/protocol/hysteria"
|
"github.com/sagernet/sing-box/protocol/hysteria"
|
||||||
@@ -19,6 +28,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/shadowtls"
|
"github.com/sagernet/sing-box/protocol/shadowtls"
|
||||||
"github.com/sagernet/sing-box/protocol/socks"
|
"github.com/sagernet/sing-box/protocol/socks"
|
||||||
"github.com/sagernet/sing-box/protocol/ssh"
|
"github.com/sagernet/sing-box/protocol/ssh"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tailscale"
|
||||||
"github.com/sagernet/sing-box/protocol/tor"
|
"github.com/sagernet/sing-box/protocol/tor"
|
||||||
"github.com/sagernet/sing-box/protocol/trojan"
|
"github.com/sagernet/sing-box/protocol/trojan"
|
||||||
"github.com/sagernet/sing-box/protocol/tuic"
|
"github.com/sagernet/sing-box/protocol/tuic"
|
||||||
@@ -26,11 +36,14 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
"github.com/sagernet/sing-box/protocol/wireguard"
|
"github.com/sagernet/sing-box/protocol/wireguard"
|
||||||
|
"github.com/sagernet/sing-box/service/derp"
|
||||||
|
"github.com/sagernet/sing-box/service/resolved"
|
||||||
|
"github.com/sagernet/sing-box/service/ssmapi"
|
||||||
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||||
_ "github.com/sagernet/sing-dns/quic"
|
_ "github.com/sagernet/sing-dns/quic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func inboundRegistry() *inbound.Registry {
|
func InboundRegistry() *inbound.Registry {
|
||||||
registry := inbound.NewRegistry()
|
registry := inbound.NewRegistry()
|
||||||
|
|
||||||
tun.RegisterInbound(registry)
|
tun.RegisterInbound(registry)
|
||||||
@@ -48,6 +61,7 @@ func inboundRegistry() *inbound.Registry {
|
|||||||
naive.RegisterInbound(registry)
|
naive.RegisterInbound(registry)
|
||||||
shadowtls.RegisterInbound(registry)
|
shadowtls.RegisterInbound(registry)
|
||||||
vless.RegisterInbound(registry)
|
vless.RegisterInbound(registry)
|
||||||
|
anytls.RegisterInbound(registry)
|
||||||
|
|
||||||
hysteria.RegisterInbound(registry)
|
hysteria.RegisterInbound(registry)
|
||||||
tuic.RegisterInbound(registry)
|
tuic.RegisterInbound(registry)
|
||||||
@@ -56,13 +70,13 @@ func inboundRegistry() *inbound.Registry {
|
|||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func outboundRegistry() *outbound.Registry {
|
func OutboundRegistry() *outbound.Registry {
|
||||||
registry := outbound.NewRegistry()
|
registry := outbound.NewRegistry()
|
||||||
|
|
||||||
direct.RegisterOutbound(registry)
|
direct.RegisterOutbound(registry)
|
||||||
|
|
||||||
block.RegisterOutbound(registry)
|
block.RegisterOutbound(registry)
|
||||||
dns.RegisterOutbound(registry)
|
protocolDNS.RegisterOutbound(registry)
|
||||||
|
|
||||||
group.RegisterSelector(registry)
|
group.RegisterSelector(registry)
|
||||||
group.RegisterURLTest(registry)
|
group.RegisterURLTest(registry)
|
||||||
@@ -76,6 +90,7 @@ func outboundRegistry() *outbound.Registry {
|
|||||||
ssh.RegisterOutbound(registry)
|
ssh.RegisterOutbound(registry)
|
||||||
shadowtls.RegisterOutbound(registry)
|
shadowtls.RegisterOutbound(registry)
|
||||||
vless.RegisterOutbound(registry)
|
vless.RegisterOutbound(registry)
|
||||||
|
anytls.RegisterOutbound(registry)
|
||||||
|
|
||||||
hysteria.RegisterOutbound(registry)
|
hysteria.RegisterOutbound(registry)
|
||||||
tuic.RegisterOutbound(registry)
|
tuic.RegisterOutbound(registry)
|
||||||
@@ -89,6 +104,57 @@ func EndpointRegistry() *endpoint.Registry {
|
|||||||
registry := endpoint.NewRegistry()
|
registry := endpoint.NewRegistry()
|
||||||
|
|
||||||
wireguard.RegisterEndpoint(registry)
|
wireguard.RegisterEndpoint(registry)
|
||||||
|
registerTailscaleEndpoint(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func DNSTransportRegistry() *dns.TransportRegistry {
|
||||||
|
registry := dns.NewTransportRegistry()
|
||||||
|
|
||||||
|
transport.RegisterTCP(registry)
|
||||||
|
transport.RegisterUDP(registry)
|
||||||
|
transport.RegisterTLS(registry)
|
||||||
|
transport.RegisterHTTPS(registry)
|
||||||
|
hosts.RegisterTransport(registry)
|
||||||
|
local.RegisterTransport(registry)
|
||||||
|
fakeip.RegisterTransport(registry)
|
||||||
|
|
||||||
|
registerQUICTransports(registry)
|
||||||
|
registerDHCPTransport(registry)
|
||||||
|
registerTailscaleTransport(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
||||||
|
tailscale.RegisterEndpoint(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
||||||
|
tailscale.RegistryTransport(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDERPService(registry *service.Registry) {
|
||||||
|
derp.Register(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerQUICTransports(registry *dns.TransportRegistry) {
|
||||||
|
quic.RegisterTransport(registry)
|
||||||
|
quic.RegisterHTTP3Transport(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerDHCPTransport(registry *dns.TransportRegistry) {
|
||||||
|
dhcp.RegisterTransport(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ServiceRegistry() *service.Registry {
|
||||||
|
registry := service.NewRegistry()
|
||||||
|
|
||||||
|
resolved.RegisterService(registry)
|
||||||
|
ssmapi.RegisterService(registry)
|
||||||
|
|
||||||
|
registerDERPService(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionInfo struct {
|
||||||
|
ID string
|
||||||
|
Conn net.Conn
|
||||||
|
PacketConn network.PacketConn
|
||||||
|
Inbound string
|
||||||
|
Type string // "tcp" or "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnTracker struct {
|
||||||
|
access sync.Mutex
|
||||||
|
connections map[string]*ConnectionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnTracker() *ConnTracker {
|
||||||
|
return &ConnTracker{
|
||||||
|
connections: make(map[string]*ConnectionInfo),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) generateConnectionID() string {
|
||||||
|
return uuid.Must(uuid.NewV4()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
||||||
|
connID := c.generateConnectionID()
|
||||||
|
connInfo := &ConnectionInfo{
|
||||||
|
ID: connID,
|
||||||
|
Conn: conn,
|
||||||
|
Inbound: metadata.Inbound,
|
||||||
|
Type: "tcp",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.trackConnection(connID, connInfo)
|
||||||
|
|
||||||
|
return c.createWrappedConn(conn, connID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) RoutedPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) network.PacketConn {
|
||||||
|
connID := c.generateConnectionID()
|
||||||
|
connInfo := &ConnectionInfo{
|
||||||
|
ID: connID,
|
||||||
|
PacketConn: conn,
|
||||||
|
Inbound: metadata.Inbound,
|
||||||
|
Type: "udp",
|
||||||
|
}
|
||||||
|
|
||||||
|
c.trackConnection(connID, connInfo)
|
||||||
|
|
||||||
|
return c.createWrappedPacketConn(conn, connID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) CloseConnByInbound(inbound string) int {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
closedCount := 0
|
||||||
|
for connID, connInfo := range c.connections {
|
||||||
|
if connInfo.Inbound == inbound {
|
||||||
|
if connInfo.Conn != nil {
|
||||||
|
connInfo.Conn.Close()
|
||||||
|
}
|
||||||
|
if connInfo.PacketConn != nil {
|
||||||
|
connInfo.PacketConn.Close()
|
||||||
|
}
|
||||||
|
delete(c.connections, connID)
|
||||||
|
closedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return closedCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) trackConnection(connID string, connInfo *ConnectionInfo) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
c.connections[connID] = connInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) untrackConnection(connID string) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
delete(c.connections, connID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) createWrappedConn(conn net.Conn, connID string) *wrappedConn {
|
||||||
|
return &wrappedConn{
|
||||||
|
Conn: conn,
|
||||||
|
connID: connID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) createWrappedPacketConn(conn network.PacketConn, connID string) *wrappedPacketConn {
|
||||||
|
return &wrappedPacketConn{
|
||||||
|
PacketConn: conn,
|
||||||
|
connID: connID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappedConn struct {
|
||||||
|
net.Conn
|
||||||
|
connID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedConn) Close() error {
|
||||||
|
connTracker.untrackConnection(w.connID)
|
||||||
|
return w.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedConn) Upstream() any {
|
||||||
|
return w.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappedPacketConn struct {
|
||||||
|
network.PacketConn
|
||||||
|
connID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedPacketConn) Close() error {
|
||||||
|
connTracker.untrackConnection(w.connID)
|
||||||
|
return w.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrappedPacketConn) Upstream() any {
|
||||||
|
return w.PacketConn
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ package core
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"s-ui/database/model"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -18,27 +19,27 @@ type Counter struct {
|
|||||||
write *atomic.Int64
|
write *atomic.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConnTracker struct {
|
type StatsTracker struct {
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
createdAt time.Time
|
|
||||||
inbounds map[string]Counter
|
inbounds map[string]Counter
|
||||||
outbounds map[string]Counter
|
outbounds map[string]Counter
|
||||||
users map[string]Counter
|
users map[string]Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnTracker() *ConnTracker {
|
func NewStatsTracker() *StatsTracker {
|
||||||
return &ConnTracker{
|
return &StatsTracker{
|
||||||
createdAt: time.Now(),
|
|
||||||
inbounds: make(map[string]Counter),
|
inbounds: make(map[string]Counter),
|
||||||
outbounds: make(map[string]Counter),
|
outbounds: make(map[string]Counter),
|
||||||
users: make(map[string]Counter),
|
users: make(map[string]Counter),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnTracker) getReadCounters(inbound string, outbound string, user string) ([]*atomic.Int64, []*atomic.Int64) {
|
func (c *StatsTracker) getReadCounters(inbound string, outbound string, user string) ([]*atomic.Int64, []*atomic.Int64) {
|
||||||
var readCounter []*atomic.Int64
|
var readCounter []*atomic.Int64
|
||||||
var writeCounter []*atomic.Int64
|
var writeCounter []*atomic.Int64
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
if inbound != "" {
|
if inbound != "" {
|
||||||
readCounter = append(readCounter, c.loadOrCreateCounter(&c.inbounds, inbound).read)
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.inbounds, inbound).read)
|
||||||
writeCounter = append(writeCounter, c.inbounds[inbound].write)
|
writeCounter = append(writeCounter, c.inbounds[inbound].write)
|
||||||
@@ -51,11 +52,10 @@ func (c *ConnTracker) getReadCounters(inbound string, outbound string, user stri
|
|||||||
readCounter = append(readCounter, c.loadOrCreateCounter(&c.users, user).read)
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.users, user).read)
|
||||||
writeCounter = append(writeCounter, c.users[user].write)
|
writeCounter = append(writeCounter, c.users[user].write)
|
||||||
}
|
}
|
||||||
c.access.Unlock()
|
|
||||||
return readCounter, writeCounter
|
return readCounter, writeCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnTracker) loadOrCreateCounter(obj *map[string]Counter, name string) Counter {
|
func (c *StatsTracker) loadOrCreateCounter(obj *map[string]Counter, name string) Counter {
|
||||||
counter, loaded := (*obj)[name]
|
counter, loaded := (*obj)[name]
|
||||||
if loaded {
|
if loaded {
|
||||||
return counter
|
return counter
|
||||||
@@ -65,17 +65,17 @@ func (c *ConnTracker) loadOrCreateCounter(obj *map[string]Counter, name string)
|
|||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnTracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
func (c *StatsTracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
||||||
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||||
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnTracker) RoutedPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) network.PacketConn {
|
func (c *StatsTracker) RoutedPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) network.PacketConn {
|
||||||
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, nil, writeCounter, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnTracker) GetStats() *[]model.Stats {
|
func (c *StatsTracker) GetStats() *[]model.Stats {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package cronjob
|
package cronjob
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"s-ui/service"
|
"github.com/alireza0/s-ui/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckCoreJob struct {
|
type CheckCoreJob struct {
|
||||||
@@ -20,11 +20,13 @@ func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Start stats job
|
// Start stats job
|
||||||
c.cron.AddJob("@every 10s", NewStatsJob())
|
c.cron.AddJob("@every 10s", NewStatsJob(trafficAge > 0))
|
||||||
// Start expiry job
|
// Start expiry job
|
||||||
c.cron.AddJob("@every 1m", NewDepleteJob())
|
c.cron.AddJob("@every 1m", NewDepleteJob())
|
||||||
// Start deleting old stats
|
// Start deleting old stats
|
||||||
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
if trafficAge > 0 {
|
||||||
|
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||||
|
}
|
||||||
// Start core if it is not running
|
// Start core if it is not running
|
||||||
c.cron.AddJob("@every 5s", NewCheckCoreJob())
|
c.cron.AddJob("@every 5s", NewCheckCoreJob())
|
||||||
}()
|
}()
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package cronjob
|
package cronjob
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"s-ui/logger"
|
"github.com/alireza0/s-ui/logger"
|
||||||
"s-ui/service"
|
"github.com/alireza0/s-ui/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DelStatsJob struct {
|
type DelStatsJob struct {
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cronjob
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alireza0/s-ui/database"
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
"github.com/alireza0/s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DepleteJob struct {
|
||||||
|
service.ClientService
|
||||||
|
service.InboundService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDepleteJob() *DepleteJob {
|
||||||
|
return new(DepleteJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DepleteJob) Run() {
|
||||||
|
inboundIds, err := s.ClientService.DepleteClients()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Disable depleted users failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(inboundIds) > 0 {
|
||||||
|
err := s.InboundService.RestartInbounds(database.GetDB(), inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("unable to restart inbounds: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cronjob
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
"github.com/alireza0/s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StatsJob struct {
|
||||||
|
service.StatsService
|
||||||
|
enableTraffic bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatsJob(saveTraffic bool) *StatsJob {
|
||||||
|
return &StatsJob{
|
||||||
|
enableTraffic: saveTraffic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatsJob) Run() {
|
||||||
|
err := s.StatsService.SaveStats(s.enableTraffic)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Get stats failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,17 @@ import (
|
|||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"s-ui/cmd/migration"
|
"runtime"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database/model"
|
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/util/common"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/s-ui/cmd/migration"
|
||||||
|
"github.com/alireza0/s-ui/config"
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
"github.com/alireza0/s-ui/logger"
|
||||||
|
"github.com/alireza0/s-ui/util/common"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -40,6 +42,7 @@ func GetDb(exclude string) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer os.Remove(dbPath)
|
||||||
|
|
||||||
err = backupDb.AutoMigrate(
|
err = backupDb.AutoMigrate(
|
||||||
&model.Setting{},
|
&model.Setting{},
|
||||||
@@ -69,29 +72,50 @@ func GetDb(exclude string) ([]byte, error) {
|
|||||||
// Perform scans and handle errors
|
// Perform scans and handle errors
|
||||||
if err := db.Model(&model.Setting{}).Scan(&settings).Error; err != nil {
|
if err := db.Model(&model.Setting{}).Scan(&settings).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(settings) > 0 {
|
||||||
|
if err := backupDb.Save(settings).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.Tls{}).Scan(&tls).Error; err != nil {
|
if err := db.Model(&model.Tls{}).Scan(&tls).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(tls) > 0 {
|
||||||
|
if err := backupDb.Save(tls).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.Inbound{}).Scan(&inbound).Error; err != nil {
|
if err := db.Model(&model.Inbound{}).Scan(&inbound).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(inbound) > 0 {
|
||||||
|
if err := backupDb.Save(inbound).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.Outbound{}).Scan(&outbound).Error; err != nil {
|
if err := db.Model(&model.Outbound{}).Scan(&outbound).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(outbound) > 0 {
|
||||||
|
if err := backupDb.Save(outbound).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.Endpoint{}).Scan(&endpoint).Error; err != nil {
|
if err := db.Model(&model.Endpoint{}).Scan(&endpoint).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(endpoint) > 0 {
|
||||||
|
if err := backupDb.Save(endpoint).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.User{}).Scan(&users).Error; err != nil {
|
if err := db.Model(&model.User{}).Scan(&users).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if len(users) > 0 {
|
||||||
|
if err := backupDb.Save(users).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.Model(&model.Client{}).Scan(&clients).Error; err != nil {
|
if err := db.Model(&model.Client{}).Scan(&clients).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
} else if len(clients) > 0 {
|
||||||
|
if err := backupDb.Save(clients).Error; err != nil {
|
||||||
// Save each model
|
|
||||||
for _, mdl := range []interface{}{settings, tls, inbound, outbound, endpoint, users, clients} {
|
|
||||||
if err := backupDb.Save(mdl).Error; err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,16 +124,20 @@ func GetDb(exclude string) ([]byte, error) {
|
|||||||
if err := db.Model(&model.Stats{}).Scan(&stats).Error; err != nil {
|
if err := db.Model(&model.Stats{}).Scan(&stats).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := backupDb.Save(stats).Error; err != nil {
|
if len(stats) > 0 {
|
||||||
return nil, err
|
if err := backupDb.Save(stats).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !exclude_changes {
|
if !exclude_changes {
|
||||||
if err := db.Model(&model.Changes{}).Scan(&changes).Error; err != nil {
|
if err := db.Model(&model.Changes{}).Scan(&changes).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := backupDb.Save(changes).Error; err != nil {
|
if len(changes) > 0 {
|
||||||
return nil, err
|
if err := backupDb.Save(changes).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +156,6 @@ func GetDb(exclude string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
defer os.Remove(dbPath)
|
|
||||||
|
|
||||||
// Read the file contents
|
// Read the file contents
|
||||||
fileContents, err := io.ReadAll(file)
|
fileContents, err := io.ReadAll(file)
|
||||||
@@ -262,7 +289,11 @@ func SendSighup() error {
|
|||||||
// Send SIGHUP to the current process
|
// Send SIGHUP to the current process
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
err := process.Signal(syscall.SIGHUP)
|
if runtime.GOOS == "windows" {
|
||||||
|
err = process.Kill()
|
||||||
|
} else {
|
||||||
|
err = process.Signal(syscall.SIGHUP)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("send signal SIGHUP failed:", err)
|
logger.Error("send signal SIGHUP failed:", err)
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database/model"
|
"github.com/alireza0/s-ui/config"
|
||||||
|
"github.com/alireza0/s-ui/database/model"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -67,7 +68,6 @@ func InitDB(dbPath string) error {
|
|||||||
db.Migrator().CreateTable(&model.Outbound{})
|
db.Migrator().CreateTable(&model.Outbound{})
|
||||||
defaultOutbound := []model.Outbound{
|
defaultOutbound := []model.Outbound{
|
||||||
{Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)},
|
{Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)},
|
||||||
{Type: "dns", Tag: "dns-out", Options: json.RawMessage(`{}`)},
|
|
||||||
}
|
}
|
||||||
db.Create(&defaultOutbound)
|
db.Create(&defaultOutbound)
|
||||||
}
|
}
|
||||||
@@ -77,8 +77,10 @@ func InitDB(dbPath string) error {
|
|||||||
&model.Tls{},
|
&model.Tls{},
|
||||||
&model.Inbound{},
|
&model.Inbound{},
|
||||||
&model.Outbound{},
|
&model.Outbound{},
|
||||||
|
&model.Service{},
|
||||||
&model.Endpoint{},
|
&model.Endpoint{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
|
&model.Tokens{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
&model.Changes{},
|
&model.Changes{},
|
||||||
@@ -54,3 +54,12 @@ type Changes struct {
|
|||||||
Action string `json:"action"`
|
Action string `json:"action"`
|
||||||
Obj json.RawMessage `json:"obj"`
|
Obj json.RawMessage `json:"obj"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tokens struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Desc string `json:"desc" form:"desc"`
|
||||||
|
Token string `json:"token" form:"token"`
|
||||||
|
Expiry int64 `json:"expiry" form:"expiry"`
|
||||||
|
UserId uint `json:"userId" form:"userId"`
|
||||||
|
User *User `json:"user" gorm:"foreignKey:UserId;references:Id"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
|
||||||
|
// Foreign key to tls table
|
||||||
|
TlsId uint `json:"tls_id" form:"tls_id"`
|
||||||
|
Tls *Tls `json:"tls" form:"tls" gorm:"foreignKey:TlsId;references:Id"`
|
||||||
|
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Service) UnmarshalJSON(data []byte) error {
|
||||||
|
var err error
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err = json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract fixed fields and store the rest in Options
|
||||||
|
if val, exists := raw["id"].(float64); exists {
|
||||||
|
i.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
i.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
i.Tag, _ = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// TlsId
|
||||||
|
if val, exists := raw["tls_id"].(float64); exists {
|
||||||
|
i.TlsId = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "tls_id")
|
||||||
|
delete(raw, "tls")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
i.Options, err = json.MarshalIndent(raw, "", " ")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (i Service) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = i.Type
|
||||||
|
combined["tag"] = i.Tag
|
||||||
|
if i.Tls != nil {
|
||||||
|
combined["tls"] = i.Tls.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(i.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Service) MarshalFull() (*map[string]interface{}, error) {
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["id"] = i.Id
|
||||||
|
combined["type"] = i.Type
|
||||||
|
combined["tag"] = i.Tag
|
||||||
|
combined["tls_id"] = i.TlsId
|
||||||
|
|
||||||
|
if i.Options != nil {
|
||||||
|
var restFields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(i.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &combined, nil
|
||||||
|
}
|
||||||
Submodule
+1
Submodule frontend added at 2d0e1fd379
@@ -1,4 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
||||||
not ie 11
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[*.{js,jsx,ts,tsx,vue}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/eslint-config-typescript',
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/dist
|
|
||||||
/bin
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# base
|
|
||||||
|
|
||||||
## Project setup
|
|
||||||
|
|
||||||
```
|
|
||||||
# yarn
|
|
||||||
yarn
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# bun
|
|
||||||
bun install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and hot-reloads for development
|
|
||||||
|
|
||||||
```
|
|
||||||
# yarn
|
|
||||||
yarn dev
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm dev
|
|
||||||
|
|
||||||
# bun
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compiles and minifies for production
|
|
||||||
|
|
||||||
```
|
|
||||||
# yarn
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm build
|
|
||||||
|
|
||||||
# bun
|
|
||||||
pnpm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lints and fixes files
|
|
||||||
|
|
||||||
```
|
|
||||||
# yarn
|
|
||||||
yarn lint
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm run lint
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm lint
|
|
||||||
|
|
||||||
# bun
|
|
||||||
pnpm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
### Customize configuration
|
|
||||||
|
|
||||||
See [Configuration Reference](https://vitejs.dev/config/).
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" href="assets/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<script>
|
|
||||||
window.BASE_URL = "{{ .BASE_URL }}"
|
|
||||||
|
|
||||||
// Dev Mode
|
|
||||||
if (window.BASE_URL.charAt(0) === '{') window.BASE_URL = "/app/"
|
|
||||||
</script>
|
|
||||||
<title>S-UI</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
Generated
-3852
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "frontend",
|
|
||||||
"version": "1.2.0-beta.3",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite --host",
|
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"lint": "eslint . --fix --ignore-path .gitignore"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@mdi/font": "7.4.47",
|
|
||||||
"axios": "^1.7.9",
|
|
||||||
"chart.js": "^4.4.7",
|
|
||||||
"clipboard": "^2.0.11",
|
|
||||||
"core-js": "^3.40.0",
|
|
||||||
"moment": "^2.30.1",
|
|
||||||
"notivue": "^2.4.5",
|
|
||||||
"pinia": "^2.3.0",
|
|
||||||
"qrcode.vue": "^3.6.0",
|
|
||||||
"roboto-fontface": "^0.10.0",
|
|
||||||
"vue": "^3.5.13",
|
|
||||||
"vue-chartjs": "^5.3.2",
|
|
||||||
"vue-i18n": "^11.0.1",
|
|
||||||
"vue-router": "^4.5.0",
|
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
|
||||||
"vuetify": "^3.7.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/types": "^7.26.5",
|
|
||||||
"@types/node": "^22.10.7",
|
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
|
||||||
"eslint-plugin-vue": "^9.32.0",
|
|
||||||
"material-design-icons-iconfont": "^6.7.0",
|
|
||||||
"sass": "1.83.4",
|
|
||||||
"typescript": "^5.7.3",
|
|
||||||
"unplugin-fonts": "^1.3.1",
|
|
||||||
"vite": "^6.0.7",
|
|
||||||
"vite-plugin-vuetify": "^2.0.4",
|
|
||||||
"vue-tsc": "^2.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-overlay
|
|
||||||
:model-value="loading"
|
|
||||||
persistent
|
|
||||||
content-class="text-center"
|
|
||||||
class="align-center justify-center"
|
|
||||||
>
|
|
||||||
<v-progress-circular
|
|
||||||
indeterminate
|
|
||||||
size="64"
|
|
||||||
></v-progress-circular>
|
|
||||||
<br />
|
|
||||||
{{ $t('loading') }}
|
|
||||||
</v-overlay>
|
|
||||||
<Message />
|
|
||||||
<router-view />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import Message from '@/components/message.vue'
|
|
||||||
import { inject, ref, Ref } from 'vue'
|
|
||||||
|
|
||||||
const loading:Ref = inject('loading')?? ref(false)
|
|
||||||
|
|
||||||
// Change page title
|
|
||||||
document.title = "S-UI " + document.location.hostname
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.v-overlay .v-list-item,
|
|
||||||
.v-field__input {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 763 B |
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<svg viewBox="1.019 0.0225 45.9789 46.9775" width="45.9789" height="46.9775" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g featurekey="symbolFeature-0" transform="matrix(0.4545450210571289, 0, 0, 0.4545450210571289, 0.7917079329490662, 1.7009549140930176)" fill="#737373">
|
|
||||||
<g xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g>
|
|
||||||
<path d="M50,99.658L0.5,70.699V29.301L50,0.341l49.5,28.959v41.398L50,99.658z M2.5,69.553L50,97.342l47.5-27.789V30.448L50,2.659 L2.5,30.448V69.553z"/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<polygon points="51,98.376 49,98.376 49,58.822 0.995,30.738 2.005,29.011 50,57.091 97.995,29.011 99.005,30.738 51,58.822 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<polyline points="28.494,14.082 76.994,42.457 71.506,45.667 23.006,17.292 "/>
|
|
||||||
<polygon points="71.507,46.246 71.254,46.098 22.754,17.724 23.259,16.861 71.507,45.087 76.003,42.457 28.241,14.514 28.746,13.65 77.983,42.457 "/>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<polyline points="71.506,45.667 71.506,57.982 71.51,57.982 76.993,54.775 76.993,42.457 "/>
|
|
||||||
<polyline points="71.006,45.667 72.006,45.667 72.006,57.113 76.493,54.487 76.493,42.457 77.493,42.457 77.493,55.062 71.646,58.482 71,58.85 "/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g featurekey="nameFeature-0" transform="matrix(1.6160469055175781, 0, 0, 1.6160469055175781, 3.2854819297790527, -21.369783401489258)" fill="#a6a6a6">
|
|
||||||
<path d="M10.904 40.4028 c-5.316 0 -9.2256 -3.048 -9.2256 -6.6192 c0 -1.8592 1.2372 -2.8152 2.5116 -2.8152 c1.0924 0 2.2288 0.7184 2.2288 2.1488 c0 1.4428 -1.4112 1.9972 -1.4112 2.9972 c0 1.782 2.974 3.0552 5.338 3.0552 c3.244 0 6.382 -1.5736 6.382 -5.102 c0 -6.2716 -14.403 -3.6536 -14.403 -13.229 c0 -5.0924 4.3436 -7.6012 9.9036 -7.6012 c4.7364 0 8.9656 2.6496 8.9656 6.1048 c0 1.946 -1.2372 2.9308 -2.4828 2.9308 c-1.1216 0 -2.258 -0.7476 -2.258 -2.178 c0 -1.5584 1.382 -1.8812 1.382 -2.8812 c0 -1.6952 -2.974 -2.7436 -5.3092 -2.7436 c-3.04 0 -5.9296 1.1848 -5.9296 4.6496 c0 5.866 15.193 3.7708 15.193 13.345 c0 4.0208 -3.8304 7.938 -10.886 7.938 z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.addr')"
|
|
||||||
hide-details
|
|
||||||
required
|
|
||||||
v-model="addr.server">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.port')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
required
|
|
||||||
v-model.number="addr.server_port"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionRemark">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('in.remark')"
|
|
||||||
hide-details
|
|
||||||
v-model="addr.remark">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<OutTLS :outbound="addr" v-if="optionTLS" />
|
|
||||||
<v-row>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-col cols="auto" align="end" justify="center">
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('in.mdOption') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRemark" color="primary" :label="$t('in.remark')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="hasTls">
|
|
||||||
<v-switch v-model="optionTLS" color="primary" :label="$t('objects.tls')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import OutTLS from '@/components/tls/OutTLS.vue'
|
|
||||||
export default {
|
|
||||||
props: ['addr', 'hasTls'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
optionTLS: {
|
|
||||||
get(): boolean { return this.$props.addr.tls != undefined },
|
|
||||||
set(v:boolean) { this.$props.addr.tls = v ? { enabled: true } : undefined; }
|
|
||||||
},
|
|
||||||
optionRemark: {
|
|
||||||
get(): boolean { return this.$props.addr.remark != undefined },
|
|
||||||
set(v:boolean) { this.$props.addr.remark = v ? '' : undefined }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
OutTLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-text-field
|
|
||||||
id="expiry"
|
|
||||||
:label="$t('date.expiry')"
|
|
||||||
v-model="dateFormatted"
|
|
||||||
prepend-inner-icon="mdi-calendar"
|
|
||||||
readonly
|
|
||||||
hide-details
|
|
||||||
></v-text-field>
|
|
||||||
<DatePicker
|
|
||||||
v-model="Input"
|
|
||||||
@input="Input=$event"
|
|
||||||
:locale="locale"
|
|
||||||
element="expiry"
|
|
||||||
compact-time
|
|
||||||
type="datetime">
|
|
||||||
<template v-slot:next-month>
|
|
||||||
<v-icon icon="mdi-chevron-right" />
|
|
||||||
</template>
|
|
||||||
<template v-slot:prev-month>
|
|
||||||
<v-icon icon="mdi-chevron-left" />
|
|
||||||
</template>
|
|
||||||
<template #submit-btn="{ submit, canSubmit }">
|
|
||||||
<v-btn
|
|
||||||
:disabled="!canSubmit"
|
|
||||||
@click="submit"
|
|
||||||
>{{ $t('submit') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<template #cancel-btn="{ vm }">
|
|
||||||
<v-btn
|
|
||||||
@click="reset(vm)"
|
|
||||||
>{{ $t('reset') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<template #now-btn="{ goToday }">
|
|
||||||
<v-btn
|
|
||||||
@click="goToday"
|
|
||||||
>{{ $t('now') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
</DatePicker>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import DatePicker from 'vue3-persian-datetime-picker'
|
|
||||||
import { i18n } from '@/locales'
|
|
||||||
import 'moment/locale/vi'
|
|
||||||
import 'moment/locale/zh-cn'
|
|
||||||
import 'moment/locale/zh-tw'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['expiry'],
|
|
||||||
emits: ['submit'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
input: new Date(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { DatePicker },
|
|
||||||
computed: {
|
|
||||||
locale() {
|
|
||||||
const l = i18n.global.locale.value
|
|
||||||
switch (l) {
|
|
||||||
case "zhHans":
|
|
||||||
return "zh-cn"
|
|
||||||
case "zhHant":
|
|
||||||
return "zh-tw"
|
|
||||||
default:
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dateFormatted() {
|
|
||||||
if (this.expDate == 0) return i18n.global.t('unlimited')
|
|
||||||
const date = new Date(this.expDate*1000)
|
|
||||||
return date.toLocaleString(this.locale)
|
|
||||||
},
|
|
||||||
expDate() {
|
|
||||||
return parseInt(this.expiry?? 0)
|
|
||||||
},
|
|
||||||
Input: {
|
|
||||||
get() { return this.expDate == 0 ? new Date() : new Date(this.expDate*1000) },
|
|
||||||
set(v:string) {
|
|
||||||
this.input = new Date(v)
|
|
||||||
this.submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateInput(v:Date) {
|
|
||||||
this.input = v
|
|
||||||
},
|
|
||||||
setNow() {
|
|
||||||
this.input = new Date()
|
|
||||||
},
|
|
||||||
submit() {
|
|
||||||
this.$emit('submit',Math.floor(this.input.getTime()/1000))
|
|
||||||
},
|
|
||||||
reset(vm:any) {
|
|
||||||
this.$emit('submit',0)
|
|
||||||
this.input = new Date()
|
|
||||||
vm.visible = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
menu(v) {
|
|
||||||
if (v) {
|
|
||||||
this.input = this.expiry == 0 ? new Date() : new Date(this.expDate*1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.vpd-addon-list,
|
|
||||||
.vpd-addon-list-item {
|
|
||||||
background-color: rgb(var(--v-theme-background)) !important;
|
|
||||||
border-color: rgb(var(--v-theme-background)) !important;
|
|
||||||
}
|
|
||||||
.vpd-content {
|
|
||||||
background-color: rgb(var(--v-theme-background)) !important;
|
|
||||||
}
|
|
||||||
.vpd-addon-list-item.vpd-selected,
|
|
||||||
.vpd-addon-list-item:hover {
|
|
||||||
background-color: rgb(var(--v-theme-primary)) !important;
|
|
||||||
}
|
|
||||||
.vpd-close-addon {
|
|
||||||
color: rgb(var(--v-theme-on-surface)) !important;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.vpd-controls {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
.vpd-month-label {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.vpd-actions button:hover {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
.vpd-wrapper[data-type=datetime].vpd-compact-time .vpd-time {
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
.vpd-time .vpd-time-h .vpd-counter-item,
|
|
||||||
.vpd-time .vpd-time-m .vpd-counter-item {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('objects.dial')" style="background-color: inherit;">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('dial.detourText')"
|
|
||||||
:items="outTags"
|
|
||||||
v-model="dial.detour">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionBind">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('dial.bindIf')"
|
|
||||||
hide-details
|
|
||||||
v-model="dial.bind_interface"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('dial.bindIp4')"
|
|
||||||
hide-details
|
|
||||||
v-model="dial.inet4_bind_address"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('dial.bindIp6')"
|
|
||||||
hide-details
|
|
||||||
v-model="dial.inet6_bind_address"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionRM">
|
|
||||||
<v-text-field
|
|
||||||
label="Linux Routing Mark"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="routingMark"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
|
||||||
<v-switch v-model="dial.reuse_addr" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionTCP">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="dial.tcp_fast_open" color="primary" label="TCP Fast Open" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="dial.tcp_multi_path" color="primary" label="TCP Multi Path" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionUDP">
|
|
||||||
<v-switch v-model="dial.udp_fragment" color="primary" label="UDP Fragment" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('dial.connTimeout')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
:suffix="$t('date.s')"
|
|
||||||
v-model.number="connectTimeout"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionDS">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('listen.domainStrategy')"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
|
||||||
v-model="dial.domain_strategy">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('dial.fbTimeout')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="50"
|
|
||||||
step="50"
|
|
||||||
:suffix="$t('date.ms')"
|
|
||||||
v-model.number="fallbackDelay"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions class="pt-0">
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('dial.options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionBind" color="primary" :label="$t('dial.bindIf')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionIPV4" color="primary" :label="$t('dial.bindIp4')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionIPV6" color="primary" :label="$t('dial.bindIp6')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRA" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionCT" color="primary" :label="$t('dial.connTimeout')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['dial', 'outTags'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
fallbackDelay: {
|
|
||||||
get() { return this.$props.dial.fallback_delay ? parseInt(this.$props.dial.fallback_delay.replace('ms','')) : 300 },
|
|
||||||
set(newValue:number) { this.$props.dial.fallback_delay = newValue > 0 ? newValue + 'ms' : '300ms' }
|
|
||||||
},
|
|
||||||
connectTimeout: {
|
|
||||||
get() { return this.$props.dial.connect_timeout ? parseInt(this.$props.dial.connect_timeout.replace('s','')) : 5 },
|
|
||||||
set(newValue:number) { this.$props.dial.connect_timeout = newValue > 0 ? newValue + 's' : '5s' }
|
|
||||||
},
|
|
||||||
routingMark: {
|
|
||||||
get() { return this.$props.dial.routing_mark?? 0 },
|
|
||||||
set(newValue:number) { this.$props.dial.routing_mark = newValue > 0 ? newValue : 0 }
|
|
||||||
},
|
|
||||||
optionDetour: {
|
|
||||||
get(): boolean { return this.$props.dial.detour != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.detour = this.outTags[0]?? '' : delete this.$props.dial.detour }
|
|
||||||
},
|
|
||||||
optionBind: {
|
|
||||||
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.bind_interface = '' : delete this.$props.dial.bind_interface }
|
|
||||||
},
|
|
||||||
optionIPV4: {
|
|
||||||
get(): boolean { return this.$props.dial.inet4_bind_address != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.inet4_bind_address = '' : delete this.$props.dial.inet4_bind_address }
|
|
||||||
},
|
|
||||||
optionIPV6: {
|
|
||||||
get(): boolean { return this.$props.dial.inet6_bind_address != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.inet6_bind_address = '' : delete this.$props.dial.inet6_bind_address }
|
|
||||||
},
|
|
||||||
optionRM: {
|
|
||||||
get(): boolean { return this.$props.dial.routing_mark != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.routing_mark = 0 : delete this.$props.dial.routing_mark }
|
|
||||||
},
|
|
||||||
optionRA: {
|
|
||||||
get(): boolean { return this.$props.dial.reuse_addr != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.reuse_addr = true : delete this.$props.dial.reuse_addr }
|
|
||||||
},
|
|
||||||
optionTCP: {
|
|
||||||
get(): boolean {
|
|
||||||
return this.$props.dial.tcp_fast_open != undefined &&
|
|
||||||
this.$props.dial.tcp_multi_path != undefined
|
|
||||||
},
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.dial.tcp_fast_open = false
|
|
||||||
this.$props.dial.tcp_multi_path = false
|
|
||||||
} else {
|
|
||||||
delete this.$props.dial.tcp_fast_open
|
|
||||||
delete this.$props.dial.tcp_multi_path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionUDP: {
|
|
||||||
get(): boolean { return this.$props.dial.udp_fragment != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.udp_fragment = true : delete this.$props.dial.udp_fragment }
|
|
||||||
},
|
|
||||||
optionCT: {
|
|
||||||
get(): boolean { return this.$props.dial.connect_timeout != undefined },
|
|
||||||
set(v:boolean) { v ? this.$props.dial.connect_timeout = '5s' : delete this.$props.dial.connect_timeout }
|
|
||||||
},
|
|
||||||
optionDS: {
|
|
||||||
get(): boolean { return this.$props.dial.domain_strategy != undefined },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.dial.domain_strategy = 'prefer_ipv4'
|
|
||||||
this.$props.dial.fallback_delay = '300ms'
|
|
||||||
} else {
|
|
||||||
delete this.$props.dial.domain_strategy
|
|
||||||
delete this.$props.dial.fallback_delay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card>
|
|
||||||
<v-card-subtitle>
|
|
||||||
{{ $t('objects.headers') }}
|
|
||||||
<v-icon @click="add_header" icon="mdi-plus"></v-icon>
|
|
||||||
</v-card-subtitle>
|
|
||||||
<v-row v-for="(header, index) in hdrs">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('objects.key')"
|
|
||||||
hide-details
|
|
||||||
@input="update_key(index,$event.target.value)"
|
|
||||||
v-model="header.name">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('objects.value')"
|
|
||||||
hide-details
|
|
||||||
@input="update_value(index,$event.target.value)"
|
|
||||||
append-icon="mdi-delete"
|
|
||||||
@click:append="del_header(index)"
|
|
||||||
v-model="header.value">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
type Header = {
|
|
||||||
name: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
add_header() {
|
|
||||||
this.hdrs = [...this.hdrs, {name: "Host", value: ""}]
|
|
||||||
},
|
|
||||||
del_header(i:number) {
|
|
||||||
let h = this.hdrs
|
|
||||||
h.splice(i,1)
|
|
||||||
this.hdrs = h
|
|
||||||
},
|
|
||||||
update_key(i:number,k:string) {
|
|
||||||
let h = this.hdrs
|
|
||||||
h[i].name = k
|
|
||||||
this.hdrs = h
|
|
||||||
},
|
|
||||||
update_value(i:number,v:string) {
|
|
||||||
let h = this.hdrs
|
|
||||||
h[i].value = v
|
|
||||||
this.hdrs = h
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hdrs: {
|
|
||||||
get() :Header[] {
|
|
||||||
let headers: Header[] = []
|
|
||||||
const h = this.$props.data.headers
|
|
||||||
if (h) {
|
|
||||||
Object.keys(h).forEach(key => {
|
|
||||||
if (Array.isArray(h[key])){
|
|
||||||
h[key].forEach((v:string) => headers.push({ name: key, value: v }))
|
|
||||||
} else {
|
|
||||||
headers.push({ name: key, value: h[key] })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
},
|
|
||||||
set(v:Header[]) {
|
|
||||||
if (v.length>0) {
|
|
||||||
let headers:any = {}
|
|
||||||
v.forEach((h:Header) => {
|
|
||||||
if (headers[h.name]) {
|
|
||||||
if (Array.isArray(headers[h.name])) {
|
|
||||||
headers[h.name].push(h.value)
|
|
||||||
} else {
|
|
||||||
headers[h.name] = [headers[h.name], h.value]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
headers[h.name] = h.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.$props.data.headers = headers
|
|
||||||
} else {
|
|
||||||
this.$props.data.headers = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('objects.listen')">
|
|
||||||
<v-row v-if="inbound.type != 'tun'">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('in.addr')"
|
|
||||||
hide-details
|
|
||||||
required
|
|
||||||
v-model="inbound.listen">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('in.port')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
required
|
|
||||||
v-model.number="inbound.listen_port"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
|
||||||
<v-select
|
|
||||||
:label="$t('listen.detourText')"
|
|
||||||
hide-details
|
|
||||||
:items="inTags"
|
|
||||||
v-model="inbound.detour">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionTCP">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.tcp_fast_open" color="primary" label="TCP Fast Open" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.tcp_multi_path" color="primary" label="TCP Multi Path" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionUDP">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.udp_fragment" color="primary" label="UDP Fragment" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="UDP NAT expiration"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
:suffix="$t('date.m')"
|
|
||||||
v-model.number="udpTimeout"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions class="pt-0" v-if="inbound.type != 'tun'">
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('listen.options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['inbound', 'inTags'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
udpTimeout: {
|
|
||||||
get() { return this.$props.inbound.udp_timeout ? parseInt(this.$props.inbound.udp_timeout.replace('m','')) : 5 },
|
|
||||||
set(newValue:number) { this.$props.inbound.udp_timeout = newValue > 0 ? newValue + 'm' : '5m' }
|
|
||||||
},
|
|
||||||
optionTCP: {
|
|
||||||
get(): boolean {
|
|
||||||
return this.$props.inbound.tcp_fast_open != undefined &&
|
|
||||||
this.$props.inbound.tcp_multi_path != undefined
|
|
||||||
},
|
|
||||||
set(v:boolean) {
|
|
||||||
this.$props.inbound.tcp_fast_open = v ? false : undefined
|
|
||||||
this.$props.inbound.tcp_multi_path = v ? false : undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionUDP: {
|
|
||||||
get(): boolean {
|
|
||||||
return this.$props.inbound.udp_fragment != undefined &&
|
|
||||||
this.$props.inbound.udp_timeout != undefined
|
|
||||||
},
|
|
||||||
set(v:boolean) {
|
|
||||||
this.$props.inbound.udp_fragment = v ? false : undefined
|
|
||||||
this.$props.inbound.udp_timeout = v ? '5m' : undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionDetour: {
|
|
||||||
get(): boolean { return this.$props.inbound.detour != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
<template>
|
|
||||||
<LogVue v-model="logModal.visible" :control="logModal" :visible="logModal.visible" />
|
|
||||||
<Backup v-model="backupModal.visible" :control="backupModal" :visible="backupModal.visible" />
|
|
||||||
<v-container class="fill-height" :loading="loading">
|
|
||||||
<v-responsive :class="reloadItems.length>0 ? 'fill-height text-center' : 'align-center'" >
|
|
||||||
<v-row class="d-flex align-center justify-center">
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-img src="@/assets/logo.svg" :width="reloadItems.length>0 ? 100 : 200"></v-img>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row class="d-flex align-center justify-center">
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-dialog v-model="menu" :close-on-content-click="false" transition="scale-transition" max-width="800">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('main.tiles') }} <v-icon icon="mdi-star-plus" /></v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card rounded="xl">
|
|
||||||
<v-card-title>
|
|
||||||
<v-row>
|
|
||||||
<v-col>
|
|
||||||
{{ $t('main.tiles') }}
|
|
||||||
</v-col>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-col cols="auto"><v-icon icon="mdi-close" @click="menu = false"></v-icon></v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-title>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-for="items in menuItems">
|
|
||||||
<v-card variant="flat" :title="items.title">
|
|
||||||
<v-list v-for="item in items.value">
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch
|
|
||||||
v-model="reloadItems"
|
|
||||||
:value="item.value"
|
|
||||||
color="primary"
|
|
||||||
:label="item.title"
|
|
||||||
hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
<v-btn variant="tonal" hide-details style="margin-inline-start: 10px;" @click="backupModal.visible = true">{{ $t('main.backup.title') }} <v-icon icon="mdi-backup-restore" /></v-btn>
|
|
||||||
<v-btn variant="tonal" hide-details style="margin-inline-start: 10px;" @click="logModal.visible = true">{{ $t('basic.log.title') }} <v-icon icon="mdi-list-box-outline" /></v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
|
||||||
<v-card class="rounded-lg" variant="outlined" height="210px"
|
|
||||||
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
|
||||||
<v-card-text style="padding: 0 16px;" align="center" justify="center">
|
|
||||||
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
|
||||||
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
|
|
||||||
<template v-if="i == 'i-sys'">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="3">{{ $t('main.info.host') }}</v-col>
|
|
||||||
<v-col cols="9" style="text-wrap: nowrap; overflow: hidden">{{ tilesData.sys?.hostName }}</v-col>
|
|
||||||
<v-col cols="3">{{ $t('main.info.cpu') }}</v-col>
|
|
||||||
<v-col cols="9">
|
|
||||||
<v-chip density="compact" variant="flat">
|
|
||||||
<v-tooltip activator="parent" location="top" style="direction: ltr;">
|
|
||||||
{{ tilesData.sys?.cpuType }}
|
|
||||||
</v-tooltip>
|
|
||||||
{{ tilesData.sys?.cpuCount }} {{ $t('main.info.core') }}
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="3">IP</v-col>
|
|
||||||
<v-col cols="9">
|
|
||||||
<v-chip density="compact" color="primary" variant="flat" v-if="tilesData.sys?.ipv4?.length>0">
|
|
||||||
<v-tooltip activator="parent" location="top" style="direction: ltr;">
|
|
||||||
<span v-html="tilesData.sys?.ipv4?.join('<br />')"></span>
|
|
||||||
</v-tooltip>
|
|
||||||
IPv4
|
|
||||||
</v-chip>
|
|
||||||
<v-chip density="compact" color="primary" variant="flat" v-if="tilesData.sys?.ipv6?.length>0">
|
|
||||||
<v-tooltip activator="parent" location="top" style="direction: ltr;">
|
|
||||||
<span v-html="tilesData.sys?.ipv6?.join('<br />')"></span>
|
|
||||||
</v-tooltip>
|
|
||||||
IPv6
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="3">S-UI</v-col>
|
|
||||||
<v-col cols="9">
|
|
||||||
<v-chip density="compact" color="blue">
|
|
||||||
v{{ tilesData.sys?.appVersion }}
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="3">{{ $t('main.info.uptime') }}</v-col>
|
|
||||||
<v-col cols="9">{{ HumanReadable.formatSecond(tilesData.uptime) }}</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<template v-if="i == 'i-sbd'">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="4">{{ $t('main.info.running') }}</v-col>
|
|
||||||
<v-col cols="8">
|
|
||||||
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
|
||||||
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
|
||||||
<v-chip density="compact" color="transparent" v-if="tilesData.sbd?.running && !loading" style="cursor: pointer;" @click="restartSingbox()">
|
|
||||||
<v-tooltip activator="parent" location="top">
|
|
||||||
{{ $t('actions.restartSb') }}
|
|
||||||
</v-tooltip>
|
|
||||||
<v-icon icon="mdi-restart" color="warning" />
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="4">{{ $t('main.info.memory') }}</v-col>
|
|
||||||
<v-col cols="8">
|
|
||||||
<v-chip density="compact" color="primary" variant="flat" v-if="tilesData.sbd?.stats?.Alloc">
|
|
||||||
{{ HumanReadable.sizeFormat(tilesData.sbd?.stats?.Alloc) }}
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="4">{{ $t('main.info.threads') }}</v-col>
|
|
||||||
<v-col cols="8">
|
|
||||||
<v-chip density="compact" color="primary" variant="flat" v-if="tilesData.sbd?.stats?.NumGoroutine">
|
|
||||||
{{ tilesData.sbd?.stats?.NumGoroutine }}
|
|
||||||
</v-chip>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="4">{{ $t('main.info.uptime') }}</v-col>
|
|
||||||
<v-col cols="8">{{ HumanReadable.formatSecond(tilesData.sbd?.stats?.Uptime) }}</v-col>
|
|
||||||
<v-col cols="4">{{ $t('online') }}</v-col>
|
|
||||||
<v-col cols="8">
|
|
||||||
<template v-if="tilesData.sbd?.running">
|
|
||||||
<v-chip density="compact" color="primary" variant="flat" v-if="Data().onlines.user">
|
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('pages.clients')" />
|
|
||||||
{{ Data().onlines.user?.length }}
|
|
||||||
</v-chip>
|
|
||||||
<v-chip density="compact" color="success" variant="flat" v-if="Data().onlines.inbound">
|
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('pages.inbounds')" />
|
|
||||||
{{ Data().onlines.inbound?.length }}
|
|
||||||
</v-chip>
|
|
||||||
<v-chip density="compact" color="info" variant="flat" v-if="Data().onlines.outbound">
|
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('pages.outbounds')" />
|
|
||||||
{{ Data().onlines.outbound?.length }}
|
|
||||||
</v-chip>
|
|
||||||
</template>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-responsive>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import HttpUtils from '@/plugins/httputil'
|
|
||||||
import { HumanReadable } from '@/plugins/utils'
|
|
||||||
import Data from '@/store/modules/data'
|
|
||||||
import Gauge from '@/components/tiles/Gauge.vue'
|
|
||||||
import History from '@/components/tiles/History.vue'
|
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
||||||
import { i18n } from '@/locales'
|
|
||||||
import LogVue from '@/layouts/modals/Logs.vue'
|
|
||||||
import Backup from '@/layouts/modals/Backup.vue'
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const menu = ref(false)
|
|
||||||
const menuItems = [
|
|
||||||
{ title: i18n.global.t('main.gauges'), value: [
|
|
||||||
{ title: i18n.global.t('main.gauge.cpu'), value: "g-cpu" },
|
|
||||||
{ title: i18n.global.t('main.gauge.mem'), value: "g-mem" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ title: i18n.global.t('main.charts'), value: [
|
|
||||||
{ title: i18n.global.t('main.chart.cpu'), value: "h-cpu" },
|
|
||||||
{ title: i18n.global.t('main.chart.mem'), value: "h-mem" },
|
|
||||||
{ title: i18n.global.t('main.chart.net'), value: "h-net" },
|
|
||||||
{ title: i18n.global.t('main.chart.pnet'), value: "hp-net" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{ title: i18n.global.t('main.infos'), value: [
|
|
||||||
{ title: i18n.global.t('main.info.sys'), value: "i-sys" },
|
|
||||||
{ title: i18n.global.t('main.info.sbd'), value: "i-sbd" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const tilesData = ref(<any>{})
|
|
||||||
|
|
||||||
const reloadItems = computed({
|
|
||||||
get() { return Data().reloadItems },
|
|
||||||
set(v:string[]) {
|
|
||||||
if (Data().reloadItems.length == 0 && v.length>0) startTimer()
|
|
||||||
if (Data().reloadItems.length > 0 && v.length == 0) stopTimer()
|
|
||||||
Data().reloadItems = v
|
|
||||||
v.length>0 ? localStorage.setItem("reloadItems",v.join(',')) : localStorage.removeItem("reloadItems")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const reloadData = async () => {
|
|
||||||
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
|
|
||||||
const data = await HttpUtils.get('api/status',{ r: request.join(',')})
|
|
||||||
if (data.success) {
|
|
||||||
tilesData.value = data.obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let intervalId: NodeJS.Timeout | null = null
|
|
||||||
|
|
||||||
const startTimer = () => {
|
|
||||||
intervalId = setInterval(() => {
|
|
||||||
reloadData()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopTimer = () => {
|
|
||||||
if (intervalId) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
intervalId = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (Data().reloadItems.length != 0) {
|
|
||||||
reloadData()
|
|
||||||
startTimer()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
stopTimer()
|
|
||||||
})
|
|
||||||
|
|
||||||
const logModal = ref({ visible: false })
|
|
||||||
|
|
||||||
const backupModal = ref({ visible: false })
|
|
||||||
|
|
||||||
const restartSingbox = async () => {
|
|
||||||
loading.value = true
|
|
||||||
await HttpUtils.post('api/restartSb',{})
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('objects.multiplex')">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" :label="$t('mux.enable')" v-model="muxEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<template v-if="mux.enabled">
|
|
||||||
<template v-if="direction=='out'">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="[ 'smux', 'yamux', 'h2mux']"
|
|
||||||
:label="$t('protocol')"
|
|
||||||
clearable
|
|
||||||
@click:clear="mux.protocol=undefined"
|
|
||||||
v-model="mux.protocol">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('mux.maxConn')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min=0
|
|
||||||
v-model.number="max_connections">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('mux.minStr')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min=0
|
|
||||||
v-model.number="min_streams">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('mux.maxStr')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:min="min_streams"
|
|
||||||
v-model.number="max_streams">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" :label="$t('mux.padding')" v-model="mux.padding" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" :label="$t('mux.enableBrutal')" v-model="burtalEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="mux.brutal?.enabled">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.upload')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
v-model.number="up_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.download')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
min="0"
|
|
||||||
v-model.number="down_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { oMultiplex } from '@/types/multiplex'
|
|
||||||
export default {
|
|
||||||
props: ['data', 'direction'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mux(): oMultiplex {
|
|
||||||
if (!Object.hasOwn(this.$props.data,"multiplex")) this.$props.data.multiplex = {}
|
|
||||||
return <oMultiplex> this.$props.data.multiplex
|
|
||||||
},
|
|
||||||
muxEnable: {
|
|
||||||
get(): boolean { return this.mux ? this.mux.enabled : false },
|
|
||||||
set(newValue:boolean) { this.$props.data.multiplex = newValue ? { enabled: newValue } : {} }
|
|
||||||
},
|
|
||||||
max_connections: {
|
|
||||||
get(): number { return this.mux.max_connections ? this.mux.max_connections : 0 },
|
|
||||||
set(newValue:number) { this.mux.max_connections = newValue > 0 ? newValue : undefined }
|
|
||||||
},
|
|
||||||
min_streams: {
|
|
||||||
get(): number { return this.mux.min_streams ? this.mux.min_streams : 0 },
|
|
||||||
set(newValue:number) { this.mux.min_streams = newValue > 0 ? newValue : undefined }
|
|
||||||
},
|
|
||||||
max_streams: {
|
|
||||||
get(): number { return this.mux.max_streams ? this.mux.max_streams : 0 },
|
|
||||||
set(newValue:number) { this.mux.max_streams = newValue > 0 ? newValue : undefined }
|
|
||||||
},
|
|
||||||
burtalEnable: {
|
|
||||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
|
||||||
set(newValue:boolean) { this.mux.brutal = newValue ? { enabled: newValue, up_mbps: 100, down_mbps: 100 } : undefined }
|
|
||||||
},
|
|
||||||
down_mbps: {
|
|
||||||
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
|
||||||
set(newValue:any) {
|
|
||||||
if (this.mux.brutal){
|
|
||||||
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
up_mbps: {
|
|
||||||
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
|
||||||
set(newValue:any) {
|
|
||||||
if (this.mux.brutal){
|
|
||||||
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('network')"
|
|
||||||
:items="networks"
|
|
||||||
v-model="Network">
|
|
||||||
</v-select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
networks: [
|
|
||||||
{ title: "TCP/UDP", value: '' },
|
|
||||||
{ title: "TCP", value: 'tcp' },
|
|
||||||
{ title: "UDP", value: 'udp' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
Network: {
|
|
||||||
get():string { return this.$props.data.network?? '' },
|
|
||||||
set(v:string) { this.$props.data.network = v != '' ? v : undefined }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="type">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.SOCKS">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="['4','4a','5']"
|
|
||||||
:label="$t('version')"
|
|
||||||
v-model="inData.out_json.version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
|
||||||
<Network :data="inData.out_json" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
|
||||||
<UoT :data="inData.out_json" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('transport.path')"
|
|
||||||
hide-details
|
|
||||||
v-model="inData.out_json.path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('types.vless.udpEnc')"
|
|
||||||
:items="['none','packetaddr','xudp']"
|
|
||||||
v-model="packet_encoding">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<template v-if="type == inTypes.VMess">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('types.vmess.security')"
|
|
||||||
:items="vmessSecurities"
|
|
||||||
v-model="inData.out_json.security">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inData.out_json.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inData.out_json.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
|
||||||
<v-text-field
|
|
||||||
label="Recv window"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="inData.out_json.recv_window">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<template v-if="type == inTypes.TUIC">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="UDP Relay Mode"
|
|
||||||
:items="['native', 'quic']"
|
|
||||||
clearable
|
|
||||||
@click:clear="delete inData.out_json.udp_relay_mode"
|
|
||||||
v-model="inData.out_json.udp_relay_mode">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.out_json.udp_over_stream" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</template>
|
|
||||||
</v-row>
|
|
||||||
<Headers :data="inData.out_json" v-if="type == inTypes.HTTP" />
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { InTypes } from '@/types/inbounds'
|
|
||||||
import Network from './Network.vue'
|
|
||||||
import UoT from './UoT.vue'
|
|
||||||
import Headers from './Headers.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['inData', 'type'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
inTypes: InTypes,
|
|
||||||
vmessSecurities: [
|
|
||||||
"auto",
|
|
||||||
"none",
|
|
||||||
"zero",
|
|
||||||
"aes-128-gcm",
|
|
||||||
"aes-128-ctr",
|
|
||||||
"chacha20-poly1305",
|
|
||||||
],
|
|
||||||
haveNetwork: [
|
|
||||||
InTypes.SOCKS,
|
|
||||||
InTypes.Shadowsocks,
|
|
||||||
InTypes.VMess,
|
|
||||||
InTypes.Trojan,
|
|
||||||
InTypes.Hysteria,
|
|
||||||
InTypes.VLESS,
|
|
||||||
InTypes.TUIC,
|
|
||||||
InTypes.Hysteria2,
|
|
||||||
],
|
|
||||||
havUoT: [
|
|
||||||
InTypes.SOCKS,
|
|
||||||
InTypes.Shadowsocks,
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
|
||||||
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
|
||||||
packet_encoding: {
|
|
||||||
get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; },
|
|
||||||
set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Network, UoT, Headers }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,382 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card style="background-color: inherit;">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" v-if="optionInbound">
|
|
||||||
<v-combobox
|
|
||||||
v-model="rule.inbound"
|
|
||||||
:items="inTags"
|
|
||||||
:label="$t('pages.inbounds')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" v-if="optionClient">
|
|
||||||
<v-combobox
|
|
||||||
v-model="rule.auth_user"
|
|
||||||
:items="clients"
|
|
||||||
:label="$t('pages.clients')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPver">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('rule.ipVer')"
|
|
||||||
:items="[4,6]"
|
|
||||||
v-model.number="rule.ip_version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="optionProtocol">
|
|
||||||
<v-combobox
|
|
||||||
v-model="rule.protocol"
|
|
||||||
:items="['http','tls', 'quic', 'stun', 'dns']"
|
|
||||||
:label="$t('protocol')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionDomain">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="domainKeys"
|
|
||||||
@update:model-value="updateDomainOption($event)"
|
|
||||||
v-model="domainOption">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.domain != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.domain') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="domain"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.domain_suffix != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.domainSufix') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="domain_suffix"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.domain_keyword != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.domainKw') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="domain_keyword"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.domain_regex != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.domainRgx') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="domain_regex"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.ip_cidr != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.ip') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="ip_cidr"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.ip_is_private != undefined">
|
|
||||||
<v-switch v-model="rule.ip_is_private" color="primary" :label="$t('rule.privateIp')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionPort">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="portKeys"
|
|
||||||
@update:model-value="updatePortOption($event)"
|
|
||||||
v-model="portOption">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.port != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.port') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="port"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.port_range != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.portRange') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="port_range"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionSrcIP">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="srcIPKeys"
|
|
||||||
@update:model-value="updateSrcIPOption($event)"
|
|
||||||
v-model="srcIPOption">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.source_ip_cidr != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.srcCidr') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="source_ip_cidr"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.source_ip_is_private != undefined">
|
|
||||||
<v-switch v-model="rule.source_ip_is_private" color="primary" :label="$t('rule.srcPrivateIp')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionSrcPort">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="srcPortKeys"
|
|
||||||
@update:model-value="updateSrcPortOption($event)"
|
|
||||||
v-model="srcPortOption">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.source_port != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.srcPort') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="source_port"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" v-if="rule.source_port_range != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.srcPortRange') + ' ' + $t('commaSeparated')"
|
|
||||||
hide-details
|
|
||||||
v-model="source_port_range"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="optionRuleSet">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-combobox
|
|
||||||
v-model="rule.rule_set"
|
|
||||||
:items="rsTags"
|
|
||||||
:label="$t('rule.ruleset')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-switch v-model="rule.rule_set_ipcidr_match_source" color="primary" :label="$t('rule.rulesetMatchSrc')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('rule.options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionInbound" color="primary" :label="$t('pages.inbounds')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionClient" color="primary" :label="$t('pages.clients')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionIPver" color="primary" :label="$t('rule.ipVer')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionProtocol" color="primary" :label="$t('protocol')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionDomain" color="primary" :label="$t('rule.domainRules')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionPort" color="primary" :label="$t('in.port')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionSrcIP" color="primary" :label="$t('rule.srcIpRules')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionSrcPort" color="primary" :label="$t('rule.srcPortRules')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRuleSet" color="primary" :label="$t('rule.ruleset')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['rule', 'clients', 'inTags', 'rsTags', 'deleteable'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
domainKeys: ['domain', 'domain_suffix', 'domain_keyword', 'domain_regex', 'ip_cidr', 'ip_is_private'],
|
|
||||||
portKeys: ['port', 'port_range'],
|
|
||||||
srcIPKeys: ['source_ip_cidr', 'source_ip_is_private'],
|
|
||||||
srcPortKeys: ['source_port', 'source_port_range'],
|
|
||||||
domainOption: 'domain',
|
|
||||||
portOption: 'port',
|
|
||||||
srcIPOption: 'source_ip_cidr',
|
|
||||||
srcPortOption: 'source_port',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateDomainOption(option:string) {
|
|
||||||
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
this.$props.rule[option] = option == 'ip_is_private' ? false : []
|
|
||||||
},
|
|
||||||
updatePortOption(option:string) {
|
|
||||||
this.portKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
this.$props.rule[option] = []
|
|
||||||
},
|
|
||||||
updateSrcIPOption(option:string) {
|
|
||||||
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
this.$props.rule[option] = option == 'source_ip_is_private' ? false : []
|
|
||||||
},
|
|
||||||
updateSrcPortOption(option:string) {
|
|
||||||
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
this.$props.rule[option] = []
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
optionInbound: {
|
|
||||||
get() { return this.$props.rule.inbound != undefined },
|
|
||||||
set(v:boolean) { this.$props.rule.inbound = v ? [] : undefined }
|
|
||||||
},
|
|
||||||
optionClient: {
|
|
||||||
get() { return this.$props.rule.auth_user != undefined },
|
|
||||||
set(v:boolean) { this.$props.rule.auth_user = v ? [] : undefined }
|
|
||||||
},
|
|
||||||
optionIPver: {
|
|
||||||
get() { return this.$props.rule.ip_version != undefined },
|
|
||||||
set(v:boolean) { this.$props.rule.ip_version = v ? 4 : undefined }
|
|
||||||
},
|
|
||||||
optionProtocol: {
|
|
||||||
get() { return this.$props.rule.protocol != undefined },
|
|
||||||
set(v:boolean) { this.$props.rule.protocol = v ? ['http'] : undefined }
|
|
||||||
},
|
|
||||||
optionDomain: {
|
|
||||||
get() { return Object.keys(this.$props.rule).some(r => this.domainKeys.includes(r)) },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.rule.domain = []
|
|
||||||
} else {
|
|
||||||
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
}
|
|
||||||
this.domainOption = 'domain'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionPort: {
|
|
||||||
get() { return Object.keys(this.$props.rule).some(r => this.portKeys.includes(r)) },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.rule.port = []
|
|
||||||
} else {
|
|
||||||
this.portKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
}
|
|
||||||
this.portOption = 'port'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionSrcIP: {
|
|
||||||
get() { return Object.keys(this.$props.rule).some(r => this.srcIPKeys.includes(r)) },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.rule.source_ip_cidr = []
|
|
||||||
} else {
|
|
||||||
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
}
|
|
||||||
this.srcIPOption = 'source_ip_cidr'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionSrcPort: {
|
|
||||||
get() { return Object.keys(this.$props.rule).some(r => this.srcPortKeys.includes(r)) },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.rule.source_port = []
|
|
||||||
} else {
|
|
||||||
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
|
||||||
}
|
|
||||||
this.srcPortOption = 'source_port'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionRuleSet: {
|
|
||||||
get() { return this.$props.rule.rule_set != undefined },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.$props.rule.rule_set = []
|
|
||||||
this.$props.rule.rule_set_ipcidr_match_source = false
|
|
||||||
} else {
|
|
||||||
delete this.$props.rule.rule_set
|
|
||||||
delete this.$props.rule.rule_set_ipcidr_match_source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
get() { return this.$props.rule.domain?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.domain = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
domain_suffix: {
|
|
||||||
get() { return this.$props.rule.domain_suffix?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.domain_suffix = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
domain_keyword: {
|
|
||||||
get() { return this.$props.rule.domain_keyword?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.domain_keyword = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
domain_regex: {
|
|
||||||
get() { return this.$props.rule.domain_regex?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.domain_regex = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
ip_cidr: {
|
|
||||||
get() { return this.$props.rule.ip_cidr?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.ip_cidr = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
get() { return this.$props.rule.port?.join(',') },
|
|
||||||
set(v:string) {
|
|
||||||
if(!v.endsWith(',')) {
|
|
||||||
this.$props.rule.port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
port_range: {
|
|
||||||
get() { return this.$props.rule.port_range?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.port_range = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
source_ip_cidr: {
|
|
||||||
get() { return this.$props.rule.source_ip_cidr?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.source_ip_cidr = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
source_port: {
|
|
||||||
get() { return this.$props.rule.source_port?.join(',') },
|
|
||||||
set(v:string) {
|
|
||||||
if(!v.endsWith(',')) {
|
|
||||||
this.$props.rule.source_port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
source_port_range: {
|
|
||||||
get() { return this.$props.rule.source_port_range?.join(',') },
|
|
||||||
set(v:string) { this.$props.rule.source_port_range = v.length>0 ? v.split(',') : [] }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
const ruleKeys = Object.keys(this.$props.rule)
|
|
||||||
if (this.optionDomain) {
|
|
||||||
const enabledOption = this.domainKeys.filter(k => ruleKeys.includes(k))
|
|
||||||
this.domainOption = enabledOption.length>0 ? enabledOption[0] : 'domain'
|
|
||||||
}
|
|
||||||
if (this.optionPort) {
|
|
||||||
const enabledOption = this.portKeys.filter(k => ruleKeys.includes(k))
|
|
||||||
this.portOption = enabledOption.length>0 ? enabledOption[0] : 'port'
|
|
||||||
}
|
|
||||||
if (this.optionSrcIP) {
|
|
||||||
const enabledOption = this.srcIPKeys.filter(k => ruleKeys.includes(k))
|
|
||||||
this.srcIPOption = enabledOption.length>0 ? enabledOption[0] : 'source_ip_cidr'
|
|
||||||
}
|
|
||||||
if (this.optionSrcPort) {
|
|
||||||
const enabledOption = this.srcPortKeys.filter(k => ruleKeys.includes(k))
|
|
||||||
this.srcPortOption = enabledOption.length>0 ? enabledOption[0] : 'source_port'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,450 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3">
|
|
||||||
<v-select
|
|
||||||
v-model="ruleToDirect"
|
|
||||||
:items="geoList"
|
|
||||||
:label="$t('setting.toDirect')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3">
|
|
||||||
<v-select
|
|
||||||
v-model="ruleToBlock"
|
|
||||||
:items="geoList"
|
|
||||||
:label="$t('setting.toBlock')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="enableLog">
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('basic.log.level')"
|
|
||||||
:items="levels"
|
|
||||||
v-model="subJsonExt.log.level">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-switch v-model="subJsonExt.log.timestamp" color="primary" :label="$t('setting.timestamp')" hide-details />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="enableDns">
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="proxyDns"
|
|
||||||
hide-details
|
|
||||||
:label="$t('setting.globalDns')"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="directDns"
|
|
||||||
hide-details
|
|
||||||
clearable
|
|
||||||
:label="$t('setting.directDns')"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" v-if="directDns.length>0">
|
|
||||||
<v-select
|
|
||||||
v-model="dnsToDirect"
|
|
||||||
:items="geositeList"
|
|
||||||
:label="$t('setting.toDirectDns')"
|
|
||||||
multiple
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<template v-if="enableInb">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3">
|
|
||||||
<v-combobox
|
|
||||||
v-model="inbounds[0].address"
|
|
||||||
:items="defaultInb[0].address"
|
|
||||||
chips
|
|
||||||
multiple
|
|
||||||
hide-details
|
|
||||||
:label="$t('in.addr')"
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-text-field
|
|
||||||
type="number"
|
|
||||||
v-model.number="inbounds[0].mtu"
|
|
||||||
hide-details
|
|
||||||
label="MTU"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3">
|
|
||||||
<v-combobox
|
|
||||||
v-model="inbounds[0].exclude_package"
|
|
||||||
:items="['ir.mci.ecareapp','com.myirancell']"
|
|
||||||
chips
|
|
||||||
multiple
|
|
||||||
hide-details
|
|
||||||
:label="$t('setting.excludePkg')"
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-switch
|
|
||||||
v-model="platformProxy"
|
|
||||||
hide-details
|
|
||||||
color="primary"
|
|
||||||
label="Platform HTTP proxy"
|
|
||||||
></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('setting.jsonSubOptions') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="enableLog" color="primary" :label="$t('basic.log.title')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="enableDns" color="primary" label="DNS" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="enableInb" color="primary" :label="$t('objects.inbound')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="enableExp" color="primary" label="Experimental" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['settings'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
subJsonExt: <any>{},
|
|
||||||
levels: ["trace", "debug", "info", "warn", "error", "fatal", "panic"],
|
|
||||||
defaultLog: {
|
|
||||||
"level": "info",
|
|
||||||
"timestamp": true
|
|
||||||
},
|
|
||||||
defaultInb: [
|
|
||||||
{
|
|
||||||
"type": "tun",
|
|
||||||
"address": [
|
|
||||||
"172.19.0.1/30",
|
|
||||||
"fdfe:dcba:9876::1/126"
|
|
||||||
],
|
|
||||||
"mtu": 9000,
|
|
||||||
"auto_route": true,
|
|
||||||
"strict_route": false,
|
|
||||||
"endpoint_independent_nat": false,
|
|
||||||
"stack": "system",
|
|
||||||
"exclude_package": [],
|
|
||||||
"platform": {
|
|
||||||
"http_proxy": {
|
|
||||||
"enabled": true,
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 2080
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"listen": "127.0.0.1",
|
|
||||||
"listen_port": 2080,
|
|
||||||
"users": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
defaultExp: {
|
|
||||||
"clash_api": {
|
|
||||||
"external_controller": "127.0.0.1:9090",
|
|
||||||
"external_ui": "ui",
|
|
||||||
"secret": "",
|
|
||||||
"external_ui_download_url": "https://mirror.ghproxy.com/https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip",
|
|
||||||
"external_ui_download_detour": "direct",
|
|
||||||
"default_mode": "rule"
|
|
||||||
},
|
|
||||||
"cache_file": {
|
|
||||||
"enabled": true,
|
|
||||||
"store_fakeip": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultDns: {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"address": "tcp://8.8.8.8",
|
|
||||||
"detour": "proxy",
|
|
||||||
"address_resolver": "local-dns",
|
|
||||||
"tag": "proxy-dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local-dns",
|
|
||||||
"address": "local",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"clash_mode": "Global",
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"172.19.0.0/30",
|
|
||||||
"fdfe:dcba:9876::1/126"
|
|
||||||
],
|
|
||||||
"action": "route",
|
|
||||||
"server": "proxy-dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"172.19.0.0/30",
|
|
||||||
"fdfe:dcba:9876::1/126"
|
|
||||||
],
|
|
||||||
"action": "route",
|
|
||||||
"server": "proxy-dns"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"final": "local-dns",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
geositeList: [
|
|
||||||
{ title: "Private", value: "geosite-private" },
|
|
||||||
{ title: "Ads", value: "geosite-ads" },
|
|
||||||
{ title: "🇮🇷 Iran", value: "geosite-ir" },
|
|
||||||
{ title: "🇨🇳 China", value: "geosite-cn" },
|
|
||||||
{ title: "🇻🇳 Vietnam", value: "geosite-vn" },
|
|
||||||
],
|
|
||||||
geoList: [
|
|
||||||
{ title: "Site-Private", value: "geoip-private" },
|
|
||||||
{ title: "IP-Private", value: "geosite-private" },
|
|
||||||
{ title: "Site-Ads", value: "geosite-ads" },
|
|
||||||
{ title: "🇮🇷 Site-Iran", value: "geosite-ir" },
|
|
||||||
{ title: "🇮🇷 IP-Iran", value: "geoip-ir" },
|
|
||||||
{ title: "🇨🇳 Site-China", value: "geosite-cn" },
|
|
||||||
{ title: "🇨🇳 IP-China", value: "geoip-cn" },
|
|
||||||
{ title: "🇻🇳 Site-Vietnam", value: "geosite-vn" },
|
|
||||||
{ title: "🇻🇳 IP-Vietnam", value: "geoip-vn" },
|
|
||||||
],
|
|
||||||
geo: [
|
|
||||||
{
|
|
||||||
tag: "geosite-ads",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ads-all.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geosite-private",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/private.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geosite-ir",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/category-ir.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geosite-cn",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geosite/cn.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geosite-vn",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://github.com/Thaomtam/Geosite-vn/raw/rule-set/Geosite-vn.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geoip-private",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/private.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geoip-ir",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/ir.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geoip-cn",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/cn.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tag: "geoip-vn",
|
|
||||||
type: "remote",
|
|
||||||
format: "binary",
|
|
||||||
url: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@sing/geo/geoip/vn.srs",
|
|
||||||
download_detour: "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
enableLog: {
|
|
||||||
get() :boolean { return this.subJsonExt?.log != undefined },
|
|
||||||
set(v:boolean) { v ? this.subJsonExt.log = this.defaultLog : delete this.subJsonExt.log }
|
|
||||||
},
|
|
||||||
enableDns: {
|
|
||||||
get() :boolean { return this.subJsonExt?.dns != undefined },
|
|
||||||
set(v:boolean) {
|
|
||||||
if (v) {
|
|
||||||
this.subJsonExt.dns = this.defaultDns
|
|
||||||
if (this.rules == undefined) this.subJsonExt.rules = []
|
|
||||||
this.subJsonExt.rules.unshift({ protocol: "dns", action: "hijack-dns" })
|
|
||||||
} else {
|
|
||||||
delete this.subJsonExt.dns
|
|
||||||
const ruleDnsIndex = this.subJsonExt?.rules?.findIndex((r:any) => r.protocol == "dns" && r.action == "hijack-dns")
|
|
||||||
if (ruleDnsIndex >= 0) this.subJsonExt.rules.splice(ruleDnsIndex,1)
|
|
||||||
if (this.rules.length == 0) delete this.subJsonExt.rules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enableInb: {
|
|
||||||
get() :boolean { return this.subJsonExt?.inbounds != undefined },
|
|
||||||
set(v:boolean) { v ? this.subJsonExt.inbounds = this.defaultInb.slice() : delete this.subJsonExt.inbounds }
|
|
||||||
},
|
|
||||||
enableExp: {
|
|
||||||
get() :boolean { return this.subJsonExt?.experimental != undefined },
|
|
||||||
set(v:boolean) { v ? this.subJsonExt.experimental = this.defaultExp : delete this.subJsonExt.experimental }
|
|
||||||
},
|
|
||||||
dns():any { return this.subJsonExt?.dns?? undefined },
|
|
||||||
proxyDns: {
|
|
||||||
get() :string { return this.dns?.servers[0]?.address?? "" },
|
|
||||||
set(v:string) { this.dns.servers[0].address = v.length>0 ? v : "8.8.8.8" }
|
|
||||||
},
|
|
||||||
directDns: {
|
|
||||||
get() :string { return this.dns?.servers?.findLast((d:any) => d.tag == "direct-dns")?.address?? "" },
|
|
||||||
set(v:string) {
|
|
||||||
const sIndex = this.dns.servers.findIndex((d:any) => d.tag == "direct-dns")
|
|
||||||
if (v?.length>0) {
|
|
||||||
if (sIndex === -1) {
|
|
||||||
this.dns.servers.push({ tag: "direct-dns", address: v, detour: "direct" })
|
|
||||||
this.dns.rules.push({ clash_mode: "Direct", action: "route", server: "direct-dns" })
|
|
||||||
} else {
|
|
||||||
this.dns.servers[sIndex].address = v
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.dns.servers.splice(sIndex,1)
|
|
||||||
this.dns.rules = this.dns.rules.filter((r:any) => r.server != "direct-dns")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
dnsToDirect: {
|
|
||||||
get() :string[] {
|
|
||||||
const ruleIndex = this.dns?.rules?.findIndex((r:any) => r.server == "direct-dns" && Object.hasOwn(r,'rule_set'))
|
|
||||||
return ruleIndex >= 0 ? this.dns.rules[ruleIndex].rule_set : []
|
|
||||||
},
|
|
||||||
set(v:string[]) {
|
|
||||||
const ruleIndex = this.dns?.rules?.findIndex((r:any) => r.server == "direct-dns" && Object.hasOwn(r,'rule_set'))
|
|
||||||
if (v.length>0) {
|
|
||||||
if (ruleIndex >= 0){
|
|
||||||
this.dns.rules[ruleIndex].rule_set = v
|
|
||||||
} else {
|
|
||||||
this.dns.rules.push({ rule_set: v, action: "route", server: "direct-dns" })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ruleIndex != -1) this.dns.rules.splice(ruleIndex,1)
|
|
||||||
}
|
|
||||||
this.updateRuleSets()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
inbounds():any[] { return this.subJsonExt?.inbounds?? undefined },
|
|
||||||
platformProxy: {
|
|
||||||
get() :boolean { return this.inbounds[0]?.platform != undefined },
|
|
||||||
set(v:boolean) { this.subJsonExt.inbounds[0].platform = v ? this.defaultInb[0].platform : undefined }
|
|
||||||
},
|
|
||||||
rules():any { return this.subJsonExt?.rules?? undefined },
|
|
||||||
ruleToDirect: {
|
|
||||||
get() :string[] {
|
|
||||||
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "direct" && Object.hasOwn(r,'rule_set'))
|
|
||||||
return ruleIndex >= 0 ? this.rules[ruleIndex].rule_set : []
|
|
||||||
},
|
|
||||||
set(v:string[]) {
|
|
||||||
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "direct" && Object.hasOwn(r,'rule_set'))
|
|
||||||
if (v.length>0) {
|
|
||||||
if (ruleIndex >= 0){
|
|
||||||
this.rules[ruleIndex].rule_set = v
|
|
||||||
} else {
|
|
||||||
if (this.rules == undefined) this.subJsonExt.rules = []
|
|
||||||
this.rules.push({ rule_set: v, action: "route", outbound: "direct" })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ruleIndex != -1) this.rules.splice(ruleIndex,1)
|
|
||||||
}
|
|
||||||
this.updateRuleSets()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ruleToBlock: {
|
|
||||||
get() :string[] {
|
|
||||||
const ruleIndex = this.rules?.findIndex((r:any) => r.action == "reject" && Object.hasOwn(r,'rule_set'))
|
|
||||||
return ruleIndex >= 0 ? this.rules[ruleIndex].rule_set : []
|
|
||||||
},
|
|
||||||
set(v:string[]) {
|
|
||||||
const ruleIndex = this.rules?.findIndex((r:any) => r.action == "reject" && Object.hasOwn(r,'rule_set'))
|
|
||||||
if (v.length>0) {
|
|
||||||
if (ruleIndex >= 0){
|
|
||||||
this.rules[ruleIndex].rule_set = v
|
|
||||||
} else {
|
|
||||||
if (this.rules == undefined) this.subJsonExt.rules = []
|
|
||||||
this.rules.push({ rule_set: v, action: "reject" })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ruleIndex != -1) this.rules.splice(ruleIndex,1)
|
|
||||||
}
|
|
||||||
this.updateRuleSets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateRuleSets(){
|
|
||||||
let tags = <string[]>[]
|
|
||||||
if (this.dns?.rules?.length>0) this.dns.rules.forEach((r:any) => { if (r.rule_set) tags.push(...r.rule_set) })
|
|
||||||
if (this.rules?.length>0) this.rules.forEach((r:any) => { if (r.rule_set) tags.push(...r.rule_set) })
|
|
||||||
if (tags.length>0){
|
|
||||||
this.subJsonExt.rule_set = this.geo.filter((g:any) => tags.includes(g.tag))
|
|
||||||
} else {
|
|
||||||
delete this.subJsonExt.rule_set
|
|
||||||
}
|
|
||||||
if (this.rules.length == 0) delete this.subJsonExt.rules
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted(){
|
|
||||||
this.subJsonExt = this.$props.settings?.subJsonExt?.length>0 ? JSON.parse(this.$props.settings.subJsonExt) : <any>{}
|
|
||||||
},
|
|
||||||
watch:{
|
|
||||||
subJsonExt:{
|
|
||||||
handler(v) {
|
|
||||||
this.$props.settings.subJsonExt = Object.keys(v).length>0 ? JSON.stringify(v, null, 2) : ""
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('objects.transport')">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('type')"
|
|
||||||
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
|
||||||
v-model="transportType">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<Http v-if="Transport.type == trspTypes.HTTP" :transport="Transport" />
|
|
||||||
<WebSocket v-if="Transport.type == trspTypes.WebSocket" :transport="Transport" />
|
|
||||||
<GRPC v-if="Transport.type == trspTypes.gRPC" :transport="Transport" />
|
|
||||||
<HttpUpgrade v-if="Transport.type == trspTypes.HTTPUpgrade" :transport="Transport" />
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { TrspTypes, Transport } from '@/types/transport'
|
|
||||||
import Http from './transports/Http.vue'
|
|
||||||
import WebSocket from './transports/WebSocket.vue'
|
|
||||||
import GRPC from './transports/gRPC.vue'
|
|
||||||
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
trspTypes: TrspTypes
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
Transport() {
|
|
||||||
return <Transport>this.$props.data.transport
|
|
||||||
},
|
|
||||||
tpEnable: {
|
|
||||||
get() { return Object.hasOwn(this.$props.data.transport, 'type') },
|
|
||||||
set(newValue: boolean) { this.$props.data.transport = newValue ? { type: 'http' } : {} }
|
|
||||||
},
|
|
||||||
transportType: {
|
|
||||||
get() { return this.Transport.type },
|
|
||||||
set(newValue: string) { this.$props.data.transport = { type: newValue } }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="UDP over TCP"
|
|
||||||
:items="versions"
|
|
||||||
v-model="udp_over_tcp">
|
|
||||||
</v-select>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
versions: [
|
|
||||||
{ title: this.$t('disable'), value: 0 },
|
|
||||||
{ title: "1", value: 1 },
|
|
||||||
{ title: "2", value: 2 },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
udp_over_tcp: {
|
|
||||||
get():number { return this.$props.data.udp_over_tcp?.version?? 0 },
|
|
||||||
set(v:number) { this.$props.data.udp_over_tcp = v > 0 ? { enabled: true, version: v } : undefined }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('pages.clients')">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select v-model="data.model" :items="initUsersModels" @update:model-value="data.values = []" hide-details></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.model == 'group'">
|
|
||||||
<v-select v-model="data.values" multiple chips :items="groupNames" :label="$t('client.group')" hide-details></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8" v-if="data.model == 'client'">
|
|
||||||
<v-select v-model="data.values" multiple chips :items="clientNames" :label="$t('pages.clients')" hide-details></v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { i18n } from '@/locales';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data', 'clients'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
initUsersModels: [
|
|
||||||
{ title: i18n.global.t('none'), value: 'none' },
|
|
||||||
{ title: i18n.global.t('all'), value: 'all' },
|
|
||||||
{ title: i18n.global.t('client.group'), value: 'group' },
|
|
||||||
{ title: i18n.global.t('pages.clients'), value: 'client' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
clientNames() {
|
|
||||||
return this.$props.clients.map((c:any) => { return { title: c.name, value: c.id } } )
|
|
||||||
},
|
|
||||||
groupNames() {
|
|
||||||
return Array.from(new Set(this.$props.clients.map((c:any) => c.group)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.addr')"
|
|
||||||
hide-details
|
|
||||||
v-model="address">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.port')"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
hide-details
|
|
||||||
v-model.number="port">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="KeepAlive"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
:suffix="$t('date.s')"
|
|
||||||
hide-details
|
|
||||||
v-model.number="keepAlive">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field v-model="data.public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field v-model="allowed_ips" :label="$t('types.wg.allowedIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { KeepAlive } from 'vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
allowed_ips: {
|
|
||||||
get() { return this.$props.data.allowed_ips?.join(',') },
|
|
||||||
set(v:string) { this.$props.data.allowed_ips = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
|
||||||
reserved: {
|
|
||||||
get() { return this.$props.data.reserved?.join(',') },
|
|
||||||
set(v:string) {
|
|
||||||
if(!v.endsWith(',')) {
|
|
||||||
this.$props.data.reserved = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
address: {
|
|
||||||
get() { return this.$props.data.address },
|
|
||||||
set(v:string) { this.$props.data.address = v.length > 0 ? v : undefined }
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
get() { return this.$props.data.port },
|
|
||||||
set(v:number) { this.$props.data.port = v > 0 ? v : undefined }
|
|
||||||
},
|
|
||||||
keepAlive: {
|
|
||||||
get() { return this.$props.data.persistent_keepalive_interval?? 0 },
|
|
||||||
set(v:number) { this.$props.data.persistent_keepalive_interval = v > 0 ? v : undefined }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Notivue v-slot="item">
|
|
||||||
<NotivueSwipe :item="item">
|
|
||||||
<Notification
|
|
||||||
:item="item"
|
|
||||||
:theme="theme"
|
|
||||||
:dir="direction"
|
|
||||||
:icons="outlinedIcons"
|
|
||||||
:hideClose="true"
|
|
||||||
@click="item.clear"
|
|
||||||
/>
|
|
||||||
</NotivueSwipe>
|
|
||||||
</Notivue>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Notivue, Notification, NotivueSwipe, outlinedIcons, pastelTheme, darkTheme } from 'notivue'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useTheme } from 'vuetify'
|
|
||||||
import vuetify from '@/plugins/vuetify';
|
|
||||||
|
|
||||||
const Theme = useTheme()
|
|
||||||
|
|
||||||
const theme = computed(() =>{
|
|
||||||
return Theme.global.name.value == "light" ? pastelTheme : darkTheme
|
|
||||||
})
|
|
||||||
|
|
||||||
const direction = computed(() => {
|
|
||||||
return vuetify.locale.isRtl ? 'rtl' : 'ltr'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--nv-z: 10020;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Direct">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.direct.overrideAddr')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.override_address">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.direct.overridePort')"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
hide-details
|
|
||||||
v-model.number="override_port">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
override_port: {
|
|
||||||
get() { return this.$props.data.override_port ? this.$props.data.override_port : ''; },
|
|
||||||
set(newValue: any) { this.$props.data.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="HTTP">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.un')"
|
|
||||||
hide-details
|
|
||||||
v-model="username">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('transport.path')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<Headers :data="data" />
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Headers from '@/components/Headers.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
username: {
|
|
||||||
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
|
||||||
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
|
||||||
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Headers }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Hysteria">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.upload')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
v-model.number="up_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.download')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
min="0"
|
|
||||||
v-model.number="down_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.hy.obfs')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.obfs">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.hy.auth')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.auth_str">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.disable_mtu_discovery" color="primary" label="Disable MTU discovery" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_conn != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="Recv window conn"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="data.recv_window_conn">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="Recv window"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="data.recv_window">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_client != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="Recv window client"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="data.recv_window_client">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.max_conn_client != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="Max conn client"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
v-model.number="data.max_conn_client">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.hy.hyOptions') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRsvConn" color="primary" label="Recv window conn" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="direction=='out'">
|
|
||||||
<v-switch v-model="optionRsvWin" color="primary" label="Recv window" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="direction=='in'">
|
|
||||||
<v-switch v-model="optionRsvClnt" color="primary" label="Recv window client" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="direction=='in'">
|
|
||||||
<v-switch v-model="optionMaxConn" color="primary" label="Max conn client" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['direction','data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
optionRsvConn: {
|
|
||||||
get(): boolean { return this.$props.data.recv_window_conn != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.recv_window_conn = v ? 15728640 : undefined }
|
|
||||||
},
|
|
||||||
optionRsvWin: {
|
|
||||||
get(): boolean { return this.$props.data.recv_window != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.recv_window = v ? 67108864 : undefined }
|
|
||||||
},
|
|
||||||
optionRsvClnt: {
|
|
||||||
get(): boolean { return this.$props.data.recv_window_client != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.recv_window_client = v ? 67108864 : undefined }
|
|
||||||
},
|
|
||||||
optionMaxConn: {
|
|
||||||
get(): boolean { return this.$props.data.max_conn_client != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.max_conn_client = v ? 1024 : undefined }
|
|
||||||
},
|
|
||||||
down_mbps: {
|
|
||||||
get() { return this.$props.data.down_mbps ? this.$props.data.down_mbps : 0 },
|
|
||||||
set(newValue:any) {
|
|
||||||
if (newValue.length != 0 ){
|
|
||||||
this.$props.data.down_mbps = newValue
|
|
||||||
this.$props.data.down = "" + newValue + " Mbps"
|
|
||||||
} else {
|
|
||||||
this.$props.data.down_mbps = 0
|
|
||||||
this.$props.data.down = "0 Mbps"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
up_mbps: {
|
|
||||||
get() { return this.$props.data.up_mbps ? this.$props.data.up_mbps : 0 },
|
|
||||||
set(newValue:number) { this.$props.data.up_mbps = newValue > 0 ? newValue : 0 }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Hysteria2">
|
|
||||||
<v-row v-if="!data.ignore_client_bandwidth">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.upload')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
min="0"
|
|
||||||
v-model.number="up_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('stats.download')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('stats.Mbps')"
|
|
||||||
min="0"
|
|
||||||
v-model.number="down_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<template v-if="direction == 'in'">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.obfs != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.hy.obfs')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.obfs.password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card subtitle="Hysteria2 Masquerade" v-if="data.masquerade != undefined">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select v-model="masqueradeType" hide-details :label="$t('type')" :items="masqTypes"></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8" v-if="masqueradeType == ''">
|
|
||||||
<v-text-field
|
|
||||||
label="HTTP3 server on auth fails"
|
|
||||||
placeholder="file:///var/www | http://127.0.0.1:8080"
|
|
||||||
v-model="data.masquerade"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8" v-if="masqueradeType == 'file'">
|
|
||||||
<v-text-field
|
|
||||||
label="File server root directory"
|
|
||||||
placeholder="/var/www"
|
|
||||||
v-model="data.masquerade.directory"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="masqueradeType == 'string'">
|
|
||||||
<v-text-field
|
|
||||||
label="HTTP Code"
|
|
||||||
type="number"
|
|
||||||
min="100"
|
|
||||||
max="599"
|
|
||||||
v-model.number="data.masquerade.status_code"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="masqueradeType == 'proxy'">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field
|
|
||||||
label="Target URL"
|
|
||||||
placeholder="http://example.com:8080"
|
|
||||||
v-model="data.masquerade.url"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch
|
|
||||||
label="Rewrite Host"
|
|
||||||
v-model="data.masquerade.rewrite_host"
|
|
||||||
color="primary"
|
|
||||||
hide-details>
|
|
||||||
</v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<template v-if="masqueradeType == 'string'">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="8">
|
|
||||||
<v-text-field
|
|
||||||
label="Content"
|
|
||||||
v-model="data.masquerade.content"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<Headers :data="data.masquerade" />
|
|
||||||
</template>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8" v-if="optionMPort">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('rule.portRange') + ' ' + $t('commaSeparated')"
|
|
||||||
v-model="server_ports">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.hy.hy2Options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<template v-if="direction == 'in'">
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMPort" color="primary" :label="$t('rule.portRange')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</template>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
import Headers from '@/components/Headers.vue'
|
|
||||||
import { i18n } from '@/locales'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['direction', 'data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
masqTypes: [
|
|
||||||
{ title: i18n.global.t('rule.simple'), value: '' },
|
|
||||||
{ title: "File server", value: "file" },
|
|
||||||
{ title: "Reverse Proxy", value: "proxy" },
|
|
||||||
{ title: "Fixed response", value: "string" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
down_mbps: {
|
|
||||||
get() { return this.$props.data.down_mbps?? 0 },
|
|
||||||
set(v:number) { this.$props.data.down_mbps = v>0 ? v : undefined }
|
|
||||||
},
|
|
||||||
up_mbps: {
|
|
||||||
get() { return this.$props.data.up_mbps?? 0 },
|
|
||||||
set(v:number) { this.$props.data.up_mbps = v>0 ? v : undefined }
|
|
||||||
},
|
|
||||||
server_ports: {
|
|
||||||
get() { return this.$props.data.server_ports?.join(',')?? [] },
|
|
||||||
set(v:string) { this.$props.data.server_ports = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
|
||||||
masqueradeType: {
|
|
||||||
get() { return typeof this.$props.data.masquerade === 'object' ? this.$props.data.masquerade.type?? '' : '' },
|
|
||||||
set(v:string) {
|
|
||||||
if (v == '') {
|
|
||||||
this.$props.data.masquerade = ''
|
|
||||||
} else {
|
|
||||||
this.$props.data.masquerade = { type: v }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionObfs: {
|
|
||||||
get(): boolean { return this.$props.data.obfs != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
|
||||||
},
|
|
||||||
optionMasq: {
|
|
||||||
get(): boolean { return this.$props.data.masquerade != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.masquerade = v ? "" : undefined }
|
|
||||||
},
|
|
||||||
optionMPort: {
|
|
||||||
get(): boolean { return this.$props.data.server_ports != undefined },
|
|
||||||
set(v:boolean) { this.$props.data.server_ports = v ? [] : undefined }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Network, Headers }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Naive">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="inbound" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['inbound'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="ShadowTls">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="[1,2,3]"
|
|
||||||
:label="$t('version')"
|
|
||||||
v-model="version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.version > 1">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
version: {
|
|
||||||
get() { return this.$props.data.version ?? 3 },
|
|
||||||
set(v: number) {
|
|
||||||
this.$props.data.version = v
|
|
||||||
if (v==1) {
|
|
||||||
delete this.$props.data.password
|
|
||||||
} else if (this.$props.data.password === undefined ) {
|
|
||||||
this.$props.data.password = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Selector">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-combobox
|
|
||||||
v-model="data.outbounds"
|
|
||||||
:items="tags"
|
|
||||||
:label="$t('pages.outbounds')"
|
|
||||||
multiple
|
|
||||||
@update:model-value="updateDefault"
|
|
||||||
chips
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-combobox
|
|
||||||
v-model="data.default"
|
|
||||||
:items="data.outbounds"
|
|
||||||
:label="$t('types.lb.defaultOut')"
|
|
||||||
clearable
|
|
||||||
hide-details
|
|
||||||
></v-combobox>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-switch v-model="data.interrupt_exist_connections" color="primary" :label="$t('types.lb.interruptConn')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data','tags'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateDefault() {
|
|
||||||
if (!this.$props.data.outbounds?.includes(this.$props.data.default)) {
|
|
||||||
delete this.$props.data.default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="ShadowTls">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="[1,2,3]"
|
|
||||||
:label="$t('version')"
|
|
||||||
:disabled="data.id > 0"
|
|
||||||
v-model="version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.password != undefined">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.shdwTls.hs')"
|
|
||||||
hide-details
|
|
||||||
v-model="Inbound.handshake.server">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.port')"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
hide-details
|
|
||||||
v-model.number="server_port">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<Dial :dial="Inbound.handshake" :outTags="outTags" />
|
|
||||||
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.shdwTls.addHS')"
|
|
||||||
hide-details
|
|
||||||
append-icon="mdi-plus"
|
|
||||||
@click:append="addHandshakeServer()"
|
|
||||||
v-model="handshake_server">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card
|
|
||||||
v-for="(value, key) in Inbound.handshake_for_server_name"
|
|
||||||
border
|
|
||||||
density="compact"
|
|
||||||
style="margin: 5px;"
|
|
||||||
color="background">
|
|
||||||
<v-card-title>
|
|
||||||
<v-row>
|
|
||||||
<v-col>{{ key }}</v-col>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-col>
|
|
||||||
<v-btn @click="Inbound.handshake_for_server_name ? delete Inbound.handshake_for_server_name[key] : null"
|
|
||||||
icon="mdi-delete"
|
|
||||||
></v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-title>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.shdwTls.hs')"
|
|
||||||
hide-details
|
|
||||||
v-model="value.server">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('out.port')"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
hide-details
|
|
||||||
v-model.number="value.server_port">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<Dial :dial="value" :outTags="outTags" />
|
|
||||||
</v-card>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ShadowTLS } from '@/types/inbounds'
|
|
||||||
import Dial from '../Dial.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data', 'outTags'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
handshake_server: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addHandshakeServer() {
|
|
||||||
this.data.handshake_for_server_name[this.handshake_server] = {}
|
|
||||||
// Clear the input field after adding the server
|
|
||||||
this.handshake_server = ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.version = this.Inbound.version
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
version: {
|
|
||||||
get() { this.version = this.Inbound.version; return this.Inbound.version; },
|
|
||||||
set(newValue: any) {
|
|
||||||
switch (newValue) {
|
|
||||||
case 1:
|
|
||||||
delete this.Inbound.password
|
|
||||||
delete this.Inbound.handshake_for_server_name
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (!this.Inbound.password) {
|
|
||||||
this.Inbound.password = ""
|
|
||||||
}
|
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
|
||||||
this.Inbound.handshake_for_server_name = {}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
delete this.Inbound.password
|
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
|
||||||
this.Inbound.handshake_for_server_name = {}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.Inbound.version = newValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Inbound(): ShadowTLS {
|
|
||||||
return <ShadowTLS>this.$props.data;
|
|
||||||
},
|
|
||||||
server_port: {
|
|
||||||
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
|
||||||
set(newValue: any) { this.Inbound.handshake.server_port = newValue.length == 0 || newValue == 0 ? 443 : parseInt(newValue); }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Dial }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Shadowsocks">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('in.ssMethod')"
|
|
||||||
:items="ssMethods"
|
|
||||||
@update:model-value="direction == 'in' ? changeMethod($event) : undefined"
|
|
||||||
v-model="data.method">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
|
||||||
<UoT :data="data" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="data.method.startsWith('2022') || direction == 'out'">
|
|
||||||
<v-col cols="12" sm="8">
|
|
||||||
<v-text-field
|
|
||||||
v-model="data.password"
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
:append-inner-icon="direction == 'in' ? 'mdi-refresh' : undefined"
|
|
||||||
@click:append-inner="changeMethod(data.method)">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
import UoT from '@/components/UoT.vue';
|
|
||||||
import RandomUtil from '@/plugins/randomUtil';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['direction','data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ssMethods: [
|
|
||||||
"none",
|
|
||||||
"aes-128-gcm",
|
|
||||||
"aes-192-gcm",
|
|
||||||
"aes-256-gcm",
|
|
||||||
"chacha20-ietf-poly1305",
|
|
||||||
"xchacha20-ietf-poly1305",
|
|
||||||
"2022-blake3-aes-128-gcm",
|
|
||||||
"2022-blake3-aes-256-gcm",
|
|
||||||
"2022-blake3-chacha20-poly1305"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
changeMethod(ssMethod :string) {
|
|
||||||
if (ssMethod.startsWith('2022')) {
|
|
||||||
this.$props.data.password = ssMethod == "2022-blake3-aes-128-gcm" ? RandomUtil.randomShadowsocksPassword(16) : RandomUtil.randomShadowsocksPassword(32)
|
|
||||||
} else {
|
|
||||||
this.$props.data.password = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Network, UoT }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="SOCKS">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.un')"
|
|
||||||
hide-details
|
|
||||||
v-model="username">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:items="['4','4a','5']"
|
|
||||||
:label="$t('version')"
|
|
||||||
v-model="data.version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<UoT :data="data" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
import UoT from '@/components/UoT.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
username: {
|
|
||||||
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
|
||||||
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
|
||||||
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Network, UoT }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="SSH">
|
|
||||||
<template v-if="optionKey">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-btn-toggle v-model="usePath"
|
|
||||||
class="rounded-xl"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
shaped
|
|
||||||
mandatory>
|
|
||||||
<v-btn
|
|
||||||
@click="data.private_key=undefined; data.private_key_path=''"
|
|
||||||
>{{ $t('tls.usePath') }}</v-btn>
|
|
||||||
<v-btn
|
|
||||||
@click="data.private_key_path=undefined; data.private_key=''"
|
|
||||||
>{{ $t('tls.useText') }}</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="usePath == 0">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('tls.keyPath')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.private_key_path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('tls.key')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.private_key">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.ssh.passphrase')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.private_key_passphrase">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.user" :label="$t('types.un')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<v-row v-if="optionHostKey">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('types.ssh.hostKey')"
|
|
||||||
hide-details
|
|
||||||
v-model="host_key">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.host_key_algorithms != undefined">
|
|
||||||
<v-text-field v-model="algorithms" :label="$t('types.ssh.algorithm') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.client_version != undefined">
|
|
||||||
<v-text-field v-model="data.client_version" :label="$t('types.ssh.clientVer')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.ssh.options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionKey" color="primary" label="SSH Key" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionHostKey" color="primary" :label="$t('types.ssh.hostKey')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionAlgorithms" color="primary" :label="$t('types.ssh.algorithm')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionVer" color="primary" :label="$t('types.ssh.clientVer')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
usePath: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
optionKey: {
|
|
||||||
get(): boolean { return this.data.private_key != undefined || this.data.private_key_path != undefined },
|
|
||||||
set(v:boolean) {
|
|
||||||
this.usePath = 0
|
|
||||||
if (v) {
|
|
||||||
this.$props.data.private_key_path = ""
|
|
||||||
delete this.$props.data.user
|
|
||||||
delete this.$props.data.password
|
|
||||||
} else {
|
|
||||||
delete this.$props.data.private_key_path
|
|
||||||
delete this.$props.data.private_key
|
|
||||||
delete this.$props.data.private_key_passphrase
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
optionHostKey: {
|
|
||||||
get(): boolean { return this.data.host_key != undefined },
|
|
||||||
set(v:boolean) { this.data.host_key = v ? '' : undefined }
|
|
||||||
},
|
|
||||||
optionAlgorithms: {
|
|
||||||
get(): boolean { return this.data.host_key_algorithms != undefined },
|
|
||||||
set(v:boolean) { this.data.host_key_algorithms = v ? [] : undefined }
|
|
||||||
},
|
|
||||||
optionVer: {
|
|
||||||
get(): boolean { return this.data.client_version != undefined },
|
|
||||||
set(v:boolean) { this.data.client_version = v ? 'SSH-2.0-OpenSSH_7.4p1' : undefined }
|
|
||||||
},
|
|
||||||
host_key: {
|
|
||||||
get(): string { return this.$props.data.host_key ? this.$props.data.host_key.join('\n') : '' },
|
|
||||||
set(v:string) { this.$props.data.host_key = v.split('\n') }
|
|
||||||
},
|
|
||||||
algorithms: {
|
|
||||||
get() { return this.$props.data.host_key_algorithms ? this.$props.data.host_key_algorithms.join(',') : '' },
|
|
||||||
set(v:string) { this.$props.data.host_key_algorithms = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="TProxy">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="inbound" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['inbound'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Tor">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.executable_path" :label="$t('types.tor.execPath')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.data_directory" :label="$t('types.tor.dataDir')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="extra_args" :label="$t('types.tor.extArgs') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
extra_args: {
|
|
||||||
get() { return this.$props.data.extra_args?.join(',') },
|
|
||||||
set(v:string) { this.$props.data.extra_args = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Trojan">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="TUIC">
|
|
||||||
<v-row v-if="direction == 'out'">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="UDP Relay Mode"
|
|
||||||
:items="['native', 'quic']"
|
|
||||||
clearable
|
|
||||||
@click:clear="delete data.udp_relay_mode"
|
|
||||||
v-model="data.udp_relay_mode">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" label="UDP Over Stream" v-model="data.udp_over_stream" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('types.tuic.congControl')"
|
|
||||||
:items="congestion_controls"
|
|
||||||
v-model="data.congestion_control">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" label="Zero-RTT Handshake" v-model="data.zero_rtt_handshake" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.tuic.authTimeout')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('date.s')"
|
|
||||||
min="1"
|
|
||||||
v-model.number="auth_timeout">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.tuic.hb')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
:suffix="$t('date.s')"
|
|
||||||
min="1"
|
|
||||||
v-model.number="heartbeat">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['direction', 'data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
congestion_controls: [
|
|
||||||
"cubic","new_reno", "bbr"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
auth_timeout: {
|
|
||||||
get() { return this.$props.data.auth_timeout ? parseInt(this.$props.data.auth_timeout.replace('s','')) : '' },
|
|
||||||
set(newValue:number) { this.$props.data.auth_timeout = newValue ? newValue + 's' : '' }
|
|
||||||
},
|
|
||||||
heartbeat: {
|
|
||||||
get() { return this.$props.data.heartbeat ? parseInt(this.$props.data.heartbeat.replace('s','')) : '' },
|
|
||||||
set(newValue:number) { this.$props.data.heartbeat = newValue ? newValue + 's' : '' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card subtitle="Tun">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="8">
|
|
||||||
<v-text-field v-model="addrs" :label="$t('types.tun.addr') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.interface_name" :label="$t('types.tun.ifName')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field type="number" v-model.number="data.mtu" label="MTU" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
type="number"
|
|
||||||
v-model.number="udpTimeout"
|
|
||||||
label="UDP timeout"
|
|
||||||
min="1"
|
|
||||||
:suffix="$t('date.m')"
|
|
||||||
hide-details>
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
v-model="data.stack"
|
|
||||||
label="Stack"
|
|
||||||
:items="['system','gvisor','mixed']"
|
|
||||||
hide-details
|
|
||||||
></v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.endpoint_independent_nat" color="primary" label="Independent NAT" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['data'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
addrs: {
|
|
||||||
get() { return this.$props.data.address?.join(',') },
|
|
||||||
set(v:string) { this.$props.data.address = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
|
||||||
udpTimeout: {
|
|
||||||
get() { return this.$props.data.udp_timeout ? parseInt(this.$props.data.udp_timeout.replace('m','')) : 5 },
|
|
||||||
set(v:number) { this.$props.data.udp_timeout = v > 0 ? v + 'm' : '5m' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user