Compare commits
224 Commits
0.0.1
...
1.2.0-beta.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 13da0b59a9 | |||
| 5713f97e42 | |||
| 7621b7a348 | |||
| 9e63944d83 | |||
| 8c9736cf19 | |||
| 5e626f7c18 | |||
| eb4c128350 | |||
| 99f555dc95 | |||
| d53d9b80f5 | |||
| 2d8b56208d | |||
| f2605a550f | |||
| e92cf56557 | |||
| 8073e8ab0a | |||
| 8bab127f19 | |||
| a77964afc0 | |||
| b2195b72b9 | |||
| 5dd0baad34 | |||
| d86adedd8b | |||
| a6dc0cc589 | |||
| 751066ac6c | |||
| dbee22b637 | |||
| 56710aef1e | |||
| 753d1f9256 | |||
| fe428ed412 | |||
| ed48cdca33 | |||
| 7a047daf6f | |||
| ecd9348a0f | |||
| f1b6c8a131 | |||
| 056c458753 | |||
| 572268e9f6 | |||
| 0101e342a0 | |||
| 8b663df1ee | |||
| 4649bd42ae | |||
| e89ac96885 | |||
| fe5b6cf922 | |||
| 42f24c45c9 | |||
| 282e244517 | |||
| 1d46d72186 | |||
| 7554b02a61 | |||
| fecb29f6ab | |||
| 7b7e5ac79d | |||
| e5fc14efd4 | |||
| 837150e065 | |||
| 93868b02d4 | |||
| 50d1177443 | |||
| d255905907 | |||
| 5f3963ff1c | |||
| bc6f356789 | |||
| 119cff3d85 | |||
| 90b2876867 | |||
| 5105c138f7 | |||
| b019633c3f | |||
| 419cce250f | |||
| bdc458dfa9 | |||
| 3dff49d6e4 | |||
| 47e3c6944a | |||
| 54e48c8c76 | |||
| 9c7814a765 | |||
| 6a174cf4db | |||
| 19e060ad33 | |||
| 5c09bc011e | |||
| f6f90b07d3 | |||
| 2da30dc596 | |||
| bdad92fe01 | |||
| 5ddee6aa12 | |||
| 6f0df2d555 | |||
| 0bb3a67f79 | |||
| 869c51885f | |||
| 7b58edeaaf | |||
| e287ced0e4 | |||
| c518bf5a86 | |||
| 89b85f818d | |||
| ea3ad15b76 | |||
| 5cc3791f79 | |||
| eef7e200ba | |||
| 39022c1b2d | |||
| 2b6874a58d | |||
| 1631ac0c30 | |||
| d70006cd91 | |||
| 19901efeaa | |||
| b2d0134567 | |||
| 58f4a676b5 | |||
| 7904cb3db0 | |||
| 6222533594 | |||
| cb4a7fe6df | |||
| 96564f1f86 | |||
| cf6b61fe96 | |||
| e1aaa3d748 | |||
| 209561497a | |||
| 60b374e5d4 | |||
| ea8538148f | |||
| b3a2078ed6 | |||
| f169064fbc | |||
| 7d441723ba | |||
| a41140190f | |||
| bb5cd91bc9 | |||
| 5b6f6daaa8 | |||
| ba06ad598d | |||
| 69725ee5af | |||
| 0d36b811dc | |||
| 6672a2721f | |||
| 6b24506ddd | |||
| 3298fd4e0d | |||
| 6dc7c93030 | |||
| 7c127f07bb | |||
| b5a2dd18f5 | |||
| 53ed86c373 | |||
| ccbd591b39 | |||
| f5792c9d82 | |||
| 4a2ac30a95 | |||
| 45b03d8472 | |||
| db3270feaa | |||
| b144aecb6a | |||
| 474e5156bb | |||
| d057076251 | |||
| 29cc6fd3e3 | |||
| dd0f770c1a | |||
| 8e369535bf | |||
| 6a5e0a940b | |||
| 3b24819309 | |||
| b5920cdc07 | |||
| cf620962bb | |||
| 17f1126c23 | |||
| 16203fdece | |||
| 12fe21906e | |||
| 1b9d5e9378 | |||
| c152a977c6 | |||
| 341baf69de | |||
| dedd4b3ee3 | |||
| bfbf9777e9 | |||
| 2cabf0aefb | |||
| c994f4b24a | |||
| f136229539 | |||
| 40fbb22b74 | |||
| 9547038164 | |||
| aca870e78f | |||
| dbf01c2086 | |||
| c3debcec5a | |||
| c179bf8a37 | |||
| 21add1f3ce | |||
| 9968f3885f | |||
| 2ac13ef8f4 | |||
| 4900c14295 | |||
| 55a6d78114 | |||
| caa115bbe3 | |||
| e3be3be9d9 | |||
| 988675a7a7 | |||
| 458f0c20da | |||
| f8fbc3c329 | |||
| 89bc3b5b23 | |||
| edfe0c86e7 | |||
| 6865c8b49d | |||
| 07947c9665 | |||
| 09616b6fac | |||
| 15105710bc | |||
| c88fb45e27 | |||
| 4f7a90b7dd | |||
| cc45e8de20 | |||
| 5805e397af | |||
| db36756515 | |||
| 609132a5b1 | |||
| c0aef193ea | |||
| 7d39252fec | |||
| b4fcec9477 | |||
| 8e2023ee66 | |||
| 8b6cd88625 | |||
| 8272285fe5 | |||
| cb606828ff | |||
| 70c986662f | |||
| bf7b42ec20 | |||
| 43d1aecec7 | |||
| 9a02e6593c | |||
| abb869c75b | |||
| fa922291ea | |||
| ae7fa7285f | |||
| a0e437a549 | |||
| 3d263d500f | |||
| c1c05c4863 | |||
| af2861f2c9 | |||
| 39ad029e20 | |||
| f545bcb30c | |||
| 819d74cf10 | |||
| 68ed8d120f | |||
| 5b804eb149 | |||
| 6a6d7d7c1a | |||
| 5726e64b9f | |||
| 52db4e5332 | |||
| b673fd9032 | |||
| e80519eac0 | |||
| 5371227fb6 | |||
| b9a7cefc7c | |||
| 13b750f965 | |||
| 4ced272d9a | |||
| 8b0cd4b89d | |||
| de43f9779c | |||
| d39df72896 | |||
| cfa5f38177 | |||
| abcf0bca99 | |||
| 5de5d6c3d0 | |||
| 944f671d96 | |||
| 00ee69e541 | |||
| f5b609204e | |||
| edc1038623 | |||
| c687a42d7e | |||
| bf1759ceda | |||
| e404342e2c | |||
| 4a3917ea5d | |||
| 037dbbd7a0 | |||
| 541e54c9a4 | |||
| 2cc78da07f | |||
| 63dc779f68 | |||
| 9ba611649c | |||
| b0817e9c05 | |||
| 8a2407fbd3 | |||
| 204e0d30e1 | |||
| 19a6053098 | |||
| e91d8038ad | |||
| 840b7ba1b1 | |||
| 233c27be23 | |||
| c54d9c15bc | |||
| 30c9ed6aa7 | |||
| 1bdf4e9c4c | |||
| 2f0d7a8502 | |||
| 99b8cbc75f |
@@ -0,0 +1 @@
|
|||||||
|
buy_me_a_coffee: alireza7
|
||||||
@@ -11,8 +11,6 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -46,10 +44,10 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -10,30 +10,46 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [amd64, arm64, arm]
|
platform:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- armv7
|
||||||
|
- armv6
|
||||||
|
- armv5
|
||||||
|
- 386
|
||||||
|
- s390x
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
cache: false
|
||||||
|
go-version-file: backend/go.mod
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install dependencies for arm64 and arm
|
- name: Install dependencies
|
||||||
if: matrix.platform == 'arm64' || matrix.platform == 'arm'
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
if [ "${{ matrix.platform }}" == "arm" ]; then
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
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
|
fi
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
@@ -44,43 +60,49 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
mv frontend/dist backend/web/html
|
mv frontend/dist backend/web/html
|
||||||
|
|
||||||
- name: Set evironments
|
- 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
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
|
export GOARCH=arm64
|
||||||
export CC=aarch64-linux-gnu-gcc
|
export CC=aarch64-linux-gnu-gcc
|
||||||
elif [ "${{ matrix.platform }}" == "arm" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=7
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
export CC=arm-linux-gnueabihf-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=6
|
||||||
|
export CC=arm-linux-gnueabihf-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=5
|
||||||
|
export CC=arm-linux-gnueabi-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
export GOARCH=386
|
||||||
|
export CC=i686-linux-gnu-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||||
|
export GOARCH=s390x
|
||||||
|
export CC=s390x-linux-gnu-gcc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build sing-box
|
### Build s-ui
|
||||||
run: |
|
|
||||||
git clone -b v1.8.5 https://github.com/SagerNet/sing-box
|
|
||||||
cd sing-box
|
|
||||||
go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
- name: Build s-ui
|
|
||||||
run: |
|
|
||||||
cd backend
|
cd backend
|
||||||
go build -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
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
mkdir s-ui
|
mkdir s-ui
|
||||||
cp sui s-ui/
|
cp sui s-ui/
|
||||||
cp s-ui.service s-ui/
|
cp s-ui.service s-ui/
|
||||||
cp sing-box.service s-ui/
|
cp s-ui.sh s-ui/
|
||||||
mkdir s-ui/bin
|
|
||||||
cp sing-box/sing-box s-ui/bin/
|
|
||||||
cp runSingbox.sh s-ui/bin/
|
|
||||||
|
|
||||||
- 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
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
|
|||||||
+8
-6
@@ -1,23 +1,25 @@
|
|||||||
FROM node:alpine as front-builder
|
FROM --platform=$BUILDPLATFORM node:alpine AS front-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
RUN npm install && npm run build
|
RUN npm install && npm run build
|
||||||
|
|
||||||
FROM golang:1.21-alpine AS backend-builder
|
FROM golang:1.23-alpine AS backend-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
ENV GOARCH=$TARGETARCH
|
||||||
|
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY backend/ ./
|
COPY backend/ ./
|
||||||
COPY --from=front-builder /app/dist/ /app/web/html/
|
COPY --from=front-builder /app/dist/ /app/web/html/
|
||||||
RUN go build -o sui main.go
|
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
|
||||||
|
|
||||||
FROM 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/
|
||||||
VOLUME [ "s-ui" ]
|
VOLUME [ "s-ui" ]
|
||||||
CMD [ "./sui" ]
|
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
**An Advanced Web Panel • Built on SagerNet/Sing-Box**
|
**An Advanced Web Panel • Built on SagerNet/Sing-Box**
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
[](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)
|
||||||
|
|
||||||
@@ -22,22 +23,56 @@
|
|||||||
| Multi-Client/Inbound | :heavy_check_mark: |
|
| Multi-Client/Inbound | :heavy_check_mark: |
|
||||||
| Advanced Traffic Routing Interface | :heavy_check_mark: |
|
| Advanced Traffic Routing Interface | :heavy_check_mark: |
|
||||||
| Client & Traffic & System Status | :heavy_check_mark: |
|
| Client & Traffic & System Status | :heavy_check_mark: |
|
||||||
| Subscription Service (link + info) | :heavy_check_mark: |
|
| Subscription Service (link/json + info)| :heavy_check_mark: |
|
||||||
| Dark/Light Theme | :heavy_check_mark: |
|
| Dark/Light Theme | :heavy_check_mark: |
|
||||||
|
|
||||||
|
|
||||||
|
## Default Installation Information
|
||||||
|
- Panel Port: 2095
|
||||||
|
- Panel Path: /app/
|
||||||
|
- Subscription Port: 2096
|
||||||
|
- Subscription Path: /sub/
|
||||||
|
- User/Password: admin
|
||||||
|
|
||||||
## Install & Upgrade to Latest Version
|
## Install & Upgrade to Latest Version
|
||||||
|
|
||||||
```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)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install Custom Version
|
## Install legacy Version
|
||||||
|
|
||||||
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `0.0.1`:
|
**Step 1:** To install your desired legacy version, add the version to the end of the installation command. e.g., ver `1.0.0`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) 0.0.1
|
VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/$VERSION/install.sh) $VERSION
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual installation
|
||||||
|
|
||||||
|
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)
|
||||||
|
3. **OPTIONAL** Copy `s-ui.sh` to /usr/bin/ and run `chmod +x /usr/bin/s-ui`.
|
||||||
|
4. Extract s-ui tar.gz file to a directory of your choice and navigate to the directory where you extracted the tar.gz file.
|
||||||
|
5. Copy *.service files to /etc/systemd/system/ and run `systemctl daemon-reload`.
|
||||||
|
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`
|
||||||
|
|
||||||
|
## Uninstall S-UI
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo -i
|
||||||
|
|
||||||
|
systemctl disable sing-box --now
|
||||||
|
systemctl disable s-ui --now
|
||||||
|
|
||||||
|
rm -f /etc/systemd/system/s-ui.service
|
||||||
|
rm -f /etc/systemd/system/sing-box.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
rm -fr /usr/local/s-ui
|
||||||
|
rm /usr/bin/s-ui
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install using Docker
|
## Install using Docker
|
||||||
@@ -55,10 +90,20 @@ curl -fsSL https://get.docker.com | sh
|
|||||||
|
|
||||||
**Step 2:** Install S-UI
|
**Step 2:** Install S-UI
|
||||||
|
|
||||||
|
> Docker compose method
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir s-ui && cd s-ui
|
||||||
|
wget -q https://raw.githubusercontent.com/alireza0/s-ui/master/docker-compose.yml
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use docker for s-ui only
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir s-ui && cd s-ui
|
mkdir s-ui && cd s-ui
|
||||||
docker run -itd \
|
docker run -itd \
|
||||||
-p 2095:2095 -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/:/usr/local/s-ui/db/ \
|
||||||
-v $PWD/cert/:/root/cert/ \
|
-v $PWD/cert/:/root/cert/ \
|
||||||
--name s-ui --restart=unless-stopped \
|
--name s-ui --restart=unless-stopped \
|
||||||
@@ -73,10 +118,64 @@ docker build -t s-ui .
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Manual run ( contribution )
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for details</summary>
|
||||||
|
|
||||||
|
### Build and run whole project
|
||||||
|
```shell
|
||||||
|
./runSUI.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### - Frontend
|
||||||
|
|
||||||
|
Frontend codes are in `frontend` folder in the root of repository.
|
||||||
|
|
||||||
|
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 codes are in `backend` folder in the root of repository.
|
||||||
|
> Please build frontend once before!
|
||||||
|
|
||||||
|
To build backend:
|
||||||
|
```shell
|
||||||
|
cd backend
|
||||||
|
|
||||||
|
# remove old frontend compiled files
|
||||||
|
rm -fr web/html/*
|
||||||
|
# apply new frontend compiled files
|
||||||
|
cp -R ../frontend/dist/ web/html/
|
||||||
|
# build
|
||||||
|
go build -o ../sui main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
To run backend (from root folder of repository):
|
||||||
|
```shell
|
||||||
|
./sui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
- English
|
- English
|
||||||
- Farsi
|
- Farsi
|
||||||
|
- Vietnamese
|
||||||
|
- Chinese (Simplified)
|
||||||
|
- Chinese (Traditional)
|
||||||
|
- Russian
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -95,10 +194,18 @@ docker build -t s-ui .
|
|||||||
|
|
||||||
## Recommended OS
|
## Recommended OS
|
||||||
|
|
||||||
|
- Ubuntu 20.04+
|
||||||
|
- Debian 11+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Ubuntu 20+
|
|
||||||
- Debian 10+
|
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
- Arch Linux
|
||||||
|
- Parch Linux
|
||||||
|
- Manjaro
|
||||||
|
- Armbian
|
||||||
|
- AlmaLinux 9+
|
||||||
|
- Rocky Linux 9+
|
||||||
|
- Oracle Linux 8+
|
||||||
|
- OpenSUSE Tubleweed
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
@@ -135,5 +242,4 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Stargazers over Time
|
## Stargazers over Time
|
||||||
|
|
||||||
[](https://starchart.cc/alireza0/s-ui)
|
[](https://starchart.cc/alireza0/s-ui)
|
||||||
|
|||||||
+175
-24
@@ -1,9 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,6 +17,10 @@ type APIHandler struct {
|
|||||||
service.UserService
|
service.UserService
|
||||||
service.ConfigService
|
service.ConfigService
|
||||||
service.ClientService
|
service.ClientService
|
||||||
|
service.TlsService
|
||||||
|
service.InboundService
|
||||||
|
service.OutboundService
|
||||||
|
service.EndpointService
|
||||||
service.PanelService
|
service.PanelService
|
||||||
service.StatsService
|
service.StatsService
|
||||||
service.ServerService
|
service.ServerService
|
||||||
@@ -27,7 +33,8 @@ func NewAPIHandler(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
||||||
g.Use(func(c *gin.Context) {
|
g.Use(func(c *gin.Context) {
|
||||||
if c.Request.URL.Path != "/api/login" && c.Request.URL.Path != "/api/logout" {
|
path := c.Request.URL.Path
|
||||||
|
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
|
||||||
checkLogin(c)
|
checkLogin(c)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -40,6 +47,8 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
action := c.Param("postAction")
|
action := c.Param("postAction")
|
||||||
remoteIP := getRemoteIp(c)
|
remoteIP := getRemoteIp(c)
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
hostname := getHostname(c)
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case "login":
|
case "login":
|
||||||
@@ -54,30 +63,53 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
logger.Infof("Unable to get session's max age from DB")
|
logger.Infof("Unable to get session's max age from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionMaxAge > 0 {
|
err = SetLoginUser(c, loginUser, sessionMaxAge)
|
||||||
err = SetMaxAge(c, sessionMaxAge*60)
|
if err == nil {
|
||||||
if err != nil {
|
logger.Info("user ", loginUser, " login success")
|
||||||
logger.Infof("Unable to set session's max age")
|
} else {
|
||||||
}
|
logger.Warning("login failed: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = SetLoginUser(c, loginUser)
|
|
||||||
logger.Info("user ", loginUser, " login success")
|
|
||||||
|
|
||||||
jsonMsg(c, "", nil)
|
jsonMsg(c, "", nil)
|
||||||
case "save":
|
case "changePass":
|
||||||
loginUser := GetLoginUser(c)
|
id := c.Request.FormValue("id")
|
||||||
data := map[string]string{}
|
oldPass := c.Request.FormValue("oldPass")
|
||||||
err = c.ShouldBind(&data)
|
newUsername := c.Request.FormValue("newUsername")
|
||||||
|
newPass := c.Request.FormValue("newPass")
|
||||||
|
err = a.UserService.ChangePass(id, oldPass, newUsername, newPass)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = a.ConfigService.SaveChanges(data, loginUser)
|
logger.Info("change user credentials success")
|
||||||
|
jsonMsg(c, "save", nil)
|
||||||
|
} else {
|
||||||
|
logger.Warning("change user credentials failed:", err)
|
||||||
|
jsonMsg(c, "", err)
|
||||||
}
|
}
|
||||||
jsonMsg(c, "save", err)
|
case "save":
|
||||||
|
obj := c.Request.FormValue("object")
|
||||||
|
act := c.Request.FormValue("action")
|
||||||
|
data := c.Request.FormValue("data")
|
||||||
|
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), 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":
|
case "restartApp":
|
||||||
err = a.PanelService.RestartPanel(3)
|
err = a.PanelService.RestartPanel(3)
|
||||||
jsonMsg(c, "restartApp", err)
|
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)
|
||||||
default:
|
default:
|
||||||
jsonMsg(c, "API call", nil)
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +131,19 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, data, nil)
|
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 "setting":
|
case "setting":
|
||||||
data, err := a.SettingService.GetAllSetting()
|
data, err := a.SettingService.GetAllSetting()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,24 +171,49 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
case "onlines":
|
case "onlines":
|
||||||
onlines, err := a.StatsService.GetOnlines()
|
onlines, err := a.StatsService.GetOnlines()
|
||||||
jsonObj(c, onlines, err)
|
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)
|
||||||
default:
|
default:
|
||||||
jsonMsg(c, "API call", nil)
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIHandler) loadData(c *gin.Context) (string, error) {
|
func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
||||||
var data string
|
data := make(map[string]interface{}, 0)
|
||||||
lu := c.Query("lu")
|
lu := c.Query("lu")
|
||||||
isUpdated, err := a.ConfigService.CheckChnages(lu)
|
isUpdated, err := a.ConfigService.CheckChanges(lu)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
onlines, err := a.StatsService.GetOnlines()
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if isUpdated {
|
if isUpdated {
|
||||||
config, err := a.ConfigService.GetConfig()
|
config, err := a.SettingService.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -151,14 +221,95 @@ func (a *APIHandler) loadData(c *gin.Context) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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])
|
subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
data = fmt.Sprintf(`{"config": %s,"clients": %s,"subURI": "%s", "onlines": %s}`, string(*config), clients, subURI, onlines)
|
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 {
|
} else {
|
||||||
data = fmt.Sprintf(`{"onlines": %s}`, onlines)
|
data["onlines"] = onlines
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
|
||||||
|
data := make(map[string]interface{}, 0)
|
||||||
|
|
||||||
|
for _, obj := range objs {
|
||||||
|
switch obj {
|
||||||
|
case "inbounds":
|
||||||
|
id := c.Query("id")
|
||||||
|
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.GetAll()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
+13
-4
@@ -16,17 +16,26 @@ func init() {
|
|||||||
gob.Register(model.User{})
|
gob.Register(model.User{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLoginUser(c *gin.Context, userName string) error {
|
func SetLoginUser(c *gin.Context, userName string, maxAge int) error {
|
||||||
|
options := sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
Secure: false,
|
||||||
|
}
|
||||||
|
if maxAge > 0 {
|
||||||
|
options.MaxAge = maxAge * 60
|
||||||
|
}
|
||||||
|
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Set(loginUser, userName)
|
s.Set(loginUser, userName)
|
||||||
|
s.Options(options)
|
||||||
|
|
||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
func SetMaxAge(c *gin.Context) error {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
s.Options(sessions.Options{
|
s.Options(sessions.Options{
|
||||||
Path: "/",
|
Path: "/",
|
||||||
MaxAge: maxAge,
|
|
||||||
})
|
})
|
||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-2
@@ -27,6 +27,14 @@ func getRemoteIp(c *gin.Context) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHostname(c *gin.Context) string {
|
||||||
|
host := c.Request.Host
|
||||||
|
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||||
|
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
func jsonMsg(c *gin.Context, msg string, err error) {
|
func jsonMsg(c *gin.Context, msg string, err error) {
|
||||||
jsonMsgObj(c, msg, nil, err)
|
jsonMsgObj(c, msg, nil, err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +54,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.Success = false
|
m.Success = false
|
||||||
m.Msg = msg + err.Error()
|
m.Msg = msg + ": " + err.Error()
|
||||||
logger.Warning("failed :", err)
|
logger.Warning("failed :", err)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
@@ -69,7 +77,7 @@ func pureJsonMsg(c *gin.Context, success bool, msg string) {
|
|||||||
func checkLogin(c *gin.Context) {
|
func checkLogin(c *gin.Context) {
|
||||||
if !IsLogin(c) {
|
if !IsLogin(c) {
|
||||||
if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
|
if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
|
||||||
pureJsonMsg(c, false, "Not authorized")
|
pureJsonMsg(c, false, "Invalid login")
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-9
@@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
|
"s-ui/core"
|
||||||
"s-ui/cronjob"
|
"s-ui/cronjob"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
@@ -15,9 +16,12 @@ import (
|
|||||||
|
|
||||||
type APP struct {
|
type APP struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
webServer *web.Server
|
configService *service.ConfigService
|
||||||
subServer *sub.Server
|
webServer *web.Server
|
||||||
cronJob *cronjob.CronJob
|
subServer *sub.Server
|
||||||
|
cronJob *cronjob.CronJob
|
||||||
|
logger *logging.Logger
|
||||||
|
core *core.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp() *APP {
|
func NewApp() *APP {
|
||||||
@@ -34,15 +38,17 @@ func (a *APP) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init Setting
|
||||||
|
a.SettingService.GetAllSetting()
|
||||||
|
|
||||||
|
a.core = core.NewCore()
|
||||||
|
|
||||||
a.cronJob = cronjob.NewCronJob()
|
a.cronJob = cronjob.NewCronJob()
|
||||||
a.webServer = web.NewServer()
|
a.webServer = web.NewServer()
|
||||||
a.subServer = sub.NewServer()
|
a.subServer = sub.NewServer()
|
||||||
|
|
||||||
configService := service.NewConfigService()
|
a.configService = service.NewConfigService(a.core)
|
||||||
err = configService.InitConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +57,13 @@ func (a *APP) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = a.cronJob.Start(loc)
|
|
||||||
|
trafficAge, err := a.SettingService.GetTrafficAge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cronJob.Start(loc, trafficAge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -66,6 +78,11 @@ func (a *APP) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.configService.StartCore("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +96,10 @@ func (a *APP) Stop() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop Web Server err:", err)
|
logger.Warning("stop Web Server err:", err)
|
||||||
}
|
}
|
||||||
|
err = a.configService.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop Core err:", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APP) initLog() {
|
func (a *APP) initLog() {
|
||||||
@@ -100,3 +121,7 @@ func (a *APP) RestartApp() {
|
|||||||
a.Stop()
|
a.Stop()
|
||||||
a.Start()
|
a.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APP) GetCore() *core.Core {
|
||||||
|
return a.core
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resetAdmin() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userService := service.UserService{}
|
||||||
|
err = userService.UpdateFirstUser("admin", "admin")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset admin credentials failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset admin credentials success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAdmin(username string, password string) {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != "" || password != "" {
|
||||||
|
userService := service.UserService{}
|
||||||
|
err := userService.UpdateFirstUser(username, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset admin credentials failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset admin credentials success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAdmin() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userService := service.UserService{}
|
||||||
|
userModel, err := userService.GetFirstUser()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current user info failed,error info:", err)
|
||||||
|
}
|
||||||
|
username := userModel.Username
|
||||||
|
userpasswd := userModel.Password
|
||||||
|
if (username == "") || (userpasswd == "") {
|
||||||
|
fmt.Println("current username or password is empty")
|
||||||
|
}
|
||||||
|
fmt.Println("First admin credentials:")
|
||||||
|
fmt.Println("\tUsername:\t", username)
|
||||||
|
fmt.Println("\tPassword:\t", userpasswd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"s-ui/cmd/migration"
|
||||||
|
"s-ui/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseCmd() {
|
||||||
|
var showVersion bool
|
||||||
|
flag.BoolVar(&showVersion, "v", false, "show version")
|
||||||
|
|
||||||
|
adminCmd := flag.NewFlagSet("admin", flag.ExitOnError)
|
||||||
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
|
|
||||||
|
var username string
|
||||||
|
var password string
|
||||||
|
var port int
|
||||||
|
var path string
|
||||||
|
var subPort int
|
||||||
|
var subPath string
|
||||||
|
var reset bool
|
||||||
|
var show bool
|
||||||
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
|
settingCmd.StringVar(&path, "path", "", "set panel path")
|
||||||
|
settingCmd.IntVar(&subPort, "subPort", 0, "set sub port")
|
||||||
|
settingCmd.StringVar(&subPath, "subPath", "", "set sub path")
|
||||||
|
|
||||||
|
adminCmd.BoolVar(&show, "show", false, "show first admin credentials")
|
||||||
|
adminCmd.BoolVar(&reset, "reset", false, "reset first admin credentials")
|
||||||
|
adminCmd.StringVar(&username, "username", "", "set login username")
|
||||||
|
adminCmd.StringVar(&password, "password", "", "set login password")
|
||||||
|
|
||||||
|
oldUsage := flag.Usage
|
||||||
|
flag.Usage = func() {
|
||||||
|
oldUsage()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Commands:")
|
||||||
|
fmt.Println(" admin set/reset/show first admin credentials")
|
||||||
|
fmt.Println(" uri Show panel URI")
|
||||||
|
fmt.Println(" migrate migrate form older version")
|
||||||
|
fmt.Println(" setting set/reset/show settings")
|
||||||
|
fmt.Println()
|
||||||
|
adminCmd.Usage()
|
||||||
|
fmt.Println()
|
||||||
|
settingCmd.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
if showVersion {
|
||||||
|
fmt.Println(config.GetVersion())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "admin":
|
||||||
|
err := adminCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case show:
|
||||||
|
showAdmin()
|
||||||
|
case reset:
|
||||||
|
resetAdmin()
|
||||||
|
default:
|
||||||
|
updateAdmin(username, password)
|
||||||
|
showAdmin()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "uri":
|
||||||
|
getPanelURI()
|
||||||
|
|
||||||
|
case "migrate":
|
||||||
|
migration.MigrateDb()
|
||||||
|
|
||||||
|
case "setting":
|
||||||
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case show:
|
||||||
|
showSetting()
|
||||||
|
case reset:
|
||||||
|
resetSetting()
|
||||||
|
default:
|
||||||
|
updateSetting(port, path, subPort, subPath)
|
||||||
|
showSetting()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("Invalid subcommands")
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func migrateClientSchema(db *gorm.DB) error {
|
||||||
|
rows, err := db.Raw("PRAGMA table_info(clients)").Rows()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var (
|
||||||
|
cid int
|
||||||
|
cname string
|
||||||
|
ctype string
|
||||||
|
notnull int
|
||||||
|
dfltValue interface{}
|
||||||
|
pk int
|
||||||
|
)
|
||||||
|
|
||||||
|
rows.Scan(&cid, &cname, &ctype, ¬null, &dfltValue, &pk)
|
||||||
|
if cname == "config" || cname == "inbounds" || cname == "links" {
|
||||||
|
if ctype == "text" {
|
||||||
|
fmt.Printf("Column %s has type TEXT\n", cname)
|
||||||
|
oldData := make([]struct {
|
||||||
|
Id uint
|
||||||
|
Data string
|
||||||
|
}, 0)
|
||||||
|
db.Model(model.Client{}).Select("id", cname+" as data").Scan(&oldData)
|
||||||
|
for _, data := range oldData {
|
||||||
|
var newData []byte
|
||||||
|
switch cname {
|
||||||
|
case "inbounds":
|
||||||
|
inbounds := strings.Split(data.Data, ",")
|
||||||
|
newData, _ = json.MarshalIndent(inbounds, "", " ")
|
||||||
|
case "config":
|
||||||
|
jsonData := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(data.Data), &jsonData)
|
||||||
|
newData, _ = json.MarshalIndent(jsonData, "", " ")
|
||||||
|
case "links":
|
||||||
|
jsonData := make([]interface{}, 0)
|
||||||
|
json.Unmarshal([]byte(data.Data), &jsonData)
|
||||||
|
newData, _ = json.MarshalIndent(jsonData, "", " ")
|
||||||
|
}
|
||||||
|
err = db.Model(model.Client{}).Where("id = ?", data.Id).UpdateColumn(cname, newData).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func changesObj(db *gorm.DB) error {
|
||||||
|
return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func to1_1(db *gorm.DB) error {
|
||||||
|
err := migrateClientSchema(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = changesObj(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InboundData struct {
|
||||||
|
Id uint
|
||||||
|
Tag string
|
||||||
|
Addrs json.RawMessage
|
||||||
|
OutJson json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveJsonToDb(db *gorm.DB) error {
|
||||||
|
binFolderPath := os.Getenv("SUI_BIN_FOLDER")
|
||||||
|
if binFolderPath == "" {
|
||||||
|
binFolderPath = "bin"
|
||||||
|
}
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configPath := dir + "/" + binFolderPath + "/config.json"
|
||||||
|
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var oldConfig map[string]interface{}
|
||||||
|
err = json.Unmarshal(data, &oldConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbounds := oldConfig["inbounds"].([]interface{})
|
||||||
|
db.Migrator().DropTable(&model.Inbound{})
|
||||||
|
db.AutoMigrate(&model.Inbound{})
|
||||||
|
for _, inbound := range oldInbounds {
|
||||||
|
inbObj, _ := inbound.(map[string]interface{})
|
||||||
|
tag, _ := inbObj["tag"].(string)
|
||||||
|
if tlsObj, ok := inbObj["tls"]; ok {
|
||||||
|
var tls_id uint
|
||||||
|
err = db.Raw("SELECT id FROM tls WHERE inbounds like ?", `%"`+tag+`"%`).Find(&tls_id).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind or Create tls_id
|
||||||
|
if tls_id > 0 {
|
||||||
|
inbObj["tls_id"] = tls_id
|
||||||
|
} else {
|
||||||
|
tls_server, _ := json.MarshalIndent(tlsObj, "", " ")
|
||||||
|
if len(tls_server) > 5 {
|
||||||
|
tlsObject := tlsObj.(map[string]interface{})
|
||||||
|
tlsClientObj := map[string]interface{}{}
|
||||||
|
if enabled, ok := tlsObject["enabled"]; ok {
|
||||||
|
tlsClientObj["enabled"] = enabled
|
||||||
|
}
|
||||||
|
if alpn, ok := tlsObject["alpn"]; ok {
|
||||||
|
tlsClientObj["alpn"] = alpn
|
||||||
|
}
|
||||||
|
if sni, ok := tlsObject["server_name"]; ok {
|
||||||
|
tlsClientObj["server_name"] = sni
|
||||||
|
}
|
||||||
|
tls_client, _ := json.MarshalIndent(tlsClientObj, "", " ")
|
||||||
|
newTls := &model.Tls{
|
||||||
|
Name: tag,
|
||||||
|
Server: tls_server,
|
||||||
|
Client: tls_client,
|
||||||
|
}
|
||||||
|
err = db.Create(newTls).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbObj["tls_id"] = newTls.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbData InboundData
|
||||||
|
db.Raw("select id,addrs,out_json from inbound_data where tag = ?", tag).Find(&inbData)
|
||||||
|
if inbData.Id > 0 {
|
||||||
|
inbObj["out_json"] = inbData.OutJson
|
||||||
|
var addrs []map[string]interface{}
|
||||||
|
json.Unmarshal(inbData.Addrs, &addrs)
|
||||||
|
for index, addr := range addrs {
|
||||||
|
if tlsEnable, ok := addr["tls"].(bool); ok {
|
||||||
|
newTls := map[string]interface{}{
|
||||||
|
"enabled": tlsEnable,
|
||||||
|
}
|
||||||
|
if insecure, ok := addr["insecure"].(bool); ok {
|
||||||
|
newTls["insecure"] = insecure
|
||||||
|
delete(addrs[index], "insecure")
|
||||||
|
}
|
||||||
|
if sni, ok := addr["server_name"].(string); ok {
|
||||||
|
newTls["server_name"] = sni
|
||||||
|
delete(addrs[index], "server_name")
|
||||||
|
}
|
||||||
|
addrs[index]["tls"] = newTls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inbObj["addrs"] = addrs
|
||||||
|
} else {
|
||||||
|
inbObj["out_json"] = json.RawMessage("{}")
|
||||||
|
inbObj["addrs"] = json.RawMessage("[]")
|
||||||
|
}
|
||||||
|
// Delete deprecated fields
|
||||||
|
delete(inbObj, "sniff")
|
||||||
|
delete(inbObj, "sniff_override_destination")
|
||||||
|
delete(inbObj, "sniff_timeout")
|
||||||
|
delete(inbObj, "domain_strategy")
|
||||||
|
inbJson, _ := json.Marshal(inbObj)
|
||||||
|
|
||||||
|
var newInbound model.Inbound
|
||||||
|
err = newInbound.UnmarshalJSON(inbJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.Create(&newInbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(oldConfig, "inbounds")
|
||||||
|
|
||||||
|
blockOutboundTags := []string{}
|
||||||
|
dnsOutboundTags := []string{}
|
||||||
|
|
||||||
|
oldOutbounds := oldConfig["outbounds"].([]interface{})
|
||||||
|
db.Migrator().DropTable(&model.Outbound{}, &model.Endpoint{})
|
||||||
|
db.AutoMigrate(&model.Outbound{}, &model.Endpoint{})
|
||||||
|
for _, outbound := range oldOutbounds {
|
||||||
|
outType, _ := outbound.(map[string]interface{})["type"].(string)
|
||||||
|
outboundRaw, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
|
if outType == "wireguard" { // Check if it is Entrypoint
|
||||||
|
var newEntrypoint model.Endpoint
|
||||||
|
err = newEntrypoint.UnmarshalJSON(outboundRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.Create(&newEntrypoint).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else { // It is Outbound
|
||||||
|
var newOutbound model.Outbound
|
||||||
|
err = newOutbound.UnmarshalJSON(outboundRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Delete deprecated fields
|
||||||
|
if newOutbound.Type == "direct" {
|
||||||
|
var options map[string]interface{}
|
||||||
|
json.Unmarshal(newOutbound.Options, &options)
|
||||||
|
delete(options, "override_address")
|
||||||
|
delete(options, "override_port")
|
||||||
|
newOutbound.Options, _ = json.Marshal(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch newOutbound.Type {
|
||||||
|
case "dns":
|
||||||
|
dnsOutboundTags = append(dnsOutboundTags, newOutbound.Tag)
|
||||||
|
case "block":
|
||||||
|
blockOutboundTags = append(blockOutboundTags, newOutbound.Tag)
|
||||||
|
default:
|
||||||
|
err = db.Create(&newOutbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(oldConfig, "outbounds")
|
||||||
|
|
||||||
|
// Check routing rules
|
||||||
|
if routingRules, ok := oldConfig["route"].(map[string]interface{}); ok {
|
||||||
|
if rules, hasRules := routingRules["rules"].([]interface{}); hasRules {
|
||||||
|
hasDns := false
|
||||||
|
for index, rule := range rules {
|
||||||
|
ruleObj, _ := rule.(map[string]interface{})
|
||||||
|
isBlock := false
|
||||||
|
isDns := false
|
||||||
|
outboundTag, _ := ruleObj["outbound"].(string)
|
||||||
|
for _, tag := range blockOutboundTags {
|
||||||
|
if tag == outboundTag {
|
||||||
|
isBlock = true
|
||||||
|
delete(ruleObj, "outbound")
|
||||||
|
ruleObj["action"] = "reject"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tag := range dnsOutboundTags {
|
||||||
|
if tag == outboundTag {
|
||||||
|
isDns = true
|
||||||
|
hasDns = true
|
||||||
|
delete(ruleObj, "outbound")
|
||||||
|
ruleObj["action"] = "hijack-dns"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isBlock && !isDns {
|
||||||
|
ruleObj["action"] = "route"
|
||||||
|
}
|
||||||
|
rules[index] = ruleObj
|
||||||
|
}
|
||||||
|
if hasDns {
|
||||||
|
rules = append(rules, map[string]interface{}{"action": "sniff"})
|
||||||
|
}
|
||||||
|
routingRules["rules"] = rules
|
||||||
|
}
|
||||||
|
oldConfig["route"] = routingRules
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove v2rayapi and clashapi from experimental config
|
||||||
|
experimental := oldConfig["experimental"].(map[string]interface{})
|
||||||
|
delete(experimental, "v2ray_api")
|
||||||
|
delete(experimental, "clash_api")
|
||||||
|
oldConfig["experimental"] = experimental
|
||||||
|
|
||||||
|
// Save the other configs
|
||||||
|
var otherConfigs json.RawMessage
|
||||||
|
otherConfigs, err = json.MarshalIndent(oldConfig, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Save(&model.Setting{
|
||||||
|
Key: "config",
|
||||||
|
Value: string(otherConfigs),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateTls(db *gorm.DB) error {
|
||||||
|
if !db.Migrator().HasColumn(&model.Tls{}, "inbounds") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Migrator().DropColumn(&model.Tls{}, "inbounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropInboundData(db *gorm.DB) error {
|
||||||
|
if !db.Migrator().HasTable(&InboundData{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Migrator().DropTable(&InboundData{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateClients(db *gorm.DB) error {
|
||||||
|
var oldClients []model.Client
|
||||||
|
err := db.Model(model.Client{}).Scan(&oldClients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, oldClient := range oldClients {
|
||||||
|
var old_inbounds []string
|
||||||
|
err = json.Unmarshal(oldClient.Inbounds, &old_inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var inbound_ids []uint
|
||||||
|
err = db.Raw("SELECT id FROM inbounds WHERE tag in ?", old_inbounds).Find(&inbound_ids).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldClients[index].Inbounds, _ = json.Marshal(inbound_ids)
|
||||||
|
}
|
||||||
|
return db.Save(oldClients).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func to1_2(db *gorm.DB) error {
|
||||||
|
err := moveJsonToDb(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = migrateTls(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dropInboundData(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return migrateClients(db)
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MigrateDb() {
|
||||||
|
// void running on first install
|
||||||
|
path := config.GetDBPath()
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
println("Database not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := gorm.Open(sqlite.Open(path))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
currentVersion := config.GetVersion()
|
||||||
|
dbVersion := ""
|
||||||
|
tx.Raw("SELECT value FROM settings WHERE key = ?", "version").Find(&dbVersion)
|
||||||
|
fmt.Println("Current version:", currentVersion, "\nDatabase version:", dbVersion)
|
||||||
|
|
||||||
|
if currentVersion == dbVersion {
|
||||||
|
fmt.Println("Database is up to date, no need to migrate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Start migrating database...")
|
||||||
|
|
||||||
|
// Before 1.2
|
||||||
|
if dbVersion == "" {
|
||||||
|
err = to1_1(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Migration to 1.1 failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = to1_2(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Migration to 1.2 failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set version
|
||||||
|
err = tx.Raw("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Update version failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Migration done!")
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/service"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resetSetting() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
err = settingService.ResetSettings()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset setting failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset setting success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSetting(port int, path string, subPort int, subPath string) {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
|
||||||
|
if port > 0 {
|
||||||
|
err := settingService.SetPort(port)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set port failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set port success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path != "" {
|
||||||
|
err := settingService.SetWebPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set path failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set path success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subPort > 0 {
|
||||||
|
err := settingService.SetSubPort(subPort)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set sub port failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set sub port success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subPath != "" {
|
||||||
|
err := settingService.SetSubPath(subPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set sub path failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set sub path success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showSetting() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
allSetting, err := settingService.GetAllSetting()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current port failed,error info:", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Current panel settings:")
|
||||||
|
fmt.Println("\tPanel port:\t", (*allSetting)["webPort"])
|
||||||
|
fmt.Println("\tPanel path:\t", (*allSetting)["webPath"])
|
||||||
|
if (*allSetting)["webListen"] != "" {
|
||||||
|
fmt.Println("\tPanel IP:\t", (*allSetting)["webListen"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["webDomain"] != "" {
|
||||||
|
fmt.Println("\tPanel Domain:\t", (*allSetting)["webDomain"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["webURI"] != "" {
|
||||||
|
fmt.Println("\tPanel URI:\t", (*allSetting)["webURI"])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Current subscription settings:")
|
||||||
|
fmt.Println("\tSub port:\t", (*allSetting)["subPort"])
|
||||||
|
fmt.Println("\tSub path:\t", (*allSetting)["subPath"])
|
||||||
|
if (*allSetting)["subListen"] != "" {
|
||||||
|
fmt.Println("\tSub IP:\t", (*allSetting)["subListen"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["subDomain"] != "" {
|
||||||
|
fmt.Println("\tSub Domain:\t", (*allSetting)["subDomain"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["subURI"] != "" {
|
||||||
|
fmt.Println("\tSub URI:\t", (*allSetting)["subURI"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPanelURI() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
Port, _ := settingService.GetPort()
|
||||||
|
BasePath, _ := settingService.GetWebPath()
|
||||||
|
Listen, _ := settingService.GetListen()
|
||||||
|
Domain, _ := settingService.GetWebDomain()
|
||||||
|
KeyFile, _ := settingService.GetKeyFile()
|
||||||
|
CertFile, _ := settingService.GetCertFile()
|
||||||
|
TLS := false
|
||||||
|
if KeyFile != "" && CertFile != "" {
|
||||||
|
TLS = true
|
||||||
|
}
|
||||||
|
Proto := ""
|
||||||
|
if TLS {
|
||||||
|
Proto = "https://"
|
||||||
|
} else {
|
||||||
|
Proto = "http://"
|
||||||
|
}
|
||||||
|
PortText := fmt.Sprintf(":%d", Port)
|
||||||
|
if (Port == 443 && TLS) || (Port == 80 && !TLS) {
|
||||||
|
PortText = ""
|
||||||
|
}
|
||||||
|
if len(Domain) > 0 {
|
||||||
|
fmt.Println(Proto + Domain + PortText + BasePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(Listen) > 0 {
|
||||||
|
fmt.Println(Proto + Listen + PortText + BasePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Local address:")
|
||||||
|
// get ip address
|
||||||
|
netInterfaces, _ := net.Interfaces()
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
|
||||||
|
addrs := netInterfaces[i].Addrs
|
||||||
|
for _, address := range addrs {
|
||||||
|
IP := strings.Split(address.Addr, "/")[0]
|
||||||
|
if strings.Contains(address.Addr, ".") {
|
||||||
|
fmt.Println(Proto + IP + PortText + BasePath)
|
||||||
|
} else if address.Addr[0:6] != "fe80::" {
|
||||||
|
fmt.Println(Proto + "[" + IP + "]" + PortText + BasePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := http.Get("https://api.ipify.org?format=text")
|
||||||
|
if err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
ip, err := io.ReadAll(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("\nGlobal address:\n%s%s%s%s\n", Proto, ip, PortText, BasePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,9 +14,6 @@ var version string
|
|||||||
//go:embed name
|
//go:embed name
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
//go:embed config.json
|
|
||||||
var defaultConfig string
|
|
||||||
|
|
||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -48,18 +46,14 @@ func IsDebug() bool {
|
|||||||
return os.Getenv("SUI_DEBUG") == "true"
|
return os.Getenv("SUI_DEBUG") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBinFolderPath() string {
|
|
||||||
binFolderPath := os.Getenv("SUI_BIN_FOLDER")
|
|
||||||
if binFolderPath == "" {
|
|
||||||
binFolderPath = "bin"
|
|
||||||
}
|
|
||||||
return binFolderPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath == "" {
|
||||||
dbFolderPath = "db"
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
dbFolderPath = "/usr/local/s-ui/db"
|
||||||
|
}
|
||||||
|
dbFolderPath = dir + "/db"
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
@@ -67,11 +61,3 @@ func GetDBFolderPath() string {
|
|||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultConfig() string {
|
|
||||||
return defaultConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEnvApi() string {
|
|
||||||
return os.Getenv("SINGBOX_API")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"log": {
|
|
||||||
"level": "info"
|
|
||||||
},
|
|
||||||
"dns": {},
|
|
||||||
"inbounds": [],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "direct",
|
|
||||||
"type": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns-out"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns-out"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"v2ray_api": {
|
|
||||||
"listen": "127.0.0.1:1080",
|
|
||||||
"stats": {
|
|
||||||
"enabled": true,
|
|
||||||
"inbounds": [],
|
|
||||||
"outbounds": [
|
|
||||||
"direct"
|
|
||||||
],
|
|
||||||
"users": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
0.0.1
|
1.2.0-beta.1
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
|
"github.com/sagernet/sing-box/route"
|
||||||
|
sbCommon "github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
|
type Box struct {
|
||||||
|
createdAt time.Time
|
||||||
|
logFactory log.Factory
|
||||||
|
logger log.ContextLogger
|
||||||
|
network *route.NetworkManager
|
||||||
|
endpoint *endpoint.Manager
|
||||||
|
inbound *inbound.Manager
|
||||||
|
outbound *outbound.Manager
|
||||||
|
connection *route.ConnectionManager
|
||||||
|
router *route.Router
|
||||||
|
services []adapter.LifecycleService
|
||||||
|
connTracker *ConnTracker
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
option.Options
|
||||||
|
Context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func Context(
|
||||||
|
ctx context.Context,
|
||||||
|
inboundRegistry adapter.InboundRegistry,
|
||||||
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
) context.Context {
|
||||||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBox(options Options) (*Box, error) {
|
||||||
|
var err error
|
||||||
|
createdAt := time.Now()
|
||||||
|
ctx := options.Context
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
|
||||||
|
if endpointRegistry == nil {
|
||||||
|
return nil, common.NewError("missing endpoint registry in context")
|
||||||
|
}
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, common.NewError("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, common.NewError("missing outbound registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
|
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
|
||||||
|
var needCacheFile bool
|
||||||
|
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
|
||||||
|
needCacheFile = true
|
||||||
|
}
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
var defaultLogWriter io.Writer
|
||||||
|
if platformInterface != nil {
|
||||||
|
defaultLogWriter = io.Discard
|
||||||
|
}
|
||||||
|
var logFactory log.Factory
|
||||||
|
logFactory, err = NewFactory(log.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: sbCommon.PtrValueOrDefault(options.Log),
|
||||||
|
DefaultWriter: defaultLogWriter,
|
||||||
|
BaseTime: createdAt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("create log factory", err)
|
||||||
|
}
|
||||||
|
factory = logFactory
|
||||||
|
|
||||||
|
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
||||||
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
|
||||||
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize network manager", err)
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
|
router, err := route.NewRouter(ctx, logFactory, routeOptions, sbCommon.PtrValueOrDefault(options.DNS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize router", err)
|
||||||
|
}
|
||||||
|
for i, endpointOptions := range options.Endpoints {
|
||||||
|
var tag string
|
||||||
|
if endpointOptions.Tag != "" {
|
||||||
|
tag = endpointOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = endpointManager.Create(ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
endpointOptions.Type,
|
||||||
|
endpointOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize endpoint["+F.ToString(i)+"] "+tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, inboundOptions := range options.Inbounds {
|
||||||
|
var tag string
|
||||||
|
if inboundOptions.Tag != "" {
|
||||||
|
tag = inboundOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = inboundManager.Create(ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
inboundOptions.Type,
|
||||||
|
inboundOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize inbound[", i, "] ", tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, outboundOptions := range options.Outbounds {
|
||||||
|
var tag string
|
||||||
|
if outboundOptions.Tag != "" {
|
||||||
|
tag = outboundOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
outboundCtx := ctx
|
||||||
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = outboundManager.Create(
|
||||||
|
outboundCtx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundManager.Initialize(sbCommon.Must1(
|
||||||
|
direct.NewOutbound(
|
||||||
|
ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger("outbound/direct"),
|
||||||
|
"direct",
|
||||||
|
option.DirectOutboundOptions{},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
if platformInterface != nil {
|
||||||
|
err = platformInterface.Initialize(networkManager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize platform interface", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if connTracker == nil {
|
||||||
|
connTracker = NewConnTracker()
|
||||||
|
}
|
||||||
|
router.SetTracker(connTracker)
|
||||||
|
|
||||||
|
var services []adapter.LifecycleService
|
||||||
|
|
||||||
|
if needCacheFile {
|
||||||
|
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
services = append(services, cacheFile)
|
||||||
|
}
|
||||||
|
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError(err, "create NTP service")
|
||||||
|
}
|
||||||
|
timeService := ntp.NewService(ntp.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Dialer: ntpDialer,
|
||||||
|
Logger: logFactory.NewLogger("ntp"),
|
||||||
|
Server: ntpOptions.ServerOptions.Build(),
|
||||||
|
Interval: time.Duration(ntpOptions.Interval),
|
||||||
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
|
})
|
||||||
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
|
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
||||||
|
}
|
||||||
|
return &Box{
|
||||||
|
network: networkManager,
|
||||||
|
endpoint: endpointManager,
|
||||||
|
inbound: inboundManager,
|
||||||
|
outbound: outboundManager,
|
||||||
|
connection: connectionManager,
|
||||||
|
router: router,
|
||||||
|
createdAt: createdAt,
|
||||||
|
logFactory: logFactory,
|
||||||
|
logger: logFactory.Logger(),
|
||||||
|
services: services,
|
||||||
|
connTracker: connTracker,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) PreStart() error {
|
||||||
|
err := s.preStart()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: remove catch error
|
||||||
|
defer func() {
|
||||||
|
v := recover()
|
||||||
|
if v != nil {
|
||||||
|
s.logger.Error(err.Error())
|
||||||
|
s.logger.Error("panic on early close: " + fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Start() error {
|
||||||
|
err := s.start()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: remove catch error
|
||||||
|
defer func() {
|
||||||
|
v := recover()
|
||||||
|
if v != nil {
|
||||||
|
s.logger.Debug(err.Error())
|
||||||
|
s.logger.Error("panic on early start: " + fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) preStart() error {
|
||||||
|
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||||
|
monitor.Start("start logger")
|
||||||
|
err := s.logFactory.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError(err, "start logger")
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) start() error {
|
||||||
|
err := s.preStart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Close() error {
|
||||||
|
select {
|
||||||
|
case <-s.done:
|
||||||
|
return os.ErrClosed
|
||||||
|
default:
|
||||||
|
close(s.done)
|
||||||
|
}
|
||||||
|
err := sbCommon.Close(
|
||||||
|
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||||
|
)
|
||||||
|
for _, lifecycleService := range s.services {
|
||||||
|
err1 := lifecycleService.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err1 := s.logFactory.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
s.logger.Debug("logger close error: ", err1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Uptime() uint32 {
|
||||||
|
return uint32(time.Now().Sub(s.createdAt).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Network() adapter.NetworkManager {
|
||||||
|
return s.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Router() adapter.Router {
|
||||||
|
return s.router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Inbound() adapter.InboundManager {
|
||||||
|
return s.inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
|
return s.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Endpoint() adapter.EndpointManager {
|
||||||
|
return s.endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) ConnTracker() *ConnTracker {
|
||||||
|
return s.connTracker
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Counter struct {
|
||||||
|
read *atomic.Int64
|
||||||
|
write *atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnTracker struct {
|
||||||
|
access sync.Mutex
|
||||||
|
createdAt time.Time
|
||||||
|
inbounds map[string]Counter
|
||||||
|
outbounds map[string]Counter
|
||||||
|
users map[string]Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnTracker() *ConnTracker {
|
||||||
|
return &ConnTracker{
|
||||||
|
createdAt: time.Now(),
|
||||||
|
inbounds: make(map[string]Counter),
|
||||||
|
outbounds: make(map[string]Counter),
|
||||||
|
users: make(map[string]Counter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) getReadCounters(inbound string, outbound string, user string) ([]*atomic.Int64, []*atomic.Int64) {
|
||||||
|
var readCounter []*atomic.Int64
|
||||||
|
var writeCounter []*atomic.Int64
|
||||||
|
c.access.Lock()
|
||||||
|
if inbound != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.inbounds, inbound).read)
|
||||||
|
writeCounter = append(writeCounter, c.inbounds[inbound].write)
|
||||||
|
}
|
||||||
|
if outbound != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.outbounds, outbound).read)
|
||||||
|
writeCounter = append(writeCounter, c.outbounds[outbound].write)
|
||||||
|
}
|
||||||
|
if user != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.users, user).read)
|
||||||
|
writeCounter = append(writeCounter, c.users[user].write)
|
||||||
|
}
|
||||||
|
c.access.Unlock()
|
||||||
|
return readCounter, writeCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) loadOrCreateCounter(obj *map[string]Counter, name string) Counter {
|
||||||
|
counter, loaded := (*obj)[name]
|
||||||
|
if loaded {
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
counter = Counter{read: &atomic.Int64{}, write: &atomic.Int64{}}
|
||||||
|
(*obj)[name] = counter
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) 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)
|
||||||
|
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 {
|
||||||
|
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||||
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) GetStats() *[]model.Stats {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
dt := time.Now().Unix()
|
||||||
|
|
||||||
|
s := []model.Stats{}
|
||||||
|
for inbound, counter := range c.inbounds {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "inbound",
|
||||||
|
Tag: inbound,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "inbound",
|
||||||
|
Tag: inbound,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for outbound, counter := range c.outbounds {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "outbound",
|
||||||
|
Tag: outbound,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "outbound",
|
||||||
|
Tag: outbound,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for user, counter := range c.users {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "user",
|
||||||
|
Tag: user,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "user",
|
||||||
|
Tag: user,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"s-ui/logger"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Core) AddInbound(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var inbound_config option.Inbound
|
||||||
|
err = inbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = inbound_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
|
||||||
|
inbound_config.Tag,
|
||||||
|
inbound_config.Type,
|
||||||
|
inbound_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveInbound(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove inbound: ", tag)
|
||||||
|
return inbound_manager.Remove(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) AddOutbound(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var outbound_config option.Outbound
|
||||||
|
|
||||||
|
err = outbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = outbound_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
||||||
|
outbound_config.Tag,
|
||||||
|
outbound_config.Type,
|
||||||
|
outbound_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveOutbound(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove outbound: ", tag)
|
||||||
|
return outbound_manager.Remove(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) AddEndpoint(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var endpoint_config option.Endpoint
|
||||||
|
|
||||||
|
err = endpoint_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = endpoint_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
||||||
|
endpoint_config.Tag,
|
||||||
|
endpoint_config.Type,
|
||||||
|
endpoint_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveEndpoint(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove endpoint: ", tag)
|
||||||
|
return endpoint_manager.Remove(tag)
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
suiLog "s-ui/logger"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlatformWriter struct{}
|
||||||
|
|
||||||
|
func (p PlatformWriter) DisableColors() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (p PlatformWriter) WriteMessage(level log.Level, message string) {
|
||||||
|
switch level {
|
||||||
|
case log.LevelInfo:
|
||||||
|
suiLog.Info(message)
|
||||||
|
case log.LevelWarn:
|
||||||
|
suiLog.Warning(message)
|
||||||
|
case log.LevelPanic:
|
||||||
|
case log.LevelFatal:
|
||||||
|
case log.LevelError:
|
||||||
|
suiLog.Error(message)
|
||||||
|
default:
|
||||||
|
suiLog.Debug(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory(options log.Options) (log.Factory, error) {
|
||||||
|
logOptions := options.Options
|
||||||
|
|
||||||
|
if logOptions.Disabled {
|
||||||
|
return log.NewNOPFactory(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var logWriter io.Writer
|
||||||
|
var logFilePath string
|
||||||
|
|
||||||
|
switch logOptions.Output {
|
||||||
|
case "":
|
||||||
|
logWriter = options.DefaultWriter
|
||||||
|
if logWriter == nil {
|
||||||
|
logWriter = os.Stderr
|
||||||
|
}
|
||||||
|
case "stderr":
|
||||||
|
logWriter = os.Stderr
|
||||||
|
case "stdout":
|
||||||
|
logWriter = os.Stdout
|
||||||
|
default:
|
||||||
|
logFilePath = logOptions.Output
|
||||||
|
}
|
||||||
|
logFormatter := log.Formatter{
|
||||||
|
BaseTime: options.BaseTime,
|
||||||
|
DisableColors: logOptions.DisableColor || logFilePath != "",
|
||||||
|
DisableTimestamp: !logOptions.Timestamp && logFilePath != "",
|
||||||
|
FullTimestamp: logOptions.Timestamp,
|
||||||
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
|
}
|
||||||
|
factory := NewDefaultFactory(
|
||||||
|
options.Context,
|
||||||
|
logFormatter,
|
||||||
|
logWriter,
|
||||||
|
logFilePath,
|
||||||
|
)
|
||||||
|
if logOptions.Level != "" {
|
||||||
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.Error("parse log level", err)
|
||||||
|
}
|
||||||
|
factory.SetLevel(logLevel)
|
||||||
|
} else {
|
||||||
|
factory.SetLevel(log.LevelTrace)
|
||||||
|
}
|
||||||
|
return factory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ log.Factory = (*defaultFactory)(nil)
|
||||||
|
|
||||||
|
type defaultFactory struct {
|
||||||
|
ctx context.Context
|
||||||
|
formatter log.Formatter
|
||||||
|
writer io.Writer
|
||||||
|
file *os.File
|
||||||
|
filePath string
|
||||||
|
level log.Level
|
||||||
|
subscriber *observable.Subscriber[log.Entry]
|
||||||
|
observer *observable.Observer[log.Entry]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultFactory(
|
||||||
|
ctx context.Context,
|
||||||
|
formatter log.Formatter,
|
||||||
|
writer io.Writer,
|
||||||
|
filePath string,
|
||||||
|
) log.ObservableFactory {
|
||||||
|
factory := &defaultFactory{
|
||||||
|
ctx: ctx,
|
||||||
|
formatter: formatter,
|
||||||
|
writer: writer,
|
||||||
|
filePath: filePath,
|
||||||
|
level: log.LevelTrace,
|
||||||
|
subscriber: observable.NewSubscriber[log.Entry](128),
|
||||||
|
}
|
||||||
|
return factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Start() error {
|
||||||
|
if f.filePath != "" {
|
||||||
|
logFile, err := filemanager.OpenFile(f.ctx, f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writer = logFile
|
||||||
|
f.file = logFile
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(f.file),
|
||||||
|
f.subscriber,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Level() log.Level {
|
||||||
|
return f.level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) SetLevel(level log.Level) {
|
||||||
|
f.level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Logger() log.ContextLogger {
|
||||||
|
return f.NewLogger("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) NewLogger(tag string) log.ContextLogger {
|
||||||
|
return &observableLogger{f, tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Subscribe() (subscription observable.Subscription[log.Entry], done <-chan struct{}, err error) {
|
||||||
|
return f.observer.Subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) UnSubscribe(sub observable.Subscription[log.Entry]) {
|
||||||
|
f.observer.UnSubscribe(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
type observableLogger struct {
|
||||||
|
*defaultFactory
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Log(ctx context.Context, level log.Level, args []any) {
|
||||||
|
level = log.OverrideLevelFromContext(level, ctx)
|
||||||
|
if level > l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := F.ToString(args...)
|
||||||
|
switch level {
|
||||||
|
case log.LevelInfo:
|
||||||
|
suiLog.Info(l.tag, msg)
|
||||||
|
case log.LevelWarn:
|
||||||
|
suiLog.Warning(l.tag, msg)
|
||||||
|
case log.LevelPanic:
|
||||||
|
case log.LevelFatal:
|
||||||
|
case log.LevelError:
|
||||||
|
suiLog.Error(l.tag, msg)
|
||||||
|
default:
|
||||||
|
suiLog.Debug(l.tag, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Trace(args ...any) {
|
||||||
|
l.TraceContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Debug(args ...any) {
|
||||||
|
l.DebugContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Info(args ...any) {
|
||||||
|
l.InfoContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Warn(args ...any) {
|
||||||
|
l.WarnContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Error(args ...any) {
|
||||||
|
l.ErrorContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Fatal(args ...any) {
|
||||||
|
l.FatalContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Panic(args ...any) {
|
||||||
|
l.PanicContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) TraceContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelTrace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) DebugContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelDebug, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) InfoContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelInfo, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelWarn, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) ErrorContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelError, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) FatalContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelFatal, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) PanicContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelPanic, args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"s-ui/logger"
|
||||||
|
|
||||||
|
sb "github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
_ "github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
|
_ "github.com/sagernet/sing-box/experimental/v2rayapi"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||||
|
_ "github.com/sagernet/sing-dns/quic"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalCtx context.Context
|
||||||
|
inbound_manager adapter.InboundManager
|
||||||
|
outbound_manager adapter.OutboundManager
|
||||||
|
endpoint_manager adapter.EndpointManager
|
||||||
|
router adapter.Router
|
||||||
|
connTracker *ConnTracker
|
||||||
|
factory log.Factory
|
||||||
|
)
|
||||||
|
|
||||||
|
type Core struct {
|
||||||
|
isRunning bool
|
||||||
|
instance *Box
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCore() *Core {
|
||||||
|
globalCtx = context.Background()
|
||||||
|
globalCtx = sb.Context(globalCtx, inboundRegistry(), outboundRegistry(), EndpointRegistry())
|
||||||
|
return &Core{
|
||||||
|
isRunning: false,
|
||||||
|
instance: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) GetCtx() context.Context {
|
||||||
|
return globalCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) GetInstance() *Box {
|
||||||
|
return c.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) Start(sbConfig []byte) error {
|
||||||
|
var opt option.Options
|
||||||
|
err := opt.UnmarshalJSONContext(globalCtx, sbConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Unmarshal config err:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.instance, err = NewBox(Options{
|
||||||
|
Context: globalCtx,
|
||||||
|
Options: opt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.instance.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCtx = service.ContextWith(globalCtx, c)
|
||||||
|
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
|
||||||
|
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
|
||||||
|
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
|
||||||
|
router = service.FromContext[adapter.Router](globalCtx)
|
||||||
|
|
||||||
|
c.isRunning = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) Stop() error {
|
||||||
|
if c.isRunning {
|
||||||
|
c.isRunning = false
|
||||||
|
return c.instance.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) IsRunning() bool {
|
||||||
|
return c.isRunning
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/block"
|
||||||
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
|
"github.com/sagernet/sing-box/protocol/dns"
|
||||||
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria2"
|
||||||
|
"github.com/sagernet/sing-box/protocol/mixed"
|
||||||
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
|
_ "github.com/sagernet/sing-box/protocol/naive/quic"
|
||||||
|
"github.com/sagernet/sing-box/protocol/redirect"
|
||||||
|
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
||||||
|
"github.com/sagernet/sing-box/protocol/shadowtls"
|
||||||
|
"github.com/sagernet/sing-box/protocol/socks"
|
||||||
|
"github.com/sagernet/sing-box/protocol/ssh"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tor"
|
||||||
|
"github.com/sagernet/sing-box/protocol/trojan"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tuic"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
|
"github.com/sagernet/sing-box/protocol/wireguard"
|
||||||
|
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||||
|
_ "github.com/sagernet/sing-dns/quic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func inboundRegistry() *inbound.Registry {
|
||||||
|
registry := inbound.NewRegistry()
|
||||||
|
|
||||||
|
tun.RegisterInbound(registry)
|
||||||
|
redirect.RegisterRedirect(registry)
|
||||||
|
redirect.RegisterTProxy(registry)
|
||||||
|
direct.RegisterInbound(registry)
|
||||||
|
|
||||||
|
socks.RegisterInbound(registry)
|
||||||
|
http.RegisterInbound(registry)
|
||||||
|
mixed.RegisterInbound(registry)
|
||||||
|
|
||||||
|
shadowsocks.RegisterInbound(registry)
|
||||||
|
vmess.RegisterInbound(registry)
|
||||||
|
trojan.RegisterInbound(registry)
|
||||||
|
naive.RegisterInbound(registry)
|
||||||
|
shadowtls.RegisterInbound(registry)
|
||||||
|
vless.RegisterInbound(registry)
|
||||||
|
|
||||||
|
hysteria.RegisterInbound(registry)
|
||||||
|
tuic.RegisterInbound(registry)
|
||||||
|
hysteria2.RegisterInbound(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func outboundRegistry() *outbound.Registry {
|
||||||
|
registry := outbound.NewRegistry()
|
||||||
|
|
||||||
|
direct.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
block.RegisterOutbound(registry)
|
||||||
|
dns.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
group.RegisterSelector(registry)
|
||||||
|
group.RegisterURLTest(registry)
|
||||||
|
|
||||||
|
socks.RegisterOutbound(registry)
|
||||||
|
http.RegisterOutbound(registry)
|
||||||
|
shadowsocks.RegisterOutbound(registry)
|
||||||
|
vmess.RegisterOutbound(registry)
|
||||||
|
trojan.RegisterOutbound(registry)
|
||||||
|
tor.RegisterOutbound(registry)
|
||||||
|
ssh.RegisterOutbound(registry)
|
||||||
|
shadowtls.RegisterOutbound(registry)
|
||||||
|
vless.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
hysteria.RegisterOutbound(registry)
|
||||||
|
tuic.RegisterOutbound(registry)
|
||||||
|
hysteria2.RegisterOutbound(registry)
|
||||||
|
wireguard.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func EndpointRegistry() *endpoint.Registry {
|
||||||
|
registry := endpoint.NewRegistry()
|
||||||
|
|
||||||
|
wireguard.RegisterEndpoint(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package cronjob
|
||||||
|
|
||||||
|
import (
|
||||||
|
"s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckCoreJob struct {
|
||||||
|
service.ConfigService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckCoreJob() *CheckCoreJob {
|
||||||
|
return &CheckCoreJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CheckCoreJob) Run() {
|
||||||
|
s.ConfigService.StartCore("")
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ func NewCronJob() *CronJob {
|
|||||||
return &CronJob{}
|
return &CronJob{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronJob) Start(loc *time.Location) error {
|
func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
||||||
c.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds())
|
c.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds())
|
||||||
c.cron.Start()
|
c.cron.Start()
|
||||||
|
|
||||||
@@ -24,7 +24,9 @@ func (c *CronJob) Start(loc *time.Location) error {
|
|||||||
// 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())
|
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||||
|
// Start core if it is not running
|
||||||
|
c.cron.AddJob("@every 5s", NewCheckCoreJob())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,16 +7,20 @@ import (
|
|||||||
|
|
||||||
type DelStatsJob struct {
|
type DelStatsJob struct {
|
||||||
service.StatsService
|
service.StatsService
|
||||||
|
trafficAge int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDelStatsJob() *DelStatsJob {
|
func NewDelStatsJob(ta int) *DelStatsJob {
|
||||||
return &DelStatsJob{}
|
return &DelStatsJob{
|
||||||
|
trafficAge: ta,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DelStatsJob) Run() {
|
func (s *DelStatsJob) Run() {
|
||||||
err := s.StatsService.DelOldStats(30)
|
err := s.StatsService.DelOldStats(s.trafficAge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Deleting old statistics failed: ", err)
|
logger.Warning("Deleting old statistics failed: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Debug("Stats older than ", s.trafficAge, " days were deleted")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DepleteJob struct {
|
type DepleteJob struct {
|
||||||
service.ConfigService
|
service.ClientService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDepleteJob() *DepleteJob {
|
func NewDepleteJob() *DepleteJob {
|
||||||
@@ -14,7 +14,7 @@ func NewDepleteJob() *DepleteJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DepleteJob) Run() {
|
func (s *DepleteJob) Run() {
|
||||||
err := s.ConfigService.DepleteClients()
|
err := s.ClientService.DepleteClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Disable depleted users failed: ", err)
|
logger.Warning("Disable depleted users failed: ", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StatsJob struct {
|
type StatsJob struct {
|
||||||
service.SingBoxService
|
service.StatsService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatsJob() *StatsJob {
|
func NewStatsJob() *StatsJob {
|
||||||
return new(StatsJob)
|
return &StatsJob{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsJob) Run() {
|
func (s *StatsJob) Run() {
|
||||||
err := s.SingBoxService.GetStats()
|
err := s.StatsService.SaveStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Get stats failed: ", err)
|
logger.Warning("Get stats failed: ", err)
|
||||||
return
|
return
|
||||||
|
|||||||
+25
-1
@@ -1,6 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
@@ -29,7 +30,7 @@ func initUser() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDB(dbPath string) error {
|
func OpenDB(dbPath string) error {
|
||||||
dir := path.Dir(dbPath)
|
dir := path.Dir(dbPath)
|
||||||
err := os.MkdirAll(dir, 01740)
|
err := os.MkdirAll(dir, 01740)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,12 +49,35 @@ func InitDB(dbPath string) error {
|
|||||||
Logger: gormLogger,
|
Logger: gormLogger,
|
||||||
}
|
}
|
||||||
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||||
|
|
||||||
|
if config.IsDebug() {
|
||||||
|
db = db.Debug()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDB(dbPath string) error {
|
||||||
|
err := OpenDB(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default Outbounds
|
||||||
|
if !db.Migrator().HasTable(&model.Outbound{}) {
|
||||||
|
db.Migrator().CreateTable(&model.Outbound{})
|
||||||
|
defaultOutbound := []model.Outbound{
|
||||||
|
{Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)},
|
||||||
|
{Type: "dns", Tag: "dns-out", Options: json.RawMessage(`{}`)},
|
||||||
|
}
|
||||||
|
db.Create(&defaultOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(
|
err = db.AutoMigrate(
|
||||||
&model.Setting{},
|
&model.Setting{},
|
||||||
|
&model.Tls{},
|
||||||
|
&model.Inbound{},
|
||||||
|
&model.Outbound{},
|
||||||
|
&model.Endpoint{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Endpoint) 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 {
|
||||||
|
o.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
o.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
o.Tag = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
o.Options, err = json.Marshal(raw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (o Endpoint) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = o.Type
|
||||||
|
combined["tag"] = o.Tag
|
||||||
|
|
||||||
|
if o.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(o.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Inbound 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"`
|
||||||
|
|
||||||
|
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||||
|
OutJson json.RawMessage `json:"out_json" form:"out_json"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inbound) 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")
|
||||||
|
delete(raw, "users")
|
||||||
|
|
||||||
|
// Addrs
|
||||||
|
i.Addrs, _ = json.MarshalIndent(raw["addrs"], "", " ")
|
||||||
|
delete(raw, "addrs")
|
||||||
|
|
||||||
|
// OutJson
|
||||||
|
i.OutJson, _ = json.MarshalIndent(raw["out_json"], "", " ")
|
||||||
|
delete(raw, "out_json")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
i.Options, err = json.MarshalIndent(raw, "", " ")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (i Inbound) 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 Inbound) 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
|
||||||
|
combined["addrs"] = i.Addrs
|
||||||
|
combined["out_json"] = i.OutJson
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@ type Setting struct {
|
|||||||
Value string `json:"value" form:"value"`
|
Value string `json:"value" form:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tls struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Name string `json:"name" form:"name"`
|
||||||
|
Server json.RawMessage `json:"server" form:"server"`
|
||||||
|
Client json.RawMessage `json:"client" form:"client"`
|
||||||
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
@@ -16,16 +23,18 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
Name string `json:"name" form:"name"`
|
Name string `json:"name" form:"name"`
|
||||||
Config string `json:"config" form:"config"`
|
Config json.RawMessage `json:"config" form:"config"`
|
||||||
Inbounds string `json:"inbounds" form:"inbounds"`
|
Inbounds json.RawMessage `json:"inbounds" form:"inbounds"`
|
||||||
Links string `json:"links" form:"links"`
|
Links json.RawMessage `json:"links" form:"links"`
|
||||||
Volume int64 `json:"volume" form:"volume"`
|
Volume int64 `json:"volume" form:"volume"`
|
||||||
Expiry int64 `json:"expiry" form:"expiry"`
|
Expiry int64 `json:"expiry" form:"expiry"`
|
||||||
Down int64 `json:"down" form:"down"`
|
Down int64 `json:"down" form:"down"`
|
||||||
Up int64 `json:"up" form:"up"`
|
Up int64 `json:"up" form:"up"`
|
||||||
|
Desc string `json:"desc" form:"desc"`
|
||||||
|
Group string `json:"group" form:"group"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) 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 {
|
||||||
|
o.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
o.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
o.Tag = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
o.Options, err = json.Marshal(raw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (o Outbound) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = o.Type
|
||||||
|
combined["tag"] = o.Tag
|
||||||
|
|
||||||
|
if o.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(o.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
+116
-52
@@ -1,67 +1,131 @@
|
|||||||
module s-ui
|
module s-ui
|
||||||
|
|
||||||
go 1.21.5
|
go 1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v1.0.1
|
||||||
github.com/gin-contrib/sessions v0.0.5
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
gorm.io/driver/sqlite v1.5.5
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
gorm.io/gorm v1.25.7
|
github.com/sagernet/sing v0.6.0-beta.9
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
|
gorm.io/driver/sqlite v1.5.7
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.4.0 // indirect
|
github.com/ebitengine/purego v0.8.1 // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
google.golang.org/grpc v1.67.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
)
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
require (
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
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/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // 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/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
|
||||||
golang.org/x/arch v0.7.0 // indirect
|
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
|
||||||
golang.org/x/net v0.20.0 // indirect
|
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
golang.org/x/tools v0.17.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
|
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.17.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
google.golang.org/grpc v1.61.0
|
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.2 // 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.7.0.20241229131914-aa9d9c62966f // indirect
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1 // 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/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
+249
-213
@@ -1,193 +1,232 @@
|
|||||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
|
||||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
|
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
|
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
|
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
|
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
|
|
||||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
||||||
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
|
||||||
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
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-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
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-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
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.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
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/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
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/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
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/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
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/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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
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/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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
|
||||||
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
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/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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
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/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
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-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1Ws=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ=
|
|
||||||
github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
|
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
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/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 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
|
||||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
|
||||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
|
||||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
|
||||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
|
||||||
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
|
|
||||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
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 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
|
||||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
|
||||||
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
|
|
||||||
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
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/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
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.5 h1:RD2j8WmJsvAbbBkAlJWaiYmnd+v/JohBiweoew7kMwo=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.8 h1:PoxDdN7y8D4oImT3cQ05Sq1ZYnYsJberkUkIEHIGwWE=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.6 h1:MPdL2Yem/xM0RhejCO7krYvl1Zbd1zkSjKluKpHnHPQ=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.6/go.mod h1:6dO5V0A37cLlhvKnxCmZinSpZXz7ZSk11x3rgI+xH1I=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.11 h1:bVR0n3oQ3hGcuc/CSS7axsOeRNCRlCGkYVOKl0wxbsw=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.11/go.mod h1:GZnZUzUHZ6Bgm7D/i8unNORv3537u1s03tLXFdxCRpg=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.15 h1:oWcs/PHgKaeWKbTfgz/020KEVvDqQv/tQWe7zpyktkc=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.15/go.mod h1:+QZDsF4HkdiGcMfz+JNOfONLh9CnZjIwJJQNWEzhiaQ=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19 h1:uL2xlXpz4t7BduLbXiLe5QqpyiMhvNNRThBzhTJ4p00=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19/go.mod h1:UXUN/lwRT9mAM8PK7upPOwgqooOV2vU+CcjBfwT1rYg=
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1/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-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-beta.2 h1:ikoQ7zTR0o/2rlI5H5FeNC0j5bQJJHb1uoyXFRu3yGk=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-beta.2/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.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.6 h1:xaIHoH78MqTSvZqQ4SQto8pC1A+X4qXReDRNaC8DQeI=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.6/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f h1:dTnXP0e3LbSa4EpUmuOGhllanKPei4vPKfzlLvk76Pc=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
|
||||||
|
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.4 h1:8uyM5fxfEXdu4RH05uOK+v25i3lTNdCYMPSAUJ14FnI=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.4/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||||
|
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/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
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/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
@@ -198,105 +237,102 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/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.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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
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/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0 h1:BDJfi3Ftx1NpQlZZPpeWJe3RDqRNyIVBs+YGG4RRMDU=
|
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0/go.mod h1:Bc3gmQWLr8UR7xBSCYI9FbfKuVvqA9lbkeBTWNRRAS4=
|
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
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-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-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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
|
||||||
|
|||||||
+14
-13
@@ -8,30 +8,27 @@ import (
|
|||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var (
|
||||||
var logBuffer []struct {
|
logger *logging.Logger
|
||||||
time string
|
logBuffer []struct {
|
||||||
level logging.Level
|
time string
|
||||||
log string
|
level logging.Level
|
||||||
}
|
log string
|
||||||
|
}
|
||||||
func init() {
|
)
|
||||||
InitLogger(logging.INFO)
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
newLogger := logging.MustGetLogger("s-ui")
|
newLogger := logging.MustGetLogger("s-ui")
|
||||||
var err error
|
var err error
|
||||||
var backend logging.Backend
|
var backend logging.Backend
|
||||||
var format logging.Formatter
|
var format logging.Formatter
|
||||||
ppid := os.Getppid()
|
|
||||||
|
|
||||||
backend, err = logging.NewSyslogBackend("")
|
backend, err = logging.NewSyslogBackend("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err)
|
fmt.Println("Unable to use syslog: " + err.Error())
|
||||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
}
|
}
|
||||||
if ppid > 0 && err != nil {
|
if err != nil {
|
||||||
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
||||||
} else {
|
} else {
|
||||||
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
||||||
@@ -45,6 +42,10 @@ func InitLogger(level logging.Level) {
|
|||||||
logger = newLogger
|
logger = newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogger() *logging.Logger {
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
|
|||||||
+11
-1
@@ -5,10 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"s-ui/app"
|
"s-ui/app"
|
||||||
|
"s-ui/cmd"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func runApp() {
|
||||||
app := app.NewApp()
|
app := app.NewApp()
|
||||||
|
|
||||||
err := app.Init()
|
err := app.Init()
|
||||||
@@ -36,3 +37,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
runApp()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cmd.ParseCmd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -9,7 +10,10 @@ import (
|
|||||||
|
|
||||||
func DomainValidator(domain string) gin.HandlerFunc {
|
func DomainValidator(domain string) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := c.Request.Host
|
||||||
|
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||||
|
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||||
|
}
|
||||||
|
|
||||||
if host != domain {
|
if host != domain {
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
|||||||
+243
-34
@@ -5,89 +5,298 @@ import (
|
|||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"strings"
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientService struct {
|
type ClientService struct {
|
||||||
|
InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) GetAll() (string, error) {
|
func (s *ClientService) GetAll() ([]model.Client, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
clients := []model.Client{}
|
clients := []model.Client{}
|
||||||
err := db.Model(model.Client{}).Scan(&clients).Error
|
err := db.Model(model.Client{}).Scan(&clients).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(clients)
|
return clients, nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error {
|
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
|
||||||
var err error
|
var err error
|
||||||
for _, change := range changes {
|
var inboundIds []uint
|
||||||
client := model.Client{}
|
|
||||||
err = json.Unmarshal(change.Obj, &client)
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var client model.Client
|
||||||
|
err = json.Unmarshal(data, &client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch change.Action {
|
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||||
case "new":
|
if err != nil {
|
||||||
err = tx.Create(&client).Error
|
return nil, err
|
||||||
case "del":
|
|
||||||
err = tx.Where("id = ?", change.Index).Delete(model.Client{}).Error
|
|
||||||
default:
|
|
||||||
err = tx.Save(client).Error
|
|
||||||
}
|
}
|
||||||
|
err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "addbulk":
|
||||||
|
var clients []*model.Client
|
||||||
|
err = json.Unmarshal(data, &clients)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Save(clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var id uint
|
||||||
|
err = json.Unmarshal(data, &id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var client model.Client
|
||||||
|
err = tx.Where("id = ?", id).First(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Where("id = ?", id).Delete(model.Client{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
|
||||||
|
return inboundIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, inbounIds []uint, hostname string) error {
|
||||||
|
var err error
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
|
||||||
|
// Zero inbounds means removing local links only
|
||||||
|
if len(inbounIds) > 0 {
|
||||||
|
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
for index, client := range clients {
|
||||||
|
var clientLinks []map[string]string
|
||||||
|
err = json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newClientLinks := []map[string]string{}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
|
||||||
|
for _, newLink := range newLinks {
|
||||||
|
newClientLinks = append(newClientLinks, map[string]string{
|
||||||
|
"remark": inbound.Tag,
|
||||||
|
"type": "local",
|
||||||
|
"uri": newLink,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add no local links
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["type"] != "local" {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[index].Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
func (s *ClientService) UpdateClientsOnInboundDelete(tx *gorm.DB, id uint, tag string) error {
|
||||||
|
var clients []model.Client
|
||||||
|
err := tx.Table("clients").
|
||||||
|
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", id).
|
||||||
|
Find(&clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
// Delete inbounds
|
||||||
|
var clientInbounds, newClientInbounds []uint
|
||||||
|
json.Unmarshal(client.Inbounds, &clientInbounds)
|
||||||
|
for _, clientInbound := range clientInbounds {
|
||||||
|
if clientInbound != id {
|
||||||
|
newClientInbounds = append(newClientInbounds, clientInbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.Inbounds, err = json.MarshalIndent(newClientInbounds, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Delete links
|
||||||
|
var clientLinks, newClientLinks []map[string]string
|
||||||
|
json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["remark"] != tag {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint, hostname string) error {
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
var clients []model.Client
|
||||||
|
err = tx.Table("clients").
|
||||||
|
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id).
|
||||||
|
Find(&clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
var clientLinks, newClientLinks []map[string]string
|
||||||
|
json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
|
||||||
|
for _, newLink := range newLinks {
|
||||||
|
newClientLinks = append(newClientLinks, map[string]string{
|
||||||
|
"remark": inbound.Tag,
|
||||||
|
"type": "local",
|
||||||
|
"uri": newLink,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["remark"] != inbound.Tag {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) DepleteClients() error {
|
||||||
var err error
|
var err error
|
||||||
var clients []model.Client
|
var clients []model.Client
|
||||||
var changes []model.Changes
|
var changes []model.Changes
|
||||||
|
var users []string
|
||||||
|
var inboundIds []uint
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||||
|
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||||
|
if err1 != nil {
|
||||||
|
logger.Error("unable to restart inbounds: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var users, inbounds []string
|
dt := time.Now().Unix()
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
logger.Debug("Client ", client.Name, " is going to be disabled")
|
logger.Debug("Client ", client.Name, " is going to be disabled")
|
||||||
users = append(users, client.Name)
|
users = append(users, client.Name)
|
||||||
userInbounds := strings.Split(client.Inbounds, ",")
|
var userInbounds []uint
|
||||||
inbounds = append(inbounds, userInbounds...)
|
json.Unmarshal(client.Inbounds, &userInbounds)
|
||||||
|
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
|
||||||
changes = append(changes, model.Changes{
|
changes = append(changes, model.Changes{
|
||||||
DateTime: time.Now().Unix(),
|
DateTime: dt,
|
||||||
Actor: "DepleteJob",
|
Actor: "DepleteJob",
|
||||||
Key: "clients",
|
Key: "clients",
|
||||||
Action: "disable",
|
Action: "disable",
|
||||||
Obj: json.RawMessage(client.Name),
|
Obj: json.RawMessage("\"" + client.Name + "\""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
if len(changes) > 0 {
|
if len(changes) > 0 {
|
||||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
err = db.Model(model.Changes{}).Create(&changes).Error
|
err = tx.Model(model.Changes{}).Create(&changes).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
LastUpdate = dt
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, inbounds, nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid duplicate inboundIds
|
||||||
|
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
|
||||||
|
m := make(map[uint]bool)
|
||||||
|
for _, v := range a {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
for _, v := range b {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
var res []uint
|
||||||
|
for k := range m {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
+193
-255
@@ -2,20 +2,27 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"s-ui/core"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/singbox"
|
"s-ui/logger"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ApiAddr string
|
var (
|
||||||
|
LastUpdate int64
|
||||||
|
corePtr *core.Core
|
||||||
|
)
|
||||||
|
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
ClientService
|
ClientService
|
||||||
singbox.Controller
|
TlsService
|
||||||
SettingService
|
SettingService
|
||||||
|
InboundService
|
||||||
|
OutboundService
|
||||||
|
EndpointService
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingBoxConfig struct {
|
type SingBoxConfig struct {
|
||||||
@@ -24,66 +31,95 @@ type SingBoxConfig struct {
|
|||||||
Ntp json.RawMessage `json:"ntp"`
|
Ntp json.RawMessage `json:"ntp"`
|
||||||
Inbounds []json.RawMessage `json:"inbounds"`
|
Inbounds []json.RawMessage `json:"inbounds"`
|
||||||
Outbounds []json.RawMessage `json:"outbounds"`
|
Outbounds []json.RawMessage `json:"outbounds"`
|
||||||
|
Endpoints []json.RawMessage `json:"endpoints"`
|
||||||
Route json.RawMessage `json:"route"`
|
Route json.RawMessage `json:"route"`
|
||||||
Experimental json.RawMessage `json:"experimental"`
|
Experimental json.RawMessage `json:"experimental"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigService() *ConfigService {
|
func NewConfigService(core *core.Core) *ConfigService {
|
||||||
|
corePtr = core
|
||||||
return &ConfigService{}
|
return &ConfigService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) InitConfig() error {
|
func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
|
||||||
configPath := config.GetBinFolderPath()
|
var err error
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
if len(data) == 0 {
|
||||||
if err != nil {
|
data, err = s.SettingService.GetConfig()
|
||||||
if os.IsNotExist(err) {
|
if err != nil {
|
||||||
defaultConfig := []byte(config.GetDefaultConfig())
|
return nil, err
|
||||||
err = os.MkdirAll(configPath, 01764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.WriteFile(configPath+"/config.json", defaultConfig, 0764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data = defaultConfig
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.RefreshApiAddr(&data)
|
singboxConfig := SingBoxConfig{}
|
||||||
}
|
err = json.Unmarshal([]byte(data), &singboxConfig)
|
||||||
|
|
||||||
func (s *ConfigService) GetConfig() (*[]byte, error) {
|
|
||||||
configPath := config.GetBinFolderPath()
|
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &data, nil
|
|
||||||
|
singboxConfig.Inbounds, err = s.InboundService.GetAllConfig(database.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
singboxConfig.Outbounds, err = s.OutboundService.GetAllConfig(database.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &singboxConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
func (s *ConfigService) StartCore(defaultConfig string) error {
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
singboxConfig, err := s.GetConfig(defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawConfig, err := json.MarshalIndent(singboxConfig, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.Start(rawConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("start sing-box err:", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("sing-box started")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) RestartCore() error {
|
||||||
|
err := s.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.StartCore("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error {
|
||||||
|
err := s.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.StartCore(string(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) StopCore() error {
|
||||||
|
err := corePtr.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("sing-box stopped")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, loginUser string, hostname string) ([]string, error) {
|
||||||
var err error
|
var err error
|
||||||
var clientChanges, settingChanges, configChanges []model.Changes
|
var inboundIds []uint
|
||||||
if _, ok := changes["clients"]; ok {
|
var inboundId uint
|
||||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["settings"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["config"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["config"]), &configChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -95,228 +131,130 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(clientChanges) > 0 {
|
switch obj {
|
||||||
err = s.ClientService.Save(tx, clientChanges)
|
case "clients":
|
||||||
|
inboundIds, err = s.ClientService.Save(tx, act, data, hostname)
|
||||||
|
case "tls":
|
||||||
|
inboundIds, err = s.TlsService.Save(tx, act, data)
|
||||||
|
case "inbounds":
|
||||||
|
inboundId, err = s.InboundService.Save(tx, act, data, hostname)
|
||||||
|
case "outbounds":
|
||||||
|
err = s.OutboundService.Save(tx, act, data)
|
||||||
|
case "endpoints":
|
||||||
|
err = s.EndpointService.Save(tx, act, data)
|
||||||
|
case "config":
|
||||||
|
err = s.SettingService.SaveConfig(tx, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = s.restartCoreWithConfig(data)
|
||||||
|
default:
|
||||||
|
return nil, common.NewError("unknown object: ", obj)
|
||||||
}
|
}
|
||||||
if len(settingChanges) > 0 {
|
|
||||||
err = s.SettingService.Save(tx, settingChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(configChanges) > 0 {
|
|
||||||
singboxConfig, err := s.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newConfig := SingBoxConfig{}
|
|
||||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, change := range configChanges {
|
|
||||||
rawObject := change.Obj
|
|
||||||
switch change.Key {
|
|
||||||
case "all":
|
|
||||||
err = json.Unmarshal(rawObject, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "log":
|
|
||||||
newConfig.Log = rawObject
|
|
||||||
case "dns":
|
|
||||||
newConfig.Dns = rawObject
|
|
||||||
case "ntp":
|
|
||||||
newConfig.Ntp = rawObject
|
|
||||||
case "route":
|
|
||||||
newConfig.Route = rawObject
|
|
||||||
case "experimental":
|
|
||||||
newConfig.Experimental = rawObject
|
|
||||||
case "inbounds":
|
|
||||||
if change.Action == "edit" {
|
|
||||||
newConfig.Inbounds[change.Index] = rawObject
|
|
||||||
} else if change.Action == "del" {
|
|
||||||
newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
|
|
||||||
} else {
|
|
||||||
newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
|
|
||||||
}
|
|
||||||
case "outbounds":
|
|
||||||
if change.Action == "edit" {
|
|
||||||
newConfig.Outbounds[change.Index] = rawObject
|
|
||||||
} else {
|
|
||||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to config.json
|
|
||||||
data, err := json.MarshalIndent(newConfig, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Save(&data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log changes
|
|
||||||
dt := time.Now().Unix()
|
|
||||||
allChanges := append(append(clientChanges, settingChanges...), configChanges...)
|
|
||||||
for index := range allChanges {
|
|
||||||
allChanges[index].DateTime = dt
|
|
||||||
allChanges[index].Actor = loginUser
|
|
||||||
}
|
|
||||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
dt := time.Now().Unix()
|
||||||
|
err = tx.Create(&model.Changes{
|
||||||
|
DateTime: dt,
|
||||||
|
Actor: loginUser,
|
||||||
|
Key: obj,
|
||||||
|
Action: act,
|
||||||
|
Obj: data,
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Commit changes so far
|
||||||
|
tx.Commit()
|
||||||
|
LastUpdate = time.Now().Unix()
|
||||||
|
var objs []string = []string{obj}
|
||||||
|
tx = db.Begin()
|
||||||
|
|
||||||
|
// Update side changes
|
||||||
|
|
||||||
|
// Update client links
|
||||||
|
if obj == "tls" && len(inboundIds) > 0 {
|
||||||
|
err = s.ClientService.UpdateLinksByInboundChange(tx, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs = append(objs, "clients")
|
||||||
|
}
|
||||||
|
if obj == "inbounds" && act != "add" {
|
||||||
|
switch act {
|
||||||
|
case "edit":
|
||||||
|
err = s.ClientService.UpdateLinksByInboundChange(tx, []uint{inboundId}, hostname)
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.ClientService.UpdateClientsOnInboundDelete(tx, inboundId, tag)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs = append(objs, "clients")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update out_json of inbounds when tls is changed
|
||||||
|
if obj == "tls" && len(inboundIds) > 0 {
|
||||||
|
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("unable to update out_json of inbounds: ", err.Error())
|
||||||
|
}
|
||||||
|
objs = append(objs, "inbounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||||
|
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||||
|
if err1 != nil {
|
||||||
|
logger.Error("unable to restart inbounds: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to start core if it is not running
|
||||||
|
if !corePtr.IsRunning() {
|
||||||
|
s.StartCore("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) CheckChnages(lu string) (bool, error) {
|
func (s *ConfigService) CheckChanges(lu string) (bool, error) {
|
||||||
if lu == "" {
|
if lu == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
db := database.GetDB()
|
if LastUpdate == 0 {
|
||||||
var count int64
|
db := database.GetDB()
|
||||||
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
|
var count int64
|
||||||
return count > 0, err
|
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
|
||||||
}
|
if err == nil {
|
||||||
|
LastUpdate = time.Now().Unix()
|
||||||
func (s *ConfigService) Save(data *[]byte) error {
|
|
||||||
configPath := config.GetBinFolderPath()
|
|
||||||
_, err := os.Stat(configPath + "/config.json")
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = os.MkdirAll(configPath, 01764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
return count > 0, err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(configPath+"/config.json", *data, 0764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.RefreshApiAddr(data)
|
|
||||||
s.Controller.Restart()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) RefreshApiAddr(data *[]byte) error {
|
|
||||||
Env_API := config.GetEnvApi()
|
|
||||||
if len(Env_API) > 0 {
|
|
||||||
ApiAddr = Env_API
|
|
||||||
} else {
|
} else {
|
||||||
var err error
|
intLu, err := strconv.ParseInt(lu, 10, 64)
|
||||||
if data == nil {
|
return LastUpdate > intLu, err
|
||||||
data, err = s.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
singboxConfig := SingBoxConfig{}
|
|
||||||
err = json.Unmarshal(*data, &singboxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var experimental struct {
|
|
||||||
V2rayApi struct {
|
|
||||||
Listen string `json:"listen"`
|
|
||||||
Stats interface{} `jaon:"stats"`
|
|
||||||
} `json:"v2ray_api"`
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(singboxConfig.Experimental, &experimental)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiAddr = experimental.V2rayApi.Listen
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) DepleteClients() error {
|
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
|
||||||
users, inbounds, err := s.ClientService.DepleteClients()
|
c, _ := strconv.Atoi(count)
|
||||||
if err != nil || len(users) == 0 || len(inbounds) == 0 {
|
whereString := "`id`>0"
|
||||||
return err
|
if len(actor) > 0 {
|
||||||
|
whereString += " and `actor`='" + actor + "'"
|
||||||
}
|
}
|
||||||
|
if len(chngKey) > 0 {
|
||||||
singboxConfig, err := s.GetConfig()
|
whereString += " and `key`='" + chngKey + "'"
|
||||||
|
}
|
||||||
|
db := database.GetDB()
|
||||||
|
var chngs []model.Changes
|
||||||
|
err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
logger.Warning(err)
|
||||||
}
|
}
|
||||||
newConfig := SingBoxConfig{}
|
return chngs
|
||||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for inbound_index, inbound := range newConfig.Inbounds {
|
|
||||||
var inboundJson map[string]interface{}
|
|
||||||
json.Unmarshal(inbound, &inboundJson)
|
|
||||||
if s.contains(inbounds, inboundJson["tag"].(string)) {
|
|
||||||
inbound_users, ok := inboundJson["users"].([]interface{})
|
|
||||||
if ok {
|
|
||||||
var updatedUsers []interface{}
|
|
||||||
for _, user := range inbound_users {
|
|
||||||
userMap, ok := user.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
name, exists := userMap["name"].(string)
|
|
||||||
if exists && s.contains(users, name) {
|
|
||||||
// Skip the user exists
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
username, exists := userMap["username"].(string)
|
|
||||||
if exists && s.contains(users, username) {
|
|
||||||
// Skip the username exists
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatedUsers = append(updatedUsers, user)
|
|
||||||
}
|
|
||||||
// Exception for Naive and ShadowTLSv3
|
|
||||||
if len(updatedUsers) == 0 {
|
|
||||||
if inboundJson["type"].(string) == "naive" ||
|
|
||||||
(inboundJson["type"].(string) == "shadowtls" &&
|
|
||||||
inboundJson["version"].(float64) == 3) {
|
|
||||||
updatedUsers = append(updatedUsers, make(map[string]interface{}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundJson["users"] = updatedUsers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiedInbound, err := json.MarshalIndent(inboundJson, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newConfig.Inbounds[inbound_index] = modifiedInbound
|
|
||||||
}
|
|
||||||
modifiedConfig, err := json.MarshalIndent(newConfig, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Save(&modifiedConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) contains(slice []string, item string) bool {
|
|
||||||
for _, str := range slice {
|
|
||||||
if str == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EndpointService struct{}
|
||||||
|
|
||||||
|
func (o *EndpointService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
endpoints := []*model.Endpoint{}
|
||||||
|
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
epData := map[string]interface{}{
|
||||||
|
"id": endpoint.Id,
|
||||||
|
"type": endpoint.Type,
|
||||||
|
"tag": endpoint.Tag,
|
||||||
|
}
|
||||||
|
if endpoint.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(endpoint.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range restFields {
|
||||||
|
epData[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, epData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var endpointsJson []json.RawMessage
|
||||||
|
var endpoints []*model.Endpoint
|
||||||
|
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
endpointJson, err := endpoint.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
endpointsJson = append(endpointsJson, endpointJson)
|
||||||
|
}
|
||||||
|
return endpointsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EndpointService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var endpoint model.Endpoint
|
||||||
|
err = endpoint.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
configData, err := endpoint.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Endpoint{}).Select("tag").Where("id = ?", endpoint.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveEndpoint(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = corePtr.AddEndpoint(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&endpoint).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveEndpoint(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Endpoint{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InboundService struct{}
|
||||||
|
|
||||||
|
func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
|
||||||
|
if ids == "" {
|
||||||
|
return s.GetAll()
|
||||||
|
}
|
||||||
|
return s.getById(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) getById(ids string) (*[]map[string]interface{}, error) {
|
||||||
|
var inbound []model.Inbound
|
||||||
|
var result []map[string]interface{}
|
||||||
|
db := database.GetDB()
|
||||||
|
err := db.Model(model.Inbound{}).Where("id in ?", strings.Split(ids, ",")).Scan(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, inb := range inbound {
|
||||||
|
inbData, err := inb.MarshalFull()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, *inbData)
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inbounds := []model.Inbound{}
|
||||||
|
err := db.Model(model.Inbound{}).Scan(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
inbData := map[string]interface{}{
|
||||||
|
"id": inbound.Id,
|
||||||
|
"type": inbound.Type,
|
||||||
|
"tag": inbound.Tag,
|
||||||
|
"tls_id": inbound.TlsId,
|
||||||
|
}
|
||||||
|
if inbound.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(inbound.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbData["listen"] = restFields["listen"]
|
||||||
|
inbData["listen_port"] = restFields["listen_port"]
|
||||||
|
}
|
||||||
|
data = append(data, inbData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inbounds := []*model.Inbound{}
|
||||||
|
err := db.Model(model.Inbound{}).Where("id in ?", ids).Scan(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbounds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) (uint, error) {
|
||||||
|
var err error
|
||||||
|
var id uint
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var inbound model.Inbound
|
||||||
|
err = inbound.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
id = inbound.Id
|
||||||
|
if inbound.TlsId > 0 {
|
||||||
|
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveInbound(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundConfig, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = corePtr.AddInbound(inboundConfig)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.FillOutJson(&inbound, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveInbound(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) UpdateOutJsons(tx *gorm.DB, inboundIds []uint, hostname string) error {
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", inboundIds).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err = util.FillOutJson(&inbound, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Where("tag = ?", inbound.Tag).Update("out_json", inbound.OutJson).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var inboundsJson []json.RawMessage
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Preload("Tls").Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
inboundJson, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inboundsJson = append(inboundsJson, inboundJson)
|
||||||
|
}
|
||||||
|
return inboundsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
|
||||||
|
switch inboundType {
|
||||||
|
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return inboundJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound map[string]interface{}
|
||||||
|
err := json.Unmarshal(inboundJson, &inbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if inboundType == "shadowsocks" {
|
||||||
|
method, _ := inbound["method"].(string)
|
||||||
|
if method == "2022-blake3-aes-128-gcm" {
|
||||||
|
inboundType = "shadowsocks16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var users []string
|
||||||
|
err = db.Raw(`SELECT json_extract(clients.config, ?)
|
||||||
|
FROM clients, json_each(clients.inbounds) as je
|
||||||
|
WHERE clients.enable = true AND je.value = ?;`,
|
||||||
|
"$."+inboundType, inboundId).Scan(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var usersJson []json.RawMessage
|
||||||
|
for _, user := range users {
|
||||||
|
usersJson = append(usersJson, json.RawMessage(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound["users"] = usersJson
|
||||||
|
return json.Marshal(inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err = corePtr.RemoveInbound(inbound.Tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inboundConfig, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||||
|
err = corePtr.AddInbound(inboundConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutboundService struct{}
|
||||||
|
|
||||||
|
func (o *OutboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
outbounds := []*model.Outbound{}
|
||||||
|
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
outData := map[string]interface{}{
|
||||||
|
"id": outbound.Id,
|
||||||
|
"type": outbound.Type,
|
||||||
|
"tag": outbound.Tag,
|
||||||
|
}
|
||||||
|
if outbound.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range restFields {
|
||||||
|
outData[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, outData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var outboundsJson []json.RawMessage
|
||||||
|
var outbounds []*model.Outbound
|
||||||
|
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
outboundJson, err := outbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outboundsJson = append(outboundsJson, outboundJson)
|
||||||
|
}
|
||||||
|
return outboundsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var outbound model.Outbound
|
||||||
|
err = outbound.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
configData, err := outbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Outbound{}).Select("tag").Where("id = ?", outbound.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveOutbound(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = corePtr.AddOutbound(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveOutbound(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Outbound{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+82
-15
@@ -1,21 +1,24 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v4/host"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v4/mem"
|
||||||
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerService struct {
|
type ServerService struct{}
|
||||||
SingBoxService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
|
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
|
||||||
status := make(map[string]interface{}, 0)
|
status := make(map[string]interface{}, 0)
|
||||||
@@ -88,15 +91,21 @@ func (s *ServerService) GetNetInfo() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
||||||
info := make(map[string]interface{}, 0)
|
var rtm runtime.MemStats
|
||||||
if s.SingBoxService.IsRunning() {
|
runtime.ReadMemStats(&rtm)
|
||||||
info["running"] = true
|
isRunning := corePtr.IsRunning()
|
||||||
sysStats, _ := s.SingBoxService.GetSysStats()
|
uptime := uint32(0)
|
||||||
info["stats"] = sysStats
|
if isRunning {
|
||||||
} else {
|
uptime = corePtr.GetInstance().Uptime()
|
||||||
info["running"] = false
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"running": isRunning,
|
||||||
|
"stats": map[string]interface{}{
|
||||||
|
"NumGoroutine": uint32(runtime.NumGoroutine()),
|
||||||
|
"Alloc": rtm.Alloc,
|
||||||
|
"Uptime": uptime,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return info
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
||||||
@@ -135,3 +144,61 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
|||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetLogs(count string, level string) []string {
|
||||||
|
c, _ := strconv.Atoi(count)
|
||||||
|
return logger.GetLogs(c, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GenKeypair(keyType string, options string) []string {
|
||||||
|
if len(keyType) == 0 {
|
||||||
|
return []string{"No keypair to generate"}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyType {
|
||||||
|
case "ech":
|
||||||
|
return s.generateECHKeyPair(options)
|
||||||
|
case "tls":
|
||||||
|
return s.generateTLSKeyPair(options)
|
||||||
|
case "reality":
|
||||||
|
return s.generateRealityKeyPair()
|
||||||
|
case "wireguard":
|
||||||
|
return generateWireGuardKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{"Failed to generate keypair"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) generateECHKeyPair(options string) []string {
|
||||||
|
parts := strings.Split(options, ",")
|
||||||
|
configPem, keyPem, err := tls.ECHKeygenDefault(parts[0], parts[1] == "true")
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate ECH keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return append(strings.Split(configPem, "\n"), strings.Split(keyPem, "\n")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) generateTLSKeyPair(serverName string) []string {
|
||||||
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, 12, 0))
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate TLS keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return append(strings.Split(string(privateKeyPem), "\n"), strings.Split(string(publicKeyPem), "\n")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) generateRealityKeyPair() []string {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate Reality keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
return []string{"PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]), "PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWireGuardKey() []string {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate wireguard keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return []string{"PrivateKey: " + privateKey.String(), "PublicKey: " + privateKey.PublicKey().String()}
|
||||||
|
}
|
||||||
|
|||||||
+117
-11
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
@@ -14,14 +15,36 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultConfig = `{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"dns": {},
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": [
|
||||||
|
"dns"
|
||||||
|
],
|
||||||
|
"outbound": "dns-out",
|
||||||
|
"action": "route"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"experimental": {}
|
||||||
|
}`
|
||||||
|
|
||||||
var defaultValueMap = map[string]string{
|
var defaultValueMap = map[string]string{
|
||||||
"webListen": "",
|
"webListen": "",
|
||||||
"webDomain": "",
|
"webDomain": "",
|
||||||
"webPort": "2095",
|
"webPort": "2095",
|
||||||
"webSecret": common.Random(32),
|
"secret": common.Random(32),
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
|
"webPath": "/app/",
|
||||||
|
"webURI": "",
|
||||||
"sessionMaxAge": "0",
|
"sessionMaxAge": "0",
|
||||||
|
"trafficAge": "30",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
"subListen": "",
|
"subListen": "",
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
@@ -33,6 +56,9 @@ var defaultValueMap = map[string]string{
|
|||||||
"subEncode": "true",
|
"subEncode": "true",
|
||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
|
"subJsonExt": "",
|
||||||
|
"config": defaultConfig,
|
||||||
|
"version": config.GetVersion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -62,11 +88,18 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Due to security principles
|
// Due to security principles
|
||||||
delete(allSetting, "webSecret")
|
delete(allSetting, "secret")
|
||||||
|
delete(allSetting, "config")
|
||||||
|
delete(allSetting, "version")
|
||||||
|
|
||||||
return &allSetting, nil
|
return &allSetting, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) ResetSettings() error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Where("1 = 1").Delete(model.Setting{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
setting := &model.Setting{}
|
setting := &model.Setting{}
|
||||||
@@ -119,9 +152,9 @@ func (s *SettingService) getBool(key string) (bool, error) {
|
|||||||
return strconv.ParseBool(str)
|
return strconv.ParseBool(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) setBool(key string, value bool) error {
|
// func (s *SettingService) setBool(key string, value bool) error {
|
||||||
return s.setString(key, strconv.FormatBool(value))
|
// return s.setString(key, strconv.FormatBool(value))
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *SettingService) getInt(key string) (int, error) {
|
func (s *SettingService) getInt(key string) (int, error) {
|
||||||
str, err := s.getString(key)
|
str, err := s.getString(key)
|
||||||
@@ -158,12 +191,36 @@ func (s *SettingService) GetKeyFile() (string, error) {
|
|||||||
return s.getString("webKeyFile")
|
return s.getString("webKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetWebPath() (string, error) {
|
||||||
|
webPath, err := s.getString("webPath")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(webPath, "/") {
|
||||||
|
webPath = "/" + webPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(webPath, "/") {
|
||||||
|
webPath += "/"
|
||||||
|
}
|
||||||
|
return webPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetWebPath(webPath string) error {
|
||||||
|
if !strings.HasPrefix(webPath, "/") {
|
||||||
|
webPath = "/" + webPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(webPath, "/") {
|
||||||
|
webPath += "/"
|
||||||
|
}
|
||||||
|
return s.setString("webPath", webPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
secret, err := s.getString("webSecret")
|
secret, err := s.getString("secret")
|
||||||
if secret == defaultValueMap["webSecret"] {
|
if secret == defaultValueMap["secret"] {
|
||||||
err := s.saveSetting("webSecret", secret)
|
err := s.saveSetting("secret", secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("save webSecret failed:", err)
|
logger.Warning("save secret failed:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []byte(secret), err
|
return []byte(secret), err
|
||||||
@@ -173,6 +230,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) {
|
|||||||
return s.getInt("sessionMaxAge")
|
return s.getInt("sessionMaxAge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTrafficAge() (int, error) {
|
||||||
|
return s.getInt("trafficAge")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
||||||
l, err := s.getString("timeLocation")
|
l, err := s.getString("timeLocation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,6 +256,10 @@ func (s *SettingService) GetSubPort() (int, error) {
|
|||||||
return s.getInt("subPort")
|
return s.getInt("subPort")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubPort(subPort int) error {
|
||||||
|
return s.setInt("subPort", subPort)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubPath() (string, error) {
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
subPath, err := s.getString("subPath")
|
subPath, err := s.getString("subPath")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -209,6 +274,16 @@ func (s *SettingService) GetSubPath() (string, error) {
|
|||||||
return subPath, nil
|
return subPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubPath(subPath string) error {
|
||||||
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return s.setString("subPath", subPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
return s.getString("subDomain")
|
return s.getString("subDomain")
|
||||||
}
|
}
|
||||||
@@ -260,6 +335,22 @@ func (s *SettingService) GetFinalSubURI(host string) (string, error) {
|
|||||||
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
|
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetConfig() (string, error) {
|
||||||
|
return s.getString("config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetConfig(config string) error {
|
||||||
|
return s.setString("config", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SaveConfig(tx *gorm.DB, config json.RawMessage) error {
|
||||||
|
configs, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||||
var err error
|
var err error
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
@@ -268,16 +359,27 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|||||||
json.Unmarshal(change.Obj, &obj)
|
json.Unmarshal(change.Obj, &obj)
|
||||||
|
|
||||||
// Secure file existance check
|
// Secure file existance check
|
||||||
if key == "webCertFile" ||
|
if obj != "" && (key == "webCertFile" ||
|
||||||
key == "webKeyFile" ||
|
key == "webKeyFile" ||
|
||||||
key == "subCertFile" ||
|
key == "subCertFile" ||
|
||||||
key == "subKeyFile" {
|
key == "subKeyFile") {
|
||||||
err = s.fileExists(obj)
|
err = s.fileExists(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError(" -> ", obj, " is not exists")
|
return common.NewError(" -> ", obj, " is not exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Correct Pathes start and ends with `/`
|
||||||
|
if key == "webPath" ||
|
||||||
|
key == "subPath" {
|
||||||
|
if !strings.HasPrefix(obj, "/") {
|
||||||
|
obj = "/" + obj
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(obj, "/") {
|
||||||
|
obj += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
|
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -286,6 +388,10 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonExt() (string, error) {
|
||||||
|
return s.getString("subJsonExt")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) fileExists(path string) error {
|
func (s *SettingService) fileExists(path string) error {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"s-ui/singbox"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SingBoxService struct {
|
|
||||||
singbox.V2rayAPI
|
|
||||||
singbox.Controller
|
|
||||||
StatsService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SingBoxService) GetStats() error {
|
|
||||||
s.V2rayAPI.Init(ApiAddr)
|
|
||||||
defer s.V2rayAPI.Close()
|
|
||||||
stats, err := s.V2rayAPI.GetStats(true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.StatsService.SaveStats(stats)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SingBoxService) GetSysStats() (*map[string]interface{}, error) {
|
|
||||||
s.V2rayAPI.Init(ApiAddr)
|
|
||||||
defer s.V2rayAPI.Close()
|
|
||||||
resp, err := s.V2rayAPI.GetSysStats()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
result["NumGoroutine"] = resp.NumGoroutine
|
|
||||||
result["Alloc"] = resp.Alloc
|
|
||||||
result["Uptime"] = resp.Uptime
|
|
||||||
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
+16
-13
@@ -1,7 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,18 +19,22 @@ var onlineResources = &onlines{}
|
|||||||
type StatsService struct {
|
type StatsService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
func (s *StatsService) SaveStats() error {
|
||||||
var err error
|
if !corePtr.IsRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stats := corePtr.GetInstance().ConnTracker().GetStats()
|
||||||
|
|
||||||
// Reset onlines
|
// Reset onlines
|
||||||
onlineResources.Inbound = nil
|
onlineResources.Inbound = nil
|
||||||
onlineResources.Outbound = nil
|
onlineResources.Outbound = nil
|
||||||
onlineResources.User = nil
|
onlineResources.User = nil
|
||||||
|
|
||||||
if len(stats) == 0 {
|
if len(*stats) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -42,7 +45,7 @@ func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, stat := range stats {
|
for _, stat := range *stats {
|
||||||
if stat.Resource == "user" {
|
if stat.Resource == "user" {
|
||||||
if stat.Direction {
|
if stat.Direction {
|
||||||
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
||||||
@@ -71,7 +74,7 @@ func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) {
|
func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model.Stats, error) {
|
||||||
var err error
|
var err error
|
||||||
var result []model.Stats
|
var result []model.Stats
|
||||||
|
|
||||||
@@ -79,19 +82,19 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.
|
|||||||
timeDiff := currentTime - (int64(limit) * 3600)
|
timeDiff := currentTime - (int64(limit) * 3600)
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Model(model.Stats{}).Where("resource = ? AND tag = ? AND date_time > ?", resorce, tag, timeDiff).Scan(&result).Error
|
resources := []string{resource}
|
||||||
|
if resource == "endpoint" {
|
||||||
|
resources = []string{"inbound", "outbound"}
|
||||||
|
}
|
||||||
|
err = db.Model(model.Stats{}).Where("resource in ? AND tag = ? AND date_time > ?", resources, tag, timeDiff).Scan(&result).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetOnlines() (string, error) {
|
func (s *StatsService) GetOnlines() (onlines, error) {
|
||||||
onlines, err := json.Marshal(onlineResources)
|
return *onlineResources, nil
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(onlines), nil
|
|
||||||
}
|
}
|
||||||
func (s *StatsService) DelOldStats(days int) error {
|
func (s *StatsService) DelOldStats(days int) error {
|
||||||
oldTime := time.Now().AddDate(0, 0, -(days)).Unix()
|
oldTime := time.Now().AddDate(0, 0, -(days)).Unix()
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+57
-1
@@ -13,6 +13,40 @@ import (
|
|||||||
type UserService struct {
|
type UserService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetFirstUser() (*model.User, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).
|
||||||
|
First(user).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||||
|
if username == "" {
|
||||||
|
return common.NewError("username can not be empty")
|
||||||
|
} else if password == "" {
|
||||||
|
return common.NewError("password can not be empty")
|
||||||
|
}
|
||||||
|
db := database.GetDB()
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).First(user).Error
|
||||||
|
if database.IsNotFound(err) {
|
||||||
|
user.Username = username
|
||||||
|
user.Password = password
|
||||||
|
return db.Model(model.User{}).Create(user).Error
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Username = username
|
||||||
|
user.Password = password
|
||||||
|
return db.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
|
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
|
||||||
user := s.CheckUser(username, password, remoteIP)
|
user := s.CheckUser(username, password, remoteIP)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@@ -36,7 +70,7 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + "-" + remoteIP
|
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + " " + remoteIP
|
||||||
err = db.Model(model.User{}).
|
err = db.Model(model.User{}).
|
||||||
Where("username = ?", username).
|
Where("username = ?", username).
|
||||||
Update("last_logins", &lastLoginTxt).Error
|
Update("last_logins", &lastLoginTxt).Error
|
||||||
@@ -45,3 +79,25 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
|
|||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetUsers() (*[]model.User, error) {
|
||||||
|
var users []model.User
|
||||||
|
db := database.GetDB()
|
||||||
|
err := db.Model(model.User{}).Select("id,username,last_logins").Scan(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) ChangePass(id string, oldPass string, newUser string, newPass string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).Where("id = ? AND password = ?", id, oldPass).First(user).Error
|
||||||
|
if err != nil || database.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Username = newUser
|
||||||
|
user.Password = newPass
|
||||||
|
return db.Save(user).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package singbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"s-ui/config"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var serviceName = "sing-box"
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetBinaryName() string {
|
|
||||||
return "sing-box"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetBinaryPath() string {
|
|
||||||
return config.GetBinFolderPath() + "/" + s.GetBinaryName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetConfigPath() string {
|
|
||||||
return config.GetBinFolderPath() + "/config.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) IsRunning() bool {
|
|
||||||
cmd := exec.Command("pgrep", "sing-box")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If pgrep found the Controller, its output will not be empty
|
|
||||||
return strings.TrimSpace(string(output)) != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) signalSingbox(signal string) error {
|
|
||||||
return os.WriteFile(config.GetBinFolderPath()+"/signal", []byte(signal), fs.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) Restart() error {
|
|
||||||
return s.signalSingbox("restart")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) Stop() error {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
return errors.New("Sing-Box is not running")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.signalSingbox("stop")
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package singbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"regexp"
|
|
||||||
"s-ui/database/model"
|
|
||||||
"s-ui/util/common"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v5/app/stats/command"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type V2rayAPI struct {
|
|
||||||
StatsServiceClient *statsService.StatsServiceClient
|
|
||||||
grpcClient *grpc.ClientConn
|
|
||||||
isConnected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) Init(ApiAddr string) (err error) {
|
|
||||||
if len(ApiAddr) == 0 {
|
|
||||||
return common.NewError("The api address is wrong: ", ApiAddr)
|
|
||||||
}
|
|
||||||
v.grpcClient, err = grpc.Dial(ApiAddr, grpc.WithInsecure())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.isConnected = true
|
|
||||||
|
|
||||||
ssClient := statsService.NewStatsServiceClient(v.grpcClient)
|
|
||||||
|
|
||||||
v.StatsServiceClient = &ssClient
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) Close() {
|
|
||||||
v.grpcClient.Close()
|
|
||||||
v.StatsServiceClient = nil
|
|
||||||
v.isConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) GetStats(reset bool) ([]*model.Stats, error) {
|
|
||||||
if v.grpcClient == nil {
|
|
||||||
return nil, common.NewError("v2ray api is not initialized")
|
|
||||||
}
|
|
||||||
var trafficRegex = regexp.MustCompile("(inbound|outbound|user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
|
||||||
|
|
||||||
client := *v.StatsServiceClient
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
request := &statsService.QueryStatsRequest{
|
|
||||||
Reset_: reset,
|
|
||||||
}
|
|
||||||
resp, err := client.QueryStats(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dt := time.Now().Unix()
|
|
||||||
stats := make([]*model.Stats, 0)
|
|
||||||
for _, stat := range resp.GetStat() {
|
|
||||||
if stat.Value > 0 {
|
|
||||||
matchs := trafficRegex.FindStringSubmatch(stat.Name)
|
|
||||||
if len(matchs) > 3 {
|
|
||||||
stat := model.Stats{
|
|
||||||
DateTime: dt,
|
|
||||||
Resource: matchs[1],
|
|
||||||
Tag: matchs[2],
|
|
||||||
Direction: matchs[3] == "uplink",
|
|
||||||
Traffic: stat.Value,
|
|
||||||
}
|
|
||||||
stats = append(stats, &stat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) GetSysStats() (*statsService.SysStatsResponse, error) {
|
|
||||||
if v.grpcClient == nil {
|
|
||||||
return nil, common.NewError("v2ray api is not initialized")
|
|
||||||
}
|
|
||||||
client := *v.StatsServiceClient
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
request := &statsService.SysStatsRequest{}
|
|
||||||
resp, err := client.GetSysStats(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/service"
|
||||||
|
"s-ui/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultJson = `
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"address": [
|
||||||
|
"172.19.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
"mtu": 9000,
|
||||||
|
"auto_route": true,
|
||||||
|
"strict_route": false,
|
||||||
|
"sniff": true,
|
||||||
|
"endpoint_independent_nat": false,
|
||||||
|
"stack": "system",
|
||||||
|
"platform": {
|
||||||
|
"http_proxy": {
|
||||||
|
"enabled": true,
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 2080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 2080,
|
||||||
|
"sniff": true,
|
||||||
|
"users": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
type JsonService struct {
|
||||||
|
service.SettingService
|
||||||
|
LinkService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
||||||
|
var jsonConfig map[string]interface{}
|
||||||
|
|
||||||
|
client, inDatas, err := j.getData(subId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds, outTags, err := j.getOutbounds(client.Config, inDatas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
links := j.LinkService.GetLinks(&client.Links, "external", "")
|
||||||
|
for index, link := range links {
|
||||||
|
json, tag, err := util.GetOutbound(link, index)
|
||||||
|
if err == nil && len(tag) > 0 {
|
||||||
|
*outbounds = append(*outbounds, *json)
|
||||||
|
*outTags = append(*outTags, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
j.addDefaultOutbounds(outbounds, outTags)
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(defaultJson), &jsonConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonConfig["outbounds"] = outbounds
|
||||||
|
|
||||||
|
// Add other objects from settings
|
||||||
|
j.addOthers(&jsonConfig)
|
||||||
|
|
||||||
|
result, _ := json.MarshalIndent(jsonConfig, "", " ")
|
||||||
|
resultStr := string(result)
|
||||||
|
return &resultStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
client := &model.Client{}
|
||||||
|
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
var clientInbounds []uint
|
||||||
|
err = json.Unmarshal(client.Inbounds, &clientInbounds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err = db.Model(model.Inbound{}).Where("id in ?", clientInbounds).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return client, inbounds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*model.Inbound) (*[]map[string]interface{}, *[]string, error) {
|
||||||
|
var outbounds []map[string]interface{}
|
||||||
|
var configs map[string]interface{}
|
||||||
|
var outTags []string
|
||||||
|
|
||||||
|
err := json.Unmarshal(clientConfig, &configs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, inData := range inbounds {
|
||||||
|
if len(inData.OutJson) < 5 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var outbound map[string]interface{}
|
||||||
|
err = json.Unmarshal(inData.OutJson, &outbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
protocol, _ := outbound["type"].(string)
|
||||||
|
config, _ := configs[protocol].(map[string]interface{})
|
||||||
|
for key, value := range config {
|
||||||
|
if key != "alterId" && key != "name" {
|
||||||
|
outbound[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrs []map[string]interface{}
|
||||||
|
err = json.Unmarshal(inData.Addrs, &addrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tag, _ := outbound["tag"].(string)
|
||||||
|
if len(addrs) == 0 {
|
||||||
|
// For mixed protocol, use separated socks and http
|
||||||
|
if protocol == "mixed" {
|
||||||
|
outbound["tag"] = tag
|
||||||
|
j.pushMixed(&outbounds, &outTags, outbound)
|
||||||
|
} else {
|
||||||
|
outTags = append(outTags, tag)
|
||||||
|
outbounds = append(outbounds, outbound)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for index, addr := range addrs {
|
||||||
|
// Copy original config
|
||||||
|
newOut := make(map[string]interface{}, len(outbound))
|
||||||
|
for key, value := range outbound {
|
||||||
|
newOut[key] = value
|
||||||
|
}
|
||||||
|
// Change and push copied config
|
||||||
|
newOut["server"], _ = addr["server"].(string)
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
newOut["server_port"] = int(port)
|
||||||
|
|
||||||
|
// Override TLS
|
||||||
|
outTls, _ := newOut["tls"].(map[string]interface{})
|
||||||
|
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
for key, value := range addrTls {
|
||||||
|
outTls[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOut["tls"] = outTls
|
||||||
|
|
||||||
|
remark, _ := addr["remark"].(string)
|
||||||
|
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
|
||||||
|
newOut["tag"] = newTag
|
||||||
|
// For mixed protocol, use separated socks and http
|
||||||
|
if protocol == "mixed" {
|
||||||
|
j.pushMixed(&outbounds, &outTags, newOut)
|
||||||
|
} else {
|
||||||
|
outTags = append(outTags, newTag)
|
||||||
|
outbounds = append(outbounds, newOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &outbounds, &outTags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, outTags *[]string) {
|
||||||
|
outbound := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"outbounds": append([]string{"auto", "direct"}, *outTags...),
|
||||||
|
"tag": "proxy",
|
||||||
|
"type": "selector",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "auto",
|
||||||
|
"type": "urltest",
|
||||||
|
"outbounds": outTags,
|
||||||
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
|
"interval": "10m",
|
||||||
|
"tolerance": 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*outbounds = append(outbound, *outbounds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) addOthers(jsonConfig *map[string]interface{}) error {
|
||||||
|
rules := []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"outbound": "direct",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"outbound": "proxy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
route := map[string]interface{}{
|
||||||
|
"auto_detect_interface": true,
|
||||||
|
"final": "proxy",
|
||||||
|
"rules": rules,
|
||||||
|
}
|
||||||
|
|
||||||
|
othersStr, err := j.SettingService.GetSubJsonExt()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(othersStr) == 0 {
|
||||||
|
(*jsonConfig)["route"] = route
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var othersJson map[string]interface{}
|
||||||
|
err = json.Unmarshal([]byte(othersStr), &othersJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["log"]; ok {
|
||||||
|
(*jsonConfig)["log"] = othersJson["log"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["dns"]; ok {
|
||||||
|
(*jsonConfig)["dns"] = othersJson["dns"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["inbounds"]; ok {
|
||||||
|
(*jsonConfig)["inbounds"] = othersJson["inbounds"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["experimental"]; ok {
|
||||||
|
(*jsonConfig)["experimental"] = othersJson["experimental"]
|
||||||
|
}
|
||||||
|
if _, ok := othersJson["rule_set"]; ok {
|
||||||
|
route["rule_set"] = othersJson["rule_set"]
|
||||||
|
}
|
||||||
|
if settingRules, ok := othersJson["rules"].([]interface{}); ok {
|
||||||
|
route["rules"] = append(rules, settingRules...)
|
||||||
|
}
|
||||||
|
(*jsonConfig)["route"] = route
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) pushMixed(outbounds *[]map[string]interface{}, outTags *[]string, out map[string]interface{}) {
|
||||||
|
socksOut := make(map[string]interface{}, 1)
|
||||||
|
httpOut := make(map[string]interface{}, 1)
|
||||||
|
for key, value := range out {
|
||||||
|
socksOut[key] = value
|
||||||
|
httpOut[key] = value
|
||||||
|
}
|
||||||
|
socksTag := fmt.Sprintf("%s-socks", out["tag"])
|
||||||
|
httpTag := fmt.Sprintf("%s-http", out["tag"])
|
||||||
|
socksOut["type"] = "socks"
|
||||||
|
httpOut["type"] = "http"
|
||||||
|
socksOut["tag"] = socksTag
|
||||||
|
httpOut["tag"] = httpTag
|
||||||
|
*outbounds = append(*outbounds, socksOut, httpOut)
|
||||||
|
*outTags = append(*outTags, socksTag, httpTag)
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"s-ui/logger"
|
||||||
|
"s-ui/util"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Uri string `json:"uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) GetLinks(linkJson *json.RawMessage, types string, clientInfo string) []string {
|
||||||
|
links := []Link{}
|
||||||
|
var result []string
|
||||||
|
err := json.Unmarshal(*linkJson, &links)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, link := range links {
|
||||||
|
switch link.Type {
|
||||||
|
case "external":
|
||||||
|
result = append(result, link.Uri)
|
||||||
|
case "sub":
|
||||||
|
result = append(result, s.getExternalSub(link.Uri)...)
|
||||||
|
case "local":
|
||||||
|
if types == "all" {
|
||||||
|
result = append(result, s.addClientInfo(link.Uri, clientInfo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) addClientInfo(uri string, clientInfo string) string {
|
||||||
|
if len(clientInfo) == 0 {
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
protocol := strings.Split(uri, "://")
|
||||||
|
if len(protocol) < 2 {
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
switch protocol[0] {
|
||||||
|
case "vmess":
|
||||||
|
var vmessJson map[string]interface{}
|
||||||
|
config, err := util.B64StrToByte(protocol[1])
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(config, &vmessJson)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
||||||
|
result, err := json.MarshalIndent(vmessJson, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
return "vmess://" + util.ByteToB64Str(result)
|
||||||
|
default:
|
||||||
|
return uri + clientInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LinkService) getExternalSub(url string) []string {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: tr}
|
||||||
|
|
||||||
|
// Make the HTTP request
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error making HTTP request:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("sub: Error reading response body:", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert if the content is Base64 encoded
|
||||||
|
links := util.StrOrBase64Encoded(string(body))
|
||||||
|
return strings.Split(links, "\n")
|
||||||
|
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
type SubHandler struct {
|
type SubHandler struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
SubService
|
SubService
|
||||||
|
JsonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubHandler(g *gin.RouterGroup) {
|
func NewSubHandler(g *gin.RouterGroup) {
|
||||||
@@ -23,17 +24,28 @@ func (s *SubHandler) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (s *SubHandler) subs(c *gin.Context) {
|
func (s *SubHandler) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
result, headers, err := s.SubService.GetSubs(subId)
|
format, isFormat := c.GetQuery("format")
|
||||||
if err != nil || result == nil {
|
if isFormat {
|
||||||
logger.Error(err)
|
result, err := s.JsonService.GetJson(subId, format)
|
||||||
c.String(400, "Error!")
|
if err != nil || result == nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
c.String(200, *result)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
result, headers, err := s.SubService.GetSubs(subId)
|
||||||
|
if err != nil || result == nil {
|
||||||
|
logger.Error(err)
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||||
|
|
||||||
c.String(200, *result)
|
c.String(200, *result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-102
@@ -1,15 +1,10 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,12 +12,7 @@ import (
|
|||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
}
|
LinkService
|
||||||
|
|
||||||
type Link struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
Uri string `json:"uri"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
||||||
@@ -35,29 +25,14 @@ func (s *SubService) GetSubs(subId string) (*string, []string, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
links := []Link{}
|
|
||||||
err = json.Unmarshal([]byte(client.Links), &links)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientInfo := ""
|
clientInfo := ""
|
||||||
subShowInfo, _ := s.SettingService.GetSubShowInfo()
|
subShowInfo, _ := s.SettingService.GetSubShowInfo()
|
||||||
if subShowInfo {
|
if subShowInfo {
|
||||||
clientInfo = s.getClientInfo(client)
|
clientInfo = s.getClientInfo(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result string
|
linksArray := s.LinkService.GetLinks(&client.Links, "all", clientInfo)
|
||||||
for _, link := range links {
|
result := strings.Join(linksArray, "\n")
|
||||||
switch link.Type {
|
|
||||||
case "external":
|
|
||||||
result += fmt.Sprintln(link.Uri)
|
|
||||||
case "sub":
|
|
||||||
result += s.getExternalSub(link.Uri)
|
|
||||||
case "local":
|
|
||||||
result += fmt.Sprintln(s.addClientInfo(link.Uri, clientInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var headers []string
|
var headers []string
|
||||||
updateInterval, _ := s.SettingService.GetSubUpdates()
|
updateInterval, _ := s.SettingService.GetSubUpdates()
|
||||||
@@ -90,80 +65,6 @@ func (s *SubService) getClientInfo(c *model.Client) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) addClientInfo(uri string, clientInfo string) string {
|
|
||||||
protocol := strings.Split(uri, "://")
|
|
||||||
if len(protocol) < 2 {
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
switch protocol[0] {
|
|
||||||
case "vmess":
|
|
||||||
var vmessJson map[string]interface{}
|
|
||||||
config, err := base64.StdEncoding.DecodeString(protocol[1])
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(config, &vmessJson)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo
|
|
||||||
result, err := json.MarshalIndent(vmessJson, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding vmess + clientInfo content:", err)
|
|
||||||
return uri
|
|
||||||
}
|
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(result)
|
|
||||||
default:
|
|
||||||
return uri + clientInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getExternalSub(url string) string {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
|
|
||||||
// Make the HTTP request
|
|
||||||
response, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error making HTTP request:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
// Read the response body
|
|
||||||
body, err := io.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error reading response body:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the content is Base64 encoded
|
|
||||||
isBase64 := s.isBase64Encoded(string(body))
|
|
||||||
if isBase64 {
|
|
||||||
// Decode Base64 content
|
|
||||||
decodedText, err := base64.StdEncoding.DecodeString(string(body))
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("sub: Error decoding Base64 content:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(decodedText)
|
|
||||||
} else {
|
|
||||||
return string(body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to check if a string is Base64 encoded
|
|
||||||
func (s *SubService) isBase64Encoded(str string) bool {
|
|
||||||
_, err := base64.StdEncoding.DecodeString(str)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) formatTraffic(trafficBytes int64) string {
|
func (s *SubService) formatTraffic(trafficBytes int64) string {
|
||||||
if trafficBytes < 1024 {
|
if trafficBytes < 1024 {
|
||||||
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
// Function to return decoded bytes if a string is Base64 encoded
|
||||||
|
func StrOrBase64Encoded(str string) string {
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(str)
|
||||||
|
if err == nil {
|
||||||
|
return string(decoded)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func B64StrToByte(str string) ([]byte, error) {
|
||||||
|
return base64.StdEncoding.DecodeString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ByteToB64Str(b []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import "math/rand"
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
var allSeq [62]rune
|
var (
|
||||||
|
allSeq []rune
|
||||||
|
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
for _, char := range chars {
|
||||||
|
allSeq = append(allSeq, char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Random(n int) string {
|
func Random(n int) string {
|
||||||
runes := make([]rune, n)
|
runes := make([]rune, n)
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
runes[i] = allSeq[rand.Intn(len(allSeq))]
|
runes[i] = allSeq[rnd.Intn(len(allSeq))]
|
||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "tuic", "vless", "trojan", "vmess"}
|
||||||
|
|
||||||
|
func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname string) []string {
|
||||||
|
inbound, err := i.MarshalFull()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tls map[string]interface{}
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
json.Unmarshal(i.Tls.Client, &tls)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userConfig map[string]map[string]interface{}
|
||||||
|
if err := json.Unmarshal(clientConfig, &userConfig); err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Addrs []map[string]interface{}
|
||||||
|
json.Unmarshal(i.Addrs, &Addrs)
|
||||||
|
if len(Addrs) == 0 {
|
||||||
|
Addrs = append(Addrs, map[string]interface{}{
|
||||||
|
"server": hostname,
|
||||||
|
"server_port": (*inbound)["listen_port"],
|
||||||
|
"remark": i.Tag,
|
||||||
|
})
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
Addrs[0]["tls"] = tls
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for index, addr := range Addrs {
|
||||||
|
addrRemark, _ := addr["remark"].(string)
|
||||||
|
Addrs[index]["remark"] = i.Tag + addrRemark
|
||||||
|
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
newTls := map[string]interface{}{}
|
||||||
|
if oldTls, hasOldTls := tls["tls"].(map[string]interface{}); hasOldTls {
|
||||||
|
for k, v := range oldTls {
|
||||||
|
newTls[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Override tls
|
||||||
|
for k, v := range addrTls {
|
||||||
|
newTls[k] = v
|
||||||
|
}
|
||||||
|
Addrs[index]["tls"] = newTls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case "shadowsocks":
|
||||||
|
return shadowsocksLink(userConfig, *inbound, Addrs)
|
||||||
|
case "naive":
|
||||||
|
return naiveLink(userConfig["naive"], *inbound, Addrs)
|
||||||
|
case "hysteria":
|
||||||
|
return hysteriaLink(userConfig["hysteria"], *inbound, Addrs)
|
||||||
|
case "hysteria2":
|
||||||
|
return hysteria2Link(userConfig["hysteria2"], *inbound, Addrs)
|
||||||
|
case "tuic":
|
||||||
|
return tuicLink(userConfig["tuic"], *inbound, Addrs)
|
||||||
|
case "vless":
|
||||||
|
return vlessLink(userConfig["vless"], *inbound, Addrs)
|
||||||
|
case "trojan":
|
||||||
|
return trojanLink(userConfig["trojan"], *inbound, Addrs)
|
||||||
|
case "vmess":
|
||||||
|
return vmessLink(userConfig["vmess"], *inbound, Addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shadowsocksLink(
|
||||||
|
userConfig map[string]map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
var userPass []string
|
||||||
|
method, _ := inbound["method"].(string)
|
||||||
|
var pass string
|
||||||
|
if method == "2022-blake3-aes-128-gcm" {
|
||||||
|
pass, _ = userConfig["shadowsocks16"]["password"].(string)
|
||||||
|
} else {
|
||||||
|
pass, _ = userConfig["shadowsocks"]["password"].(string)
|
||||||
|
}
|
||||||
|
userPass = append(userPass, pass)
|
||||||
|
|
||||||
|
if strings.HasPrefix(method, "2022") {
|
||||||
|
inbPass, _ := inbound["password"].(string)
|
||||||
|
userPass = append(userPass, inbPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
uriBase := fmt.Sprintf("ss://%s", toBase64([]byte(fmt.Sprintf("%s:%s", method, strings.Join(userPass, ":")))))
|
||||||
|
|
||||||
|
var links []string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
links = append(links, fmt.Sprintf("%s@%s:%d", uriBase, addr["server"].(string), uint(port)))
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func naiveLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
username, _ := userConfig["username"].(string)
|
||||||
|
|
||||||
|
baseUri := "http2://"
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
params["padding"] = "1"
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["peer"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["tfo"] = "1"
|
||||||
|
} else {
|
||||||
|
params["tfo"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := baseUri + toBase64([]byte(fmt.Sprintf("%s:%s@%s:%d", username, password, addr["server"].(string), uint(port))))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteriaLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
baseUri := "hysteria://"
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if upmbps, ok := inbound["up_mbps"].(string); ok {
|
||||||
|
params["up_mbps"] = upmbps
|
||||||
|
}
|
||||||
|
if downmbps, ok := inbound["down_mbps"].(string); ok {
|
||||||
|
params["down_mbps"] = downmbps
|
||||||
|
}
|
||||||
|
if auth, ok := userConfig["auth_str"].(string); ok {
|
||||||
|
params["auth"] = auth
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["peer"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"].(string); ok {
|
||||||
|
params["obfs"] = obfs
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["fastopen"] = "1"
|
||||||
|
} else {
|
||||||
|
params["fastopen"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteria2Link(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
baseUri := fmt.Sprintf("%s%s@", "hysteria2://", password)
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if upmbps, ok := inbound["up_mbps"].(string); ok {
|
||||||
|
params["up_mbps"] = upmbps
|
||||||
|
}
|
||||||
|
if downmbps, ok := inbound["down_mbps"].(string); ok {
|
||||||
|
params["down_mbps"] = downmbps
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"].(map[string]interface{}); ok {
|
||||||
|
if obfsType, ok := obfs["type"].(string); ok {
|
||||||
|
params["obfs"] = obfsType
|
||||||
|
}
|
||||||
|
if obfsPassword, ok := obfs["password"].(string); ok {
|
||||||
|
params["obfs-password"] = obfsPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["fastopen"] = "1"
|
||||||
|
} else {
|
||||||
|
params["fastopen"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuicLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
baseUri := fmt.Sprintf("%s%s:%s@", "tuic://", uuid, password)
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
if disableSni, ok := tls["disable_sni"].(bool); ok && disableSni {
|
||||||
|
params["disableSni"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if congestionControl, ok := inbound["congestion_control"].(string); ok {
|
||||||
|
params["congestion_control"] = congestionControl
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func vlessLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
baseParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := baseParams
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
params["security"] = "reality"
|
||||||
|
if pbk, ok := reality["public_key"].(string); ok {
|
||||||
|
params["pbk"] = pbk
|
||||||
|
}
|
||||||
|
if sid, ok := reality["short_id"].(string); ok {
|
||||||
|
params["sid"] = sid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params["security"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
if flow, ok := userConfig["flow"].(string); ok {
|
||||||
|
params["flow"] = flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("vless://%s@%s:%d", uuid, addr["server"].(string), uint(port))
|
||||||
|
uri = addParams(uri, params, addr["remark"].(string))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojanLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
baseParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := baseParams
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
params["security"] = "reality"
|
||||||
|
if pbk, ok := reality["public_key"].(string); ok {
|
||||||
|
params["pbk"] = pbk
|
||||||
|
}
|
||||||
|
if sid, ok := reality["short_id"].(string); ok {
|
||||||
|
params["sid"] = sid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params["security"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("trojan://%s@%s:%d", password, addr["server"].(string), uint(port))
|
||||||
|
uri = addParams(uri, params, addr["remark"].(string))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmessLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
trasportParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
baseParams := map[string]interface{}{
|
||||||
|
"v": 2,
|
||||||
|
"id": uuid,
|
||||||
|
"aid": 0,
|
||||||
|
}
|
||||||
|
if trasportParams["type"] == "http" || trasportParams["type"] == "tcp" {
|
||||||
|
baseParams["net"] = "tcp"
|
||||||
|
if trasportParams["type"] == "http" {
|
||||||
|
baseParams["type"] = "http"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseParams["net"] = trasportParams["type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
obj := baseParams
|
||||||
|
obj["addr"], _ = addr["server"].(string)
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
obj["port"] = uint(port)
|
||||||
|
obj["ps"], _ = addr["remark"].(string)
|
||||||
|
if trasportParams["host"] != "" {
|
||||||
|
obj["host"] = trasportParams["host"]
|
||||||
|
}
|
||||||
|
if trasportParams["path"] != "" {
|
||||||
|
obj["path"] = trasportParams["path"]
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
obj["tls"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
obj["allowInsecure"] = 1
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
obj["sni"] = sni
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj["tls"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("vmess://%s", toBase64(jsonStr))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBase64(d []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addParams(uri string, params map[string]string, remark string) string {
|
||||||
|
URL, _ := url.Parse(uri)
|
||||||
|
q := URL.Query()
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
URL.RawQuery = q.Encode()
|
||||||
|
URL.Fragment = remark
|
||||||
|
return URL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransportParams(t interface{}) map[string]string {
|
||||||
|
params := map[string]string{}
|
||||||
|
trasport, _ := t.(map[string]interface{})
|
||||||
|
if transportType, ok := trasport["type"].(string); ok {
|
||||||
|
params["type"] = transportType
|
||||||
|
} else {
|
||||||
|
params["type"] = "tcp"
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
switch params["type"] {
|
||||||
|
case "http":
|
||||||
|
if host, ok := trasport["host"].([]interface{}); ok {
|
||||||
|
var hosts []string
|
||||||
|
for _, v := range host {
|
||||||
|
hosts = append(hosts, v.(string))
|
||||||
|
}
|
||||||
|
params["host"] = strings.Join(hosts, ",")
|
||||||
|
}
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
case "ws":
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
if headers, ok := trasport["headers"].(map[string]interface{}); ok {
|
||||||
|
if host, ok := headers["Host"].(string); ok {
|
||||||
|
params["peer"] = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "grpc":
|
||||||
|
if serviceName, ok := trasport["service_name"].(string); ok {
|
||||||
|
params["serviceName"] = serviceName
|
||||||
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
if host, ok := trasport["host"].(string); ok {
|
||||||
|
params["peer"] = host
|
||||||
|
}
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOutbound(uri string, i int) (*map[string]interface{}, string, error) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err == nil {
|
||||||
|
switch u.Scheme {
|
||||||
|
case "vmess":
|
||||||
|
return vmess(u.Host, i)
|
||||||
|
case "vless":
|
||||||
|
return vless(u, i)
|
||||||
|
case "trojan":
|
||||||
|
return trojan(u, i)
|
||||||
|
case "hy", "hysteria":
|
||||||
|
return hy(u, i)
|
||||||
|
case "hy2", "hysteria2":
|
||||||
|
return hy2(u, i)
|
||||||
|
case "tuic":
|
||||||
|
return tuic(u, i)
|
||||||
|
case "ss", "shadowsocks":
|
||||||
|
return ss(u, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, "", common.NewError("Unsupported link format")
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmess(data string, i int) (*map[string]interface{}, string, error) {
|
||||||
|
dataByte, err := B64StrToByte(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
var dataJson map[string]interface{}
|
||||||
|
err = json.Unmarshal(dataByte, &dataJson)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
transport := map[string]interface{}{}
|
||||||
|
tp_net, _ := dataJson["net"].(string)
|
||||||
|
tp_type, _ := dataJson["type"].(string)
|
||||||
|
tp_host, _ := dataJson["host"].(string)
|
||||||
|
tp_path, _ := dataJson["path"].(string)
|
||||||
|
switch strings.ToLower(tp_net) {
|
||||||
|
case "tcp", "":
|
||||||
|
if tp_type == "http" {
|
||||||
|
transport["type"] = tp_type
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
}
|
||||||
|
case "http", "h2":
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
case "ws":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["early_data_header_name"] = "Sec-WebSocket-Protocol"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["headers"] = map[string]interface{}{
|
||||||
|
"Host": tp_host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "quic":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
case "grpc":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["service_name"] = tp_path
|
||||||
|
case "httpupgrade":
|
||||||
|
transport["type"] = tp_net
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["host"] = tp_host
|
||||||
|
default:
|
||||||
|
return nil, "", common.NewError("Invalid vmess")
|
||||||
|
}
|
||||||
|
tls := map[string]interface{}{}
|
||||||
|
vmess_tls, _ := dataJson["tls"].(string)
|
||||||
|
if vmess_tls == "tls" {
|
||||||
|
tls["enabled"] = true
|
||||||
|
tls_sni, _ := dataJson["sni"].(string)
|
||||||
|
tls_alpn, _ := dataJson["alpn"].(string)
|
||||||
|
_, tls_insecure := dataJson["allowInsecure"]
|
||||||
|
tls_fp, _ := dataJson["fp"].(string)
|
||||||
|
if len(tls_sni) > 0 {
|
||||||
|
tls["server_name"] = tls_sni
|
||||||
|
}
|
||||||
|
if len(tls_alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||||
|
}
|
||||||
|
if tls_insecure {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if len(tls_fp) > 0 {
|
||||||
|
tls["utls"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"fingerprint": tls_fp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag, _ := dataJson["ps"].(string)
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, tag)
|
||||||
|
}
|
||||||
|
alter_id, ok := dataJson["aid"].(int)
|
||||||
|
if !ok {
|
||||||
|
alter_id = 0
|
||||||
|
}
|
||||||
|
vmess := map[string]interface{}{
|
||||||
|
"type": "vmess",
|
||||||
|
"tag": tag,
|
||||||
|
"server": dataJson["add"],
|
||||||
|
"server_port": dataJson["port"],
|
||||||
|
"uuid": dataJson["id"],
|
||||||
|
"security": "auto",
|
||||||
|
"alter_id": alter_id,
|
||||||
|
"tls": tls,
|
||||||
|
"transport": transport,
|
||||||
|
}
|
||||||
|
return &vmess, tag, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func vless(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
security := query.Get("security")
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 80
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
} else {
|
||||||
|
if security == "tls" || security == "reality" {
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tp_type := query.Get("type")
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
vless := map[string]interface{}{
|
||||||
|
"type": "vless",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"uuid": u.User.Username(),
|
||||||
|
"flow": query.Get("flow"),
|
||||||
|
"tls": getTls(security, &query),
|
||||||
|
"transport": getTransport(tp_type, &query),
|
||||||
|
}
|
||||||
|
return &vless, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojan(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
security := query.Get("security")
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 80
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
} else {
|
||||||
|
if security == "tls" || security == "reality" {
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tp_type := query.Get("type")
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
trojan := map[string]interface{}{
|
||||||
|
"type": "trojan",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"password": u.User.Username(),
|
||||||
|
"tls": getTls(security, &query),
|
||||||
|
"transport": getTransport(tp_type, &query),
|
||||||
|
}
|
||||||
|
return &trojan, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hy(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("peer"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("insecure")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
hy := map[string]interface{}{
|
||||||
|
"type": "hysteria",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"obfs": query.Get("obfsParam"),
|
||||||
|
"auth_str": query.Get("auth"),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||||
|
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||||
|
recv_window_conn, _ := strconv.Atoi(query.Get("recv_window_conn"))
|
||||||
|
recv_window, _ := strconv.Atoi(query.Get("recv_window"))
|
||||||
|
if down > 0 {
|
||||||
|
hy["down_mbps"] = down
|
||||||
|
}
|
||||||
|
if up > 0 {
|
||||||
|
hy["up_mbps"] = up
|
||||||
|
}
|
||||||
|
if recv_window_conn > 0 {
|
||||||
|
hy["recv_window_conn"] = recv_window_conn
|
||||||
|
}
|
||||||
|
if recv_window > 0 {
|
||||||
|
hy["recv_window"] = recv_window
|
||||||
|
}
|
||||||
|
return &hy, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hy2(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("sni"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("insecure")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
hy2 := map[string]interface{}{
|
||||||
|
"type": "hysteria2",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"password": u.User.Username(),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
down, _ := strconv.Atoi(query.Get("downmbps"))
|
||||||
|
up, _ := strconv.Atoi(query.Get("upmbps"))
|
||||||
|
obfs := query.Get("obfs")
|
||||||
|
if down > 0 {
|
||||||
|
hy2["down_mbps"] = down
|
||||||
|
}
|
||||||
|
if up > 0 {
|
||||||
|
hy2["up_mbps"] = up
|
||||||
|
}
|
||||||
|
if obfs == "salamander" {
|
||||||
|
hy2["obfs"] = map[string]interface{}{
|
||||||
|
"type": "salamander",
|
||||||
|
"password": query.Get("obfs-password"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &hy2, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuic(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tls := map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": query.Get("sni"),
|
||||||
|
}
|
||||||
|
alpn := query.Get("alpn")
|
||||||
|
insecure := query.Get("allow_insecure")
|
||||||
|
disable_sni := query.Get("disable_sni")
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(alpn, ",")
|
||||||
|
}
|
||||||
|
if insecure == "1" || insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if disable_sni == "1" || disable_sni == "true" {
|
||||||
|
tls["disable_sni"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
password, _ := u.User.Password()
|
||||||
|
tuic := map[string]interface{}{
|
||||||
|
"type": "tuic",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"uuid": u.User.Username(),
|
||||||
|
"password": password,
|
||||||
|
"congestion_control": query.Get("congestion_control"),
|
||||||
|
"udp_relay_mode": query.Get("udp_relay_mode"),
|
||||||
|
"tls": tls,
|
||||||
|
}
|
||||||
|
return &tuic, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ss(u *url.URL, i int) (*map[string]interface{}, string, error) {
|
||||||
|
query, _ := url.ParseQuery(u.RawQuery)
|
||||||
|
host, portStr, _ := net.SplitHostPort(u.Host)
|
||||||
|
port := 443
|
||||||
|
if len(portStr) > 0 {
|
||||||
|
port, _ = strconv.Atoi(portStr)
|
||||||
|
}
|
||||||
|
method := u.User.Username()
|
||||||
|
password, ok := u.User.Password()
|
||||||
|
if !ok {
|
||||||
|
decrypted := StrOrBase64Encoded(method)
|
||||||
|
decrypted_arr := strings.Split(decrypted, ":")
|
||||||
|
if len(decrypted_arr) > 1 {
|
||||||
|
method = decrypted_arr[0]
|
||||||
|
password = strings.Join(decrypted_arr[1:], ":")
|
||||||
|
} else {
|
||||||
|
return nil, "", common.NewError("Unsupported shadowsocks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := u.Fragment
|
||||||
|
if i > 0 {
|
||||||
|
tag = fmt.Sprintf("%d.%s", i, u.Fragment)
|
||||||
|
}
|
||||||
|
ss := map[string]interface{}{
|
||||||
|
"type": "shadowsocks",
|
||||||
|
"tag": tag,
|
||||||
|
"server": host,
|
||||||
|
"server_port": port,
|
||||||
|
"method": method,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
|
||||||
|
v2ray_type := query.Get("type")
|
||||||
|
if len(v2ray_type) > 0 {
|
||||||
|
pl_arr := []string{}
|
||||||
|
host_header := query.Get("host")
|
||||||
|
if query.Get("security") == "tls" {
|
||||||
|
pl_arr = append(pl_arr, "tls")
|
||||||
|
}
|
||||||
|
if v2ray_type == "quic" {
|
||||||
|
pl_arr = append(pl_arr, "mode=quic")
|
||||||
|
}
|
||||||
|
if len(host_header) > 0 {
|
||||||
|
pl_arr = append(pl_arr, "host="+host_header)
|
||||||
|
}
|
||||||
|
ss["plugin"] = "v2ray-plugin"
|
||||||
|
ss["plugin_opts"] = strings.Join(pl_arr, ";")
|
||||||
|
}
|
||||||
|
plugin := query.Get("plugin")
|
||||||
|
if len(plugin) > 0 {
|
||||||
|
pl_arr := strings.Split(plugin, ";")
|
||||||
|
if len(pl_arr) > 0 {
|
||||||
|
ss["plugin"] = pl_arr[0]
|
||||||
|
ss["plugin_opts"] = strings.Join(pl_arr[1:], ";")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ss, tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransport(tp_type string, q *url.Values) *map[string]interface{} {
|
||||||
|
transport := map[string]interface{}{}
|
||||||
|
tp_host := q.Get("host")
|
||||||
|
tp_path := q.Get("path")
|
||||||
|
switch strings.ToLower(tp_type) {
|
||||||
|
case "tcp", "":
|
||||||
|
if q.Get("headerType") == "http" {
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
}
|
||||||
|
case "http", "h2":
|
||||||
|
transport["type"] = "http"
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["host"] = strings.Split(tp_host, ",")
|
||||||
|
}
|
||||||
|
transport["path"] = tp_path
|
||||||
|
case "ws":
|
||||||
|
transport["type"] = "ws"
|
||||||
|
transport["path"] = tp_path
|
||||||
|
if len(tp_host) > 0 {
|
||||||
|
transport["headers"] = map[string]interface{}{
|
||||||
|
"Host": tp_host,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "quic":
|
||||||
|
transport["type"] = "quic"
|
||||||
|
case "grpc":
|
||||||
|
transport["type"] = "grpc"
|
||||||
|
transport["service_name"] = q.Get("serviceName")
|
||||||
|
case "httpupgrade":
|
||||||
|
transport["type"] = "httpupgrade"
|
||||||
|
transport["path"] = tp_path
|
||||||
|
transport["host"] = tp_host
|
||||||
|
}
|
||||||
|
return &transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTls(security string, q *url.Values) *map[string]interface{} {
|
||||||
|
tls := map[string]interface{}{}
|
||||||
|
tls_fp := q.Get("fp")
|
||||||
|
tls_sni := q.Get("sni")
|
||||||
|
tls_insecure := q.Get("allowInsecure")
|
||||||
|
tls_alpn := q.Get("alpn")
|
||||||
|
switch security {
|
||||||
|
case "tls":
|
||||||
|
tls["enabled"] = true
|
||||||
|
case "reality":
|
||||||
|
tls["enabled"] = true
|
||||||
|
tls["reality"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"public_key": q.Get("pbk"),
|
||||||
|
"short_id": q.Get("sid"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(tls_sni) > 0 {
|
||||||
|
tls["server_name"] = tls_sni
|
||||||
|
}
|
||||||
|
if len(tls_alpn) > 0 {
|
||||||
|
tls["alpn"] = strings.Split(tls_alpn, ",")
|
||||||
|
}
|
||||||
|
if tls_insecure == "1" || tls_insecure == "true" {
|
||||||
|
tls["insecure"] = true
|
||||||
|
}
|
||||||
|
if len(tls_fp) > 0 {
|
||||||
|
tls["utls"] = map[string]interface{}{
|
||||||
|
"enabled": true,
|
||||||
|
"fingerprint": tls_fp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &tls
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"s-ui/database/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fill Inbound's out_json
|
||||||
|
func FillOutJson(i *model.Inbound, hostname string) error {
|
||||||
|
var outJson map[string]interface{}
|
||||||
|
err := json.Unmarshal(i.OutJson, &outJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
addTls(&outJson, i.Tls)
|
||||||
|
} else {
|
||||||
|
delete(outJson, "tls")
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound, err := i.MarshalFull()
|
||||||
|
|
||||||
|
outJson["type"] = i.Type
|
||||||
|
outJson["tag"] = i.Tag
|
||||||
|
outJson["server"] = hostname
|
||||||
|
outJson["server_port"] = (*inbound)["listen_port"]
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case "http", "socks", "mixed":
|
||||||
|
case "shadowsocks":
|
||||||
|
shadowsocksOut(&outJson, *inbound)
|
||||||
|
return nil
|
||||||
|
case "shadowtls":
|
||||||
|
shadowTlsOut(&outJson, *inbound)
|
||||||
|
case "hysteria":
|
||||||
|
hysteriaOut(&outJson, *inbound)
|
||||||
|
case "hysteria2":
|
||||||
|
hysteria2Out(&outJson, *inbound)
|
||||||
|
case "tuic":
|
||||||
|
tuicOut(&outJson, *inbound)
|
||||||
|
case "vless":
|
||||||
|
vlessOut(&outJson, *inbound)
|
||||||
|
case "trojan":
|
||||||
|
trojanOut(&outJson, *inbound)
|
||||||
|
case "vmess":
|
||||||
|
vmessOut(&outJson, *inbound)
|
||||||
|
default:
|
||||||
|
for key := range outJson {
|
||||||
|
delete(outJson, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.OutJson, err = json.MarshalIndent(outJson, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTls function
|
||||||
|
func addTls(out *map[string]interface{}, tls *model.Tls) {
|
||||||
|
var tlsServer, tlsConfig map[string]interface{}
|
||||||
|
err := json.Unmarshal(tls.Server, &tlsServer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(tls.Client, &tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled, ok := tlsServer["enabled"]; ok {
|
||||||
|
tlsConfig["enabled"] = enabled
|
||||||
|
}
|
||||||
|
if serverName, ok := tlsServer["server_name"]; ok {
|
||||||
|
tlsConfig["server_name"] = serverName
|
||||||
|
}
|
||||||
|
if alpn, ok := tlsServer["alpn"]; ok {
|
||||||
|
tlsConfig["alpn"] = alpn
|
||||||
|
}
|
||||||
|
if minVersion, ok := tlsServer["min_version"]; ok {
|
||||||
|
tlsConfig["min_version"] = minVersion
|
||||||
|
}
|
||||||
|
if maxVersion, ok := tlsServer["max_version"]; ok {
|
||||||
|
tlsConfig["max_version"] = maxVersion
|
||||||
|
}
|
||||||
|
if cipherSuites, ok := tlsServer["cipher_suites"]; ok {
|
||||||
|
tlsConfig["cipher_suites"] = cipherSuites
|
||||||
|
}
|
||||||
|
if reality, ok := tlsServer["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
realityConfig := tlsConfig["reality"].(map[string]interface{})
|
||||||
|
realityConfig["enabled"] = true
|
||||||
|
if shortIDs, ok := reality["short_id"].([]interface{}); ok && len(shortIDs) > 0 {
|
||||||
|
realityConfig["short_id"] = shortIDs[rand.Intn(len(shortIDs))]
|
||||||
|
}
|
||||||
|
tlsConfig["reality"] = realityConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
(*out)["tls"] = tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol-specific functions
|
||||||
|
func shadowsocksOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if method, ok := inbound["method"].(string); ok {
|
||||||
|
(*out)["method"] = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shadowTlsOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if version, ok := inbound["version"].(float64); ok && int(version) == 3 {
|
||||||
|
(*out)["version"] = 3
|
||||||
|
} else {
|
||||||
|
for key := range *out {
|
||||||
|
delete(*out, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*out)["tls"] = map[string]interface{}{"enabled": true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteriaOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if upMbps, ok := inbound["down_mbps"]; ok {
|
||||||
|
(*out)["up_mbps"] = upMbps
|
||||||
|
}
|
||||||
|
if downMbps, ok := inbound["up_mbps"]; ok {
|
||||||
|
(*out)["down_mbps"] = downMbps
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"]; ok {
|
||||||
|
(*out)["obfs"] = obfs
|
||||||
|
}
|
||||||
|
if recvWindow, ok := inbound["recv_window_conn"]; ok {
|
||||||
|
(*out)["recv_window_conn"] = recvWindow
|
||||||
|
}
|
||||||
|
if disableMTU, ok := inbound["disable_mtu_discovery"]; ok {
|
||||||
|
(*out)["disable_mtu_discovery"] = disableMTU
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteria2Out(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if upMbps, ok := inbound["down_mbps"]; ok {
|
||||||
|
(*out)["up_mbps"] = upMbps
|
||||||
|
}
|
||||||
|
if downMbps, ok := inbound["up_mbps"]; ok {
|
||||||
|
(*out)["down_mbps"] = downMbps
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"]; ok {
|
||||||
|
(*out)["obfs"] = obfs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuicOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if congestionControl, ok := inbound["congestion_control"].(string); ok {
|
||||||
|
(*out)["congestion_control"] = congestionControl
|
||||||
|
} else {
|
||||||
|
(*out)["congestion_control"] = "cubic"
|
||||||
|
}
|
||||||
|
if zeroRTT, ok := inbound["zero_rtt_handshake"].(bool); ok {
|
||||||
|
(*out)["zero_rtt_handshake"] = zeroRTT
|
||||||
|
}
|
||||||
|
if heartbeat, ok := inbound["heartbeat"]; ok {
|
||||||
|
(*out)["heartbeat"] = heartbeat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vlessOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojanOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmessOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-11
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"embed"
|
"embed"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
@@ -53,6 +54,19 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
// Load the HTML template
|
||||||
|
t := template.New("").Funcs(engine.FuncMap)
|
||||||
|
template, err := t.ParseFS(content, "html/index.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
engine.SetHTMLTemplate(template)
|
||||||
|
|
||||||
|
base_url, err := s.settingService.GetWebPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
webDomain, err := s.settingService.GetWebDomain()
|
webDomain, err := s.settingService.GetWebDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -68,10 +82,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine.Use(gzip.Gzip(gzip.DefaultCompression))
|
engine.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
assetsBasePath := "/assets/"
|
assetsBasePath := base_url + "assets/"
|
||||||
|
|
||||||
store := cookie.NewStore(secret)
|
store := cookie.NewStore(secret)
|
||||||
engine.Use(sessions.Sessions("session", store))
|
engine.Use(sessions.Sessions("s-ui", store))
|
||||||
|
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
uri := c.Request.RequestURI
|
uri := c.Request.RequestURI
|
||||||
if strings.HasPrefix(uri, assetsBasePath) {
|
if strings.HasPrefix(uri, assetsBasePath) {
|
||||||
@@ -87,26 +102,29 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine.StaticFS(assetsBasePath, http.FS(assetsFS))
|
engine.StaticFS(assetsBasePath, http.FS(assetsFS))
|
||||||
|
|
||||||
group_api := engine.Group("/api")
|
group_api := engine.Group(base_url + "api")
|
||||||
api.NewAPIHandler(group_api)
|
api.NewAPIHandler(group_api)
|
||||||
|
|
||||||
// Serve index.html as the entry point
|
// Serve index.html as the entry point
|
||||||
// Handle all other routes by serving index.html
|
// Handle all other routes by serving index.html
|
||||||
engine.NoRoute(func(c *gin.Context) {
|
engine.NoRoute(func(c *gin.Context) {
|
||||||
if c.Request.URL.Path != "/login" && !api.IsLogin(c) {
|
if c.Request.URL.Path == strings.TrimSuffix(base_url, "/") {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
c.Redirect(http.StatusTemporaryRedirect, base_url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.URL.Path == "/login" && api.IsLogin(c) {
|
if !strings.HasPrefix(c.Request.URL.Path, base_url) {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/")
|
c.String(404, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := content.ReadFile("html/index.html")
|
if c.Request.URL.Path != base_url+"login" && !api.IsLogin(c) {
|
||||||
if err != nil {
|
c.Redirect(http.StatusTemporaryRedirect, base_url+"login")
|
||||||
c.String(http.StatusInternalServerError, "Internal Server Error")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data(http.StatusOK, "text/html", data)
|
if c.Request.URL.Path == base_url+"login" && api.IsLogin(c) {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, base_url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "index.html", gin.H{"BASE_URL": base_url})
|
||||||
})
|
})
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
cd frontend
|
cd frontend
|
||||||
|
npm i
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
cd ..
|
cd ..
|
||||||
cd backend
|
cd backend
|
||||||
echo "Backend"
|
echo "Backend"
|
||||||
|
|
||||||
|
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 -o ../sui main.go
|
go build -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o ../sui main.go
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
services:
|
||||||
|
s-ui:
|
||||||
|
image: alireza7/s-ui
|
||||||
|
container_name: s-ui
|
||||||
|
hostname: "s-ui"
|
||||||
|
volumes:
|
||||||
|
- "./db:/app/db"
|
||||||
|
- "./cert:/app/cert"
|
||||||
|
tty: true
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "2095:2095"
|
||||||
|
- "2096:2096"
|
||||||
|
networks:
|
||||||
|
- s-ui
|
||||||
|
entrypoint: "./entrypoint.sh"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
s-ui:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
./sui migrate
|
||||||
|
./sui
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="assets/favicon.ico" />
|
<link rel="icon" href="assets/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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>
|
<title>S-UI</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
Generated
+1387
-2794
File diff suppressed because it is too large
Load Diff
+24
-24
@@ -1,42 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "1.2.0-beta.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint . --fix --ignore-path .gitignore"
|
"lint": "eslint . --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "7.0.96",
|
"@mdi/font": "7.4.47",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.7.4",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.3",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"core-js": "^3.29.0",
|
"core-js": "^3.37.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"notivue": "^2.4.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "^0.10.0",
|
||||||
"vue": "^3.2.0",
|
"vue": "^3.4.31",
|
||||||
"vue-chartjs": "^5.3.0",
|
"vue-chartjs": "^5.3.1",
|
||||||
"vue-i18n": "^9.8.0",
|
"vue-i18n": "^9.14.2",
|
||||||
"vue-router": "^4.0.0",
|
"vue-router": "^4.4.0",
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
"vue3-persian-datetime-picker": "^1.2.2",
|
||||||
"vuetify": "^3.0.0"
|
"vuetify": "^3.6.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.21.4",
|
"@babel/types": "^7.24.7",
|
||||||
"@types/node": "^18.15.0",
|
"@types/node": "^20.14.9",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^5.0.5",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"eslint-plugin-vue": "^9.26.0",
|
||||||
"eslint": "^8.22.0",
|
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
|
||||||
"material-design-icons-iconfont": "^6.7.0",
|
"material-design-icons-iconfont": "^6.7.0",
|
||||||
"sass": "^1.60.0",
|
"sass": "1.77.6",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.5.2",
|
||||||
"unplugin-fonts": "^1.0.3",
|
"unplugin-fonts": "^1.1.1",
|
||||||
"vite": "^4.2.0",
|
"vite": "^5.4.6",
|
||||||
"vite-plugin-vuetify": "^1.0.0",
|
"vite-plugin-vuetify": "^2.0.3",
|
||||||
"vue-tsc": "^1.2.0"
|
"vue-tsc": "^2.0.22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<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>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
v-model="Input"
|
v-model="Input"
|
||||||
@input="Input=$event"
|
@input="Input=$event"
|
||||||
:locale="$i18n.locale"
|
:locale="locale"
|
||||||
element="expiry"
|
element="expiry"
|
||||||
compact-time
|
compact-time
|
||||||
type="datetime">
|
type="datetime">
|
||||||
@@ -42,6 +42,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DatePicker from 'vue3-persian-datetime-picker'
|
import DatePicker from 'vue3-persian-datetime-picker'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
import 'moment/locale/vi'
|
||||||
|
import 'moment/locale/zh-cn'
|
||||||
|
import 'moment/locale/zh-tw'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['expiry'],
|
props: ['expiry'],
|
||||||
@@ -54,10 +57,21 @@ export default {
|
|||||||
},
|
},
|
||||||
components: { DatePicker },
|
components: { DatePicker },
|
||||||
computed: {
|
computed: {
|
||||||
|
locale() {
|
||||||
|
const l = i18n.global.locale.value
|
||||||
|
switch (l) {
|
||||||
|
case "zhHans":
|
||||||
|
return "zh-cn"
|
||||||
|
case "zhHant":
|
||||||
|
return "zh-tw"
|
||||||
|
default:
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
},
|
||||||
dateFormatted() {
|
dateFormatted() {
|
||||||
if (this.expDate == 0) return i18n.global.t('unlimited')
|
if (this.expDate == 0) return i18n.global.t('unlimited')
|
||||||
const date = new Date(this.expDate*1000)
|
const date = new Date(this.expDate*1000)
|
||||||
return date.toLocaleString(i18n.global.locale.value)
|
return date.toLocaleString(this.locale)
|
||||||
},
|
},
|
||||||
expDate() {
|
expDate() {
|
||||||
return parseInt(this.expiry?? 0)
|
return parseInt(this.expiry?? 0)
|
||||||
@@ -99,18 +113,18 @@ export default {
|
|||||||
<style>
|
<style>
|
||||||
.vpd-addon-list,
|
.vpd-addon-list,
|
||||||
.vpd-addon-list-item {
|
.vpd-addon-list-item {
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: rgb(var(--v-theme-background)) !important;
|
||||||
border-color: rgb(var(--v-theme-background));
|
border-color: rgb(var(--v-theme-background)) !important;
|
||||||
}
|
}
|
||||||
.vpd-content {
|
.vpd-content {
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: rgb(var(--v-theme-background)) !important;
|
||||||
}
|
}
|
||||||
.vpd-addon-list-item.vpd-selected,
|
.vpd-addon-list-item.vpd-selected,
|
||||||
.vpd-addon-list-item:hover {
|
.vpd-addon-list-item:hover {
|
||||||
background-color: rgb(var(--v-theme-primary));
|
background-color: rgb(var(--v-theme-primary)) !important;
|
||||||
}
|
}
|
||||||
.vpd-close-addon {
|
.vpd-close-addon {
|
||||||
color: rgb(var(--v-theme-on-surface));
|
color: rgb(var(--v-theme-on-surface)) !important;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.vpd-controls {
|
.vpd-controls {
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Dial" style="background-color: inherit;">
|
<v-card :subtitle="$t('objects.dial')" style="background-color: inherit;">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||||
<v-text-field
|
<v-select
|
||||||
label="Forward to Outbound tag"
|
hide-details
|
||||||
hide-details
|
:label="$t('dial.detourText')"
|
||||||
v-model="dial.detour"></v-text-field>
|
:items="outTags"
|
||||||
|
v-model="dial.detour">
|
||||||
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionBind">
|
<v-col cols="12" sm="6" md="4" v-if="optionBind">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to Network Interface"
|
:label="$t('dial.bindIf')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.bind_interface"></v-text-field>
|
v-model="dial.bind_interface"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -17,13 +19,13 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to IPv4"
|
:label="$t('dial.bindIp4')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.inet4_bind_address"></v-text-field>
|
v-model="dial.inet4_bind_address"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to IPv6"
|
:label="$t('dial.bindIp6')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.inet6_bind_address"></v-text-field>
|
v-model="dial.inet6_bind_address"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
v-model.number="routingMark"></v-text-field>
|
v-model.number="routingMark"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
||||||
<v-switch v-model="dial.reuse_addr" color="primary" label="Reuse listener address" hide-details></v-switch>
|
<v-switch v-model="dial.reuse_addr" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionTCP">
|
<v-row v-if="optionTCP">
|
||||||
@@ -55,11 +57,11 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Connection Timeout"
|
:label="$t('dial.connTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
suffix="s"
|
:suffix="$t('date.s')"
|
||||||
v-model.number="connectTimeout"></v-text-field>
|
v-model.number="connectTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -67,22 +69,19 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
clearable
|
:label="$t('listen.domainStrategy')"
|
||||||
@click:clear="delete dial.domain_strategy"
|
|
||||||
width="100"
|
|
||||||
label="Domain to IP Strategy"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||||
v-model="dial.domain_strategy">
|
v-model="dial.domain_strategy">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Fallback Timeout"
|
:label="$t('dial.fbTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="50"
|
min="50"
|
||||||
step="50"
|
step="50"
|
||||||
suffix="ms"
|
:suffix="$t('date.ms')"
|
||||||
v-model.number="fallbackDelay"></v-text-field>
|
v-model.number="fallbackDelay"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -90,39 +89,39 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Dial Options</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('dial.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDetour" color="primary" label="Detour" hide-details></v-switch>
|
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionBind" color="primary" label="Bind Interface" hide-details></v-switch>
|
<v-switch v-model="optionBind" color="primary" :label="$t('dial.bindIf')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionIPV4" color="primary" label="Bind to IPv4" hide-details></v-switch>
|
<v-switch v-model="optionIPV4" color="primary" :label="$t('dial.bindIp4')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionIPV6" color="primary" label="Bind to IPv6" hide-details></v-switch>
|
<v-switch v-model="optionIPV6" color="primary" :label="$t('dial.bindIp6')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
<v-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionRA" color="primary" label="Reuse Address" hide-details></v-switch>
|
<v-switch v-model="optionRA" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionTCP" color="primary" label="TCP Options" hide-details></v-switch>
|
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionUDP" color="primary" label="UDP Options" hide-details></v-switch>
|
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionCT" color="primary" label="Connection Timeout" hide-details></v-switch>
|
<v-switch v-model="optionCT" color="primary" :label="$t('dial.connTimeout')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDS" color="primary" label="Domain Strategy" hide-details></v-switch>
|
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -133,7 +132,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['dial'],
|
props: ['dial', 'outTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false
|
menu: false
|
||||||
@@ -154,7 +153,7 @@ export default {
|
|||||||
},
|
},
|
||||||
optionDetour: {
|
optionDetour: {
|
||||||
get(): boolean { return this.$props.dial.detour != undefined },
|
get(): boolean { return this.$props.dial.detour != undefined },
|
||||||
set(v:boolean) { v ? this.$props.dial.detour = '' : delete this.$props.dial.detour }
|
set(v:boolean) { v ? this.$props.dial.detour = this.outTags[0]?? '' : delete this.$props.dial.detour }
|
||||||
},
|
},
|
||||||
optionBind: {
|
optionBind: {
|
||||||
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<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,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('in.multiplex')">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" label="Enable Multiplex" v-model="muxEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
|
||||||
<v-switch color="primary" label="Reject Non-Padded" v-model="mux.padding" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
|
||||||
<v-switch color="primary" label="Enable Brutal" v-model="burtalEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="mux.brutal?.enabled">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Uplink Bandwidth"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
suffix="Mbps"
|
|
||||||
v-model.number="up_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Downlink Bandwidth"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
suffix="Mbps"
|
|
||||||
min="0"
|
|
||||||
v-model.number="down_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { iMultiplex } from '@/types/inMultiplex'
|
|
||||||
export default {
|
|
||||||
props: ['inbound'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mux(): iMultiplex {
|
|
||||||
return <iMultiplex> this.$props.inbound.multiplex
|
|
||||||
},
|
|
||||||
muxEnable: {
|
|
||||||
get(): boolean { return this.$props.inbound.multiplex ? this.mux.enabled : false },
|
|
||||||
set(newValue:boolean) { this.$props.inbound.multiplex = newValue ? { enabled: newValue } : {} }
|
|
||||||
},
|
|
||||||
burtalEnable: {
|
|
||||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
|
||||||
set(newValue:boolean) { this.mux.brutal = { enabled: newValue, up_mbps: 100, down_mbps: 100 } }
|
|
||||||
},
|
|
||||||
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,214 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('in.tls')">
|
|
||||||
<v-row v-if="tlsOptional">
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<template v-if="tls.enabled">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-btn-toggle v-model="usePath"
|
|
||||||
class="rounded-xl"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
shaped
|
|
||||||
mandatory>
|
|
||||||
<v-btn
|
|
||||||
@click="tls.key=undefined; tls.certificate=undefined"
|
|
||||||
>{{ $t('tls.usePath') }}</v-btn>
|
|
||||||
<v-btn
|
|
||||||
@click="tls.key_path=undefined; tls.certificate_path=undefined"
|
|
||||||
>{{ $t('tls.useText') }}</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="usePath == 0">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('tls.certPath')"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.certificate_path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('tls.keyPath')"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.key_path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('tls.cert')"
|
|
||||||
hide-details
|
|
||||||
v-model="certText">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('tls.key')"
|
|
||||||
hide-details
|
|
||||||
v-model="keyText">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.server_name != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="SNI"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.server_name">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.alpn">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="ALPN"
|
|
||||||
multiple
|
|
||||||
:items="alpn"
|
|
||||||
v-model="tls.alpn">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="Minimum Version"
|
|
||||||
:items="tlsVersions"
|
|
||||||
v-model="tls.min_version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="Maximum Version"
|
|
||||||
:items="tlsVersions"
|
|
||||||
v-model="tls.max_version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="Cipher Suites"
|
|
||||||
multiple
|
|
||||||
:items="cipher_suites"
|
|
||||||
v-model="tls.cipher_suites">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details>TLS Options</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMinV" color="primary" label="Min Version" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMaxV" color="primary" label="Max Version" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionCS" color="primary" label="Cipher Suites" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { iTls, defaultInTls } from '@/types/inTls'
|
|
||||||
export default {
|
|
||||||
props: ['inbound'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
usePath: 0,
|
|
||||||
defaults: defaultInTls,
|
|
||||||
alpn: [
|
|
||||||
{ title: "H3", value: 'HTTP/3' },
|
|
||||||
{ title: "H2", value: 'HTTP/2' },
|
|
||||||
{ title: "Http1.1", value: 'HTTP/1.1' },
|
|
||||||
],
|
|
||||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
|
||||||
cipher_suites: [
|
|
||||||
{ title: "Automatic", value: "" },
|
|
||||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "RSA-AES256-CBC-SHA", value: "TLS_RSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "RSA-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "RSA-AES256-GCM-SHA384", value: "TLS_RSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "AES128-GCM-SHA256", value: "TLS_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "AES256-GCM-SHA384", value: "TLS_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "CHACHA20-POLY1305-SHA256", value: "TLS_CHACHA20_POLY1305_SHA256" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES128-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES256-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-RSA-AES128-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-RSA-AES256-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES128-GCM-SHA256", value: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES256-GCM-SHA384", value: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "ECDHE-RSA-AES128-GCM-SHA256", value: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "ECDHE-RSA-AES256-GCM-SHA384", value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
|
|
||||||
{ title: "ECDHE-RSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
tls(): iTls {
|
|
||||||
return <iTls> this.$props.inbound.tls
|
|
||||||
},
|
|
||||||
tlsEnable: {
|
|
||||||
get() { return Object.hasOwn(this.$props.inbound.tls, 'enabled') ? this.tls.enabled : false },
|
|
||||||
set(newValue: boolean) { this.$props.inbound.tls = newValue ? { enabled: true } : {} }
|
|
||||||
},
|
|
||||||
tlsOptional(): boolean {
|
|
||||||
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
|
||||||
},
|
|
||||||
certText: {
|
|
||||||
get(): string { return this.tls.certificate ? this.tls.certificate.join('\n') : '' },
|
|
||||||
set(newValue:string) { this.tls.certificate = newValue.split('\n') }
|
|
||||||
},
|
|
||||||
keyText: {
|
|
||||||
get(): string { return this.tls.key ? this.tls.key.join('\n') : '' },
|
|
||||||
set(newValue:string) { this.tls.key = newValue.split('\n') }
|
|
||||||
},
|
|
||||||
optionSNI: {
|
|
||||||
get(): boolean { return this.tls.server_name != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.tls.server_name = v ? '' : undefined }
|
|
||||||
},
|
|
||||||
optionALPN: {
|
|
||||||
get(): boolean { return this.tls.alpn != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.tls.alpn = v ? defaultInTls.alpn : undefined }
|
|
||||||
},
|
|
||||||
optionMinV: {
|
|
||||||
get(): boolean { return this.tls.min_version != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.tls.min_version = v ? defaultInTls.min_version : undefined }
|
|
||||||
},
|
|
||||||
optionMaxV: {
|
|
||||||
get(): boolean { return this.tls.max_version != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.tls.max_version = v ? defaultInTls.max_version : undefined }
|
|
||||||
},
|
|
||||||
optionCS: {
|
|
||||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Listen">
|
<v-card :subtitle="$t('objects.listen')">
|
||||||
<v-row>
|
<v-row v-if="inbound.type != 'tun'">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('in.addr')"
|
:label="$t('in.addr')"
|
||||||
@@ -14,34 +14,20 @@
|
|||||||
:label="$t('in.port')"
|
:label="$t('in.port')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
required
|
required
|
||||||
v-model.number="inbound.listen_port"></v-text-field>
|
v-model.number="inbound.listen_port"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||||
<v-text-field
|
<v-select
|
||||||
label="Forward to Inbound tag"
|
:label="$t('listen.detourText')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.detour"></v-text-field>
|
:items="inTags"
|
||||||
</v-col>
|
v-model="inbound.detour">
|
||||||
<v-col cols="12" sm="6" md="4">
|
</v-select>
|
||||||
<v-switch v-model="inbound.sniff" color="primary" :label="$t('in.sniffing')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="inbound.sniff">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.sniff_override_destination" color="primary" label="Override Sniffed Domain" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Sniffing Timeout"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="50"
|
|
||||||
step="50"
|
|
||||||
suffix="ms"
|
|
||||||
v-model.number="sniffTimeout"></v-text-field>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionTCP">
|
<v-row v-if="optionTCP">
|
||||||
@@ -62,40 +48,26 @@
|
|||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
suffix="Min"
|
:suffix="$t('date.m')"
|
||||||
v-model.number="udpTimeout"></v-text-field>
|
v-model.number="udpTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionDS">
|
<v-card-actions class="pt-0" v-if="inbound.type != 'tun'">
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
width="100"
|
|
||||||
label="Domain to IP Strategy"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
|
||||||
v-model="inbound.domain_strategy">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions class="pt-0">
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Listen Options</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('listen.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionTCP" color="primary" label="TCP Options" hide-details></v-switch>
|
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionUDP" color="primary" label="UDP Options" hide-details></v-switch>
|
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDetour" color="primary" label="Detour" hide-details></v-switch>
|
<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="optionDS" color="primary" label="Domain Strategy" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -106,7 +78,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['inbound', 'inTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false
|
menu: false
|
||||||
@@ -117,10 +89,6 @@ export default {
|
|||||||
get() { return this.$props.inbound.udp_timeout ? parseInt(this.$props.inbound.udp_timeout.replace('m','')) : 5 },
|
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' }
|
set(newValue:number) { this.$props.inbound.udp_timeout = newValue > 0 ? newValue + 'm' : '5m' }
|
||||||
},
|
},
|
||||||
sniffTimeout: {
|
|
||||||
get() { return this.$props.inbound.sniff_timeout ? parseInt(this.$props.inbound.sniff_timeout.replace('ms','')) : 300 },
|
|
||||||
set(newValue:number) { this.$props.inbound.sniff_timeout = newValue > 0 ? newValue + 'ms' : '300ms' }
|
|
||||||
},
|
|
||||||
optionTCP: {
|
optionTCP: {
|
||||||
get(): boolean {
|
get(): boolean {
|
||||||
return this.$props.inbound.tcp_fast_open != undefined &&
|
return this.$props.inbound.tcp_fast_open != undefined &&
|
||||||
@@ -138,16 +106,12 @@ export default {
|
|||||||
},
|
},
|
||||||
set(v:boolean) {
|
set(v:boolean) {
|
||||||
this.$props.inbound.udp_fragment = v ? false : undefined
|
this.$props.inbound.udp_fragment = v ? false : undefined
|
||||||
this.$props.inbound.udp_timeout = v ? false : undefined
|
this.$props.inbound.udp_timeout = v ? '5m' : undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optionDetour: {
|
optionDetour: {
|
||||||
get(): boolean { return this.$props.inbound.detour != undefined },
|
get(): boolean { return this.$props.inbound.detour != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.detour = v ? '' : undefined }
|
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
||||||
},
|
|
||||||
optionDS: {
|
|
||||||
get(): boolean { return this.$props.inbound.domain_strategy != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.domain_strategy = v ? 'prefer_ipv4' : undefined }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container class="fill-height">
|
<LogVue
|
||||||
|
v-model="logModal.visible"
|
||||||
|
:visible="logModal.visible"
|
||||||
|
@close="closeLogs"
|
||||||
|
/>
|
||||||
|
<v-container class="fill-height" :loading="loading">
|
||||||
<v-responsive :class="reloadItems.length>0 ? 'fill-height text-center' : 'align-center'" >
|
<v-responsive :class="reloadItems.length>0 ? 'fill-height text-center' : 'align-center'" >
|
||||||
<v-row class="d-flex align-center justify-center">
|
<v-row class="d-flex align-center justify-center">
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
@@ -10,7 +15,7 @@
|
|||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-dialog v-model="menu" :close-on-content-click="false" transition="scale-transition" max-width="800">
|
<v-dialog v-model="menu" :close-on-content-click="false" transition="scale-transition" max-width="800">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" variant="tonal">{{ $t('main.tiles') }} <v-icon icon="mdi-star-plus" /></v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('main.tiles') }} <v-icon icon="mdi-star-plus" /></v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card rounded="xl">
|
<v-card rounded="xl">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
@@ -45,9 +50,9 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
||||||
<v-card class="rounded-lg" variant="outlined" height="200px"
|
<v-card class="rounded-lg" variant="outlined" height="210px"
|
||||||
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
||||||
<v-card-text style="padding: 0 16px;">
|
<v-card-text style="padding: 0 16px;" align="center" justify="center">
|
||||||
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
||||||
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
|
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
|
||||||
<template v-if="i == 'i-sys'">
|
<template v-if="i == 'i-sys'">
|
||||||
@@ -80,13 +85,19 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3">S-UI</v-col>
|
<v-col cols="3">S-UI</v-col>
|
||||||
<v-col cols="9">
|
<v-col cols="9">
|
||||||
<v-chip density="compact" color="primary" variant="flat">
|
<v-chip density="compact" color="blue">
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ $t('main.info.threads') }}: {{ tilesData.sys?.appThreads }}<br />
|
{{ $t('main.info.threads') }}: {{ tilesData.sys?.appThreads }}<br />
|
||||||
{{ $t('main.info.memory') }}: {{ HumanReadable.sizeFormat(tilesData.sys?.appMem) }}
|
{{ $t('main.info.memory') }}: {{ HumanReadable.sizeFormat(tilesData.sys?.appMem) }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
v{{ tilesData.sys?.appVersion }}
|
v{{ tilesData.sys?.appVersion }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
|
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs()">
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('basic.log.title') + " - S-UI" }}
|
||||||
|
</v-tooltip>
|
||||||
|
<v-icon icon="mdi-list-box-outline" color="blue" />
|
||||||
|
</v-chip>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="3">{{ $t('main.info.uptime') }}</v-col>
|
<v-col cols="3">{{ $t('main.info.uptime') }}</v-col>
|
||||||
<v-col cols="9">{{ HumanReadable.formatSecond(tilesData.uptime) }}</v-col>
|
<v-col cols="9">{{ HumanReadable.formatSecond(tilesData.uptime) }}</v-col>
|
||||||
@@ -98,6 +109,12 @@
|
|||||||
<v-col cols="8">
|
<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="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="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>
|
||||||
<v-col cols="4">{{ $t('main.info.memory') }}</v-col>
|
<v-col cols="4">{{ $t('main.info.memory') }}</v-col>
|
||||||
<v-col cols="8">
|
<v-col cols="8">
|
||||||
@@ -148,7 +165,9 @@ import Gauge from '@/components/tiles/Gauge.vue'
|
|||||||
import History from '@/components/tiles/History.vue'
|
import History from '@/components/tiles/History.vue'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
import LogVue from '@/layouts/modals/Logs.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
const menu = ref(false)
|
const menu = ref(false)
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ title: i18n.global.t('main.gauges'), value: [
|
{ title: i18n.global.t('main.gauges'), value: [
|
||||||
@@ -184,7 +203,7 @@ const reloadItems = computed({
|
|||||||
|
|
||||||
const reloadData = async () => {
|
const reloadData = async () => {
|
||||||
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
|
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
|
||||||
const data = await HttpUtils.get('/api/status',{ r: request.join(',')})
|
const data = await HttpUtils.get('api/status',{ r: request.join(',')})
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
tilesData.value = data.obj
|
tilesData.value = data.obj
|
||||||
}
|
}
|
||||||
@@ -215,4 +234,22 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const logModal = ref({
|
||||||
|
visible: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const openLogs = () => {
|
||||||
|
logModal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeLogs = () => {
|
||||||
|
logModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const restartSingbox = async () => {
|
||||||
|
loading.value = true
|
||||||
|
await HttpUtils.post('api/restartSb',{})
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
<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>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
networks: [
|
networks: [
|
||||||
@@ -21,8 +21,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Network: {
|
Network: {
|
||||||
get():string { return this.$props.inbound.network?? '' },
|
get():string { return this.$props.data.network?? '' },
|
||||||
set(v:string) { this.$props.inbound.network = v != '' ? v : undefined }
|
set(v:string) { this.$props.data.network = v != '' ? v : undefined }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
<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,
|
||||||
|
"sniff": true,
|
||||||
|
"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,
|
||||||
|
"sniff": true,
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "rcode://success",
|
||||||
|
"tag": "block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"source_ip_cidr": [
|
||||||
|
"172.19.0.0/30"
|
||||||
|
],
|
||||||
|
"server": "proxy-dns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source_ip_cidr": [
|
||||||
|
"172.19.0.0/30"
|
||||||
|
],
|
||||||
|
"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", outbound: "dns-out" })
|
||||||
|
} else {
|
||||||
|
delete this.subJsonExt.dns
|
||||||
|
const ruleDnsIndex = this.subJsonExt?.rules?.findIndex((r:any) => r.protocol == "dns" && r.outbound == "dns-out")
|
||||||
|
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", 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, 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, outbound: "direct" })
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ruleIndex != -1) this.rules.splice(ruleIndex,1)
|
||||||
|
}
|
||||||
|
this.updateRuleSets()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ruleToBlock: {
|
||||||
|
get() :string[] {
|
||||||
|
const ruleIndex = this.rules?.findIndex((r:any) => r.outbound == "block" && 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 == "block" && 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, outbound: "block" })
|
||||||
|
}
|
||||||
|
} 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,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('in.transport')">
|
<v-card :subtitle="$t('objects.transport')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
width="100"
|
|
||||||
:label="$t('type')"
|
:label="$t('type')"
|
||||||
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
||||||
v-model="transportType">
|
v-model="transportType">
|
||||||
@@ -28,7 +27,7 @@ import WebSocket from './transports/WebSocket.vue'
|
|||||||
import GRPC from './transports/gRPC.vue'
|
import GRPC from './transports/gRPC.vue'
|
||||||
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
trspTypes: TrspTypes
|
trspTypes: TrspTypes
|
||||||
@@ -36,15 +35,15 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Transport() {
|
Transport() {
|
||||||
return <Transport>this.$props.inbound.transport
|
return <Transport>this.$props.data.transport
|
||||||
},
|
},
|
||||||
tpEnable: {
|
tpEnable: {
|
||||||
get() { return Object.hasOwn(this.$props.inbound.transport, 'type') },
|
get() { return Object.hasOwn(this.$props.data.transport, 'type') },
|
||||||
set(newValue: boolean) { this.$props.inbound.transport = newValue ? { type: 'http' } : {} }
|
set(newValue: boolean) { this.$props.data.transport = newValue ? { type: 'http' } : {} }
|
||||||
},
|
},
|
||||||
transportType: {
|
transportType: {
|
||||||
get() { return this.Transport.type },
|
get() { return this.Transport.type },
|
||||||
set(newValue: string) { this.$props.inbound.transport = { type: newValue } }
|
set(newValue: string) { this.$props.data.transport = { type: newValue } }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<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,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Clients">
|
<v-card :subtitle="$t('pages.clients')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch
|
<v-switch
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound', 'id'],
|
props: ['inbound'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hasUser: false,
|
hasUser: false,
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<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="port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="KeepAlive"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
hide-details
|
||||||
|
v-model="data.persistent_keepalive_interval">
|
||||||
|
</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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,18 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-snackbar
|
<Notivue v-slot="item">
|
||||||
v-model="sb.showMsg"
|
<NotivueSwipe :item="item">
|
||||||
location="top"
|
<Notification
|
||||||
:color="snackbar.color"
|
:item="item"
|
||||||
:timeout="snackbar.timeout">
|
:theme="theme"
|
||||||
{{ snackbar.message }}
|
:dir="direction"
|
||||||
</v-snackbar>
|
:icons="outlinedIcons"
|
||||||
|
:hideClose="true"
|
||||||
|
@click="item.clear"
|
||||||
|
/>
|
||||||
|
</NotivueSwipe>
|
||||||
|
</Notivue>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { Notivue, Notification, NotivueSwipe, outlinedIcons, pastelTheme, darkTheme } from 'notivue'
|
||||||
import Message from '@/store/modules/message'
|
import { computed } from 'vue'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import vuetify from '@/plugins/vuetify';
|
||||||
|
|
||||||
const sb = Message()
|
const Theme = useTheme()
|
||||||
|
|
||||||
const snackbar = ref(sb.snackbar)
|
const theme = computed(() =>{
|
||||||
|
return Theme.global.name.value == "light" ? pastelTheme : darkTheme
|
||||||
|
})
|
||||||
|
|
||||||
|
const direction = computed(() => {
|
||||||
|
return vuetify.locale.isRtl ? 'rtl' : 'ltr'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--nv-z: 10020;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -2,22 +2,22 @@
|
|||||||
<v-card subtitle="Direct">
|
<v-card subtitle="Direct">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Override Address"
|
:label="$t('types.direct.overrideAddr')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.override_address">
|
v-model="data.override_address">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Override Port"
|
:label="$t('types.direct.overridePort')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="override_port">
|
v-model.number="override_port">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -28,16 +28,16 @@
|
|||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {}
|
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); }
|
||||||
},
|
},
|
||||||
computed: {
|
},
|
||||||
override_port: {
|
components: { Network }
|
||||||
get() { return this.$props.inbound.override_port ? this.$props.inbound.override_port : ''; },
|
|
||||||
set(newValue: any) { this.$props.inbound.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { Network }
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<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>
|
||||||
@@ -3,19 +3,19 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Uplink Limit"
|
:label="$t('stats.upload')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
v-model.number="up_mbps">
|
v-model.number="up_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Downlink Limit"
|
:label="$t('stats.download')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
min="0"
|
min="0"
|
||||||
v-model.number="down_mbps">
|
v-model.number="down_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
@@ -24,40 +24,136 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="obfs Password"
|
:label="$t('types.hy.obfs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.obfs">
|
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-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</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>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction','data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
menu: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
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: {
|
down_mbps: {
|
||||||
get() { return this.$props.inbound.down_mbps ? this.$props.inbound.down_mbps : 0 },
|
get() { return this.$props.data.down_mbps ? this.$props.data.down_mbps : 0 },
|
||||||
set(newValue:any) {
|
set(newValue:any) {
|
||||||
if (newValue.length != 0 ){
|
if (newValue.length != 0 ){
|
||||||
this.$props.inbound.down_mbps = newValue
|
this.$props.data.down_mbps = newValue
|
||||||
this.$props.inbound.down = "" + newValue + " Mbps"
|
this.$props.data.down = "" + newValue + " Mbps"
|
||||||
} else {
|
} else {
|
||||||
this.$props.inbound.down_mbps = 0
|
this.$props.data.down_mbps = 0
|
||||||
this.$props.inbound.down = "0 Mbps"
|
this.$props.data.down = "0 Mbps"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
up_mbps: {
|
up_mbps: {
|
||||||
get() { return this.$props.inbound.up_mbps ? this.$props.inbound.up_mbps : 0 },
|
get() { return this.$props.data.up_mbps ? this.$props.data.up_mbps : 0 },
|
||||||
set(newValue:number) { this.$props.inbound.up_mbps = newValue > 0 ? newValue : 0 }
|
set(newValue:number) { this.$props.data.up_mbps = newValue > 0 ? newValue : 0 }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: { Network }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,57 +1,145 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Hysteria2">
|
<v-card subtitle="Hysteria2">
|
||||||
<v-row>
|
<v-row v-if="!data.ignore_client_bandwidth">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Masquerade"
|
:label="$t('stats.upload')"
|
||||||
hide-details
|
|
||||||
v-model="hysteria2.masquerade"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="hysteria2.ignore_client_bandwidth" color="primary" label="Ignore Client Bandwidth" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="!hysteria2.ignore_client_bandwidth">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Uplink Limit"
|
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
|
min="0"
|
||||||
v-model.number="up_mbps">
|
v-model.number="up_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Downlink Limit"
|
:label="$t('stats.download')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
min="0"
|
min="0"
|
||||||
v-model.number="down_mbps">
|
v-model.number="down_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="hysteria2.obfs">
|
<template v-if="direction == 'in'">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-row>
|
||||||
<v-text-field
|
<v-col cols="12" sm="6" md="4">
|
||||||
label="obfs Password"
|
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
||||||
hide-details
|
</v-col>
|
||||||
v-model="hysteria2.obfs.password">
|
<v-col cols="12" sm="6" md="4" v-if="data.obfs != undefined">
|
||||||
</v-text-field>
|
<v-text-field
|
||||||
</v-col>
|
:label="$t('types.hy.obfs')"
|
||||||
</v-row>
|
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-text-field
|
||||||
|
label="Rewrite Host"
|
||||||
|
placeholder="example.com"
|
||||||
|
v-model="data.masquerade.rewrite_host"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</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-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Options</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.hy.hy2Options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<template v-if="direction == 'in'">
|
||||||
<v-switch v-model="optionObfs" color="primary" label="Obfs" hide-details></v-switch>
|
<v-list-item>
|
||||||
</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-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -60,32 +148,59 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Hysteria2, createInbound } from '@/types/inbounds'
|
import Network from '@/components/Network.vue'
|
||||||
|
import Headers from '@/components/Headers.vue'
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction', 'data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
hysteria2: <Hysteria2> createInbound("hysteria2",{ "tag": "" }),
|
masqTypes: [
|
||||||
|
{ title: i18n.global.t('rule.simple'), value: '' },
|
||||||
|
{ title: "File server", value: "file" },
|
||||||
|
{ title: "Reverse Proxy", value: "proxy" },
|
||||||
|
{ title: "Fixed response", value: "string" },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
down_mbps: {
|
down_mbps: {
|
||||||
get() { return this.hysteria2.down_mbps ? this.hysteria2.down_mbps : 0 },
|
get() { return this.$props.data.down_mbps?? 0 },
|
||||||
set(newValue:any) { this.hysteria2.down_mbps = newValue.length == 0 ? undefined : this.hysteria2.down_mbps }
|
set(v:number) { this.$props.data.down_mbps = v>0 ? v : undefined }
|
||||||
},
|
},
|
||||||
up_mbps: {
|
up_mbps: {
|
||||||
get() { return this.hysteria2.up_mbps ? this.hysteria2.up_mbps : 0 },
|
get() { return this.$props.data.up_mbps?? 0 },
|
||||||
set(newValue:any) { this.hysteria2.up_mbps = newValue.length == 0 ? undefined : this.hysteria2.up_mbps }
|
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: {
|
optionObfs: {
|
||||||
get(): boolean { return this.hysteria2.obfs != undefined },
|
get(): boolean { return this.$props.data.obfs != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.obfs = v ? { type: "salamander", password: ""} : 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 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
components: { Network, Headers }
|
||||||
this.hysteria2 = <Hysteria2> this.$props.inbound
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-card subtitle="Naive">
|
<v-card subtitle="Naive">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="inbound" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<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>
|
||||||
@@ -5,41 +5,41 @@
|
|||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
:items="[1,2,3]"
|
:items="[1,2,3]"
|
||||||
label="Version"
|
:label="$t('version')"
|
||||||
v-model="version">
|
v-model="version">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="inbound.password != undefined">
|
<v-col cols="12" sm="6" md="4" v-if="data.password != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Password"
|
:label="$t('types.pw')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.password">
|
v-model="data.password">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Handshake Server"
|
:label="$t('types.shdwTls.hs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="Inbound.handshake.server">
|
v-model="Inbound.handshake.server">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Server Port"
|
:label="$t('out.port')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="server_port">
|
v-model.number="server_port">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Dial :dial="Inbound.handshake" />
|
<Dial :dial="Inbound.handshake" :outTags="outTags" />
|
||||||
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Add Hanshake Server"
|
:label="$t('types.shdwTls.addHS')"
|
||||||
hide-details
|
hide-details
|
||||||
append-icon="mdi-plus"
|
append-icon="mdi-plus"
|
||||||
@click:append="addHandshakeServer()"
|
@click:append="addHandshakeServer()"
|
||||||
@@ -67,22 +67,22 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Handshake Server"
|
:label="$t('types.shdwTls.hs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="value.server">
|
v-model="value.server">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Server Port"
|
:label="$t('out.port')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="value.server_port">
|
v-model.number="value.server_port">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Dial :dial="value" />
|
<Dial :dial="value" :outTags="outTags" />
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -92,62 +92,62 @@ import { ShadowTLS } from '@/types/inbounds'
|
|||||||
import Dial from '../Dial.vue'
|
import Dial from '../Dial.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data', 'outTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handshake_server: ''
|
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.users
|
||||||
|
delete this.Inbound.handshake_for_server_name
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (!this.Inbound.password) {
|
||||||
|
this.Inbound.password = ""
|
||||||
|
}
|
||||||
|
delete this.Inbound.users
|
||||||
|
if (!this.Inbound.handshake_for_server_name) {
|
||||||
|
this.Inbound.handshake_for_server_name = {}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
delete this.Inbound.password
|
||||||
|
if (!Object.hasOwn(this.Inbound, 'users')) {
|
||||||
|
this.Inbound.users = []
|
||||||
|
}
|
||||||
|
if (!this.Inbound.handshake_for_server_name) {
|
||||||
|
this.Inbound.handshake_for_server_name = {}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
this.Inbound.version = newValue;
|
||||||
methods: {
|
|
||||||
addHandshakeServer() {
|
|
||||||
this.inbound.handshake_for_server_name[this.handshake_server] = {}
|
|
||||||
// Clear the input field after adding the server
|
|
||||||
this.handshake_server = ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
Inbound(): ShadowTLS {
|
||||||
this.version = this.Inbound.version
|
return <ShadowTLS>this.$props.data;
|
||||||
},
|
},
|
||||||
computed: {
|
server_port: {
|
||||||
version: {
|
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
||||||
get() { this.version = this.Inbound.version; return this.Inbound.version; },
|
set(newValue: any) { this.Inbound.handshake.server_port = newValue.length == 0 || newValue == 0 ? 443 : parseInt(newValue); }
|
||||||
set(newValue: any) {
|
|
||||||
switch (newValue) {
|
|
||||||
case 1:
|
|
||||||
this.Inbound.password = undefined
|
|
||||||
this.Inbound.users = undefined
|
|
||||||
this.Inbound.handshake_for_server_name = undefined
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (!this.Inbound.password) {
|
|
||||||
this.Inbound.password = ""
|
|
||||||
}
|
|
||||||
this.Inbound.users = undefined
|
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
|
||||||
this.Inbound.handshake_for_server_name = {}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
this.Inbound.password = undefined
|
|
||||||
if (Object.hasOwn(this.Inbound, 'users')) {
|
|
||||||
this.Inbound.users = []
|
|
||||||
}
|
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
|
||||||
this.Inbound.handshake_for_server_name = {}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.Inbound.version = newValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Inbound(): ShadowTLS {
|
|
||||||
return <ShadowTLS>this.$props.inbound;
|
|
||||||
},
|
|
||||||
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 }
|
},
|
||||||
|
components: { Dial }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -4,16 +4,28 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Method"
|
:label="$t('in.ssMethod')"
|
||||||
:items="ssMethods"
|
:items="ssMethods"
|
||||||
v-model="inbound.method">
|
@update:model-value="direction == 'in' ? changeMethod($event) : undefined"
|
||||||
|
v-model="data.method">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="inbound.password" label="Password" hide-details></v-text-field>
|
<Network :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
||||||
<Network :inbound="inbound" />
|
<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-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -21,24 +33,35 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
import UoT from '@/components/UoT.vue';
|
||||||
|
import RandomUtil from '@/plugins/randomUtil';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction','data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ssMethods: [
|
ssMethods: [
|
||||||
"none",
|
"none",
|
||||||
"aes-128-gcm",
|
"aes-128-gcm",
|
||||||
"aes-192-gcm",
|
"aes-192-gcm",
|
||||||
"aes-256-gcm",
|
"aes-256-gcm",
|
||||||
"chacha20-ietf-poly1305",
|
"chacha20-ietf-poly1305",
|
||||||
"xchacha20-ietf-poly1305",
|
"xchacha20-ietf-poly1305",
|
||||||
"2022-blake3-aes-128-gcm",
|
"2022-blake3-aes-128-gcm",
|
||||||
"2022-blake3-aes-256-gcm",
|
"2022-blake3-aes-256-gcm",
|
||||||
"2022-blake3-chacha20-poly1305"
|
"2022-blake3-chacha20-poly1305"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Network }
|
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>
|
</script>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<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>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-card subtitle="TProxy">
|
<v-card subtitle="TProxy">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="inbound" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<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>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user