Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -0,0 +1 @@
|
|||||||
|
buy_me_a_coffee: alireza7
|
||||||
@@ -47,7 +47,7 @@ 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: core/
|
context: core/
|
||||||
push: true
|
push: true
|
||||||
|
|||||||
@@ -44,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,linux/arm/v7,linux/386
|
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 }}
|
||||||
@@ -14,7 +14,10 @@ jobs:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
- armv7
|
- armv7
|
||||||
|
- armv6
|
||||||
|
- armv5
|
||||||
- 386
|
- 386
|
||||||
|
- s390x
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -24,23 +27,29 @@ jobs:
|
|||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
cache: false
|
cache: false
|
||||||
go-version: '1.22'
|
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
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update && sudo apt-get install upx -yq
|
sudo apt-get update
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
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
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
sudo apt install gcc-i686-linux-gnu
|
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
|
||||||
@@ -63,28 +72,35 @@ jobs:
|
|||||||
export GOARCH=arm
|
export GOARCH=arm
|
||||||
export GOARM=7
|
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
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
export GOARCH=386
|
export GOARCH=386
|
||||||
export CC=i686-linux-gnu-gcc
|
export CC=i686-linux-gnu-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||||
|
export GOARCH=s390x
|
||||||
|
export CC=s390x-linux-gnu-gcc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#### Build Sing-Box
|
#### Build Sing-Box
|
||||||
git clone -b v1.8.13 https://github.com/SagerNet/sing-box
|
export VERSION=v1.10.1
|
||||||
|
git clone -b $VERSION https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
go build -tags with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor \
|
||||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a \
|
-v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' -s -w -buildid=" \
|
||||||
-tags='netgo osusergo static_build with_quic with_grpc with_wireguard with_ech with_utls with_reality_server with_acme with_v2ray_api with_clash_api with_gvisor' \
|
|
||||||
-o sing-box ./cmd/sing-box
|
-o sing-box ./cmd/sing-box
|
||||||
upx --ultra-brute -9 -v --lzma --best --force sing-box
|
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
### Build s-ui
|
### Build s-ui
|
||||||
cd backend
|
cd backend
|
||||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
go build -o ../sui main.go
|
||||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a -tags='netgo osusergo static_build sqlite_omit_load_extension' \
|
|
||||||
-o ../sui main.go
|
|
||||||
cd ..
|
cd ..
|
||||||
upx --ultra-brute -9 -v --lzma --best --force sui
|
|
||||||
|
|
||||||
mkdir s-ui
|
mkdir s-ui
|
||||||
cp sui s-ui/
|
cp sui s-ui/
|
||||||
|
|||||||
+8
-6
@@ -1,23 +1,25 @@
|
|||||||
FROM --platform=$BUILDPLATFORM 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 --platform=$BUILDPLATFORM golang:1.22-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" -o sui main.go
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM 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" ]
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
| 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 Informarion
|
## Default Installation Information
|
||||||
- Panel Port: 2095
|
- Panel Port: 2095
|
||||||
- Panel Path: /app/
|
- Panel Path: /app/
|
||||||
- Subscription Port: 2096
|
- Subscription Port: 2096
|
||||||
@@ -40,12 +40,12 @@
|
|||||||
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
|
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) 1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Uninstall S-UI
|
## Uninstall S-UI
|
||||||
@@ -80,7 +80,7 @@ curl -fsSL https://get.docker.com | sh
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir s-ui && cd s-ui
|
mkdir s-ui && cd s-ui
|
||||||
wget -q https://raw.githubusercontent.com/alireza0/s-ui/main/docker-compose.yml
|
wget -q https://raw.githubusercontent.com/alireza0/s-ui/master/docker-compose.yml
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -104,6 +104,56 @@ 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 localy for instant developement 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
|
||||||
@@ -111,6 +161,7 @@ docker build -t s-ui .
|
|||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Chinese (Simplified)
|
- Chinese (Simplified)
|
||||||
- Chinese (Traditional)
|
- Chinese (Traditional)
|
||||||
|
- Russian
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -129,10 +180,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
|
||||||
|
|
||||||
@@ -169,4 +228,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)
|
||||||
|
|||||||
+57
-14
@@ -1,9 +1,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
|
"s-ui/singbox"
|
||||||
|
"s-ui/util"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,9 +16,12 @@ type APIHandler struct {
|
|||||||
service.UserService
|
service.UserService
|
||||||
service.ConfigService
|
service.ConfigService
|
||||||
service.ClientService
|
service.ClientService
|
||||||
|
service.TlsService
|
||||||
|
service.InDataService
|
||||||
service.PanelService
|
service.PanelService
|
||||||
service.StatsService
|
service.StatsService
|
||||||
service.ServerService
|
service.ServerService
|
||||||
|
singbox.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIHandler(g *gin.RouterGroup) {
|
func NewAPIHandler(g *gin.RouterGroup) {
|
||||||
@@ -55,14 +59,7 @@ 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 {
|
|
||||||
logger.Infof("Unable to set session's max age")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = SetLoginUser(c, loginUser)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Info("user ", loginUser, " login success")
|
logger.Info("user ", loginUser, " login success")
|
||||||
} else {
|
} else {
|
||||||
@@ -94,6 +91,13 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
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.Controller.Restart()
|
||||||
|
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, "API call", nil)
|
||||||
}
|
}
|
||||||
@@ -151,19 +155,45 @@ 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":
|
||||||
|
service := c.Query("s")
|
||||||
|
count := c.Query("c")
|
||||||
|
level := c.Query("l")
|
||||||
|
logs := a.ServerService.GetLogs(service, 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, "API call", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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("sing-box", "1", "debug")
|
||||||
|
if len(logs) > 0 {
|
||||||
|
data["lastLog"] = logs[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -176,13 +206,26 @@ 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
|
||||||
|
}
|
||||||
|
inData, err := a.InDataService.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"] = *config
|
||||||
|
data["clients"] = clients
|
||||||
|
data["tls"] = tlsConfigs
|
||||||
|
data["inData"] = inData
|
||||||
|
data["subURI"] = subURI
|
||||||
|
data["onlines"] = onlines
|
||||||
} else {
|
} else {
|
||||||
data = fmt.Sprintf(`{"onlines": %s}`, onlines)
|
data["onlines"] = onlines
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
+14
-5
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
|
|
||||||
sessions "github.com/Calidity/gin-sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func ParseCmd() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" admin set/reset/show first admin credentials")
|
fmt.Println(" admin set/reset/show first admin credentials")
|
||||||
|
fmt.Println(" migrate migrate form older version")
|
||||||
fmt.Println(" setting set/reset/show settings")
|
fmt.Println(" setting set/reset/show settings")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
adminCmd.Usage()
|
adminCmd.Usage()
|
||||||
@@ -70,6 +71,9 @@ func ParseCmd() {
|
|||||||
showAdmin()
|
showAdmin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "migrate":
|
||||||
|
migrateDb()
|
||||||
|
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func migrateDb() {
|
||||||
|
// void running on first install
|
||||||
|
path := config.GetDBPath()
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := gorm.Open(sqlite.Open(path))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fmt.Println("Start migrating database...")
|
||||||
|
err = migrateClientSchema(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = changesObj(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Migration done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.AutoMigrate(model.Client{})
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -79,3 +79,12 @@ func GetDefaultConfig() string {
|
|||||||
func GetEnvApi() string {
|
func GetEnvApi() string {
|
||||||
return os.Getenv("SINGBOX_API")
|
return os.Getenv("SINGBOX_API")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsSystemd() bool {
|
||||||
|
pid := os.Getppid()
|
||||||
|
cmdline, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return string(cmdline) == "systemd\n"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.0.3
|
1.1.0
|
||||||
@@ -22,4 +22,5 @@ func (s *DelStatsJob) Run() {
|
|||||||
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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,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 +48,19 @@ 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)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDB(dbPath string) error {
|
||||||
|
err := OpenDB(dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(
|
err = db.AutoMigrate(
|
||||||
&model.Setting{},
|
&model.Setting{},
|
||||||
|
&model.Tls{},
|
||||||
|
&model.InboundData{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
|
|||||||
@@ -8,6 +8,21 @@ 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"`
|
||||||
|
Inbounds json.RawMessage `json:"inbounds" form:"inbounds"`
|
||||||
|
Server json.RawMessage `json:"server" form:"server"`
|
||||||
|
Client json.RawMessage `json:"client" form:"client"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundData struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Tag string `json:"tag" form:"tag"`
|
||||||
|
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||||
|
OutJson json.RawMessage `json:"outJson" form:"outJson"`
|
||||||
|
}
|
||||||
|
|
||||||
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,17 +31,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" from:"desc"`
|
Desc string `json:"desc" form:"desc"`
|
||||||
|
Group string `json:"group" form:"group"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
|
|||||||
+43
-39
@@ -1,64 +1,68 @@
|
|||||||
module s-ui
|
module s-ui
|
||||||
|
|
||||||
go 1.22.0
|
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-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
gorm.io/driver/sqlite v1.5.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
gorm.io/gorm v1.25.7
|
github.com/v2fly/v2ray-core/v5 v5.17.1
|
||||||
|
google.golang.org/grpc v1.67.1
|
||||||
|
gorm.io/driver/sqlite v1.5.6
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.4.0 // indirect
|
github.com/adrg/xdg v0.5.0 // indirect
|
||||||
github.com/bytedance/sonic v1.11.1 // indirect
|
github.com/bytedance/sonic v1.12.3 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/cloudwego/iasm v0.2.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-ole/go-ole v1.3.0 // 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/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // 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/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/arch v0.7.0 // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/crypto v0.21.0 // indirect
|
golang.org/x/arch v0.11.0 // indirect
|
||||||
golang.org/x/net v0.23.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/sys v0.18.0 // indirect
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.18.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1
|
|
||||||
google.golang.org/grpc v1.62.0
|
|
||||||
)
|
|
||||||
|
|||||||
+111
-166
@@ -1,30 +1,26 @@
|
|||||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
||||||
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 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
|
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/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
|
||||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
|
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
|
||||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM=
|
||||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn4=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
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/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=
|
||||||
@@ -33,51 +29,43 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFP
|
|||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
|
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
|
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
|
||||||
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/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/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
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.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 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/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI=
|
||||||
|
github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
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=
|
||||||
@@ -85,49 +73,42 @@ 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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls=
|
github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
|
||||||
github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k=
|
github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
|
||||||
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/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.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
|
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
|
||||||
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
|
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/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.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/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||||
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
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 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
@@ -141,6 +122,8 @@ github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1W
|
|||||||
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
|
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
|
||||||
github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ=
|
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/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
github.com/onsi/ginkgo/v2 v2.10.0 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/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
@@ -149,46 +132,38 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
||||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||||
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 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
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 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
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 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
||||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
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.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
|
||||||
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
|
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
||||||
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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
|
||||||
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.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||||
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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
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/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
|
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
|
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
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 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -197,23 +172,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
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=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.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/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
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/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
|
||||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
|
||||||
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/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=
|
||||||
@@ -222,90 +192,65 @@ github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre
|
|||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M=
|
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0 h1:BDJfi3Ftx1NpQlZZPpeWJe3RDqRNyIVBs+YGG4RRMDU=
|
github.com/v2fly/v2ray-core/v5 v5.17.1 h1:IIMMtmRdaG5HTYNn6VX1xKULknJl7nhkSFnmoTb5TDQ=
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0/go.mod h1:Bc3gmQWLr8UR7xBSCYI9FbfKuVvqA9lbkeBTWNRRAS4=
|
github.com/v2fly/v2ray-core/v5 v5.17.1/go.mod h1:IhDN0rhXJnNcs9jUuC5sILTGCT2L+4yr0+tfD8ZVuL8=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
||||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
|
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
|
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
|
||||||
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
||||||
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
|
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
||||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
|
||||||
golang.org/x/sync v0.6.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-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-20210630005230-0f9fa26af87c/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
|
||||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
|
||||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
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/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
|
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
|
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
|
||||||
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=
|
|
||||||
|
|||||||
+11
-13
@@ -3,35 +3,33 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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)
|
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 config.IsSystemd() && 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}`)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -14,18 +13,14 @@ import (
|
|||||||
type ClientService struct {
|
type ClientService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
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, changes []model.Changes) error {
|
||||||
@@ -62,18 +57,20 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dt := time.Now().Unix()
|
||||||
var users, inbounds []string
|
var users, inbounds []string
|
||||||
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 []string
|
||||||
|
json.Unmarshal(client.Inbounds, &userInbounds)
|
||||||
inbounds = append(inbounds, userInbounds...)
|
inbounds = append(inbounds, 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 + "\""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +84,7 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
LastUpdate = dt
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, inbounds, nil
|
return users, inbounds, nil
|
||||||
|
|||||||
+110
-51
@@ -6,14 +6,20 @@ import (
|
|||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
|
"s-ui/logger"
|
||||||
"s-ui/singbox"
|
"s-ui/singbox"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ApiAddr string
|
var ApiAddr string
|
||||||
|
var LastUpdate int64
|
||||||
|
var IsSystemd bool
|
||||||
|
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
ClientService
|
ClientService
|
||||||
|
TlsService
|
||||||
|
InDataService
|
||||||
singbox.Controller
|
singbox.Controller
|
||||||
SettingService
|
SettingService
|
||||||
}
|
}
|
||||||
@@ -33,6 +39,7 @@ func NewConfigService() *ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) InitConfig() error {
|
func (s *ConfigService) InitConfig() error {
|
||||||
|
IsSystemd = config.IsSystemd()
|
||||||
configPath := config.GetBinFolderPath()
|
configPath := config.GetBinFolderPath()
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
data, err := os.ReadFile(configPath + "/config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,27 +58,50 @@ func (s *ConfigService) InitConfig() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.RefreshApiAddr(&data)
|
var singboxConfig SingBoxConfig
|
||||||
|
err = json.Unmarshal(data, &singboxConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.RefreshApiAddr(&singboxConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) GetConfig() (*[]byte, error) {
|
func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
||||||
configPath := config.GetBinFolderPath()
|
configPath := config.GetBinFolderPath()
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
data, err := os.ReadFile(configPath + "/config.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &data, nil
|
singboxConfig := SingBoxConfig{}
|
||||||
|
err = json.Unmarshal(data, &singboxConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &singboxConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
||||||
var err error
|
var err error
|
||||||
var clientChanges, settingChanges, configChanges []model.Changes
|
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
|
||||||
if _, ok := changes["clients"]; ok {
|
if _, ok := changes["clients"]; ok {
|
||||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if _, ok := changes["tls"]; ok {
|
||||||
|
err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := changes["inData"]; ok {
|
||||||
|
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, ok := changes["settings"]; ok {
|
if _, ok := changes["settings"]; ok {
|
||||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -101,6 +131,18 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(tlsChanges) > 0 {
|
||||||
|
err = s.TlsService.Save(tx, tlsChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(inChanges) > 0 {
|
||||||
|
err = s.InDataService.Save(tx, inChanges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(settingChanges) > 0 {
|
if len(settingChanges) > 0 {
|
||||||
err = s.SettingService.Save(tx, settingChanges)
|
err = s.SettingService.Save(tx, settingChanges)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,11 +154,7 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newConfig := SingBoxConfig{}
|
newConfig := *singboxConfig
|
||||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, change := range configChanges {
|
for _, change := range configChanges {
|
||||||
rawObject := change.Obj
|
rawObject := change.Obj
|
||||||
switch change.Key {
|
switch change.Key {
|
||||||
@@ -154,12 +192,7 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to config.json
|
err = s.Save(&newConfig)
|
||||||
data, err := json.MarshalIndent(newConfig, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Save(&data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -167,30 +200,45 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
|
|
||||||
// Log changes
|
// Log changes
|
||||||
dt := time.Now().Unix()
|
dt := time.Now().Unix()
|
||||||
allChanges := append(append(clientChanges, settingChanges...), configChanges...)
|
allChanges := append(clientChanges, settingChanges...)
|
||||||
for index := range allChanges {
|
allChanges = append(allChanges, configChanges...)
|
||||||
allChanges[index].DateTime = dt
|
allChanges = append(allChanges, tlsChanges...)
|
||||||
allChanges[index].Actor = loginUser
|
allChanges = append(allChanges, inChanges...)
|
||||||
}
|
if len(allChanges) > 0 {
|
||||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
for index := range allChanges {
|
||||||
if err != nil {
|
allChanges[index].DateTime = dt
|
||||||
return err
|
allChanges[index].Actor = loginUser
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LastUpdate = dt
|
||||||
|
|
||||||
return nil
|
return 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()
|
||||||
|
}
|
||||||
|
return count > 0, err
|
||||||
|
} else {
|
||||||
|
intLu, err := strconv.ParseInt(lu, 10, 64)
|
||||||
|
return LastUpdate > intLu, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) Save(data *[]byte) error {
|
func (s *ConfigService) Save(singboxConfig *SingBoxConfig) error {
|
||||||
configPath := config.GetBinFolderPath()
|
configPath := config.GetBinFolderPath()
|
||||||
_, err := os.Stat(configPath + "/config.json")
|
_, err := os.Stat(configPath + "/config.json")
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -202,35 +250,36 @@ func (s *ConfigService) Save(data *[]byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(configPath+"/config.json", *data, 0764)
|
data, err := json.MarshalIndent(singboxConfig, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.RefreshApiAddr(data)
|
err = os.WriteFile(configPath+"/config.json", data, 0764)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.RefreshApiAddr(singboxConfig)
|
||||||
s.Controller.Restart()
|
s.Controller.Restart()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) RefreshApiAddr(data *[]byte) error {
|
func (s *ConfigService) RefreshApiAddr(singboxConfig *SingBoxConfig) error {
|
||||||
Env_API := config.GetEnvApi()
|
Env_API := config.GetEnvApi()
|
||||||
if len(Env_API) > 0 {
|
if len(Env_API) > 0 {
|
||||||
ApiAddr = Env_API
|
ApiAddr = Env_API
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
if data == nil {
|
if singboxConfig == nil {
|
||||||
data, err = s.GetConfig()
|
singboxConfig, err = s.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
singboxConfig := SingBoxConfig{}
|
|
||||||
err = json.Unmarshal(*data, &singboxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var experimental struct {
|
var experimental struct {
|
||||||
V2rayApi struct {
|
V2rayApi struct {
|
||||||
Listen string `json:"listen"`
|
Listen string `json:"listen"`
|
||||||
@@ -257,12 +306,7 @@ func (s *ConfigService) DepleteClients() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newConfig := SingBoxConfig{}
|
for inbound_index, inbound := range singboxConfig.Inbounds {
|
||||||
err = json.Unmarshal(*singboxConfig, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for inbound_index, inbound := range newConfig.Inbounds {
|
|
||||||
var inboundJson map[string]interface{}
|
var inboundJson map[string]interface{}
|
||||||
json.Unmarshal(inbound, &inboundJson)
|
json.Unmarshal(inbound, &inboundJson)
|
||||||
if s.contains(inbounds, inboundJson["tag"].(string)) {
|
if s.contains(inbounds, inboundJson["tag"].(string)) {
|
||||||
@@ -301,13 +345,10 @@ func (s *ConfigService) DepleteClients() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newConfig.Inbounds[inbound_index] = modifiedInbound
|
singboxConfig.Inbounds[inbound_index] = modifiedInbound
|
||||||
}
|
}
|
||||||
modifiedConfig, err := json.MarshalIndent(newConfig, "", " ")
|
|
||||||
if err != nil {
|
err = s.Save(singboxConfig)
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.Save(&modifiedConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -322,3 +363,21 @@ func (s *ConfigService) contains(slice []string, item string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
|
||||||
|
c, _ := strconv.Atoi(count)
|
||||||
|
whereString := "`id`>0"
|
||||||
|
if len(actor) > 0 {
|
||||||
|
whereString += " and `actor`='" + actor + "'"
|
||||||
|
}
|
||||||
|
if len(chngKey) > 0 {
|
||||||
|
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 {
|
||||||
|
logger.Warning(err)
|
||||||
|
}
|
||||||
|
return chngs
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InDataService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InDataService) GetAll() ([]model.InboundData, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inData := []model.InboundData{}
|
||||||
|
err := db.Model(model.InboundData{}).Scan(&inData).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return inData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InDataService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||||
|
var err error
|
||||||
|
for _, change := range changes {
|
||||||
|
inData := model.InboundData{}
|
||||||
|
err = json.Unmarshal(change.Obj, &inData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch change.Action {
|
||||||
|
case "new":
|
||||||
|
err = tx.Create(&inData).Error
|
||||||
|
case "del":
|
||||||
|
err = tx.Where("id = ?", change.Index).Delete(model.InboundData{}).Error
|
||||||
|
default:
|
||||||
|
err = tx.Save(inData).Error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
@@ -135,3 +138,49 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
|||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetLogs(service string, count string, level string) []string {
|
||||||
|
c, _ := strconv.Atoi(count)
|
||||||
|
|
||||||
|
if service == "s-ui" {
|
||||||
|
return logger.GetLogs(c, level)
|
||||||
|
}
|
||||||
|
var lines []string
|
||||||
|
var cmdArgs []string
|
||||||
|
if IsSystemd {
|
||||||
|
cmdArgs = []string{"journalctl", "-u", service, "--no-pager", "-n", count, "-p", level}
|
||||||
|
} else {
|
||||||
|
cmdArgs = []string{"tail", "/logs/" + service + ".log", "-n", count}
|
||||||
|
}
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to get logs!", err.Error()}
|
||||||
|
}
|
||||||
|
lines = strings.Split(out.String(), "\n")
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GenKeypair(keyType string, options string) []string {
|
||||||
|
if len(keyType) == 0 {
|
||||||
|
return []string{"No keypair to generate"}
|
||||||
|
}
|
||||||
|
sbExec := s.GetBinaryPath()
|
||||||
|
cmdArgs := []string{"generate", keyType + "-keypair"}
|
||||||
|
if keyType == "tls" || keyType == "ech" {
|
||||||
|
cmdArgs = append(cmdArgs, options)
|
||||||
|
}
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(sbExec, cmdArgs...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate keypair"}
|
||||||
|
}
|
||||||
|
return strings.Split(out.String(), "\n")
|
||||||
|
}
|
||||||
|
|||||||
+16
-11
@@ -18,7 +18,7 @@ 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/",
|
"webPath": "/app/",
|
||||||
@@ -36,6 +36,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"subEncode": "true",
|
"subEncode": "true",
|
||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
|
"subJsonExt": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -65,7 +66,7 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Due to security principles
|
// Due to security principles
|
||||||
delete(allSetting, "webSecret")
|
delete(allSetting, "secret")
|
||||||
|
|
||||||
return &allSetting, nil
|
return &allSetting, nil
|
||||||
}
|
}
|
||||||
@@ -127,9 +128,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)
|
||||||
@@ -191,11 +192,11 @@ func (s *SettingService) SetWebPath(webPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -318,10 +319,10 @@ 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")
|
||||||
@@ -347,6 +348,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,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"
|
||||||
@@ -86,12 +85,8 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.
|
|||||||
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,46 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TlsService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
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, changes []model.Changes) error {
|
||||||
|
var err error
|
||||||
|
for _, change := range changes {
|
||||||
|
tlsConfig := model.Tls{}
|
||||||
|
err = json.Unmarshal(change.Obj, &tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch change.Action {
|
||||||
|
case "new":
|
||||||
|
err = tx.Create(&tlsConfig).Error
|
||||||
|
case "del":
|
||||||
|
err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error
|
||||||
|
default:
|
||||||
|
err = tx.Save(tlsConfig).Error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v5/app/stats/command"
|
statsService "github.com/v2fly/v2ray-core/v5/app/stats/command"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
)
|
)
|
||||||
|
|
||||||
type V2rayAPI struct {
|
type V2rayAPI struct {
|
||||||
@@ -22,7 +23,7 @@ func (v *V2rayAPI) Init(ApiAddr string) (err error) {
|
|||||||
if len(ApiAddr) == 0 {
|
if len(ApiAddr) == 0 {
|
||||||
return common.NewError("The api address is wrong: ", ApiAddr)
|
return common.NewError("The api address is wrong: ", ApiAddr)
|
||||||
}
|
}
|
||||||
v.grpcClient, err = grpc.Dial(ApiAddr, grpc.WithInsecure())
|
v.grpcClient, err = grpc.NewClient(ApiAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
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.InboundData, 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 inbounds []string
|
||||||
|
err = json.Unmarshal(client.Inbounds, &inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
inDatas := &[]model.InboundData{}
|
||||||
|
err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return client, inDatas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]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 *inDatas {
|
||||||
|
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" && key != "username" {
|
||||||
|
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 {
|
||||||
|
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
|
||||||
|
newTls, overrideTls := addr["tls"].(bool)
|
||||||
|
if overrideTls {
|
||||||
|
tlsIf := map[string]interface{}{}
|
||||||
|
if newTls {
|
||||||
|
tlsIf["enabled"] = true
|
||||||
|
newSNI, overrideSNI := addr["server_name"].(string)
|
||||||
|
if overrideSNI {
|
||||||
|
tlsIf["server_name"] = newSNI
|
||||||
|
}
|
||||||
|
newInsecure, overrideInsecure := addr["insecure"].(bool)
|
||||||
|
if overrideInsecure {
|
||||||
|
tlsIf["insecure"] = newInsecure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newOut["tls"] = tlsIf
|
||||||
|
}
|
||||||
|
remark, _ := addr["remark"].(string)
|
||||||
|
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
|
||||||
|
outTags = append(outTags, newTag)
|
||||||
|
newOut["tag"] = 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
|
||||||
|
}
|
||||||
@@ -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,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
|
||||||
|
}
|
||||||
+2
-2
@@ -18,9 +18,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
sessions "github.com/Calidity/gin-sessions"
|
|
||||||
"github.com/Calidity/gin-sessions/cookie"
|
|
||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS singbox-builder
|
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS singbox-builder
|
||||||
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
ARG SINGBOX_VER=v1.8.10
|
ARG SINGBOX_VER=v1.10.1
|
||||||
ARG SINGBOX_TAGS="with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor"
|
ARG SINGBOX_TAGS="with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor"
|
||||||
ARG GOPROXY=""
|
ARG GOPROXY=""
|
||||||
ENV GOPROXY ${GOPROXY}
|
ENV GOPROXY ${GOPROXY}
|
||||||
@@ -18,7 +18,7 @@ RUN set -ex \
|
|||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM alpine
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
ENV TZ=Asia/Tehran
|
ENV TZ=Asia/Tehran
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
+20
-3
@@ -20,7 +20,17 @@ terminateSingbox()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadSingbox()
|
||||||
|
{
|
||||||
|
if kill -0 $tokill > /dev/null 2>&1; then
|
||||||
|
kill -HUP $tokill
|
||||||
|
else
|
||||||
|
runSingbox
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
trap terminateSingbox SIGINT SIGTERM SIGKILL
|
trap terminateSingbox SIGINT SIGTERM SIGKILL
|
||||||
|
trap reloadSingbox SIGHUP
|
||||||
|
|
||||||
runSingbox
|
runSingbox
|
||||||
|
|
||||||
@@ -32,14 +42,21 @@ do
|
|||||||
echo "Signal received: $signal"
|
echo "Signal received: $signal"
|
||||||
# Remove singnal file
|
# Remove singnal file
|
||||||
rm -f signal >> /dev/null 2>&1
|
rm -f signal >> /dev/null 2>&1
|
||||||
case ${signal} in
|
case ${signal} in
|
||||||
"stop")
|
"stop")
|
||||||
terminateSingbox
|
terminateSingbox
|
||||||
;;
|
;;
|
||||||
"restart")
|
"restart")
|
||||||
terminateSingbox
|
reloadSingbox
|
||||||
runSingbox
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if sin-box crashed
|
||||||
|
if ! kill -0 $tokill > /dev/null 2>&1; then
|
||||||
|
if [ "$signal" != "stop" ]; then
|
||||||
|
echo "Sing-Box with PID $tokill crashed. Breaking the loop..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
+42
-7
@@ -1,6 +1,4 @@
|
|||||||
---
|
---
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
s-ui:
|
s-ui:
|
||||||
image: alireza7/s-ui
|
image: alireza7/s-ui
|
||||||
@@ -8,8 +6,9 @@ services:
|
|||||||
hostname: "S-UI docker"
|
hostname: "S-UI docker"
|
||||||
volumes:
|
volumes:
|
||||||
- "singbox:/app/bin"
|
- "singbox:/app/bin"
|
||||||
- "$PWD/db:/app/db"
|
- "./db:/app/db"
|
||||||
- "$PWD/cert:/app/cert"
|
- "./cert:/app/cert"
|
||||||
|
- "logs:/logs"
|
||||||
environment:
|
environment:
|
||||||
SINGBOX_API: "sing-box:1080"
|
SINGBOX_API: "sing-box:1080"
|
||||||
SUI_DB_FOLDER: "db"
|
SUI_DB_FOLDER: "db"
|
||||||
@@ -20,14 +19,23 @@ services:
|
|||||||
- "2096:2096"
|
- "2096:2096"
|
||||||
networks:
|
networks:
|
||||||
- s-ui
|
- s-ui
|
||||||
entrypoint: "./sui"
|
links:
|
||||||
|
- syslog
|
||||||
|
logging:
|
||||||
|
driver: syslog
|
||||||
|
options:
|
||||||
|
tag: "s-ui"
|
||||||
|
syslog-address: "udp://127.0.0.1:1514"
|
||||||
|
entrypoint: "./entrypoint.sh"
|
||||||
|
depends_on:
|
||||||
|
- syslog
|
||||||
|
|
||||||
sing-box:
|
sing-box:
|
||||||
image: alireza7/s-ui-singbox
|
image: alireza7/s-ui-singbox
|
||||||
container_name: sing-box
|
container_name: sing-box
|
||||||
volumes:
|
volumes:
|
||||||
- "singbox:/app/"
|
- "singbox:/app/"
|
||||||
- "$PWD/cert:/cert"
|
- "./cert:/cert"
|
||||||
networks:
|
networks:
|
||||||
- s-ui
|
- s-ui
|
||||||
ports:
|
ports:
|
||||||
@@ -36,12 +44,39 @@ services:
|
|||||||
- "2443:2443"
|
- "2443:2443"
|
||||||
- "3443:3443"
|
- "3443:3443"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
links:
|
||||||
|
- syslog
|
||||||
|
logging:
|
||||||
|
driver: syslog
|
||||||
|
options:
|
||||||
|
tag: "sing-box"
|
||||||
|
syslog-address: "udp://127.0.0.1:1514"
|
||||||
depends_on:
|
depends_on:
|
||||||
- s-ui
|
- s-ui
|
||||||
|
- syslog
|
||||||
|
|
||||||
|
syslog:
|
||||||
|
image: rsyslog/syslog_appliance_alpine
|
||||||
|
container_name: syslog
|
||||||
|
volumes:
|
||||||
|
- "logs:/logs"
|
||||||
|
networks:
|
||||||
|
- s-ui
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:1514:1514/udp"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- RSYSLOG_CONF_GLOBAL_CONF=template(name="RemoteLogs" type="string" string="/logs/%programname%.log")
|
||||||
|
- RSYSLOG_CONF_INPUT_UDP="input(type=\"imudp\" port=\"1514\" ruleset=\"remote\")"
|
||||||
|
- RSYSLOG_CONF_RULESET_REMOTE="ruleset(name=\"remote\") { action(type=\"omfile\" dynaFile=\"RemoteLogs\") }"
|
||||||
|
command: >
|
||||||
|
sh -c 'touch /config/container_config'
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
s-ui:
|
s-ui:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
singbox:
|
logs:
|
||||||
|
singbox:
|
||||||
|
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
./sui migrate
|
||||||
|
./sui
|
||||||
Generated
+1322
-2740
File diff suppressed because it is too large
Load Diff
+23
-24
@@ -1,43 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "1.1.0",
|
||||||
"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",
|
"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.13.1",
|
||||||
"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.5.3",
|
"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,114 @@
|
|||||||
|
<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>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionTLS">
|
||||||
|
<v-switch
|
||||||
|
:label="$t('tls.enable')"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
@update:model-value="updateTls($event)"
|
||||||
|
v-model="addr.tls" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionSNI">
|
||||||
|
<v-text-field
|
||||||
|
label="SNI"
|
||||||
|
hide-details
|
||||||
|
v-model="addr.server_name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionInsecure">
|
||||||
|
<v-switch
|
||||||
|
:label="$t('tls.insecure')"
|
||||||
|
hide-details
|
||||||
|
color="primary"
|
||||||
|
v-model="addr.insecure" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<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-item v-if="addr.tls">
|
||||||
|
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="addr.tls">
|
||||||
|
<v-switch v-model="optionInsecure" color="primary" :label="$t('tls.insecure')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
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 ? true : undefined; this.updateTls(v) }
|
||||||
|
},
|
||||||
|
optionSNI: {
|
||||||
|
get(): boolean { return this.$props.addr.server_name != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.server_name = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionRemark: {
|
||||||
|
get(): boolean { return this.$props.addr.remark != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.remark = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionInsecure: {
|
||||||
|
get(): boolean { return this.$props.addr.insecure != undefined },
|
||||||
|
set(v:boolean) { this.$props.addr.insecure = v ? false : undefined }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTls(v:boolean) {
|
||||||
|
if (!v) {
|
||||||
|
delete this.$props.addr.insecure
|
||||||
|
delete this.$props.addr.server_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
<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-cn'
|
||||||
import 'moment/locale/zh-tw'
|
import 'moment/locale/zh-tw'
|
||||||
|
|
||||||
@@ -58,7 +59,14 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
locale() {
|
locale() {
|
||||||
const l = i18n.global.locale.value
|
const l = i18n.global.locale.value
|
||||||
return l.replace('zh', 'zh-')
|
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')
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
<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>{{ $t('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>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('objects.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')"
|
||||||
@@ -78,11 +78,11 @@
|
|||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-card-actions class="pt-0">
|
<v-card-actions class="pt-0" v-if="inbound.type != 'tun'">
|
||||||
<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>{{ $t('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>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container class="fill-height">
|
<LogVue
|
||||||
|
v-model="logModal.visible"
|
||||||
|
:visible="logModal.visible"
|
||||||
|
:logType="logModal.logType"
|
||||||
|
@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 +16,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,7 +51,7 @@
|
|||||||
</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;" align="center" justify="center">
|
<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'" />
|
||||||
@@ -80,13 +86,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('s-ui')">
|
||||||
|
<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>
|
||||||
@@ -97,7 +109,19 @@
|
|||||||
<v-col cols="4">{{ $t('main.info.running') }}</v-col>
|
<v-col cols="4">{{ $t('main.info.running') }}</v-col>
|
||||||
<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" style="cursor: pointer;" @click="openLogs('sing-box')">
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('basic.log.title') + " - Sing-Box" }}
|
||||||
|
</v-tooltip>
|
||||||
|
<v-icon icon="mdi-list-box-outline" :color="tilesData.sbd?.running ? 'success': 'error'" />
|
||||||
|
</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 +172,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: [
|
||||||
@@ -215,4 +241,25 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopTimer()
|
stopTimer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const logModal = ref({
|
||||||
|
visible: false,
|
||||||
|
logType: "s-ui"
|
||||||
|
})
|
||||||
|
|
||||||
|
const openLogs = (logType: string) => {
|
||||||
|
logModal.value.logType = logType
|
||||||
|
logModal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeLogs = () => {
|
||||||
|
logModal.value.logType = "s-ui"
|
||||||
|
logModal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const restartSingbox = async () => {
|
||||||
|
loading.value = true
|
||||||
|
await HttpUtils.post('api/restartSb',{})
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
mux(): oMultiplex {
|
mux(): oMultiplex {
|
||||||
|
if (!Object.hasOwn(this.$props.data,"multiplex")) this.$props.data.multiplex = {}
|
||||||
return <oMultiplex> this.$props.data.multiplex
|
return <oMultiplex> this.$props.data.multiplex
|
||||||
},
|
},
|
||||||
muxEnable: {
|
muxEnable: {
|
||||||
|
|||||||
@@ -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.outJson.version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
||||||
|
<Network :data="inData.outJson" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
||||||
|
<UoT :data="inData.outJson" />
|
||||||
|
</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.outJson.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.outJson.security">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="inData.outJson.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.outJson.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.outJson.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.outJson.udp_relay_mode"
|
||||||
|
v-model="inData.outJson.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.outJson.udp_over_stream" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
</v-row>
|
||||||
|
<Headers :data="inData.outJson" 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.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
|
||||||
|
set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network, UoT, Headers }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" v-if="rule.source_ip_cidr != undefined">
|
<v-col cols="12" sm="6" v-if="rule.source_ip_cidr != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('rule.srcIp') + ' ' + $t('commaSeparated')"
|
:label="$t('rule.srcCidr') + ' ' + $t('commaSeparated')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="source_ip_cidr"></v-text-field>
|
v-model="source_ip_cidr"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -20,16 +20,6 @@
|
|||||||
v-model.number="override_port">
|
v-model.number="override_port">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
|
||||||
<v-select
|
|
||||||
:label="$t('types.direct.proxyProtocol')"
|
|
||||||
:items="[1,2]"
|
|
||||||
hide-details
|
|
||||||
clearable
|
|
||||||
@click:clear="delete data.proxy_protocol"
|
|
||||||
v-model.number="data.proxy_protocol">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
<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>{{ $t('types.hy.hyOptions') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.hy.hyOptions') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Hysteria2">
|
<v-card subtitle="Hysteria2">
|
||||||
<v-row v-if="direction == 'in'">
|
<v-row v-if="direction == 'in'">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4" v-if="data.masquerade != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="HTTP3 server on auth fail"
|
label="HTTP3 server on auth fail"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="data.obfs">
|
<v-row v-if="data.obfs != undefined">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('types.hy.obfs')"
|
:label="$t('types.hy.obfs')"
|
||||||
@@ -59,13 +59,16 @@
|
|||||||
<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>{{ $t('types.hy.hy2Options') }}</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>
|
<v-list-item>
|
||||||
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -95,6 +98,10 @@ export default {
|
|||||||
optionObfs: {
|
optionObfs: {
|
||||||
get(): boolean { return this.$props.data.obfs != undefined },
|
get(): boolean { return this.$props.data.obfs != undefined },
|
||||||
set(v:boolean) { this.$props.data.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 }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Network }
|
components: { Network }
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
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>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<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="$t('types.shdwTls.adHS')"
|
:label="$t('types.shdwTls.addHS')"
|
||||||
hide-details
|
hide-details
|
||||||
append-icon="mdi-plus"
|
append-icon="mdi-plus"
|
||||||
@click:append="addHandshakeServer()"
|
@click:append="addHandshakeServer()"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
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>
|
||||||
@@ -114,22 +114,22 @@ export default {
|
|||||||
set(newValue: any) {
|
set(newValue: any) {
|
||||||
switch (newValue) {
|
switch (newValue) {
|
||||||
case 1:
|
case 1:
|
||||||
this.Inbound.password = undefined
|
delete this.Inbound.password
|
||||||
this.Inbound.users = undefined
|
delete this.Inbound.users
|
||||||
this.Inbound.handshake_for_server_name = undefined
|
delete this.Inbound.handshake_for_server_name
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
if (!this.Inbound.password) {
|
if (!this.Inbound.password) {
|
||||||
this.Inbound.password = ""
|
this.Inbound.password = ""
|
||||||
}
|
}
|
||||||
this.Inbound.users = undefined
|
delete this.Inbound.users
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
if (!this.Inbound.handshake_for_server_name) {
|
||||||
this.Inbound.handshake_for_server_name = {}
|
this.Inbound.handshake_for_server_name = {}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
this.Inbound.password = undefined
|
delete this.Inbound.password
|
||||||
if (Object.hasOwn(this.Inbound, 'users')) {
|
if (!Object.hasOwn(this.Inbound, 'users')) {
|
||||||
this.Inbound.users = []
|
this.Inbound.users = []
|
||||||
}
|
}
|
||||||
if (!this.Inbound.handshake_for_server_name) {
|
if (!this.Inbound.handshake_for_server_name) {
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
hide-details
|
hide-details
|
||||||
:label="$t('in.ssMethod')"
|
:label="$t('in.ssMethod')"
|
||||||
:items="ssMethods"
|
:items="ssMethods"
|
||||||
|
@update:model-value="changeMethod($event)"
|
||||||
v-model="data.method">
|
v-model="data.method">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :data="data" />
|
<Network :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -19,12 +17,24 @@
|
|||||||
<UoT :data="data" />
|
<UoT :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-row v-if="data.method.startsWith('2022')">
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field
|
||||||
|
v-model="data.password"
|
||||||
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
append-inner-icon="mdi-refresh"
|
||||||
|
@click:append-inner="changeMethod(data.method)">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<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 UoT from '@/components/UoT.vue';
|
||||||
|
import RandomUtil from '@/plugins/randomUtil';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['direction','data'],
|
props: ['direction','data'],
|
||||||
@@ -43,6 +53,15 @@ export default {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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 }
|
components: { Network, UoT }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<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>{{ $t('types.ssh.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.ssh.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Tun">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field v-model="addrs" :label="$t('types.tun.addr') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.interface_name" :label="$t('types.tun.ifName')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field type="number" v-model.number="data.mtu" label="MTU" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
type="number"
|
||||||
|
v-model.number="udpTimeout"
|
||||||
|
label="UDP timeout"
|
||||||
|
min="1"
|
||||||
|
:suffix="$t('date.m')"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="data.stack"
|
||||||
|
label="Stack"
|
||||||
|
:items="['system','gvisor','mixed']"
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.endpoint_independent_nat" color="primary" label="Independent NAT" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
addrs: {
|
||||||
|
get() { return this.$props.data.address?.join(',') },
|
||||||
|
set(v:string) { this.$props.data.address = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
udpTimeout: {
|
||||||
|
get() { return this.$props.data.udp_timeout ? parseInt(this.$props.data.udp_timeout.replace('m','')) : 5 },
|
||||||
|
set(v:number) { this.$props.data.udp_timeout = v > 0 ? v + 'm' : '5m' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
<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>{{ $t('types.lb.urlTestOptions') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.lb.urlTestOptions') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<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>{{ $t('types.wg.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('types.wg.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ const gaugeColor = computed(() => {
|
|||||||
background: `rgb(var(--v-theme-${gaugeColor}))`
|
background: `rgb(var(--v-theme-${gaugeColor}))`
|
||||||
}">
|
}">
|
||||||
</div>
|
</div>
|
||||||
<span class="gauge__cover" dir="ltr" v-html="data.text">
|
<div class="gauge__cover"><span dir="ltr" v-html="data.text"></span></div>
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="ACME" style="background-color: inherit;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('enable')" v-model="enabled" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="8" v-if="enabled">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.domain') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="domains">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="enabled">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionDir">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.acme.dataDir')"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.data_directory">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionDefault">
|
||||||
|
<v-combobox
|
||||||
|
v-model="acme.default_server_name"
|
||||||
|
:items="acme.domain"
|
||||||
|
:label="$t('tls.acme.defaultDomain')"
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionEmail">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('email')"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.email">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionChallenge">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('tls.acme.httpChallenge')" v-model="acme.disable_http_challenge" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('tls.acme.tlsChallenge')" v-model="acme.disable_tls_alpn_challenge" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionPorts">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.acme.altHport')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=1
|
||||||
|
max="65532"
|
||||||
|
v-model.number="acme.alternative_http_port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.acme.altTport')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=1
|
||||||
|
max="65532"
|
||||||
|
v-model.number="acme.alternative_tls_port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionProvider">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="caProvider"
|
||||||
|
:items="providerList"
|
||||||
|
:label="$t('tls.acme.caProvider')"
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="8" v-if="caProvider == ''">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.acme.customCa')"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.provider">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="acme.external_account != undefined">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="Key ID"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.external_account.key_id">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="MAC Key"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.external_account.mac_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="acme.dns01_challenge != undefined">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
:label="$t('tls.acme.dns01Provider')"
|
||||||
|
hide-details
|
||||||
|
:items="dnsProviders.map(d => d.provider)"
|
||||||
|
@update:model-value="acme.dns01_challenge = { provider: $event }"
|
||||||
|
v-model="acme.dns01_challenge.provider">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4"
|
||||||
|
v-for="item in dnsProviders.filter(d => d.provider == acme.dns01_challenge?.provider)[0]?.params"
|
||||||
|
:key="item">
|
||||||
|
<v-text-field
|
||||||
|
:label="item"
|
||||||
|
hide-details
|
||||||
|
v-model="acme.dns01_challenge[item]">
|
||||||
|
</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('tls.acme.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionDir" color="primary" :label="$t('tls.acme.dataDir')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionDefault" color="primary" :label="$t('tls.acme.defaultDomain')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionEmail" color="primary" :label="$t('email')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionChallenge" color="primary" :label="$t('tls.acme.disableChallenges')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionPorts" color="primary" :label="$t('tls.acme.altPorts')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionProvider" color="primary" :label="$t('tls.acme.caProvider')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionExt" color="primary" :label="$t('tls.acme.extAcc')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionDns01" color="primary" :label="$t('tls.acme.dns01')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { acme } from '@/types/inTls'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['tls'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
providerList: [
|
||||||
|
{ title: "Let's Encrypt", value: "letsencrypt" },
|
||||||
|
{ title: "ZeroSSL", value: "zerossl" },
|
||||||
|
{ title: "Custom", value: "" }
|
||||||
|
],
|
||||||
|
dnsProviders: [
|
||||||
|
{ provider: "cloudflare", params: [ "api_token" ] },
|
||||||
|
{ provider: "alidns", params: [ "access_key_id","access_key_secret","region_id" ] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
acme() {
|
||||||
|
return <acme>this.$props.tls.acme
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
get() { return this.acme != undefined },
|
||||||
|
set(v: boolean) { this.$props.tls.acme = v ? { domain: [] } : undefined }
|
||||||
|
},
|
||||||
|
domains: {
|
||||||
|
get() { return this.acme?.domain ? this.acme.domain.join(',') : "" },
|
||||||
|
set(v: string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.acme.domain = v.length > 0 ? v.split(',') : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
caProvider: {
|
||||||
|
get() { return this.acme?.provider && ['letsencrypt','zerossl'].includes(this.acme.provider) ? this.acme?.provider : '' },
|
||||||
|
set(v: string) { this.acme.provider = ['letsencrypt','zerossl'].includes(v) ? v : 'https://' }
|
||||||
|
},
|
||||||
|
optionDir: {
|
||||||
|
get(): boolean { return this.acme?.data_directory != undefined },
|
||||||
|
set(v:boolean) { this.acme.data_directory = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionDefault: {
|
||||||
|
get(): boolean { return this.acme?.default_server_name != undefined },
|
||||||
|
set(v:boolean) { this.acme.default_server_name = v ? this.domains.length>0 ? this.domains[0] : '' : undefined }
|
||||||
|
},
|
||||||
|
optionEmail: {
|
||||||
|
get(): boolean { return this.acme?.email != undefined },
|
||||||
|
set(v:boolean) { this.acme.email = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionChallenge: {
|
||||||
|
get(): boolean { return this.acme?.disable_http_challenge != undefined || this.acme?.disable_tls_alpn_challenge != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.acme.disable_http_challenge = false
|
||||||
|
this.acme.disable_tls_alpn_challenge = false
|
||||||
|
} else {
|
||||||
|
delete this.acme.disable_http_challenge
|
||||||
|
delete this.acme.disable_tls_alpn_challenge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionPorts: {
|
||||||
|
get(): boolean { return this.acme?.alternative_http_port != undefined || this.acme?.alternative_tls_port != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.acme.alternative_http_port = 80
|
||||||
|
this.acme.alternative_tls_port = 443
|
||||||
|
} else {
|
||||||
|
delete this.acme.alternative_http_port
|
||||||
|
delete this.acme.alternative_tls_port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionProvider: {
|
||||||
|
get(): boolean { return this.acme?.provider != undefined },
|
||||||
|
set(v:boolean) { this.acme.provider = v ? 'letsencrypt' : undefined }
|
||||||
|
},
|
||||||
|
optionExt: {
|
||||||
|
get(): boolean { return this.acme?.external_account != undefined },
|
||||||
|
set(v:boolean) { this.acme.external_account = v ? { key_id: '', mac_key: '' } : undefined }
|
||||||
|
},
|
||||||
|
optionDns01: {
|
||||||
|
get(): boolean { return this.acme?.dns01_challenge != undefined },
|
||||||
|
set(v:boolean) { this.acme.dns01_challenge = v ? { provider: 'cloudflare' } : undefined }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="ECH" style="background-color: inherit;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('enable')" v-model="enabled" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="enabled">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" label="Post-Quantum Schemes" v-model="ech.pq_signature_schemes_enabled" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" label="Disable Adaptive Size" v-model="ech.dynamic_record_sizing_disabled" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn-toggle v-model="useEchPath"
|
||||||
|
class="rounded-xl"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
shaped
|
||||||
|
mandatory>
|
||||||
|
<v-btn
|
||||||
|
@click="delete ech.key"
|
||||||
|
>{{ $t('tls.usePath') }}</v-btn>
|
||||||
|
<v-btn
|
||||||
|
@click="delete ech.key_path"
|
||||||
|
>{{ $t('tls.useText') }}</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn
|
||||||
|
variant="tonal"
|
||||||
|
density="compact"
|
||||||
|
icon="mdi-key-star"
|
||||||
|
@click="genECH"
|
||||||
|
:loading="loading">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('actions.generate') }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="useEchPath == 0">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.keyPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="ech.key_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.key')"
|
||||||
|
hide-details
|
||||||
|
v-model="echKeyText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.cert')"
|
||||||
|
hide-details
|
||||||
|
v-model="echConfigText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
import HttpUtils from '@/plugins/httputil'
|
||||||
|
import { ech } from '@/types/inTls'
|
||||||
|
import { push } from 'notivue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['iTls','oTls'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
useEchPath: this.$props.iTls?.ech?.key? 1:0,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async genECH(){
|
||||||
|
this.loading = true
|
||||||
|
const msg = await HttpUtils.get('api/keypairs', { k: "ech", o: this.iTls.server_name?? "''" })
|
||||||
|
this.loading = false
|
||||||
|
if (msg.success && this.iTls.ech && this.oTls.ech) {
|
||||||
|
this.iTls.ech.key_path=undefined
|
||||||
|
this.useEchPath = 1
|
||||||
|
if (msg.obj.length>0){
|
||||||
|
let config = <string[]>[]
|
||||||
|
let key = <string[]>[]
|
||||||
|
let isConfig = false
|
||||||
|
let isKey = false
|
||||||
|
|
||||||
|
msg.obj.forEach((line:string) => {
|
||||||
|
if (line === "-----BEGIN ECH CONFIGS-----") {
|
||||||
|
isConfig = true
|
||||||
|
isKey = false
|
||||||
|
config.push(line)
|
||||||
|
} else if (line === "-----END ECH CONFIGS-----") {
|
||||||
|
isConfig = false
|
||||||
|
config.push(line)
|
||||||
|
} else if (line === "-----BEGIN ECH KEYS-----") {
|
||||||
|
isKey = true
|
||||||
|
isConfig = false
|
||||||
|
key.push(line)
|
||||||
|
} else if (line === "-----END ECH KEYS-----") {
|
||||||
|
isKey = false
|
||||||
|
key.push(line)
|
||||||
|
} else if (isConfig) {
|
||||||
|
config.push(line)
|
||||||
|
} else if (isKey) {
|
||||||
|
key.push(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.iTls.ech.key = key?? undefined
|
||||||
|
this.oTls.ech.config = config?? undefined
|
||||||
|
|
||||||
|
} else {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error') + ": " + msg.obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
ech() {
|
||||||
|
return <ech>this.$props.iTls.ech
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
get() { return this.ech?.enabled?? false },
|
||||||
|
set(v: boolean) {
|
||||||
|
this.$props.iTls.ech = v ? { enabled: true } : undefined
|
||||||
|
this.$props.oTls.ech = v ? {} : undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
echKeyText: {
|
||||||
|
get(): string { return this.ech?.key ? this.ech.key.join('\n') : '' },
|
||||||
|
set(newValue:string) { this.ech.key = newValue.split('\n') }
|
||||||
|
},
|
||||||
|
echConfigText: {
|
||||||
|
get(): string { return this.oTls.ech?.config ? this.oTls.ech.config.join('\n') : '' },
|
||||||
|
set(newValue:string) { this.oTls.ech.config = newValue.split('\n') }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('objects.tls')">
|
<v-card :subtitle="$t('objects.tls')">
|
||||||
<v-row v-if="tlsOptional">
|
<v-row>
|
||||||
<v-col cols="auto">
|
<v-col cols="12" sm="6" md="4" v-if="tlsOptional">
|
||||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="tls.enabled">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('template')"
|
||||||
|
:items="tlsItems"
|
||||||
|
@update:model-value="changeTlsItem($event)"
|
||||||
|
v-model="tlsId">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<template v-if="tls.enabled">
|
<template v-if="tls.enabled && tlsId == 0">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-btn-toggle v-model="usePath"
|
<v-btn-toggle v-model="usePath"
|
||||||
@@ -103,44 +112,45 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
<v-card-actions v-if="tls.enabled">
|
<v-card-actions v-if="tls.enabled && tlsId == 0">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { i18n } from '@/locales'
|
||||||
import { iTls, defaultInTls } from '@/types/inTls'
|
import { iTls, defaultInTls } from '@/types/inTls'
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['inbound', 'tlsConfigs', 'tls_id'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
usePath: 0,
|
usePath: this.$props.inbound.tls.key == undefined ? 0 : 1,
|
||||||
defaults: defaultInTls,
|
defaults: defaultInTls,
|
||||||
alpn: [
|
alpn: [
|
||||||
{ title: "H3", value: 'h3' },
|
{ title: "H3", value: 'h3' },
|
||||||
@@ -173,9 +183,19 @@ export default {
|
|||||||
tls(): iTls {
|
tls(): iTls {
|
||||||
return <iTls> this.$props.inbound.tls
|
return <iTls> this.$props.inbound.tls
|
||||||
},
|
},
|
||||||
|
tlsItems(): any[] {
|
||||||
|
return [ { title: i18n.global.t('none'), value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
||||||
|
},
|
||||||
|
tlsId: {
|
||||||
|
get() { return this.tls_id.value?? 0 },
|
||||||
|
set(newValue: boolean) { this.$props.tls_id.value = newValue }
|
||||||
|
},
|
||||||
tlsEnable: {
|
tlsEnable: {
|
||||||
get() { return Object.hasOwn(this.$props.inbound.tls, 'enabled') ? this.tls.enabled : false },
|
get() { return this.tls.enabled?? false },
|
||||||
set(newValue: boolean) { this.$props.inbound.tls = newValue ? { enabled: true } : {} }
|
set(newValue: boolean) {
|
||||||
|
this.$props.inbound.tls = newValue ? { enabled: true } : {}
|
||||||
|
this.$props.tls_id.value = 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tlsOptional(): boolean {
|
tlsOptional(): boolean {
|
||||||
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
||||||
@@ -190,23 +210,33 @@ export default {
|
|||||||
},
|
},
|
||||||
optionSNI: {
|
optionSNI: {
|
||||||
get(): boolean { return this.tls.server_name != undefined },
|
get(): boolean { return this.tls.server_name != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.tls.server_name = v ? '' : undefined }
|
set(v:boolean) { this.tls.server_name = v ? '' : undefined }
|
||||||
},
|
},
|
||||||
optionALPN: {
|
optionALPN: {
|
||||||
get(): boolean { return this.tls.alpn != undefined },
|
get(): boolean { return this.tls.alpn != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.tls.alpn = v ? defaultInTls.alpn : undefined }
|
set(v:boolean) { this.tls.alpn = v ? defaultInTls.alpn : undefined }
|
||||||
},
|
},
|
||||||
optionMinV: {
|
optionMinV: {
|
||||||
get(): boolean { return this.tls.min_version != undefined },
|
get(): boolean { return this.tls.min_version != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.tls.min_version = v ? defaultInTls.min_version : undefined }
|
set(v:boolean) { this.tls.min_version = v ? defaultInTls.min_version : undefined }
|
||||||
},
|
},
|
||||||
optionMaxV: {
|
optionMaxV: {
|
||||||
get(): boolean { return this.tls.max_version != undefined },
|
get(): boolean { return this.tls.max_version != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.tls.max_version = v ? defaultInTls.max_version : undefined }
|
set(v:boolean) { this.tls.max_version = v ? defaultInTls.max_version : undefined }
|
||||||
},
|
},
|
||||||
optionCS: {
|
optionCS: {
|
||||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
set(v:boolean) { this.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeTlsItem(id: number){
|
||||||
|
if (id>0) {
|
||||||
|
const tlsConfig = this.$props.tlsConfigs?.findLast((t:any) => t.id == id)
|
||||||
|
if (tlsConfig) this.$props.inbound.tls = tlsConfig.server
|
||||||
|
} else {
|
||||||
|
this.$props.inbound.tls = { enabled: this.tls.enabled }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<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>{{ $t('tls.options') }}</v-btn>
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -222,8 +222,8 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
usePath: 0,
|
usePath: this.$props.outbound?.tls?.certificate? 1:0,
|
||||||
useEchPath: 0,
|
useEchPath: this.$props.outbound?.tls.ech?.config? 1:0,
|
||||||
defaults: defaultOutTls,
|
defaults: defaultOutTls,
|
||||||
alpn: [
|
alpn: [
|
||||||
{ title: "H3", value: 'h3' },
|
{ title: "H3", value: 'h3' },
|
||||||
@@ -2,20 +2,22 @@
|
|||||||
<v-app-bar :elevation="5">
|
<v-app-bar :elevation="5">
|
||||||
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
||||||
<span v-else style="width: 24px"></span>
|
<span v-else style="width: 24px"></span>
|
||||||
<v-app-bar-title :text="$t(<string>$router.currentRoute.value.name)" class="align-center text-center " />
|
<v-app-bar-title :text="$t(<string>route.name)" class="align-center text-center " />
|
||||||
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
|
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
|
||||||
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref,watch } from "vue"
|
import { computed, ref } from "vue"
|
||||||
import { useTheme } from "vuetify"
|
import { useTheme } from "vuetify"
|
||||||
import { FindDiff } from "@/plugins/utils"
|
import { FindDiff } from "@/plugins/utils"
|
||||||
import Data from "@/store/modules/data"
|
import Data from "@/store/modules/data"
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
defineProps(['isMobile'])
|
defineProps(['isMobile'])
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
||||||
|
|
||||||
@@ -32,11 +34,11 @@ const saveChanges = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oldData = computed((): any => {
|
const oldData = computed((): any => {
|
||||||
return {config: store.oldData.config, clients: store.oldData.clients}
|
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs, inData: store.oldData.inData}
|
||||||
})
|
})
|
||||||
|
|
||||||
const newData = computed((): any => {
|
const newData = computed((): any => {
|
||||||
return {config: store.config, clients: store.clients}
|
return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
|
||||||
})
|
})
|
||||||
|
|
||||||
const stateChange = computed((): any => {
|
const stateChange = computed((): any => {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const menu = [
|
|||||||
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
||||||
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
||||||
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
||||||
|
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
||||||
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
||||||
{ title: 'pages.admins', icon: 'mdi-account-tie', path: '/admins' },
|
{ title: 'pages.admins', icon: 'mdi-account-tie', path: '/admins' },
|
||||||
{ title: 'pages.settings', icon: 'mdi-cog', path: '/settings' },
|
{ title: 'pages.settings', icon: 'mdi-cog', path: '/settings' },
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="90%" max-width="800" :loading="loading">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('admin.changes') }}</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto"><v-icon icon="mdi-close-box" @click="$emit('close')" /></v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="4" md="3">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('admin.actor')"
|
||||||
|
:items="['', 'DepleteJob', ...admins]"
|
||||||
|
v-model="user"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="4" md="3">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('admin.key')"
|
||||||
|
:items="['', 'inbounds', 'outbounds', 'clients', 'route', 'tls', 'experimental']"
|
||||||
|
v-model="key"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6" sm="4" md="3">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('count')"
|
||||||
|
:items="[10,20,30,50,100]"
|
||||||
|
v-model.number="chngCount"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto" align="center" justify="center">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-refresh"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadData">
|
||||||
|
<v-icon />
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-data-table
|
||||||
|
:headers="changesHeaders"
|
||||||
|
:items="changes"
|
||||||
|
item-value="id"
|
||||||
|
density="compact"
|
||||||
|
show-expand
|
||||||
|
items-per-page="10"
|
||||||
|
>
|
||||||
|
<template v-slot:item.dateTime="{ value }">
|
||||||
|
<v-chip variant="text" dir="ltr" density="compact">
|
||||||
|
{{ dateFormatted(value) }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item.action="{ value }">
|
||||||
|
<v-chip density="compact">
|
||||||
|
{{ $t('actions.' + value) }}
|
||||||
|
</v-chip>
|
||||||
|
</template>
|
||||||
|
<template v-slot:expanded-row="{ columns, item }">
|
||||||
|
<tr>
|
||||||
|
<td :colspan="columns.length">
|
||||||
|
<v-card dir="ltr" v-if="item.index>0">Index: {{ item.index }}</v-card>
|
||||||
|
<v-card style="background-color: background" dir="ltr"><pre>{{ item.obj }}</pre></v-card>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</v-data-table>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
import HttpUtils from '@/plugins/httputil'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['admins', 'actor', 'visible'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
changes: <any[]>[],
|
||||||
|
user: '',
|
||||||
|
key: '',
|
||||||
|
chngCount: 10,
|
||||||
|
expanded: [],
|
||||||
|
changesHeaders: [
|
||||||
|
{ title: 'ID', key: 'id' },
|
||||||
|
{ title: i18n.global.t('admin.date') + '-' + i18n.global.t('admin.time'), key: 'dateTime' },
|
||||||
|
{ title: i18n.global.t('admin.actor'), key: 'Actor' },
|
||||||
|
{ title: i18n.global.t('admin.key'), key: 'key' },
|
||||||
|
{ title: i18n.global.t('admin.action'), key: 'action' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadData() {
|
||||||
|
this.loading = true
|
||||||
|
const data = await HttpUtils.get('api/changes',{ a: this.user, k: this.key, c: this.chngCount })
|
||||||
|
if (data.success) {
|
||||||
|
this.changes = data.obj?? []
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dateFormatted(dt: number): string {
|
||||||
|
const date = new Date(dt*1000)
|
||||||
|
return date.toLocaleString(this.locale)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
locale() {
|
||||||
|
const l = i18n.global.locale.value
|
||||||
|
switch (l) {
|
||||||
|
case "zhHans":
|
||||||
|
return "zh-cn"
|
||||||
|
case "zhHant":
|
||||||
|
return "zh-tw"
|
||||||
|
default:
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
this.changes = []
|
||||||
|
this.user = this.$props.actor
|
||||||
|
this.key = ''
|
||||||
|
this.chngCount = 10
|
||||||
|
if (newValue) {
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
{{ $t('actions.' + title) + " " + $t('objects.client') }}
|
{{ $t('actions.' + title) + " " + $t('objects.client') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text style="padding: 0 16px;">
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
<v-container style="padding: 0;">
|
<v-container style="padding: 0;">
|
||||||
<v-tabs
|
<v-tabs
|
||||||
v-model="tab"
|
v-model="tab"
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" v-model="client.enable" :label="$t('enable')" hide-details></v-switch>
|
<v-switch color="primary" v-model="client.enable" :label="$t('enable')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-combobox v-model="client.group" :items="groups" :label="$t('client.group')" hide-details></v-combobox>
|
||||||
|
</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">
|
||||||
@@ -38,6 +41,33 @@
|
|||||||
<DatePick :expiry="expDate" @submit="setDate" />
|
<DatePick :expiry="expDate" @submit="setDate" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-row v-if="index != -1">
|
||||||
|
<v-col cols="12" sm="6" md="4" class="d-flex flex-column">
|
||||||
|
<div class="d-flex justify-space-between align-center">
|
||||||
|
<div>
|
||||||
|
{{ $t('stats.usage') }}: {{ total }}<sup dir="ltr" v-if="percent>0">({{ percent }}%)</sup>
|
||||||
|
</div>
|
||||||
|
<v-btn density="compact" variant="text" icon="mdi-restore" @click="client.up=0;client.down=0">
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('reset') }}
|
||||||
|
</v-tooltip>
|
||||||
|
<v-icon />
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
<v-progress-linear
|
||||||
|
v-model="percent"
|
||||||
|
:color="percentColor"
|
||||||
|
v-if="client.volume>0"
|
||||||
|
bottom
|
||||||
|
>
|
||||||
|
</v-progress-linear>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-icon icon="mdi-upload" color="orange" /><span class="text-orange">{{ up }}</span>
|
||||||
|
/
|
||||||
|
<v-icon icon="mdi-download" color="success" /><span class="text-success">{{ down }}</span>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-combobox
|
<v-combobox
|
||||||
@@ -156,9 +186,10 @@
|
|||||||
import { Link } from '@/plugins/link'
|
import { Link } from '@/plugins/link'
|
||||||
import { createClient, randomConfigs, updateConfigs } from '@/types/clients'
|
import { createClient, randomConfigs, updateConfigs } from '@/types/clients'
|
||||||
import DatePick from '@/components/DateTime.vue'
|
import DatePick from '@/components/DateTime.vue'
|
||||||
|
import { HumanReadable } from '@/plugins/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'index', 'inboundTags', 'stats'],
|
props: ['visible', 'data', 'index', 'inboundTags', 'groups', 'stats'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -179,7 +210,7 @@ export default {
|
|||||||
const newData = JSON.parse(this.$props.data)
|
const newData = JSON.parse(this.$props.data)
|
||||||
this.client = createClient(newData)
|
this.client = createClient(newData)
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
this.clientConfig = JSON.parse(this.client.config)
|
this.clientConfig = this.client.config
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.client = createClient()
|
this.client = createClient()
|
||||||
@@ -187,10 +218,9 @@ export default {
|
|||||||
this.clientConfig = randomConfigs('client')
|
this.clientConfig = randomConfigs('client')
|
||||||
}
|
}
|
||||||
this.clientStats = this.$props.stats
|
this.clientStats = this.$props.stats
|
||||||
const allLinks = <Link[]>JSON.parse(this.client.links)
|
this.links = this.client.links.filter(l => l.type == 'local')
|
||||||
this.links = allLinks.filter(l => l.type == 'local')
|
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
||||||
this.extLinks = allLinks.filter(l => l.type == 'external')
|
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
||||||
this.subLinks = allLinks.filter(l => l.type == 'sub')
|
|
||||||
this.tab = "t1"
|
this.tab = "t1"
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
@@ -199,11 +229,11 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.client.config = updateConfigs(JSON.stringify(this.clientConfig), this.client.name)
|
this.client.config = updateConfigs(this.clientConfig, this.client.name)
|
||||||
this.client.links = JSON.stringify([
|
this.client.links = [
|
||||||
...this.links,
|
...this.links,
|
||||||
...this.extLinks.filter(l => l.uri != ''),
|
...this.extLinks.filter(l => l.uri != ''),
|
||||||
...this.subLinks.filter(l => l.uri != '')])
|
...this.subLinks.filter(l => l.uri != '')]
|
||||||
this.$emit('save', this.client, this.clientStats)
|
this.$emit('save', this.client, this.clientStats)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
@@ -213,8 +243,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
clientInbounds: {
|
clientInbounds: {
|
||||||
get() { return this.client.inbounds == "" ? [] : this.client.inbounds.split(',').filter(i => this.inboundTags.includes(i)) },
|
get() { return this.client.inbounds.length>0 ? this.client.inbounds.filter(i => this.inboundTags.includes(i)) : [] },
|
||||||
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? "" : newValue.join(',') }
|
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? [] : newValue }
|
||||||
},
|
},
|
||||||
expDate: {
|
expDate: {
|
||||||
get() { return this.client.expiry},
|
get() { return this.client.expiry},
|
||||||
@@ -223,7 +253,12 @@ export default {
|
|||||||
Volume: {
|
Volume: {
|
||||||
get() { return this.client.volume == 0 ? 0 : (this.client.volume / (1024 ** 3)) },
|
get() { return this.client.volume == 0 ? 0 : (this.client.volume / (1024 ** 3)) },
|
||||||
set(v:number) { this.client.volume = v > 0 ? v*(1024 ** 3) : 0 }
|
set(v:number) { this.client.volume = v > 0 ? v*(1024 ** 3) : 0 }
|
||||||
}
|
},
|
||||||
|
up() :string { return HumanReadable.sizeFormat(this.client.up) },
|
||||||
|
down() :string { return HumanReadable.sizeFormat(this.client.down) },
|
||||||
|
total() :string { return HumanReadable.sizeFormat(this.client.down + this.client.up) },
|
||||||
|
percent() :number { return this.client.volume>0 ? Math.round((this.client.up + this.client.down) *100 / this.client.volume) : 0 },
|
||||||
|
percentColor() :string { return (this.client.up+this.client.down) >= this.client.volume ? 'error' : this.percent>90 ? 'warning' : 'success' },
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newValue) {
|
visible(newValue) {
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('bulk.add') }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
|
<v-container style="padding: 0;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model.number="count" type="number" min="1" max="100" :label="$t('count')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-combobox
|
||||||
|
chips
|
||||||
|
multiple
|
||||||
|
v-model="bulkData.name"
|
||||||
|
:items="patterns"
|
||||||
|
:label="$t('client.name')"
|
||||||
|
hide-details>
|
||||||
|
</v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-combobox
|
||||||
|
chips
|
||||||
|
multiple
|
||||||
|
v-model="bulkData.desc"
|
||||||
|
:items="patterns"
|
||||||
|
:label="$t('client.desc')"
|
||||||
|
hide-details>
|
||||||
|
</v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-combobox v-model="bulkData.group" :items="groups" :label="$t('client.group')" hide-details></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model.number="bulkData.Volume" type="number" min="0" :label="$t('stats.volume')" suffix="GiB" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<DatePick :expiry="bulkData.expiry" @submit="setDate" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-combobox
|
||||||
|
v-model="bulkData.clientInbounds"
|
||||||
|
:items="inboundTags"
|
||||||
|
:label="$t('client.inboundTags')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-switch v-model="bulkData.clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="outlined"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import DatePick from '@/components/DateTime.vue'
|
||||||
|
import { push } from 'notivue'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
import { Client, createClient, randomConfigs } from '@/types/clients'
|
||||||
|
import { i18n } from '@/locales';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'inboundTags', 'groups'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
count: 1,
|
||||||
|
clients: <Client[]>[],
|
||||||
|
bulkData: {
|
||||||
|
name: <any[]>[],
|
||||||
|
desc: <any[]>[],
|
||||||
|
group: '',
|
||||||
|
clientInbounds: [],
|
||||||
|
expiry: 0,
|
||||||
|
Volume: 0,
|
||||||
|
clientStats: false,
|
||||||
|
},
|
||||||
|
patterns: [
|
||||||
|
{ title: i18n.global.t("bulk.random"), value: "random" },
|
||||||
|
{ title: i18n.global.t("bulk.order"), value: "order" },
|
||||||
|
],
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetData() {
|
||||||
|
this.count = 1,
|
||||||
|
this.clients = [],
|
||||||
|
this.bulkData = {
|
||||||
|
name: [this.patterns[1], "-", this.patterns[0]],
|
||||||
|
desc: [],
|
||||||
|
group: '',
|
||||||
|
clientInbounds: [],
|
||||||
|
expiry: 0,
|
||||||
|
Volume: 0,
|
||||||
|
clientStats: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
if (this.bulkData.name.findIndex(n => typeof(n) == 'object') == -1) {
|
||||||
|
push.error(i18n.global.t('error.dplData'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
for(let i=0;i<this.count;i++){
|
||||||
|
const name = this.genByPattern(this.bulkData.name, i)
|
||||||
|
this.clients.push(createClient({
|
||||||
|
enable: true,
|
||||||
|
name: name,
|
||||||
|
config: randomConfigs(name),
|
||||||
|
inbounds: this.bulkData.clientInbounds,
|
||||||
|
links: [],
|
||||||
|
volume: this.bulkData.Volume*(1024 ** 3),
|
||||||
|
expiry: this.bulkData.expiry,
|
||||||
|
up: 0,
|
||||||
|
down: 0,
|
||||||
|
desc: this.genByPattern(this.bulkData.desc, i),
|
||||||
|
group: this.bulkData.group
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
|
||||||
|
this.resetData() // reset to default
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
genByPattern(pattern: any[], order :number){
|
||||||
|
if (pattern.length == 0) return RandomUtil.randomSeq(8)
|
||||||
|
let result = ''
|
||||||
|
pattern.forEach(p => {
|
||||||
|
switch(typeof p){
|
||||||
|
case 'object':
|
||||||
|
switch(p.value){
|
||||||
|
case "random":
|
||||||
|
result += RandomUtil.randomSeq(8)
|
||||||
|
break
|
||||||
|
case "order":
|
||||||
|
result += order+1
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result += p
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
setDate(v:number){
|
||||||
|
this.bulkData.expiry = v
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.resetData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { DatePick },
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -5,35 +5,66 @@
|
|||||||
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
<v-row>
|
<v-container style="padding: 0;">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-row>
|
||||||
<v-select
|
<v-col cols="12" sm="6" md="4">
|
||||||
hide-details
|
<v-select
|
||||||
:label="$t('type')"
|
hide-details
|
||||||
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
:label="$t('type')"
|
||||||
v-model="inbound.type"
|
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
||||||
@update:modelValue="changeType">
|
v-model="inbound.type"
|
||||||
</v-select>
|
@update:modelValue="changeType">
|
||||||
</v-col>
|
</v-select>
|
||||||
<v-col cols="12" sm="6" md="4">
|
</v-col>
|
||||||
<v-text-field v-model="inbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
<v-col cols="12" sm="6" md="4">
|
||||||
</v-col>
|
<v-text-field v-model="inbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
</v-row>
|
</v-col>
|
||||||
<Listen :inbound="inbound" :inTags="inTags" />
|
</v-row>
|
||||||
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
<v-tabs
|
||||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
v-if="HasInData.includes(inbound.type)"
|
||||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
v-model="side"
|
||||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
density="compact"
|
||||||
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
fixed-tabs
|
||||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
align-tabs="center"
|
||||||
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
>
|
||||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
<v-tab value="s">{{ $t('in.sSide') }}</v-tab>
|
||||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
<v-tab value="c">{{ $t('in.cSide') }}</v-tab>
|
||||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
</v-tabs>
|
||||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" />
|
<v-window v-model="side" style="margin-top: 10px;">
|
||||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
<v-window-item value="s">
|
||||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
<Listen :inbound="inbound" :inTags="inTags" />
|
||||||
|
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
||||||
|
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||||
|
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||||
|
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||||
|
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
||||||
|
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
||||||
|
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
||||||
|
<Tun v-if="inbound.type == inTypes.Tun" :data="inbound" />
|
||||||
|
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||||
|
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||||
|
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
||||||
|
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||||
|
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||||
|
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||||
|
</v-window-item>
|
||||||
|
<v-window-item value="c">
|
||||||
|
<OutJsonVue :inData="inData" :type="inbound.type" />
|
||||||
|
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inData.outJson" />
|
||||||
|
<v-card>
|
||||||
|
<v-card-subtitle>{{ $t('in.multiDomain') }}
|
||||||
|
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<template v-for="addr,index in inData.addrs">
|
||||||
|
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inData.addrs.splice(index,1)" />
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<AddrVue :addr="addr" :hasTls="Object.hasOwn(inbound,'tls')" />
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -57,9 +88,11 @@
|
|||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { InTypes, createInbound } from '@/types/inbounds'
|
import { InTypes, createInbound } from '@/types/inbounds'
|
||||||
|
import { Addr, InData } from '@/plugins/inData'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
|
||||||
import Listen from '@/components/Listen.vue'
|
import Listen from '@/components/Listen.vue'
|
||||||
import Direct from '@/components/protocols/Direct.vue'
|
import Direct from '@/components/protocols/Direct.vue'
|
||||||
import Users from '@/components/Users.vue'
|
import Users from '@/components/Users.vue'
|
||||||
@@ -69,44 +102,89 @@ import Hysteria2 from '@/components/protocols/Hysteria2.vue'
|
|||||||
import Naive from '@/components/protocols/Naive.vue'
|
import Naive from '@/components/protocols/Naive.vue'
|
||||||
import ShadowTls from '@/components/protocols/ShadowTls.vue'
|
import ShadowTls from '@/components/protocols/ShadowTls.vue'
|
||||||
import Tuic from '@/components/protocols/Tuic.vue'
|
import Tuic from '@/components/protocols/Tuic.vue'
|
||||||
import InTls from '@/components/InTLS.vue'
|
import Tun from '@/components/protocols/Tun.vue'
|
||||||
|
import InTls from '@/components/tls/InTLS.vue'
|
||||||
import TProxy from '@/components/protocols/TProxy.vue'
|
import TProxy from '@/components/protocols/TProxy.vue'
|
||||||
import RandomUtil from '@/plugins/randomUtil'
|
|
||||||
import Multiplex from '@/components/Multiplex.vue'
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
|
import AddrVue from '@/components/Addr.vue'
|
||||||
|
import OutJsonVue from '@/components/OutJson.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags'],
|
props: ['visible', 'data', 'cData', 'index', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inbound: createInbound("direct",{ "tag": "" }),
|
inbound: createInbound("direct",{ "tag": "" }),
|
||||||
|
inData: <InData>{},
|
||||||
title: "add",
|
title: "add",
|
||||||
loading: false,
|
loading: false,
|
||||||
|
side: "s",
|
||||||
inTypes: InTypes,
|
inTypes: InTypes,
|
||||||
inboundStats: false,
|
inboundStats: false,
|
||||||
|
tls_id: { value: 0 },
|
||||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||||
|
HasInData: [
|
||||||
|
InTypes.SOCKS,
|
||||||
|
InTypes.HTTP,
|
||||||
|
InTypes.Shadowsocks,
|
||||||
|
InTypes.VMess,
|
||||||
|
InTypes.ShadowTLS,
|
||||||
|
InTypes.Trojan,
|
||||||
|
InTypes.Hysteria,
|
||||||
|
InTypes.VLESS,
|
||||||
|
InTypes.TUIC,
|
||||||
|
InTypes.Hysteria2,
|
||||||
|
InTypes.Naive,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.id != -1) {
|
if (this.$props.index != -1) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
const newData = JSON.parse(this.$props.data)
|
||||||
this.inbound = createInbound(newData.type, newData)
|
this.inbound = createInbound(newData.type, newData)
|
||||||
|
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
this.inData = this.$props.cData?.length> 0 ? <InData>JSON.parse(this.$props.cData) : <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||||
|
this.tls_id.value = 0
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
}
|
}
|
||||||
this.inboundStats = this.$props.stats
|
this.inboundStats = this.$props.stats
|
||||||
|
this.side = "s"
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
// Tag change only in add outbound
|
if (!this.inbound.listen_port) this.inbound.listen_port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
const tag = this.$props.id != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
// Tag change only in add inbound
|
||||||
|
const tag = this.$props.index != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||||
// Use previous data
|
// Use previous data
|
||||||
const prevConfig = { tag: tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
const prevConfig = { tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||||
this.inbound = createInbound(this.inbound.type, prevConfig)
|
this.inbound = createInbound(this.inbound.type, this.inbound.type != this.inTypes.Tun ? prevConfig : { tag: tag })
|
||||||
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
|
if (this.inData.id == -1) this.inData.id = 0
|
||||||
|
this.inData.addrs = []
|
||||||
|
this.inData.outJson = {}
|
||||||
|
this.inData.tag = tag
|
||||||
|
} else {
|
||||||
|
this.inData = <InData>{id: -1}
|
||||||
|
}
|
||||||
|
this.tls_id.value = 0
|
||||||
|
this.side = "s"
|
||||||
|
},
|
||||||
|
add_addr() {
|
||||||
|
this.inData.addrs.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.updateData() // reset
|
this.updateData() // reset
|
||||||
@@ -114,7 +192,7 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$emit('save', this.inbound, this.inboundStats)
|
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value, this.inData)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -125,6 +203,10 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Transport }
|
components: {
|
||||||
|
Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks,
|
||||||
|
Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Tun, Transport,
|
||||||
|
AddrVue, OutJsonVue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="90%" max-width="1200" :loading="loading">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('basic.log.title') + " - " + (logType == 's-ui'? "S-UI" : "Sing-Box") }}</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-icon icon="mdi-close" @click="$emit('close')" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('basic.log.level')"
|
||||||
|
:items="logLevels"
|
||||||
|
v-model="logLevel"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('count')"
|
||||||
|
:items="[10,20,30,50,100]"
|
||||||
|
v-model.number="logCount"
|
||||||
|
@update:model-value="loadData">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="auto" align="center" justify="center">
|
||||||
|
<v-btn
|
||||||
|
icon="mdi-refresh"
|
||||||
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadData">
|
||||||
|
<v-icon />
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card style="background-color: background" dir="ltr" v-html="lines.join('<br />')"></v-card>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import HttpUtils from '@/plugins/httputil';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['logType', 'visible'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
lines: [],
|
||||||
|
logLevel: 'info',
|
||||||
|
logLevels: [
|
||||||
|
{ title: 'DEBUG', value: 'debug' },
|
||||||
|
{ title: 'INFO', value: 'info' },
|
||||||
|
{ title: 'WARNING', value: 'warning' },
|
||||||
|
{ title: 'ERROR', value: 'err' },
|
||||||
|
],
|
||||||
|
logCount: 10,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadData() {
|
||||||
|
this.loading = true
|
||||||
|
const data = await HttpUtils.get('api/logs',{ s: this.$props.logType, c: this.logCount, l: this.logLevel })
|
||||||
|
if (data.success) {
|
||||||
|
this.lines = data.obj?? []
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
this.lines = []
|
||||||
|
this.logLevel = 'info'
|
||||||
|
this.logCount = 10
|
||||||
|
if (newValue) {
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -5,61 +5,84 @@
|
|||||||
{{ $t('actions.' + title) + " " + $t('objects.outbound') }}
|
{{ $t('actions.' + title) + " " + $t('objects.outbound') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
<v-row>
|
<v-container style="padding: 0;">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-tabs
|
||||||
<v-select
|
v-model="tab"
|
||||||
hide-details
|
align-tabs="center"
|
||||||
:label="$t('type')"
|
>
|
||||||
:items="Object.keys(outTypes).map((key,index) => ({title: key, value: Object.values(outTypes)[index]}))"
|
<v-tab value="t1">{{ $t('client.basics') }}</v-tab>
|
||||||
v-model="outbound.type"
|
<v-tab value="t2">{{ $t('client.external') }}</v-tab>
|
||||||
@update:modelValue="changeType">
|
</v-tabs>
|
||||||
</v-select>
|
<v-window v-model="tab">
|
||||||
</v-col>
|
<v-window-item value="t1">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-row>
|
||||||
<v-text-field v-model="outbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
<v-col cols="12" sm="6" md="4">
|
||||||
</v-col>
|
<v-select
|
||||||
</v-row>
|
hide-details
|
||||||
<v-row v-if="!NoServer.includes(outbound.type)">
|
:label="$t('type')"
|
||||||
<v-col cols="12" sm="6" md="4">
|
:items="Object.keys(outTypes).map((key,index) => ({title: key, value: Object.values(outTypes)[index]}))"
|
||||||
<v-text-field
|
v-model="outbound.type"
|
||||||
:label="$t('out.addr')"
|
@update:modelValue="changeType">
|
||||||
hide-details
|
</v-select>
|
||||||
v-model="outbound.server">
|
</v-col>
|
||||||
</v-text-field>
|
<v-col cols="12" sm="6" md="4">
|
||||||
</v-col>
|
<v-text-field v-model="outbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
<v-col cols="12" sm="6" md="4">
|
</v-col>
|
||||||
<v-text-field
|
</v-row>
|
||||||
:label="$t('out.addr')"
|
<v-row v-if="!NoServer.includes(outbound.type)">
|
||||||
type="number"
|
<v-col cols="12" sm="6" md="4">
|
||||||
min="0"
|
<v-text-field
|
||||||
hide-details
|
:label="$t('out.addr')"
|
||||||
v-model.number="outbound.server_port">
|
hide-details
|
||||||
</v-text-field>
|
v-model="outbound.server">
|
||||||
</v-col>
|
</v-text-field>
|
||||||
</v-row>
|
</v-col>
|
||||||
<Direct v-if="outbound.type == outTypes.Direct" direction="out" :data="outbound" />
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
<v-text-field
|
||||||
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
:label="$t('out.port')"
|
||||||
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
type="number"
|
||||||
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
min="0"
|
||||||
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
hide-details
|
||||||
<Wireguard v-if="outbound.type == outTypes.Wireguard" :data="outbound" />
|
v-model.number="outbound.server_port">
|
||||||
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
</v-text-field>
|
||||||
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
</v-col>
|
||||||
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
</v-row>
|
||||||
<Tuic v-if="outbound.type == outTypes.TUIC" direction="out" :data="outbound" />
|
<Direct v-if="outbound.type == outTypes.Direct" direction="out" :data="outbound" />
|
||||||
<Hysteria2 v-if="outbound.type == outTypes.Hysteria2" direction="out" :data="outbound" />
|
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
||||||
<Tor v-if="outbound.type == outTypes.Tor" :data="outbound" />
|
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
||||||
<Ssh v-if="outbound.type == outTypes.SSH" :data="outbound" />
|
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
||||||
<Selector v-if="outbound.type == outTypes.Selector" :data="outbound" :tags="tags" />
|
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
||||||
<UrlTest v-if="outbound.type == outTypes.URLTest" :data="outbound" :tags="tags" />
|
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
||||||
|
<Wireguard v-if="outbound.type == outTypes.Wireguard" :data="outbound" />
|
||||||
|
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
||||||
|
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
||||||
|
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
||||||
|
<Tuic v-if="outbound.type == outTypes.TUIC" direction="out" :data="outbound" />
|
||||||
|
<Hysteria2 v-if="outbound.type == outTypes.Hysteria2" direction="out" :data="outbound" />
|
||||||
|
<Tor v-if="outbound.type == outTypes.Tor" :data="outbound" />
|
||||||
|
<Ssh v-if="outbound.type == outTypes.SSH" :data="outbound" />
|
||||||
|
<Selector v-if="outbound.type == outTypes.Selector" :data="outbound" :tags="tags" />
|
||||||
|
<UrlTest v-if="outbound.type == outTypes.URLTest" :data="outbound" :tags="tags" />
|
||||||
|
|
||||||
<Transport v-if="Object.hasOwn(outbound,'transport')" :data="outbound" />
|
<Transport v-if="Object.hasOwn(outbound,'transport')" :data="outbound" />
|
||||||
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
||||||
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
||||||
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
||||||
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||||
|
</v-window-item>
|
||||||
|
<v-window-item value="t2">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field v-model="link" :label="$t('client.external')" hide-details />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" align="center">
|
||||||
|
<v-btn hide-details variant="tonal" :loading="loading" @click="linkConvert">{{ $t('submit') }}</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -89,7 +112,7 @@ import RandomUtil from '@/plugins/randomUtil'
|
|||||||
import Dial from '@/components/Dial.vue'
|
import Dial from '@/components/Dial.vue'
|
||||||
import Multiplex from '@/components/Multiplex.vue'
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
import OutTLS from '@/components/OutTLS.vue'
|
import OutTLS from '@/components/tls/OutTLS.vue'
|
||||||
import Direct from '@/components/protocols/Direct.vue'
|
import Direct from '@/components/protocols/Direct.vue'
|
||||||
import Socks from '@/components/protocols/Socks.vue'
|
import Socks from '@/components/protocols/Socks.vue'
|
||||||
import Http from '@/components/protocols/Http.vue'
|
import Http from '@/components/protocols/Http.vue'
|
||||||
@@ -106,6 +129,7 @@ import Tor from '@/components/protocols/Tor.vue'
|
|||||||
import Ssh from '@/components/protocols/Ssh.vue'
|
import Ssh from '@/components/protocols/Ssh.vue'
|
||||||
import Selector from '@/components/protocols/Selector.vue'
|
import Selector from '@/components/protocols/Selector.vue'
|
||||||
import UrlTest from '@/components/protocols/UrlTest.vue'
|
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||||
|
import HttpUtils from '@/plugins/httputil'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
@@ -113,6 +137,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
outbound: createOutbound("direct",{ "tag": "" }),
|
outbound: createOutbound("direct",{ "tag": "" }),
|
||||||
title: "add",
|
title: "add",
|
||||||
|
tab: "t1",
|
||||||
|
link: "",
|
||||||
loading: false,
|
loading: false,
|
||||||
outTypes: OutTypes,
|
outTypes: OutTypes,
|
||||||
outboundStats: false,
|
outboundStats: false,
|
||||||
@@ -131,6 +157,7 @@ export default {
|
|||||||
this.outbound = createOutbound("direct",{ tag: "direct-" + RandomUtil.randomSeq(3) })
|
this.outbound = createOutbound("direct",{ tag: "direct-" + RandomUtil.randomSeq(3) })
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
}
|
}
|
||||||
|
this.tab = "t1"
|
||||||
this.outboundStats = this.$props.stats
|
this.outboundStats = this.$props.stats
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
@@ -149,6 +176,18 @@ export default {
|
|||||||
this.$emit('save', this.outbound, this.outboundStats)
|
this.$emit('save', this.outbound, this.outboundStats)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
|
async linkConvert() {
|
||||||
|
if (this.link.length>0){
|
||||||
|
this.loading = true
|
||||||
|
const msg = await HttpUtils.post('api/linkConvert', { link: this.link })
|
||||||
|
this.loading = false
|
||||||
|
if (msg.success) {
|
||||||
|
this.outbound = msg.obj
|
||||||
|
this.tab = "t1"
|
||||||
|
this.link = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newValue) {
|
visible(newValue) {
|
||||||
|
|||||||
@@ -1,27 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog transition="dialog-bottom-transition" width="400">
|
<v-dialog transition="dialog-bottom-transition" width="400">
|
||||||
<v-card class="rounded-lg">
|
<v-card class="rounded-lg" id="qrcode-modal">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>QrCode</v-col>
|
<v-col>QrCode</v-col>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-col cols="1"><v-icon icon="mdi-close-box" @click="$emit('close')" ></v-icon></v-col>
|
<v-col cols="auto"><v-icon icon="mdi-close-box" @click="$emit('close')" /></v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text style="overflow-y: auto; padding: 0">
|
||||||
<v-row>
|
<v-tabs
|
||||||
<v-col style="text-align: center;" @click="copyToClipboard(clientSub)">
|
v-model="tab"
|
||||||
<v-chip>{{ $t('setting.sub') }}</v-chip>
|
density="compact"
|
||||||
<QrcodeVue :value="clientSub" :size="300" :margin="1" style="border-radius: 1rem;" />
|
fixed-tabs
|
||||||
</v-col>
|
align-tabs="center"
|
||||||
</v-row>
|
>
|
||||||
<v-row v-for="l in clientLinks">
|
<v-tab value="sub">{{ $t('setting.sub') }}</v-tab>
|
||||||
<v-col style="text-align: center;" @click="copyToClipboard(l.uri)">
|
<v-tab value="link">{{ $t('client.links') }}</v-tab>
|
||||||
<v-chip>{{ l.remark }}</v-chip><br />
|
</v-tabs>
|
||||||
<QrcodeVue :value="l.uri" :size="300" :margin="1" style="border-radius: 1rem;" />
|
<v-window v-model="tab" style="margin-top: 10px;">
|
||||||
</v-col>
|
<v-window-item value="sub">
|
||||||
</v-row>
|
<v-row>
|
||||||
|
<v-col style="text-align: center;">
|
||||||
|
<v-chip>{{ $t('setting.sub') }}</v-chip><br />
|
||||||
|
<QrcodeVue :value="clientSub" :size="size" @click="copyToClipboard(clientSub)" :margin="1" style="border-radius: 1rem;" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col style="text-align: center;">
|
||||||
|
<v-chip>{{ $t('setting.jsonSub') }}</v-chip><br />
|
||||||
|
<QrcodeVue :value="clientSub + '?format=json'" :size="size" @click="copyToClipboard(clientSub + '?format=json')" :margin="1" style="border-radius: 1rem;" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col style="text-align: center;">
|
||||||
|
<v-chip>SING-BOX</v-chip><br />
|
||||||
|
<QrcodeVue :value="singbox" :size="size" @click="copyToClipboard(singbox)" :margin="1" style="border-radius: .8rem;" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
<v-window-item value="link">
|
||||||
|
<v-row v-for="l in clientLinks">
|
||||||
|
<v-col style="text-align: center;">
|
||||||
|
<v-chip>{{ l.remark?? $t('client.' + l.type) }}</v-chip><br />
|
||||||
|
<QrcodeVue :value="l.uri" :size="size" @click="copyToClipboard(l.uri)" :margin="1" style="border-radius: .5rem;" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
|
</v-window>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@@ -31,38 +58,46 @@
|
|||||||
import QrcodeVue from 'qrcode.vue'
|
import QrcodeVue from 'qrcode.vue'
|
||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import Clipboard from 'clipboard'
|
import Clipboard from 'clipboard'
|
||||||
import Message from '@/store/modules/message'
|
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
import { push } from 'notivue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['index'],
|
props: ['index', 'visible'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
msg: Message(),
|
tab: "sub",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(txt:string) {
|
copyToClipboard(txt:string) {
|
||||||
|
const hiddenButton = document.createElement('button')
|
||||||
|
hiddenButton.className = 'clipboard-btn'
|
||||||
|
document.body.appendChild(hiddenButton)
|
||||||
|
|
||||||
const clipboard = new Clipboard('.clipboard-btn', {
|
const clipboard = new Clipboard('.clipboard-btn', {
|
||||||
text: () => txt
|
text: () => txt,
|
||||||
|
container: document.getElementById('qrcode-modal')?? undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
clipboard.on('success', () => {
|
clipboard.on('success', () => {
|
||||||
clipboard.destroy()
|
clipboard.destroy()
|
||||||
this.msg.showMessage(i18n.global.t('copyToClipboard') + " : " + i18n.global.t('success'),'success',5000)
|
push.success({
|
||||||
|
message: i18n.global.t('success') + ": " + i18n.global.t('copyToClipboard'),
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
clipboard.on('error', () => {
|
clipboard.on('error', () => {
|
||||||
clipboard.destroy()
|
clipboard.destroy()
|
||||||
this.msg.showMessage(i18n.global.t('copyToClipboard') + " : " + i18n.global.t('failed'),'error',5000)
|
push.error({
|
||||||
|
message: i18n.global.t('failed') + ": " + i18n.global.t('copyToClipboard'),
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Perform click on hidden button to trigger copy
|
// Perform click on hidden button to trigger copy
|
||||||
const hiddenButton = document.createElement('button');
|
hiddenButton.click()
|
||||||
hiddenButton.className = 'clipboard-btn';
|
document.body.removeChild(hiddenButton)
|
||||||
document.body.appendChild(hiddenButton);
|
|
||||||
hiddenButton.click();
|
|
||||||
document.body.removeChild(hiddenButton);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -74,10 +109,26 @@ export default {
|
|||||||
clientSub() {
|
clientSub() {
|
||||||
return Data().subURI + this.client.name
|
return Data().subURI + this.client.name
|
||||||
},
|
},
|
||||||
|
singbox() {
|
||||||
|
const url = Data().subURI + this.client.name + "?format=json"
|
||||||
|
return "sing-box://import-remote-profile?url=" + encodeURIComponent(url) + "#" + this.client.name
|
||||||
|
},
|
||||||
clientLinks() {
|
clientLinks() {
|
||||||
return JSON.parse(this.client.links?? "[]")
|
return this.client.links?? []
|
||||||
|
},
|
||||||
|
size() {
|
||||||
|
if (window.innerWidth > 380) return 300
|
||||||
|
if (window.innerWidth > 330) return 280
|
||||||
|
return 250
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.tab = "sub"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
components: { QrcodeVue }
|
components: { QrcodeVue }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
<v-card class="rounded-lg" :loading="loading" color="background">
|
<v-card class="rounded-lg" :loading="loading" color="background">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col cols="auto">
|
||||||
{{ $t('stats.graphTitle') + " - " + $t('objects.' + resource) + " : " + tag }}
|
{{ $t('stats.graphTitle') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-col cols="auto"><v-icon icon="mdi-close" @click="$emit('close')"></v-icon></v-col>
|
<v-col cols="auto"><v-icon icon="mdi-close" @click="$emit('close')"></v-icon></v-col>
|
||||||
@@ -12,7 +12,13 @@
|
|||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text style="padding: 0 16px;">
|
<v-card-text style="padding: 0 16px;">
|
||||||
<v-container id="container">
|
<div style="text-align: center; margin: 5px;">
|
||||||
|
{{ $t('objects.' + resource) + " : " + tag }}
|
||||||
|
</div>
|
||||||
|
<v-radio-group v-model="limit" @change="loadData" density="compact" :loading="loading" inline hide-details>
|
||||||
|
<v-radio v-for="p in periods" :label="p.title" :value="p.value"></v-radio>
|
||||||
|
</v-radio-group>
|
||||||
|
<v-container id="container" style="height:40vh;">
|
||||||
<v-alert :text="$t('noData')" type="warning" variant="outlined" v-if="alert"></v-alert>
|
<v-alert :text="$t('noData')" type="warning" variant="outlined" v-if="alert"></v-alert>
|
||||||
<Line v-if="loaded" :data="usage" :options="<any>options" />
|
<Line v-if="loaded" :data="usage" :options="<any>options" />
|
||||||
</v-container>
|
</v-container>
|
||||||
@@ -60,13 +66,29 @@ export default {
|
|||||||
loaded: false,
|
loaded: false,
|
||||||
alert: false,
|
alert: false,
|
||||||
intervalId: <any>0,
|
intervalId: <any>0,
|
||||||
|
limit: 1,
|
||||||
|
periods: [
|
||||||
|
{ value: 1, title: i18n.global.n(1) + i18n.global.t('date.h')},
|
||||||
|
{ value: 6, title: i18n.global.n(6) + i18n.global.t('date.h')},
|
||||||
|
{ value: 12, title: i18n.global.n(12) + i18n.global.t('date.h')},
|
||||||
|
{ value: 24, title: i18n.global.n(1) + i18n.global.t('date.d')},
|
||||||
|
{ value: 48, title: i18n.global.n(2) + i18n.global.t('date.d')},
|
||||||
|
{ value: 240, title: i18n.global.n(10) + i18n.global.t('date.d')},
|
||||||
|
{ value: 480, title: i18n.global.n(20) + i18n.global.t('date.d')},
|
||||||
|
{ value: 720, title: i18n.global.n(30) + i18n.global.t('date.d')},
|
||||||
|
{ value: 1440, title: i18n.global.n(60) + i18n.global.t('date.d')},
|
||||||
|
{ value: 2160, title: i18n.global.n(90) + i18n.global.t('date.d')},
|
||||||
|
],
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: true,
|
maintainAspectRatio: false,
|
||||||
interaction: {
|
interaction: {
|
||||||
intersect: false,
|
intersect: false,
|
||||||
mode: 'index',
|
mode: 'index',
|
||||||
},
|
},
|
||||||
|
elements: {
|
||||||
|
point: { pointStyle: 'crossRot' }
|
||||||
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
@@ -99,13 +121,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadData(limit: number) {
|
async loadData() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const data = await HttpUtils.get('api/stats', { resource: this.resource, tag: this.tag, limit: limit })
|
const data = await HttpUtils.get('api/stats', { resource: this.resource, tag: this.tag, limit: this.limit })
|
||||||
if (data.success && data.obj) {
|
if (data.success && data.obj) {
|
||||||
const obj = <any[]>data.obj
|
const obj = <any[]>data.obj
|
||||||
const l = String(i18n.global.locale) == 'fa' ? "fa-IR" : "en-US"
|
const l = String(i18n.global.locale) == 'fa' ? "fa-IR" : "en-US"
|
||||||
const oneStep = limit * 3600 * 1000 / 360 // Each 10 sec
|
const oneStep = this.limit * 3600 * 1000 / 360 // Each 10 sec
|
||||||
const now = new Date().getTime()
|
const now = new Date().getTime()
|
||||||
const steps = <number[]>[]
|
const steps = <number[]>[]
|
||||||
for (let i = 360; i >= 0; i--) {
|
for (let i = 360; i >= 0; i--) {
|
||||||
@@ -145,8 +167,10 @@ export default {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
|
this.alert = false
|
||||||
} else {
|
} else {
|
||||||
this.alert = true
|
this.alert = true
|
||||||
|
this.loaded = false
|
||||||
}
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
@@ -163,10 +187,10 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
visible(v) {
|
visible(v) {
|
||||||
if (v) {
|
if (v) {
|
||||||
const limit = 1
|
this.limit = 1
|
||||||
this.loadData(limit)
|
this.loadData()
|
||||||
this.intervalId = setInterval(() => {
|
this.intervalId = setInterval(() => {
|
||||||
this.loadData(limit)
|
this.loadData()
|
||||||
}, 10000)
|
}, 10000)
|
||||||
} else {
|
} else {
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
|
|||||||
@@ -0,0 +1,551 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " " + $t('objects.tls') }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('client.name')"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col align="end">
|
||||||
|
<v-btn-toggle v-model="tlsType"
|
||||||
|
class="rounded-xl"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
@update:model-value="changeTlsType"
|
||||||
|
shaped
|
||||||
|
mandatory>
|
||||||
|
<v-btn>TLS</v-btn>
|
||||||
|
<v-btn>Reality</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="inTls.server_name != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="SNI"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.server_name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<template v-if="tlsType == 0">
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="inTls.min_version">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.minVer')"
|
||||||
|
:items="tlsVersions"
|
||||||
|
v-model="inTls.min_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="inTls.max_version">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.maxVer')"
|
||||||
|
:items="tlsVersions"
|
||||||
|
v-model="inTls.max_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="inTls.alpn">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="ALPN"
|
||||||
|
multiple
|
||||||
|
:items="alpn"
|
||||||
|
v-model="inTls.alpn">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="8" v-if="inTls.cipher_suites != undefined">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.cs')"
|
||||||
|
multiple
|
||||||
|
:items="cipher_suites"
|
||||||
|
v-model="inTls.cipher_suites">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="tlsType == 0">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-btn-toggle v-model="usePath"
|
||||||
|
class="rounded-xl"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
shaped
|
||||||
|
mandatory>
|
||||||
|
<v-btn
|
||||||
|
@click="inTls.key=undefined; inTls.certificate=undefined"
|
||||||
|
>{{ $t('tls.usePath') }}</v-btn>
|
||||||
|
<v-btn
|
||||||
|
@click="inTls.key_path=undefined; inTls.certificate_path=undefined"
|
||||||
|
>{{ $t('tls.useText') }}</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn
|
||||||
|
variant="tonal"
|
||||||
|
density="compact"
|
||||||
|
icon="mdi-key-star"
|
||||||
|
@click="genSelfSigned"
|
||||||
|
:loading="loading">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('actions.generate') }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="usePath == 0">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.certPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.certificate_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.keyPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.key_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.cert')"
|
||||||
|
hide-details
|
||||||
|
v-model="certText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<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-switch color="primary" :label="$t('tls.disableSni')" v-model="disableSni" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('tls.insecure')" v-model="insecure" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="outTls.reality && inTls.reality">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.shdwTls.hs')"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.reality.handshake.server">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.port')"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
hide-details
|
||||||
|
v-model="server_port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn
|
||||||
|
variant="tonal"
|
||||||
|
density="compact"
|
||||||
|
icon="mdi-key-star"
|
||||||
|
@click="genRealityKey"
|
||||||
|
:loading="loading">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top">
|
||||||
|
{{ $t('actions.generate') }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.privKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.reality.private_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.pubKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="outTls.reality.public_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field
|
||||||
|
label="Short IDs"
|
||||||
|
hide-details
|
||||||
|
append-icon="mdi-refresh"
|
||||||
|
@click:append="randomSID"
|
||||||
|
v-model="short_id">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionTime">
|
||||||
|
<v-text-field
|
||||||
|
label="Max Time Diference"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
:suffix="$t('date.m')"
|
||||||
|
hide-details
|
||||||
|
v-model="max_time">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<v-row v-if="outTls.utls != undefined">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Fingerprint"
|
||||||
|
:items="fingerprints"
|
||||||
|
v-model="outTls.utls.fingerprint">
|
||||||
|
</v-select>
|
||||||
|
</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('tls.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<template v-if="tlsType == 0">
|
||||||
|
<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="$t('tls.minVer')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionTime" color="primary" label="Max Time Difference" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionFP" color="primary" label="UTLS" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<AcmeVue :tls="inTls" />
|
||||||
|
<EchVue :iTls="inTls" :oTls="outTls" />
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { iTls, defaultInTls } from '@/types/inTls'
|
||||||
|
import { oTls, defaultOutTls } from '@/types/outTls'
|
||||||
|
import AcmeVue from '@/components/tls/Acme.vue'
|
||||||
|
import EchVue from '@/components/tls/Ech.vue'
|
||||||
|
import HttpUtils from '@/plugins/httputil'
|
||||||
|
import { push } from 'notivue'
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'index'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tls: { id: -1, name: '', inbounds: [], server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||||
|
title: "add",
|
||||||
|
loading: false,
|
||||||
|
menu: false,
|
||||||
|
tlsType: 0,
|
||||||
|
usePath: 0,
|
||||||
|
alpn: [
|
||||||
|
{ title: "H3", value: 'h3' },
|
||||||
|
{ title: "H2", value: 'h2' },
|
||||||
|
{ title: "Http/1.1", value: 'http/1.1' },
|
||||||
|
],
|
||||||
|
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
||||||
|
cipher_suites: [
|
||||||
|
{ 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" }
|
||||||
|
],
|
||||||
|
fingerprints: [
|
||||||
|
{ title: "Chrome", value: "chrome" },
|
||||||
|
{ title: "Chrome PSK", value: "chrome_psk" },
|
||||||
|
{ title: "Chrome PSK Shuffle", value: "chrome_psk_shuffle" },
|
||||||
|
{ title: "Chrome Padding PSK Shuffle", value: "chrome_padding_psk_shuffle" },
|
||||||
|
{ title: "Chrome Post-Quantum", value: "chrome_pq" },
|
||||||
|
{ title: "Chrome Post-Quantum PSK", value: "chrome_pq_psk" },
|
||||||
|
{ title: "Firefox", value: "firefox" },
|
||||||
|
{ title: "Microsoft Edge", value: "edge" },
|
||||||
|
{ title: "Apple Safari", value: "safari" },
|
||||||
|
{ title: "360", value: "360" },
|
||||||
|
{ title: "QQ", value: "qq" },
|
||||||
|
{ title: "Apple IOS", value: "ios" },
|
||||||
|
{ title: "Android", value: "android" },
|
||||||
|
{ title: "Random", value: "random" },
|
||||||
|
{ title: "Randomized", value: "randomized" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.index != -1) {
|
||||||
|
const newData = JSON.parse(this.$props.data)
|
||||||
|
this.tls = newData
|
||||||
|
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
||||||
|
this.usePath = newData.server?.key == undefined ? 0 : 1
|
||||||
|
this.title = "edit"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tls = { id: 0, name: '', inbounds: [], server: {enabled: true}, client: {} }
|
||||||
|
this.tlsType = 0
|
||||||
|
this.usePath = 0
|
||||||
|
this.title = "add"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeTlsType(){
|
||||||
|
if (this.tlsType) {
|
||||||
|
this.tls.server = <iTls>{
|
||||||
|
enabled: true,
|
||||||
|
reality: { enabled: true, handshake: { server_port: 443 }, short_id: RandomUtil.randomShortId() },
|
||||||
|
server_name: "" }
|
||||||
|
this.tls.client = <oTls>{ reality: { public_key: "" } }
|
||||||
|
} else {
|
||||||
|
this.tls.server = <iTls>{ enabled: true }
|
||||||
|
this.tls.client = <oTls>{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.updateData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
this.$emit('save', this.tls)
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async genSelfSigned(){
|
||||||
|
this.loading = true
|
||||||
|
const msg = await HttpUtils.get('api/keypairs', { k: "tls", o: this.inTls.server_name?? "''" })
|
||||||
|
this.loading = false
|
||||||
|
if (msg.success) {
|
||||||
|
this.inTls.key_path=undefined
|
||||||
|
this.inTls.certificate_path=undefined
|
||||||
|
this.usePath = 1
|
||||||
|
if (msg.obj.length>0){
|
||||||
|
let privateKey = <string[]>[]
|
||||||
|
let publicKey = <string[]>[]
|
||||||
|
let isPrivateKey = false
|
||||||
|
let isPublicKey = false
|
||||||
|
|
||||||
|
msg.obj.forEach((line:string) => {
|
||||||
|
if (line === "-----BEGIN PRIVATE KEY-----") {
|
||||||
|
isPrivateKey = true
|
||||||
|
isPublicKey = false
|
||||||
|
privateKey.push(line)
|
||||||
|
} else if (line === "-----END PRIVATE KEY-----") {
|
||||||
|
isPrivateKey = false
|
||||||
|
privateKey.push(line)
|
||||||
|
} else if (line === "-----BEGIN CERTIFICATE-----") {
|
||||||
|
isPublicKey = true
|
||||||
|
isPrivateKey = false
|
||||||
|
publicKey.push(line)
|
||||||
|
} else if (line === "-----END CERTIFICATE-----") {
|
||||||
|
isPublicKey = false
|
||||||
|
publicKey.push(line)
|
||||||
|
} else if (isPrivateKey) {
|
||||||
|
privateKey.push(line)
|
||||||
|
} else if (isPublicKey) {
|
||||||
|
publicKey.push(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.inTls.key = privateKey?? undefined
|
||||||
|
this.inTls.certificate = publicKey?? undefined
|
||||||
|
|
||||||
|
} else {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error') + ": " + msg.obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async genRealityKey(){
|
||||||
|
this.loading = true
|
||||||
|
const msg = await HttpUtils.get('api/keypairs', { k: "reality" })
|
||||||
|
this.loading = false
|
||||||
|
if (msg.success) {
|
||||||
|
msg.obj.forEach((line:string) => {
|
||||||
|
if (this.inTls.reality && this.outTls.reality){
|
||||||
|
if (line.startsWith("PrivateKey")){
|
||||||
|
this.inTls.reality.private_key = line.substring(12)
|
||||||
|
}
|
||||||
|
if (line.startsWith("PublicKey")){
|
||||||
|
this.outTls.reality.public_key = line.substring(11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error') + ": " + msg.obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
randomSID(){
|
||||||
|
this.short_id = RandomUtil.randomShortId().join(',')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
inTls(): iTls {
|
||||||
|
return <iTls> this.tls.server
|
||||||
|
},
|
||||||
|
outTls(): oTls {
|
||||||
|
return <oTls> this.tls.client
|
||||||
|
},
|
||||||
|
certText: {
|
||||||
|
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
||||||
|
set(v:string) { this.inTls.certificate = v.split('\n') }
|
||||||
|
},
|
||||||
|
keyText: {
|
||||||
|
get(): string { return this.inTls.key ? this.inTls.key.join('\n') : '' },
|
||||||
|
set(v:string) { this.inTls.key = v.split('\n') }
|
||||||
|
},
|
||||||
|
disableSni: {
|
||||||
|
get() { return this.outTls.disable_sni ?? false },
|
||||||
|
set(v: boolean) { this.outTls.disable_sni = v ? true : undefined }
|
||||||
|
},
|
||||||
|
insecure: {
|
||||||
|
get() { return this.outTls.insecure ?? false },
|
||||||
|
set(v: boolean) { this.outTls.insecure = v ? true : undefined }
|
||||||
|
},
|
||||||
|
server_port: {
|
||||||
|
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
||||||
|
set(v: any) {
|
||||||
|
if (this.inTls.reality){
|
||||||
|
this.inTls.reality.handshake.server_port = v.length == 0 || v == 0 ? 443 : parseInt(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
short_id: {
|
||||||
|
get() { return this.inTls.reality?.short_id ? this.inTls.reality.short_id.join(',') : undefined },
|
||||||
|
set(v: string) {
|
||||||
|
if (this.inTls.reality){
|
||||||
|
this.inTls.reality.short_id = v.length > 0 ? v.split(',') : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
max_time: {
|
||||||
|
get() { return this.inTls?.reality?.max_time_difference ? this.inTls.reality.max_time_difference.replace('m','') : 1 },
|
||||||
|
set(v: number) {
|
||||||
|
if (this.inTls.reality){
|
||||||
|
this.inTls.reality.max_time_difference = v > 0 ? v + 'm' : '1m'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSNI: {
|
||||||
|
get(): boolean { return this.inTls.server_name != undefined },
|
||||||
|
set(v:boolean) { this.inTls.server_name = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionALPN: {
|
||||||
|
get(): boolean { return this.inTls.alpn != undefined },
|
||||||
|
set(v:boolean) { this.inTls.alpn = v ? defaultInTls.alpn : undefined }
|
||||||
|
},
|
||||||
|
optionMinV: {
|
||||||
|
get(): boolean { return this.inTls.min_version != undefined },
|
||||||
|
set(v:boolean) { this.inTls.min_version = v ? defaultInTls.min_version : undefined }
|
||||||
|
},
|
||||||
|
optionMaxV: {
|
||||||
|
get(): boolean { return this.inTls.max_version != undefined },
|
||||||
|
set(v:boolean) { this.inTls.max_version = v ? defaultInTls.max_version : undefined }
|
||||||
|
},
|
||||||
|
optionCS: {
|
||||||
|
get(): boolean { return this.inTls.cipher_suites != undefined },
|
||||||
|
set(v:boolean) { this.inTls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
||||||
|
},
|
||||||
|
optionFP: {
|
||||||
|
get(): boolean { return this.outTls.utls != undefined },
|
||||||
|
set(v:boolean) { this.outTls.utls = v ? defaultOutTls.utls : undefined }
|
||||||
|
},
|
||||||
|
optionEch: {
|
||||||
|
get(): boolean { return this.outTls.ech != undefined },
|
||||||
|
set(v:boolean) { this.outTls.ech = v ? defaultOutTls.ech : undefined }
|
||||||
|
},
|
||||||
|
optionTime: {
|
||||||
|
get(): boolean { return this.inTls?.reality?.max_time_difference != undefined },
|
||||||
|
set(v:boolean) { if (this.inTls.reality) this.inTls.reality.max_time_difference = v ? "1m" : undefined }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(v) {
|
||||||
|
if (v) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { AcmeVue, EchVue }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,6 +4,9 @@ export default {
|
|||||||
failed: "failed",
|
failed: "failed",
|
||||||
enable: "Enable",
|
enable: "Enable",
|
||||||
disable: "Disable",
|
disable: "Disable",
|
||||||
|
none: "None",
|
||||||
|
all: "All",
|
||||||
|
filter: "Filter",
|
||||||
loading: "Loading...",
|
loading: "Loading...",
|
||||||
confirm: "Are you sure ?",
|
confirm: "Are you sure ?",
|
||||||
yes: "yes",
|
yes: "yes",
|
||||||
@@ -21,9 +24,13 @@ export default {
|
|||||||
invalidLogin: "Invalid Login!",
|
invalidLogin: "Invalid Login!",
|
||||||
online: "Online",
|
online: "Online",
|
||||||
version: "Version",
|
version: "Version",
|
||||||
|
email: "Email",
|
||||||
commaSeparated: "(comma separated)",
|
commaSeparated: "(comma separated)",
|
||||||
|
count: "Count",
|
||||||
|
template: "Template",
|
||||||
error: {
|
error: {
|
||||||
dplData: "Duplicate Data",
|
dplData: "Duplicate Data",
|
||||||
|
core: "Sing-Box Error",
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "Login",
|
login: "Login",
|
||||||
@@ -32,6 +39,7 @@ export default {
|
|||||||
outbounds: "Outbounds",
|
outbounds: "Outbounds",
|
||||||
clients: "Clients",
|
clients: "Clients",
|
||||||
rules: "Rules",
|
rules: "Rules",
|
||||||
|
tls: "TLS Settings",
|
||||||
basics: "Basics",
|
basics: "Basics",
|
||||||
admins: "Admins",
|
admins: "Admins",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
@@ -83,13 +91,19 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "Action",
|
action: "Action",
|
||||||
add: "Add",
|
add: "Add",
|
||||||
|
new: "New",
|
||||||
edit: "Edit",
|
edit: "Edit",
|
||||||
del: "Delete",
|
del: "Delete",
|
||||||
|
clone: "Clone",
|
||||||
save: "Save",
|
save: "Save",
|
||||||
update: "Update",
|
update: "Update",
|
||||||
submit: "Submit",
|
submit: "Submit",
|
||||||
|
set: "Set",
|
||||||
|
generate: "Generate",
|
||||||
|
disable: "Disable",
|
||||||
close: "Close",
|
close: "Close",
|
||||||
restartApp: "Restart App",
|
restartApp: "Restart App",
|
||||||
|
restartSb: "Restart Singbox",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: "Login",
|
title: "Login",
|
||||||
@@ -109,6 +123,10 @@ export default {
|
|||||||
lastLogin: "Last login",
|
lastLogin: "Last login",
|
||||||
date: "Date",
|
date: "Date",
|
||||||
time: "Time",
|
time: "Time",
|
||||||
|
changes: "Changes",
|
||||||
|
actor: "Actor",
|
||||||
|
key: "Key",
|
||||||
|
action: "Action",
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "Interface",
|
interface: "Interface",
|
||||||
@@ -128,10 +146,20 @@ export default {
|
|||||||
path: "Default Path",
|
path: "Default Path",
|
||||||
update: "Automatic Update Time",
|
update: "Automatic Update Time",
|
||||||
subUri: "Subscription URI",
|
subUri: "Subscription URI",
|
||||||
|
jsonSub: "JSON Subscription",
|
||||||
|
toDirect: "Route to Direct",
|
||||||
|
toBlock: "Route to Block",
|
||||||
|
timestamp: "Timestamp",
|
||||||
|
globalDns: "Global DNS",
|
||||||
|
directDns: "Direct DNS",
|
||||||
|
toDirectDns: "Route to Direct DNS",
|
||||||
|
jsonSubOptions: "Other Options",
|
||||||
|
excludePkg: "Exclude Packages",
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "Name",
|
name: "Name",
|
||||||
desc: "Description",
|
desc: "Description",
|
||||||
|
group: "Group",
|
||||||
inboundTags: "Inbound Tags",
|
inboundTags: "Inbound Tags",
|
||||||
basics: "Basics",
|
basics: "Basics",
|
||||||
config: "Config",
|
config: "Config",
|
||||||
@@ -139,13 +167,17 @@ export default {
|
|||||||
external: "External Link",
|
external: "External Link",
|
||||||
sub: "External Subscription",
|
sub: "External Subscription",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "Add Bulk",
|
||||||
|
order: "Order",
|
||||||
|
random: "Random",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "Username",
|
un: "Username",
|
||||||
pw: "Password",
|
pw: "Password",
|
||||||
direct: {
|
direct: {
|
||||||
overrideAddr: "Override Address",
|
overrideAddr: "Override Address",
|
||||||
overridePort: "Override Port",
|
overridePort: "Override Port",
|
||||||
proxyProtocol: "Proxy Protocol",
|
|
||||||
},
|
},
|
||||||
hy: {
|
hy: {
|
||||||
obfs: "Obfuscated Password",
|
obfs: "Obfuscated Password",
|
||||||
@@ -175,6 +207,10 @@ export default {
|
|||||||
authTimeout: "Authentication Timeout",
|
authTimeout: "Authentication Timeout",
|
||||||
hb: "Heartbeat",
|
hb: "Heartbeat",
|
||||||
},
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "Addresses",
|
||||||
|
ifName: "Interface Name",
|
||||||
|
},
|
||||||
vless: {
|
vless: {
|
||||||
flow: "Flow",
|
flow: "Flow",
|
||||||
udpEnc: "UDP Packet Encoding",
|
udpEnc: "UDP Packet Encoding",
|
||||||
@@ -213,6 +249,11 @@ export default {
|
|||||||
port: "Port",
|
port: "Port",
|
||||||
clients: "Enable Clients",
|
clients: "Enable Clients",
|
||||||
ssMethod: "Method",
|
ssMethod: "Method",
|
||||||
|
sSide: "Server Side",
|
||||||
|
cSide: "Client Side",
|
||||||
|
multiDomain: "Multi Domain",
|
||||||
|
remark: "Remark",
|
||||||
|
mdOption: "Multi Domain Options",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "Sniffing",
|
sniffing: "Sniffing",
|
||||||
@@ -302,6 +343,7 @@ export default {
|
|||||||
final: "Final",
|
final: "Final",
|
||||||
server: "Server",
|
server: "Server",
|
||||||
firstServer: "First Server",
|
firstServer: "First Server",
|
||||||
|
addrResolver: "Address Resolver",
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
title: "Routing",
|
title: "Routing",
|
||||||
@@ -326,9 +368,26 @@ export default {
|
|||||||
minVer: "Minimum Version",
|
minVer: "Minimum Version",
|
||||||
maxVer: "Maximum Version",
|
maxVer: "Maximum Version",
|
||||||
cs: "Cipher suits",
|
cs: "Cipher suits",
|
||||||
|
privKey: "Private Key",
|
||||||
pubKey: "Public Key",
|
pubKey: "Public Key",
|
||||||
disableSni: "Disable SNI",
|
disableSni: "Disable SNI",
|
||||||
insecure: "Allow Insecure",
|
insecure: "Allow Insecure",
|
||||||
|
acme: {
|
||||||
|
options: "ACME Options",
|
||||||
|
dataDir: "Data Directory",
|
||||||
|
defaultDomain: "Default Domain",
|
||||||
|
disableChallenges: "Disable Challenges",
|
||||||
|
httpChallenge: "Disable HTTP Challenge",
|
||||||
|
tlsChallenge: "Disable TLS Challenge",
|
||||||
|
altPorts: "Alternative Ports",
|
||||||
|
altHport: "Alternative HTTP Port",
|
||||||
|
altTport: "Alternative TLS Port",
|
||||||
|
caProvider: "CA Provider",
|
||||||
|
customCa: "Custom CA Provider",
|
||||||
|
extAcc: "External Account",
|
||||||
|
dns01: "DNS01 Challenge",
|
||||||
|
dns01Provider: "DNS01 Challenge Provider",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
upload: "Upload",
|
upload: "Upload",
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export default {
|
|||||||
failed: "خطا",
|
failed: "خطا",
|
||||||
enable: "فعال",
|
enable: "فعال",
|
||||||
disable: "غیرفعال",
|
disable: "غیرفعال",
|
||||||
|
none: "هیچ",
|
||||||
|
all: "همه",
|
||||||
|
filter: "فیلتر",
|
||||||
loading: "در حال بارگذاری...",
|
loading: "در حال بارگذاری...",
|
||||||
confirm: "آیا مطمئن هستید ؟",
|
confirm: "آیا مطمئن هستید ؟",
|
||||||
yes: "بله",
|
yes: "بله",
|
||||||
@@ -21,9 +24,13 @@ export default {
|
|||||||
invalidLogin: "ورود نامعتبر!",
|
invalidLogin: "ورود نامعتبر!",
|
||||||
online: "آنلاین",
|
online: "آنلاین",
|
||||||
version: "نسخه",
|
version: "نسخه",
|
||||||
|
email: "ایمیل",
|
||||||
commaSeparated: "(جداشده با کاما)",
|
commaSeparated: "(جداشده با کاما)",
|
||||||
|
count: "تعداد",
|
||||||
|
template: "الگو",
|
||||||
error: {
|
error: {
|
||||||
dplData: "داده تکراری",
|
dplData: "داده تکراری",
|
||||||
|
core: "خطا در سینگباکس",
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "ورود",
|
login: "ورود",
|
||||||
@@ -32,6 +39,7 @@ export default {
|
|||||||
outbounds: "خروجیها",
|
outbounds: "خروجیها",
|
||||||
clients: "کاربران",
|
clients: "کاربران",
|
||||||
rules: "قوانین",
|
rules: "قوانین",
|
||||||
|
tls: "رمزنگاریها",
|
||||||
basics: "ترازها",
|
basics: "ترازها",
|
||||||
admins: "ادمینها",
|
admins: "ادمینها",
|
||||||
settings: "پیکربندی",
|
settings: "پیکربندی",
|
||||||
@@ -82,13 +90,19 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "فرمان",
|
action: "فرمان",
|
||||||
add: "ایجاد",
|
add: "ایجاد",
|
||||||
|
new: "جدید",
|
||||||
edit: "ویرایش",
|
edit: "ویرایش",
|
||||||
del: "حذف",
|
del: "حذف",
|
||||||
|
clone: "شبیهسازی",
|
||||||
save: "ذخیره",
|
save: "ذخیره",
|
||||||
update: "بروزرسانی",
|
update: "بروزرسانی",
|
||||||
submit: "ارسال",
|
submit: "ارسال",
|
||||||
|
set: "تنظیم",
|
||||||
|
generate: "تولید",
|
||||||
|
disable: "غیرفعال",
|
||||||
close: "بستن",
|
close: "بستن",
|
||||||
restartApp: "ریستارت پنل",
|
restartApp: "ریستارت پنل",
|
||||||
|
restartSb: "ریستارت سینگباکس",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: "ورود",
|
title: "ورود",
|
||||||
@@ -108,6 +122,10 @@ export default {
|
|||||||
lastLogin: "آخرین ورود",
|
lastLogin: "آخرین ورود",
|
||||||
date: "تاریخ",
|
date: "تاریخ",
|
||||||
time: "ساعت",
|
time: "ساعت",
|
||||||
|
changes: "تغییرات",
|
||||||
|
actor: "مجری",
|
||||||
|
key: "کلید",
|
||||||
|
action: "عمل",
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "نما",
|
interface: "نما",
|
||||||
@@ -127,10 +145,20 @@ export default {
|
|||||||
path: "مسیر پیشفرض",
|
path: "مسیر پیشفرض",
|
||||||
update: "زمان بروزرسانی خودکار",
|
update: "زمان بروزرسانی خودکار",
|
||||||
subUri: "آدرس نهایی سابسکریپشن",
|
subUri: "آدرس نهایی سابسکریپشن",
|
||||||
|
jsonSub: "سابسکریپشن JSON",
|
||||||
|
toDirect: "هدایت مستقیم",
|
||||||
|
toBlock: "بستن مسیر",
|
||||||
|
timestamp: "نمایش زمان",
|
||||||
|
globalDns: "DNS کلی",
|
||||||
|
directDns: "DNS مستقیم",
|
||||||
|
toDirectDns: "هدایت به DNS مستقیم",
|
||||||
|
jsonSubOptions: "گزینههای دیگر",
|
||||||
|
excludePkg: "نرمافزارهای استثنا",
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "نام",
|
name: "نام",
|
||||||
desc: "شرح",
|
desc: "شرح",
|
||||||
|
group: "گروه",
|
||||||
inboundTags: "برچسبهای ورودی",
|
inboundTags: "برچسبهای ورودی",
|
||||||
basics: "پایه",
|
basics: "پایه",
|
||||||
config: "تنظیم",
|
config: "تنظیم",
|
||||||
@@ -138,13 +166,17 @@ export default {
|
|||||||
external: "لینک خارجی",
|
external: "لینک خارجی",
|
||||||
sub: "سابسکریپشن خارجی",
|
sub: "سابسکریپشن خارجی",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "ایجاد انبوه",
|
||||||
|
order: "ترتیب",
|
||||||
|
random: "تصادفی",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "نام کاربری",
|
un: "نام کاربری",
|
||||||
pw: "رمز",
|
pw: "رمز",
|
||||||
direct: {
|
direct: {
|
||||||
overrideAddr: "جایگزین آدرس",
|
overrideAddr: "جایگزین آدرس",
|
||||||
overridePort: "جایگزین پورت",
|
overridePort: "جایگزین پورت",
|
||||||
proxyProtocol: "پروتکل پراکسی",
|
|
||||||
},
|
},
|
||||||
hy: {
|
hy: {
|
||||||
obfs: "رمز مبهم کننده",
|
obfs: "رمز مبهم کننده",
|
||||||
@@ -174,6 +206,10 @@ export default {
|
|||||||
authTimeout: "مهلت احراز هویت",
|
authTimeout: "مهلت احراز هویت",
|
||||||
hb: "ضربان قلب",
|
hb: "ضربان قلب",
|
||||||
},
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "آدرسها",
|
||||||
|
ifName: "نام اینترفیس",
|
||||||
|
},
|
||||||
vless: {
|
vless: {
|
||||||
flow: "جریان",
|
flow: "جریان",
|
||||||
udpEnc: "کدگذاری بسته UDP",
|
udpEnc: "کدگذاری بسته UDP",
|
||||||
@@ -212,6 +248,11 @@ export default {
|
|||||||
port: "پورت",
|
port: "پورت",
|
||||||
clients: "فعالسازی کاربران",
|
clients: "فعالسازی کاربران",
|
||||||
ssMethod: "روش",
|
ssMethod: "روش",
|
||||||
|
sSide: "سمت سرور",
|
||||||
|
cSide: "سمت کاربر",
|
||||||
|
multiDomain: "دامنه چندگانه",
|
||||||
|
remark: "شرح",
|
||||||
|
mdOption: "گزینههای دامنه چندگانه",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "شنود آدرس",
|
sniffing: "شنود آدرس",
|
||||||
@@ -272,7 +313,7 @@ export default {
|
|||||||
privateIp: "آدرس های IP نامعتبر",
|
privateIp: "آدرس های IP نامعتبر",
|
||||||
port: "پورتها",
|
port: "پورتها",
|
||||||
portRange: "محدودههای پورت",
|
portRange: "محدودههای پورت",
|
||||||
srcIp: "محدودههای آدرس IP مبدا",
|
srcCidr: "محدودههای آدرس IP مبدا",
|
||||||
srcPrivateIp: "آدرسهای IP مبدا نامعتبر",
|
srcPrivateIp: "آدرسهای IP مبدا نامعتبر",
|
||||||
srcPort: "پورتهای مبدا",
|
srcPort: "پورتهای مبدا",
|
||||||
srcPortRange: "محدوده پورتهای منبع",
|
srcPortRange: "محدوده پورتهای منبع",
|
||||||
@@ -301,6 +342,7 @@ export default {
|
|||||||
final: "سرور نهایی",
|
final: "سرور نهایی",
|
||||||
server: "سرور",
|
server: "سرور",
|
||||||
firstServer: "سرور نخست",
|
firstServer: "سرور نخست",
|
||||||
|
addrResolver: "حل کننده دامنه",
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
title: "مسیریابی",
|
title: "مسیریابی",
|
||||||
@@ -325,9 +367,26 @@ export default {
|
|||||||
minVer: "کمینه نسخه",
|
minVer: "کمینه نسخه",
|
||||||
maxVer: "بیشینه نسخه",
|
maxVer: "بیشینه نسخه",
|
||||||
cs: "مدلهای رمزنگاری",
|
cs: "مدلهای رمزنگاری",
|
||||||
|
privKey: "کلید خصوصی",
|
||||||
pubKey: "کلید عمومی",
|
pubKey: "کلید عمومی",
|
||||||
disableSni: "غیرفعالسازی SNI",
|
disableSni: "غیرفعالسازی SNI",
|
||||||
insecure: "تایید ارتباط ناامن",
|
insecure: "تایید ارتباط ناامن",
|
||||||
|
acme: {
|
||||||
|
options: "گزینههای ACME",
|
||||||
|
dataDir: "مسیر دادهها",
|
||||||
|
defaultDomain: "دامنه پیشفرض",
|
||||||
|
disableChallenges: "بستن چالشها",
|
||||||
|
httpChallenge: "بستن چالش HTTP",
|
||||||
|
tlsChallenge: "بستن چالش TLS",
|
||||||
|
altPorts: "پورتهای جایگزین",
|
||||||
|
altHport: "پورت جایگزین HTTP",
|
||||||
|
altTport: "پورت جایگزین TLS",
|
||||||
|
caProvider: "فراهم کننده گواهی",
|
||||||
|
customCa: "فراهم کننده دیگر",
|
||||||
|
extAcc: "حساب خارجی",
|
||||||
|
dns01: "چالش DNS01",
|
||||||
|
dns01Provider: "فراهم کننده چالش DNS01",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
upload: "آپلود",
|
upload: "آپلود",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import fa from './fa'
|
|||||||
import vi from './vi'
|
import vi from './vi'
|
||||||
import zhcn from './zhcn'
|
import zhcn from './zhcn'
|
||||||
import zhtw from './zhtw'
|
import zhtw from './zhtw'
|
||||||
|
import ru from './ru'
|
||||||
|
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
@@ -13,8 +14,9 @@ export const i18n = createI18n({
|
|||||||
en: en,
|
en: en,
|
||||||
fa: fa,
|
fa: fa,
|
||||||
vi: vi,
|
vi: vi,
|
||||||
zhcn: zhcn,
|
zhHans: zhcn,
|
||||||
zhtw: zhtw
|
zhHant: zhtw,
|
||||||
|
ru: ru
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ export const languages = [
|
|||||||
{ title: 'English', value: 'en' },
|
{ title: 'English', value: 'en' },
|
||||||
{ title: 'فارسی', value: 'fa' },
|
{ title: 'فارسی', value: 'fa' },
|
||||||
{ title: 'Tiếng Việt', value: 'vi' },
|
{ title: 'Tiếng Việt', value: 'vi' },
|
||||||
{ title: '简体中文', value: 'zhcn' },
|
{ title: '简体中文', value: 'zhHans' },
|
||||||
{ title: '繁體中文', value: 'zhtw' },
|
{ title: '繁體中文', value: 'zhHant' },
|
||||||
|
{ title: 'Русский', value: 'ru' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,422 @@
|
|||||||
|
export default {
|
||||||
|
message: "Добро пожаловать",
|
||||||
|
success: "успех",
|
||||||
|
failed: "ошибка",
|
||||||
|
enable: "Включить",
|
||||||
|
disable: "Отключить",
|
||||||
|
none: "Никакие",
|
||||||
|
all: "Все",
|
||||||
|
filter: "Фильтр",
|
||||||
|
loading: "Загрузка...",
|
||||||
|
confirm: "Вы уверены?",
|
||||||
|
yes: "да",
|
||||||
|
no: "нет",
|
||||||
|
unlimited: "бесконечный",
|
||||||
|
remained: "Осталось",
|
||||||
|
type: "Тип",
|
||||||
|
protocol: "Протокол",
|
||||||
|
submit: "Отправить",
|
||||||
|
reset: "Сбросить",
|
||||||
|
now: "Сейчас",
|
||||||
|
network: "Сеть",
|
||||||
|
copyToClipboard: "Копировать в буфер обмена",
|
||||||
|
noData: "Нет данных!",
|
||||||
|
invalidLogin: "Неверный логин!",
|
||||||
|
online: "В сети",
|
||||||
|
version: "Версия",
|
||||||
|
email: "Электронная почта",
|
||||||
|
commaSeparated: "(разделено запятыми)",
|
||||||
|
count: "Количество",
|
||||||
|
template: "Шаблон",
|
||||||
|
error: {
|
||||||
|
dplData: "Дублирующие данные",
|
||||||
|
core: "Ошибка Sing-Box",
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
login: "Вход",
|
||||||
|
home: "Главная",
|
||||||
|
inbounds: "Входящие",
|
||||||
|
outbounds: "Исходящие",
|
||||||
|
clients: "Клиенты",
|
||||||
|
rules: "Правила",
|
||||||
|
tls: "Настройки TLS",
|
||||||
|
basics: "Основы",
|
||||||
|
admins: "Администраторы",
|
||||||
|
settings: "Настройки",
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
tiles: "Плитки",
|
||||||
|
gauges: "Датчики",
|
||||||
|
charts: "Графики",
|
||||||
|
infos: "Информация",
|
||||||
|
gauge: {
|
||||||
|
cpu: "Загрузка ЦП",
|
||||||
|
mem: "Загрузка ОЗУ",
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
cpu: "Мониторинг ЦП",
|
||||||
|
mem: "Мониторинг ОЗУ",
|
||||||
|
net: "Сетевой трафик",
|
||||||
|
pnet: "Сетевые пакеты",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
sys: "Информация о системе",
|
||||||
|
sbd: "Информация о Sing-Box",
|
||||||
|
host: "Хост",
|
||||||
|
cpu: "ЦП",
|
||||||
|
core: "Ядро",
|
||||||
|
uptime: "Время работы",
|
||||||
|
threads: "Потоки",
|
||||||
|
memory: "Память",
|
||||||
|
running: "Работает"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
objects: {
|
||||||
|
inbound: "Входящий",
|
||||||
|
client: "Клиент",
|
||||||
|
outbound: "Исходящий",
|
||||||
|
rule: "Правило",
|
||||||
|
user: "Пользователь",
|
||||||
|
tag: "Тег",
|
||||||
|
listen: "Прослушивание",
|
||||||
|
dial: "Звонок",
|
||||||
|
tls: "TLS",
|
||||||
|
multiplex: "Мультиплекс",
|
||||||
|
transport: "Транспорт",
|
||||||
|
method: "Метод",
|
||||||
|
headers: "Заголовки",
|
||||||
|
key: "Ключ",
|
||||||
|
value: "Значение",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
action: "Действие",
|
||||||
|
add: "Добавить",
|
||||||
|
new: "Новый",
|
||||||
|
edit: "Редактировать",
|
||||||
|
del: "Удалить",
|
||||||
|
clone: "Клонировать",
|
||||||
|
save: "Сохранить",
|
||||||
|
update: "Обновить",
|
||||||
|
submit: "Отправить",
|
||||||
|
set: "Установить",
|
||||||
|
generate: "Генерировать",
|
||||||
|
disable: "Отключить",
|
||||||
|
close: "Закрыть",
|
||||||
|
restartApp: "Перезапустить приложение",
|
||||||
|
restartSb: "Перезапустить Singbox",
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
title: "Вход",
|
||||||
|
username: "Имя пользователя",
|
||||||
|
unRules: "Имя пользователя не может быть пустым",
|
||||||
|
password: "Пароль",
|
||||||
|
pwRules: "Пароль не может быть пустым",
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
logout: "Выйти",
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
changeCred: "Изменить учетные данные",
|
||||||
|
oldPass: "Текущий пароль",
|
||||||
|
newUname: "Новое имя пользователя",
|
||||||
|
newPass: "Новый пароль",
|
||||||
|
lastLogin: "Последний вход",
|
||||||
|
date: "Дата",
|
||||||
|
time: "Время",
|
||||||
|
changes: "Изменения",
|
||||||
|
actor: "Исполнитель",
|
||||||
|
key: "Ключ",
|
||||||
|
action: "Действие",
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
interface: "Интерфейс",
|
||||||
|
sub: "Подписка",
|
||||||
|
addr: "Адрес",
|
||||||
|
port: "Порт",
|
||||||
|
webPath: "Базовый URI",
|
||||||
|
domain: "Домен",
|
||||||
|
sslKey: "Путь к SSL ключу",
|
||||||
|
sslCert: "Путь к SSL сертификату",
|
||||||
|
webUri: "URI панели",
|
||||||
|
sessionAge: "Максимальная длительность сессии",
|
||||||
|
trafficAge: "Максимальная длительность трафика",
|
||||||
|
timeLoc: "Часовой пояс",
|
||||||
|
subEncode: "Включить кодирование",
|
||||||
|
subInfo: "Включить информацию о клиенте",
|
||||||
|
path: "Путь по умолчанию",
|
||||||
|
update: "Время автоматического обновления",
|
||||||
|
subUri: "URI подписки",
|
||||||
|
jsonSub: "JSON подписка",
|
||||||
|
toDirect: "Маршрутизация на Direct",
|
||||||
|
toBlock: "Маршрутизация на Block",
|
||||||
|
timestamp: "Метка времени",
|
||||||
|
globalDns: "Глобальный DNS",
|
||||||
|
directDns: "Прямой DNS",
|
||||||
|
toDirectDns: "Маршрутизация на Direct DNS",
|
||||||
|
jsonSubOptions: "Другие параметры",
|
||||||
|
excludePkg: "Исключить пакеты",
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
name: "Имя",
|
||||||
|
desc: "Описание",
|
||||||
|
group: "Группа",
|
||||||
|
inboundTags: "Теги входящих",
|
||||||
|
basics: "Основы",
|
||||||
|
config: "Конфигурация",
|
||||||
|
links: "Ссылки",
|
||||||
|
external: "Внешняя ссылка",
|
||||||
|
sub: "Внешняя подписка",
|
||||||
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "Добавить пакетно",
|
||||||
|
order: "Порядок",
|
||||||
|
random: "Случайный",
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
un: "Имя пользователя",
|
||||||
|
pw: "Пароль",
|
||||||
|
direct: {
|
||||||
|
overrideAddr: "Переопределить адрес",
|
||||||
|
overridePort: "Переопределить порт",
|
||||||
|
},
|
||||||
|
hy: {
|
||||||
|
obfs: "Обфусцированный пароль",
|
||||||
|
auth: "Пароль аутентификации",
|
||||||
|
hyOptions: "Параметры Hysteria",
|
||||||
|
hy2Options: "Параметры Hysteria2",
|
||||||
|
ignoreBw: "Игнорировать пропускную способность клиента",
|
||||||
|
},
|
||||||
|
shdwTls: {
|
||||||
|
hs: "Сервер рукопожатий",
|
||||||
|
addHS: "Добавить сервер рукопожатий",
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
passphrase: "Парольная фраза",
|
||||||
|
hostKey: "Ключи хоста",
|
||||||
|
algorithm: "Алгоритмы ключей",
|
||||||
|
clientVer: "Версия клиента",
|
||||||
|
options: "Параметры SSH",
|
||||||
|
},
|
||||||
|
tor: {
|
||||||
|
execPath: "Путь к исполняемому файлу",
|
||||||
|
dataDir: "Каталог данных",
|
||||||
|
extArgs: "Дополнительные аргументы",
|
||||||
|
},
|
||||||
|
tuic: {
|
||||||
|
congControl: "Контроль перегрузок",
|
||||||
|
authTimeout: "Таймаут аутентификации",
|
||||||
|
hb: "Сердцебиение",
|
||||||
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "Адреса",
|
||||||
|
ifName: "Имя интерфейса",
|
||||||
|
},
|
||||||
|
vless: {
|
||||||
|
flow: "Поток",
|
||||||
|
udpEnc: "Кодирование UDP пакетов",
|
||||||
|
},
|
||||||
|
vmess: {
|
||||||
|
security: "Безопасность",
|
||||||
|
globalPadding: "Глобальная подкладка",
|
||||||
|
authLen: "Длина шифрования",
|
||||||
|
},
|
||||||
|
wg: {
|
||||||
|
privKey: "Приватный ключ",
|
||||||
|
pubKey: "Публичный ключ пира",
|
||||||
|
psk: "Предварительно разделенный ключ",
|
||||||
|
localIp: "Локальные IP",
|
||||||
|
worker: "Работники",
|
||||||
|
ifName: "Имя интерфейса",
|
||||||
|
sysIf: "Системный интерфейс",
|
||||||
|
gso: "Отключение сегментации",
|
||||||
|
options: "Параметры Wireguard",
|
||||||
|
multiPeer: "Множественный пир",
|
||||||
|
allowedIp: "Разрешенные IP",
|
||||||
|
peer: "Пир",
|
||||||
|
peers: "Пиры",
|
||||||
|
},
|
||||||
|
lb: {
|
||||||
|
defaultOut: "Исходящий по умолчанию",
|
||||||
|
interruptConn: "Прервать существующие соединения",
|
||||||
|
testUrl: "Тестовый URL",
|
||||||
|
interval: "Интервал",
|
||||||
|
tolerance: "Толерантность",
|
||||||
|
urlTestOptions: "Параметры теста URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
in: {
|
||||||
|
addr: "Адрес",
|
||||||
|
port: "Порт",
|
||||||
|
clients: "Включить клиентов",
|
||||||
|
ssMethod: "Метод",
|
||||||
|
sSide: "Сторона сервера",
|
||||||
|
cSide: "Сторона клиента",
|
||||||
|
multiDomain: "Мультидомен",
|
||||||
|
remark: "Примечание",
|
||||||
|
mdOption: "Параметры мультидомена",
|
||||||
|
},
|
||||||
|
listen: {
|
||||||
|
sniffing: "Обнаружение",
|
||||||
|
sniffingTimeout: "Таймаут обнаружения",
|
||||||
|
sniffingOverride: "Переопределение назначения",
|
||||||
|
options: "Параметры прослушивания",
|
||||||
|
tcpOptions: "Параметры TCP",
|
||||||
|
udpOptions: "Параметры UDP",
|
||||||
|
detour: "Обход",
|
||||||
|
detourText: "Переадресация на входящий",
|
||||||
|
domainStrategy: "Стратегия домена",
|
||||||
|
},
|
||||||
|
dial: {
|
||||||
|
bindIf: "Привязка к сетевому интерфейсу",
|
||||||
|
bindIp4: "Привязка к IPv4",
|
||||||
|
bindIp6: "Привязка к IPv6",
|
||||||
|
reuseAddr: "Повторное использование адреса слушателя",
|
||||||
|
connTimeout: "Таймаут подключения",
|
||||||
|
fbTimeout: "Таймаут возврата",
|
||||||
|
options: "Параметры вызова",
|
||||||
|
detourText: "Переадресация на исходящий",
|
||||||
|
},
|
||||||
|
transport: {
|
||||||
|
enable: "Включить транспорт",
|
||||||
|
host: "Хост",
|
||||||
|
hosts: "Хосты",
|
||||||
|
path: "Путь",
|
||||||
|
httpMethod: "Метод запроса",
|
||||||
|
idleTimeout: "Таймаут бездействия",
|
||||||
|
pingTimeout: "Таймаут пинга",
|
||||||
|
grpcServiceName: "Имя службы",
|
||||||
|
grpcPws: "Разрешить без потока",
|
||||||
|
},
|
||||||
|
mux: {
|
||||||
|
enable: "Включить мультиплекс",
|
||||||
|
maxConn: "Максимальное количество соединений",
|
||||||
|
minStr: "Минимальное количество потоков",
|
||||||
|
maxStr: "Максимальное количество потоков",
|
||||||
|
padding: "Только подкладка",
|
||||||
|
enableBrutal: "Включить Brutal",
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
addr: "Адрес сервера",
|
||||||
|
port: "Порт сервера",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
add: "Добавить правило",
|
||||||
|
simple: "Простое",
|
||||||
|
logical: "Логическое",
|
||||||
|
mode: "Режим",
|
||||||
|
invert: "Инвертировать",
|
||||||
|
ipVer: "Версия IP",
|
||||||
|
domain: "Домены",
|
||||||
|
domainSufix: "Суффиксы доменов",
|
||||||
|
domainKw: "Ключевые слова домена",
|
||||||
|
domainRgx: "Регулярные выражения домена",
|
||||||
|
ip: "CIDR IP",
|
||||||
|
privateIp: "Недействительные диапазоны IP",
|
||||||
|
port: "Порты",
|
||||||
|
portRange: "Диапазоны портов",
|
||||||
|
srcCidr: "CIDR исходного IP",
|
||||||
|
srcPrivateIp: "Недействительные исходные IP",
|
||||||
|
srcPort: "Исходные порты",
|
||||||
|
srcPortRange: "Диапазоны исходных портов",
|
||||||
|
ruleset: "Наборы правил",
|
||||||
|
rulesetMatchSrc: "Набор правил для соответствия источника IPcidr",
|
||||||
|
options: "Параметры правила",
|
||||||
|
domainRules: "Домен/IP",
|
||||||
|
srcIpRules: "Источник IP",
|
||||||
|
srcPortRules: "Источник порта",
|
||||||
|
},
|
||||||
|
ruleset: {
|
||||||
|
add: "Добавить набор правил",
|
||||||
|
format: "Формат данных",
|
||||||
|
interval: "Интервалы обновления",
|
||||||
|
remote: "Удаленный",
|
||||||
|
local: "Локальный",
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
log: {
|
||||||
|
title: "Журналы",
|
||||||
|
level: "Уровень",
|
||||||
|
output: "Вывод",
|
||||||
|
timestamp: "Включить метку времени",
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
final: "Итоговый",
|
||||||
|
server: "Сервер",
|
||||||
|
firstServer: "Первый сервер",
|
||||||
|
addrResolver: "Разрешение адреса",
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
title: "Маршрутизация",
|
||||||
|
defaultOut: "Исходящий по умолчанию",
|
||||||
|
defaultIf: "Сетевой интерфейс по умолчанию",
|
||||||
|
defaultRm: "Маршрут по умолчанию",
|
||||||
|
autoBind: "Автопривязка сетевого интерфейса",
|
||||||
|
},
|
||||||
|
exp: {
|
||||||
|
storeFakeIp: "Хранить поддельный IP",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
enable: "Включить TLS",
|
||||||
|
usePath: "Использовать путь",
|
||||||
|
useText: "Использовать текст",
|
||||||
|
certPath: "Путь к файлу сертификата",
|
||||||
|
keyPath: "Путь к файлу ключа",
|
||||||
|
cert: "Сертификат",
|
||||||
|
key: "Ключ",
|
||||||
|
options: "Параметры TLS",
|
||||||
|
minVer: "Минимальная версия",
|
||||||
|
maxVer: "Максимальная версия",
|
||||||
|
cs: "Наборы шифров",
|
||||||
|
privKey: "Приватный ключ",
|
||||||
|
pubKey: "Публичный ключ",
|
||||||
|
disableSni: "Отключить SNI",
|
||||||
|
insecure: "Разрешить небезопасное",
|
||||||
|
acme: {
|
||||||
|
options: "Параметры ACME",
|
||||||
|
dataDir: "Каталог данных",
|
||||||
|
defaultDomain: "Домен по умолчанию",
|
||||||
|
disableChallenges: "Отключить вызовы",
|
||||||
|
httpChallenge: "Отключить HTTP вызов",
|
||||||
|
tlsChallenge: "Отключить TLS вызов",
|
||||||
|
altPorts: "Альтернативные порты",
|
||||||
|
altHport: "Альтернативный HTTP порт",
|
||||||
|
altTport: "Альтернативный TLS порт",
|
||||||
|
caProvider: "Поставщик CA",
|
||||||
|
customCa: "Пользовательский поставщик CA",
|
||||||
|
extAcc: "Внешний аккаунт",
|
||||||
|
dns01: "DNS01 вызов",
|
||||||
|
dns01Provider: "Поставщик DNS01 вызова",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
upload: "Загрузка",
|
||||||
|
download: "Скачивание",
|
||||||
|
volume: "Объем",
|
||||||
|
usage: "Использование",
|
||||||
|
enable: "Включить статистику",
|
||||||
|
graphTitle: "График трафика",
|
||||||
|
B: "Б",
|
||||||
|
KB: "КБ",
|
||||||
|
MB: "МБ",
|
||||||
|
GB: "ГБ",
|
||||||
|
TB: "ТБ",
|
||||||
|
PB: "ПБ",
|
||||||
|
p: "п",
|
||||||
|
Kp: "Кп",
|
||||||
|
Mp: "Мп",
|
||||||
|
Gb: "Гб",
|
||||||
|
bps: "б/с",
|
||||||
|
Kbps: "Кб/с",
|
||||||
|
Mbps: "Мб/с",
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
expiry: "Срок действия",
|
||||||
|
expired: "Истек",
|
||||||
|
d: "д",
|
||||||
|
h: "ч",
|
||||||
|
m: "м",
|
||||||
|
s: "с",
|
||||||
|
ms: "мс",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -4,6 +4,9 @@ export default {
|
|||||||
failed: "Thất bại",
|
failed: "Thất bại",
|
||||||
enable: "Kích hoạt",
|
enable: "Kích hoạt",
|
||||||
disable: "Vô hiệu hóa",
|
disable: "Vô hiệu hóa",
|
||||||
|
none: "Không",
|
||||||
|
all: "Tất cả",
|
||||||
|
filter: "Bộ lọc",
|
||||||
loading: "Đang tải...",
|
loading: "Đang tải...",
|
||||||
confirm: "Bạn chắc chắn chứ?",
|
confirm: "Bạn chắc chắn chứ?",
|
||||||
yes: "có",
|
yes: "có",
|
||||||
@@ -21,9 +24,13 @@ export default {
|
|||||||
invalidLogin: "Đăng nhập không hợp lệ!",
|
invalidLogin: "Đăng nhập không hợp lệ!",
|
||||||
online: "Trực tuyến",
|
online: "Trực tuyến",
|
||||||
version: "Phiên bản",
|
version: "Phiên bản",
|
||||||
|
email: "Email",
|
||||||
commaSeparated: "(được phân tách bằng dấu phẩy)",
|
commaSeparated: "(được phân tách bằng dấu phẩy)",
|
||||||
|
count: "Đếm",
|
||||||
|
template: "Mẫu",
|
||||||
error: {
|
error: {
|
||||||
dplData: "Dữ liệu trùng lặp",
|
dplData: "Dữ liệu trùng lặp",
|
||||||
|
core: "Lỗi Sing-Box",
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "Đăng nhập",
|
login: "Đăng nhập",
|
||||||
@@ -32,6 +39,7 @@ export default {
|
|||||||
outbounds: "Đầu ra",
|
outbounds: "Đầu ra",
|
||||||
clients: "Khách hàng",
|
clients: "Khách hàng",
|
||||||
rules: "Quy tắc",
|
rules: "Quy tắc",
|
||||||
|
tls: "Cài đặt TLS",
|
||||||
basics: "Cơ bản",
|
basics: "Cơ bản",
|
||||||
admins: "Quản trị viên",
|
admins: "Quản trị viên",
|
||||||
settings: "Cài đặt",
|
settings: "Cài đặt",
|
||||||
@@ -83,13 +91,19 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "Hành động",
|
action: "Hành động",
|
||||||
add: "Thêm",
|
add: "Thêm",
|
||||||
|
new: "Mới",
|
||||||
edit: "Chỉnh sửa",
|
edit: "Chỉnh sửa",
|
||||||
del: "Xóa",
|
del: "Xóa",
|
||||||
|
clone: "Nhân bản",
|
||||||
save: "Lưu",
|
save: "Lưu",
|
||||||
update: "Cập nhật",
|
update: "Cập nhật",
|
||||||
submit: "Gửi",
|
submit: "Gửi",
|
||||||
|
set: "Đặt",
|
||||||
|
generate: "Tạo ra",
|
||||||
|
disable: "Vô hiệu hóa",
|
||||||
close: "Đóng",
|
close: "Đóng",
|
||||||
restartApp: "Khởi động lại ứng dụng",
|
restartApp: "Khởi động lại ứng dụng",
|
||||||
|
restartSb: "Khởi động lại Singbox",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: "Đăng nhập",
|
title: "Đăng nhập",
|
||||||
@@ -109,6 +123,10 @@ export default {
|
|||||||
lastLogin: "Lân đăng nhập cuôi",
|
lastLogin: "Lân đăng nhập cuôi",
|
||||||
date: "Ngày",
|
date: "Ngày",
|
||||||
time: "Thời gian",
|
time: "Thời gian",
|
||||||
|
changes: "Thay đổi",
|
||||||
|
actor: "Diễn viên",
|
||||||
|
key: "Khóa",
|
||||||
|
action: "Hành động",
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "Giao diện",
|
interface: "Giao diện",
|
||||||
@@ -128,10 +146,20 @@ export default {
|
|||||||
path: "Đường dẫn mặc định",
|
path: "Đường dẫn mặc định",
|
||||||
update: "Thời gian cập nhật tự động",
|
update: "Thời gian cập nhật tự động",
|
||||||
subUri: "URI đăng ký",
|
subUri: "URI đăng ký",
|
||||||
|
jsonSub: "Đăng ký JSON",
|
||||||
|
toDirect: "Chuyển hướng tới Trực tiếp",
|
||||||
|
toBlock: "Chuyển hướng tới Chặn",
|
||||||
|
timestamp: "Dấu thời gian",
|
||||||
|
globalDns: "DNS Toàn cầu",
|
||||||
|
directDns: "DNS Trực tiếp",
|
||||||
|
toDirectDns: "Chuyển hướng tới DNS Trực tiếp",
|
||||||
|
jsonSubOptions: "Tùy chọn Khác",
|
||||||
|
excludePkg: "Loại trừ Gói",
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "Tên",
|
name: "Tên",
|
||||||
desc: "Mô tả",
|
desc: "Mô tả",
|
||||||
|
group: "Nhóm",
|
||||||
inboundTags: "Thẻ đầu vào",
|
inboundTags: "Thẻ đầu vào",
|
||||||
basics: "Cơ bản",
|
basics: "Cơ bản",
|
||||||
config: "Cấu hình",
|
config: "Cấu hình",
|
||||||
@@ -139,13 +167,17 @@ export default {
|
|||||||
external: "Liên kết bên ngoài",
|
external: "Liên kết bên ngoài",
|
||||||
sub: "Đăng ký bên ngoài",
|
sub: "Đăng ký bên ngoài",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "Thêm Hàng loạt",
|
||||||
|
order: "Sắp xếp",
|
||||||
|
random: "Ngẫu nhiên",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "Tên người dùng",
|
un: "Tên người dùng",
|
||||||
pw: "Mật khẩu",
|
pw: "Mật khẩu",
|
||||||
direct: {
|
direct: {
|
||||||
overrideAddr: "Ghi đè Địa chỉ",
|
overrideAddr: "Ghi đè Địa chỉ",
|
||||||
overridePort: "Ghi đè Cổng",
|
overridePort: "Ghi đè Cổng",
|
||||||
proxyProtocol: "Giao thức Proxy",
|
|
||||||
},
|
},
|
||||||
hy: {
|
hy: {
|
||||||
obfs: "Mật khẩu đã được Ẩn",
|
obfs: "Mật khẩu đã được Ẩn",
|
||||||
@@ -175,6 +207,10 @@ export default {
|
|||||||
authTimeout: "Thời gian chờ Xác thực",
|
authTimeout: "Thời gian chờ Xác thực",
|
||||||
hb: "Nhịp tim",
|
hb: "Nhịp tim",
|
||||||
},
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "Địa chỉ",
|
||||||
|
ifName: "Tên Giao diện",
|
||||||
|
},
|
||||||
vless: {
|
vless: {
|
||||||
flow: "Luồng",
|
flow: "Luồng",
|
||||||
udpEnc: "Mã hóa Gói UDP",
|
udpEnc: "Mã hóa Gói UDP",
|
||||||
@@ -214,6 +250,11 @@ export default {
|
|||||||
sniffing: "Đang Sniffing",
|
sniffing: "Đang Sniffing",
|
||||||
clients: "Kích hoạt khách hàng",
|
clients: "Kích hoạt khách hàng",
|
||||||
ssMethod: "Phương thức",
|
ssMethod: "Phương thức",
|
||||||
|
sSide: "Phía Máy chủ",
|
||||||
|
cSide: "Phía Khách hàng",
|
||||||
|
multiDomain: "Nhiều Tên miền",
|
||||||
|
remark: "Ghi chú",
|
||||||
|
mdOption: "Tùy chọn Nhiều Tên miền",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "Đang Sniffing",
|
sniffing: "Đang Sniffing",
|
||||||
@@ -303,6 +344,7 @@ export default {
|
|||||||
final: "Cuối cùng",
|
final: "Cuối cùng",
|
||||||
server: "Máy chủ",
|
server: "Máy chủ",
|
||||||
firstServer: "Máy chủ Đầu tiên",
|
firstServer: "Máy chủ Đầu tiên",
|
||||||
|
addrResolver: "Trình phân giải địa chỉ",
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
title: "Định tuyến",
|
title: "Định tuyến",
|
||||||
@@ -327,9 +369,26 @@ export default {
|
|||||||
minVer: "Phiên bản Tối thiểu",
|
minVer: "Phiên bản Tối thiểu",
|
||||||
maxVer: "Phiên bản Tối đa",
|
maxVer: "Phiên bản Tối đa",
|
||||||
cs: "Các bộ mã hóa",
|
cs: "Các bộ mã hóa",
|
||||||
|
privKey: "Khóa riêng",
|
||||||
pubKey: "Khóa Công khai",
|
pubKey: "Khóa Công khai",
|
||||||
disableSni: "Tắt SNI",
|
disableSni: "Tắt SNI",
|
||||||
insecure: "Cho phép Không an toàn",
|
insecure: "Cho phép Không an toàn",
|
||||||
|
acme: {
|
||||||
|
options: "Tùy chọn ACME",
|
||||||
|
dataDir: "Thư mục Dữ liệu",
|
||||||
|
defaultDomain: "Tên miền Mặc định",
|
||||||
|
disableChallenges: "Vô hiệu hóa Thách thức",
|
||||||
|
httpChallenge: "Vô hiệu hóa Thách thức HTTP",
|
||||||
|
tlsChallenge: "Vô hiệu hóa Thách thức TLS",
|
||||||
|
altPorts: "Cổng Thay thế",
|
||||||
|
altHport: "Cổng HTTP Thay thế",
|
||||||
|
altTport: "Cổng TLS Thay thế",
|
||||||
|
caProvider: "Nhà cung cấp CA",
|
||||||
|
customCa: "Nhà cung cấp CA Tùy chỉnh",
|
||||||
|
extAcc: "Tài khoản Bên ngoài",
|
||||||
|
dns01: "Thách thức DNS01",
|
||||||
|
dns01Provider: "Nhà cung cấp Thách thức DNS01"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
upload: "Tải lên",
|
upload: "Tải lên",
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export default {
|
|||||||
failed: "失败",
|
failed: "失败",
|
||||||
enable: "启用",
|
enable: "启用",
|
||||||
disable: "禁用",
|
disable: "禁用",
|
||||||
|
none: "无",
|
||||||
|
all: "全部",
|
||||||
|
filter: "过滤器",
|
||||||
loading: "加载中...",
|
loading: "加载中...",
|
||||||
confirm: "是否确定?",
|
confirm: "是否确定?",
|
||||||
yes: "确认",
|
yes: "确认",
|
||||||
@@ -21,9 +24,13 @@ export default {
|
|||||||
invalidLogin: "登录无效!",
|
invalidLogin: "登录无效!",
|
||||||
online: "在线",
|
online: "在线",
|
||||||
version: "版本",
|
version: "版本",
|
||||||
|
email: "电子邮件",
|
||||||
commaSeparated: "(逗号分隔)",
|
commaSeparated: "(逗号分隔)",
|
||||||
|
count: "计数",
|
||||||
|
template: "模板",
|
||||||
error: {
|
error: {
|
||||||
dplData: "重复数据",
|
dplData: "重复数据",
|
||||||
|
core: "Sing-Box 错误",
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "登录",
|
login: "登录",
|
||||||
@@ -32,6 +39,7 @@ export default {
|
|||||||
outbounds: "出站管理",
|
outbounds: "出站管理",
|
||||||
clients: "用户管理",
|
clients: "用户管理",
|
||||||
rules: "路由列表",
|
rules: "路由列表",
|
||||||
|
tls: "TLS 设置",
|
||||||
basics: "基础信息",
|
basics: "基础信息",
|
||||||
admins: "管理员",
|
admins: "管理员",
|
||||||
settings: "设置",
|
settings: "设置",
|
||||||
@@ -70,26 +78,32 @@ export default {
|
|||||||
rule: "规则",
|
rule: "规则",
|
||||||
user: "用户",
|
user: "用户",
|
||||||
tag: "标签",
|
tag: "标签",
|
||||||
listen: "听",
|
listen: "监听",
|
||||||
dial: "拨号",
|
dial: "拨号",
|
||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "多路复用",
|
multiplex: "多路复用",
|
||||||
transport: "传输",
|
transport: "传输",
|
||||||
method: "方法",
|
method: "方法",
|
||||||
headers: "标头",
|
headers: "标头",
|
||||||
key: "钥匙",
|
key: "键",
|
||||||
value: "价值",
|
value: "值",
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
action: "操作",
|
action: "操作",
|
||||||
add: "添加",
|
add: "添加",
|
||||||
|
new: "新建",
|
||||||
edit: "编辑",
|
edit: "编辑",
|
||||||
del: "删除",
|
del: "删除",
|
||||||
|
clone: "克隆",
|
||||||
save: "保存",
|
save: "保存",
|
||||||
update: "更新",
|
update: "更新",
|
||||||
submit: "提交",
|
submit: "提交",
|
||||||
|
set: "设置",
|
||||||
|
generate: "生成",
|
||||||
|
disable: "禁用",
|
||||||
close: "关闭",
|
close: "关闭",
|
||||||
restartApp: "重启面板",
|
restartApp: "重启面板",
|
||||||
|
restartSb: "重启 Singbox",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: "登录",
|
title: "登录",
|
||||||
@@ -109,29 +123,43 @@ export default {
|
|||||||
lastLogin: "上次登录",
|
lastLogin: "上次登录",
|
||||||
date: "日期",
|
date: "日期",
|
||||||
time: "时间",
|
time: "时间",
|
||||||
|
changes: "更改",
|
||||||
|
actor: "执行者",
|
||||||
|
key: "键",
|
||||||
|
action: "操作",
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "界面",
|
interface: "界面",
|
||||||
sub: "订阅",
|
sub: "订阅",
|
||||||
addr: "地址",
|
addr: "地址",
|
||||||
port: "端口",
|
port: "端口",
|
||||||
webPath: "基本 URI",
|
webPath: "面板路径",
|
||||||
domain: "域名",
|
domain: "域名",
|
||||||
sslKey: "SSL 密钥 (Key) 路径",
|
sslKey: "SSL 密钥 (Key) 路径",
|
||||||
sslCert: "SSL 证书 (cert) 路径",
|
sslCert: "SSL 证书 (cert) 路径",
|
||||||
webUri: "面板 URI",
|
webUri: "面板 URI",
|
||||||
sessionAge: "会话最大连接数",
|
sessionAge: "会话超时时限",
|
||||||
trafficAge: "流量最大年龄",
|
trafficAge: "流量过期时限",
|
||||||
timeLoc: "时区",
|
timeLoc: "时区",
|
||||||
subEncode: "启用编码",
|
subEncode: "启用 Base64 编码",
|
||||||
subInfo: "启用用户信息",
|
subInfo: "启用用户信息",
|
||||||
path: "默认路径",
|
path: "默认路径",
|
||||||
update: "自动更新时间",
|
update: "自动更新时间",
|
||||||
subUri: "订阅 URL",
|
subUri: "订阅 URI",
|
||||||
|
jsonSub: "JSON 订阅",
|
||||||
|
toDirect: "路由到直连",
|
||||||
|
toBlock: "路由到阻止",
|
||||||
|
timestamp: "时间戳",
|
||||||
|
globalDns: "全局 DNS",
|
||||||
|
directDns: "直连 DNS",
|
||||||
|
toDirectDns: "路由到直连 DNS",
|
||||||
|
jsonSubOptions: "其他选项",
|
||||||
|
excludePkg: "排除包",
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "名称",
|
name: "名称",
|
||||||
desc: "描述",
|
desc: "描述",
|
||||||
|
group: "组",
|
||||||
inboundTags: "入站标签",
|
inboundTags: "入站标签",
|
||||||
basics: "基础",
|
basics: "基础",
|
||||||
config: "配置",
|
config: "配置",
|
||||||
@@ -139,13 +167,17 @@ export default {
|
|||||||
external: "外部链接",
|
external: "外部链接",
|
||||||
sub: "外部订阅",
|
sub: "外部订阅",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "批量添加",
|
||||||
|
order: "排序",
|
||||||
|
random: "随机",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "用户名",
|
un: "用户名",
|
||||||
pw: "密码",
|
pw: "密码",
|
||||||
direct: {
|
direct: {
|
||||||
overrideAddr: "覆盖地址",
|
overrideAddr: "覆盖地址",
|
||||||
overridePort: "覆盖端口",
|
overridePort: "覆盖端口",
|
||||||
proxyProtocol: "代理协议",
|
|
||||||
},
|
},
|
||||||
hy: {
|
hy: {
|
||||||
obfs: "混淆密码",
|
obfs: "混淆密码",
|
||||||
@@ -173,10 +205,14 @@ export default {
|
|||||||
tuic: {
|
tuic: {
|
||||||
congControl: "拥塞控制",
|
congControl: "拥塞控制",
|
||||||
authTimeout: "认证超时",
|
authTimeout: "认证超时",
|
||||||
hb: "心跳",
|
hb: "心跳包",
|
||||||
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "地址",
|
||||||
|
ifName: "接口名称",
|
||||||
},
|
},
|
||||||
vless: {
|
vless: {
|
||||||
flow: "流量",
|
flow: "流控",
|
||||||
udpEnc: "UDP 数据包编码",
|
udpEnc: "UDP 数据包编码",
|
||||||
},
|
},
|
||||||
vmess: {
|
vmess: {
|
||||||
@@ -214,22 +250,27 @@ export default {
|
|||||||
sniffing: "嗅探",
|
sniffing: "嗅探",
|
||||||
clients: "启用客户端",
|
clients: "启用客户端",
|
||||||
ssMethod: "方法",
|
ssMethod: "方法",
|
||||||
|
sSide: "服务器端",
|
||||||
|
cSide: "客户端",
|
||||||
|
multiDomain: "多域名",
|
||||||
|
remark: "备注",
|
||||||
|
mdOption: "多域名选项",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "嗅探",
|
sniffing: "嗅探",
|
||||||
sniffingTimeout: "嗅探超时",
|
sniffingTimeout: "嗅探超时",
|
||||||
sniffingOverride: "覆盖目的地",
|
sniffingOverride: "覆盖目标地址",
|
||||||
options: "监听选项",
|
options: "监听选项",
|
||||||
tcpOptions: "TCP选项",
|
tcpOptions: "TCP 选项",
|
||||||
udpOptions: "UDP选项",
|
udpOptions: "UDP 选项",
|
||||||
detour: "绕道",
|
detour: "转发",
|
||||||
detourText: "转发到入站",
|
detourText: "转发到入站",
|
||||||
domainStrategy: "域名策略",
|
domainStrategy: "域名解析策略",
|
||||||
},
|
},
|
||||||
dial: {
|
dial: {
|
||||||
bindIf: "绑定到网络接口",
|
bindIf: "绑定到网络接口",
|
||||||
bindIp4: "绑定到IPv4",
|
bindIp4: "绑定到 IPv4",
|
||||||
bindIp6: "绑定到IPv6",
|
bindIp6: "绑定到 IPv6",
|
||||||
reuseAddr: "重用监听地址",
|
reuseAddr: "重用监听地址",
|
||||||
connTimeout: "连接超时",
|
connTimeout: "连接超时",
|
||||||
fbTimeout: "回退超时",
|
fbTimeout: "回退超时",
|
||||||
@@ -238,22 +279,22 @@ export default {
|
|||||||
},
|
},
|
||||||
transport: {
|
transport: {
|
||||||
enable: "启用传输",
|
enable: "启用传输",
|
||||||
host: "主机",
|
host: "主机域名",
|
||||||
hosts: "主机列表",
|
hosts: "主机域名列表",
|
||||||
path: "路径",
|
path: "HTTP 请求路径",
|
||||||
httpMethod: "请求方法",
|
httpMethod: "HTTP 请求方法",
|
||||||
idleTimeout: "空闲超时",
|
idleTimeout: "空闲超时",
|
||||||
pingTimeout: "Ping超时",
|
pingTimeout: "Ping 超时",
|
||||||
grpcServiceName: "服务名称",
|
grpcServiceName: "gRPC 服务名称",
|
||||||
grpcPws: "允许无流",
|
grpcPws: "允许无流时保持连接",
|
||||||
},
|
},
|
||||||
mux: {
|
mux: {
|
||||||
enable: "启用多路复用",
|
enable: "启用多路复用",
|
||||||
maxConn: "最大连接数",
|
maxConn: "最大连接数",
|
||||||
minStr: "最小流数",
|
minStr: "最小流数",
|
||||||
maxStr: "最大流数",
|
maxStr: "最大流数",
|
||||||
padding: "仅填充",
|
padding: "仅允许填充连接",
|
||||||
enableBrutal: "启用强力模式",
|
enableBrutal: "启用 TCP Brutal",
|
||||||
},
|
},
|
||||||
out: {
|
out: {
|
||||||
addr: "服务器地址",
|
addr: "服务器地址",
|
||||||
@@ -264,22 +305,22 @@ export default {
|
|||||||
simple: "简单",
|
simple: "简单",
|
||||||
logical: "逻辑",
|
logical: "逻辑",
|
||||||
mode: "模式",
|
mode: "模式",
|
||||||
invert: "反转",
|
invert: "反选结果",
|
||||||
ipVer: "IP 版本",
|
ipVer: "IP 版本",
|
||||||
domain: "域名",
|
domain: "域名",
|
||||||
domainSufix: "域名后缀",
|
domainSufix: "域名后缀",
|
||||||
domainKw: "域名关键词",
|
domainKw: "域名关键词",
|
||||||
domainRgx: "域名正则表达式",
|
domainRgx: "域名正则表达式",
|
||||||
ip: "IP CIDR",
|
ip: "IP CIDR",
|
||||||
privateIp: "无效 IP 范围",
|
privateIp: "匹配非公开 IP",
|
||||||
port: "端口",
|
port: "端口",
|
||||||
portRange: "端口范围",
|
portRange: "端口范围",
|
||||||
srcCidr: "源 IP CIDR",
|
srcCidr: "源 IP CIDR",
|
||||||
srcPrivateIp: "无效源 IP",
|
srcPrivateIp: "匹配非公开源 IP",
|
||||||
srcPort: "源端口",
|
srcPort: "源端口",
|
||||||
srcPortRange: "源端口范围",
|
srcPortRange: "源端口范围",
|
||||||
ruleset: "规则集",
|
ruleset: "规则集",
|
||||||
rulesetMatchSrc: "规则集 IP CIDR 匹配源",
|
rulesetMatchSrc: "规则集 IP CIDR 匹配源 IP",
|
||||||
options: "规则选项",
|
options: "规则选项",
|
||||||
domainRules: "域名/IP",
|
domainRules: "域名/IP",
|
||||||
srcIpRules: "源 IP",
|
srcIpRules: "源 IP",
|
||||||
@@ -303,6 +344,7 @@ export default {
|
|||||||
final: "最终",
|
final: "最终",
|
||||||
server: "服务器",
|
server: "服务器",
|
||||||
firstServer: "首选服务器",
|
firstServer: "首选服务器",
|
||||||
|
addrResolver: "地址解析器",
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
title: "路由",
|
title: "路由",
|
||||||
@@ -312,7 +354,7 @@ export default {
|
|||||||
autoBind: "自动绑定网卡",
|
autoBind: "自动绑定网卡",
|
||||||
},
|
},
|
||||||
exp: {
|
exp: {
|
||||||
storeFakeIp: "存储虚假 IP",
|
storeFakeIp: "持久化 Fake-IP",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tls : {
|
tls : {
|
||||||
@@ -327,9 +369,26 @@ export default {
|
|||||||
minVer: "最低版本",
|
minVer: "最低版本",
|
||||||
maxVer: "最高版本",
|
maxVer: "最高版本",
|
||||||
cs: "密码套件",
|
cs: "密码套件",
|
||||||
|
privKey: "私钥",
|
||||||
pubKey: "公钥",
|
pubKey: "公钥",
|
||||||
disableSni: "禁用SNI",
|
disableSni: "禁用 SNI",
|
||||||
insecure: "允许不安全",
|
insecure: "允许不安全",
|
||||||
|
acme: {
|
||||||
|
options: "ACME 选项",
|
||||||
|
dataDir: "数据目录",
|
||||||
|
defaultDomain: "默认域名",
|
||||||
|
disableChallenges: "禁用挑战",
|
||||||
|
httpChallenge: "禁用 HTTP 挑战",
|
||||||
|
tlsChallenge: "禁用 TLS 挑战",
|
||||||
|
altPorts: "替代端口",
|
||||||
|
altHport: "替代 HTTP 端口",
|
||||||
|
altTport: "替代 TLS 端口",
|
||||||
|
caProvider: "CA 提供商",
|
||||||
|
customCa: "自定义 CA 提供商",
|
||||||
|
extAcc: "外部账户",
|
||||||
|
dns01: "DNS01 挑战",
|
||||||
|
dns01Provider: "DNS01 挑战提供商"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
upload: "上传",
|
upload: "上传",
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ export default {
|
|||||||
failed: "失敗",
|
failed: "失敗",
|
||||||
enable: "啟用",
|
enable: "啟用",
|
||||||
disable: "禁用",
|
disable: "禁用",
|
||||||
|
none: "無",
|
||||||
|
all: "全部",
|
||||||
|
filter: "過濾器",
|
||||||
loading: "加載中...",
|
loading: "加載中...",
|
||||||
confirm: "是否確定?",
|
confirm: "是否確定?",
|
||||||
yes: "確認",
|
yes: "確認",
|
||||||
@@ -22,9 +25,13 @@ export default {
|
|||||||
invalidLogin: "登錄無效!",
|
invalidLogin: "登錄無效!",
|
||||||
online: "在線",
|
online: "在線",
|
||||||
version: "版本",
|
version: "版本",
|
||||||
|
email: "電子郵件",
|
||||||
commaSeparated: "(逗號分隔)",
|
commaSeparated: "(逗號分隔)",
|
||||||
|
count: "計數",
|
||||||
|
template: "模板",
|
||||||
error: {
|
error: {
|
||||||
dplData: "重複數據",
|
dplData: "重複數據",
|
||||||
|
core: "Sing-Box 錯誤",
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "登錄",
|
login: "登錄",
|
||||||
@@ -33,6 +40,7 @@ export default {
|
|||||||
outbounds: "出站管理",
|
outbounds: "出站管理",
|
||||||
clients: "用戶管理",
|
clients: "用戶管理",
|
||||||
rules: "路由列表",
|
rules: "路由列表",
|
||||||
|
tls: "TLS 設置",
|
||||||
basics: "基礎信息",
|
basics: "基礎信息",
|
||||||
admins: "管理員",
|
admins: "管理員",
|
||||||
settings: "設置",
|
settings: "設置",
|
||||||
@@ -84,13 +92,19 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "操作",
|
action: "操作",
|
||||||
add: "添加",
|
add: "添加",
|
||||||
|
new: "新建",
|
||||||
edit: "編輯",
|
edit: "編輯",
|
||||||
del: "刪除",
|
del: "刪除",
|
||||||
|
clone: "克隆",
|
||||||
save: "保存",
|
save: "保存",
|
||||||
update: "更新",
|
update: "更新",
|
||||||
submit: "提交",
|
submit: "提交",
|
||||||
|
set: "設置",
|
||||||
|
generate: "生成",
|
||||||
|
disable: "禁用",
|
||||||
close: "關閉",
|
close: "關閉",
|
||||||
restartApp: "重啟面板",
|
restartApp: "重啟面板",
|
||||||
|
restartSb: "重啟 Singbox",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
title: "登錄",
|
title: "登錄",
|
||||||
@@ -110,6 +124,10 @@ export default {
|
|||||||
lastLogin: "上次登入",
|
lastLogin: "上次登入",
|
||||||
date: "日期",
|
date: "日期",
|
||||||
time: "時間",
|
time: "時間",
|
||||||
|
changes: "更改",
|
||||||
|
actor: "執行者",
|
||||||
|
key: "鍵",
|
||||||
|
action: "操作",
|
||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "界面",
|
interface: "界面",
|
||||||
@@ -129,10 +147,20 @@ export default {
|
|||||||
path: "默認路徑",
|
path: "默認路徑",
|
||||||
update: "自動更新時間",
|
update: "自動更新時間",
|
||||||
subUri: "訂閱 URL",
|
subUri: "訂閱 URL",
|
||||||
|
jsonSub: "JSON 訂閱",
|
||||||
|
toDirect: "路由到直連",
|
||||||
|
toBlock: "路由到阻止",
|
||||||
|
timestamp: "時間戳",
|
||||||
|
globalDns: "全局 DNS",
|
||||||
|
directDns: "直連 DNS",
|
||||||
|
toDirectDns: "路由到直連 DNS",
|
||||||
|
jsonSubOptions: "其他選項",
|
||||||
|
excludePkg: "排除包",
|
||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "名稱",
|
name: "名稱",
|
||||||
desc: "描述",
|
desc: "描述",
|
||||||
|
group: "組",
|
||||||
inboundTags: "入站標簽",
|
inboundTags: "入站標簽",
|
||||||
basics: "基礎",
|
basics: "基礎",
|
||||||
config: "配置",
|
config: "配置",
|
||||||
@@ -140,13 +168,17 @@ export default {
|
|||||||
external: "外部鏈接",
|
external: "外部鏈接",
|
||||||
sub: "外部訂閱",
|
sub: "外部訂閱",
|
||||||
},
|
},
|
||||||
|
bulk: {
|
||||||
|
add: "批量添加",
|
||||||
|
order: "排序",
|
||||||
|
random: "隨機",
|
||||||
|
},
|
||||||
types: {
|
types: {
|
||||||
un: "用戶名",
|
un: "用戶名",
|
||||||
pw: "密碼",
|
pw: "密碼",
|
||||||
direct: {
|
direct: {
|
||||||
overrideAddr: "覆蓋地址",
|
overrideAddr: "覆蓋地址",
|
||||||
overridePort: "覆蓋端口",
|
overridePort: "覆蓋端口",
|
||||||
proxyProtocol: "代理協議",
|
|
||||||
},
|
},
|
||||||
hy: {
|
hy: {
|
||||||
obfs: "混淆密碼",
|
obfs: "混淆密碼",
|
||||||
@@ -176,6 +208,10 @@ export default {
|
|||||||
authTimeout: "身份驗證超時",
|
authTimeout: "身份驗證超時",
|
||||||
hb: "心跳",
|
hb: "心跳",
|
||||||
},
|
},
|
||||||
|
tun: {
|
||||||
|
addr: "地址",
|
||||||
|
ifName: "介面名稱",
|
||||||
|
},
|
||||||
vless: {
|
vless: {
|
||||||
flow: "流量",
|
flow: "流量",
|
||||||
udpEnc: "UDP 封包編碼",
|
udpEnc: "UDP 封包編碼",
|
||||||
@@ -215,6 +251,11 @@ export default {
|
|||||||
sniffing: "嗅探",
|
sniffing: "嗅探",
|
||||||
clients: "啟用客戶端",
|
clients: "啟用客戶端",
|
||||||
ssMethod: "方法",
|
ssMethod: "方法",
|
||||||
|
sSide: "服務器端",
|
||||||
|
cSide: "客戶端",
|
||||||
|
multiDomain: "多域名",
|
||||||
|
remark: "備註",
|
||||||
|
mdOption: "多域名選項",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "嗅探",
|
sniffing: "嗅探",
|
||||||
@@ -304,6 +345,7 @@ export default {
|
|||||||
final: "最終",
|
final: "最終",
|
||||||
server: "服務器",
|
server: "服務器",
|
||||||
firstServer: "首選服務器",
|
firstServer: "首選服務器",
|
||||||
|
addrResolver: "地址解析器",
|
||||||
},
|
},
|
||||||
routing: {
|
routing: {
|
||||||
title: "路由",
|
title: "路由",
|
||||||
@@ -328,9 +370,26 @@ export default {
|
|||||||
minVer: "最低版本",
|
minVer: "最低版本",
|
||||||
maxVer: "最高版本",
|
maxVer: "最高版本",
|
||||||
cs: "加密套件",
|
cs: "加密套件",
|
||||||
|
privKey: "私鑰",
|
||||||
pubKey: "公鑰",
|
pubKey: "公鑰",
|
||||||
disableSni: "停用 SNI",
|
disableSni: "停用 SNI",
|
||||||
insecure: "允許不安全連線",
|
insecure: "允許不安全連線",
|
||||||
|
acme: {
|
||||||
|
options: "ACME 選項",
|
||||||
|
dataDir: "數據目錄",
|
||||||
|
defaultDomain: "默認域名",
|
||||||
|
disableChallenges: "禁用挑戰",
|
||||||
|
httpChallenge: "禁用 HTTP 挑戰",
|
||||||
|
tlsChallenge: "禁用 TLS 挑戰",
|
||||||
|
altPorts: "替代端口",
|
||||||
|
altHport: "替代 HTTP 端口",
|
||||||
|
altTport: "替代 TLS 端口",
|
||||||
|
caProvider: "CA 提供商",
|
||||||
|
customCa: "自定義 CA 提供商",
|
||||||
|
extAcc: "外部賬戶",
|
||||||
|
dns01: "DNS01 挑戰",
|
||||||
|
dns01Provider: "DNS01 挑戰提供商"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
upload: "上傳",
|
upload: "上傳",
|
||||||
|
|||||||
@@ -23,6 +23,22 @@ import { registerPlugins } from '@/plugins'
|
|||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import Vue3PersianDatetimePicker from 'vue3-persian-datetime-picker'
|
import Vue3PersianDatetimePicker from 'vue3-persian-datetime-picker'
|
||||||
|
|
||||||
|
// Notivue
|
||||||
|
import { createNotivue } from 'notivue'
|
||||||
|
import 'notivue/notification.css'
|
||||||
|
import 'notivue/animations.css'
|
||||||
|
const notivue = createNotivue({
|
||||||
|
position: 'bottom-center',
|
||||||
|
limit: 4,
|
||||||
|
enqueue: false,
|
||||||
|
avoidDuplicates: true,
|
||||||
|
notifications: {
|
||||||
|
global: {
|
||||||
|
duration: 3000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@@ -34,5 +50,6 @@ app
|
|||||||
.use(router)
|
.use(router)
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
|
.use(notivue)
|
||||||
.component('DatePicker', Vue3PersianDatetimePicker)
|
.component('DatePicker', Vue3PersianDatetimePicker)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|||||||
@@ -4,9 +4,26 @@ axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded
|
|||||||
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'
|
||||||
|
|
||||||
axios.defaults.baseURL = "./"
|
axios.defaults.baseURL = "./"
|
||||||
|
const pendingRequests = new Map()
|
||||||
|
|
||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
// Generate a unique key for the request
|
||||||
|
const requestKey = `${config.method}:${config.url}`
|
||||||
|
|
||||||
|
// Check if there is already a pending request with the same key
|
||||||
|
if (pendingRequests.has(requestKey)) {
|
||||||
|
const cancelSource = pendingRequests.get(requestKey)
|
||||||
|
cancelSource.cancel('Duplicate request cancelled')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new cancel token for the request
|
||||||
|
const cancelSource = axios.CancelToken.source()
|
||||||
|
config.cancelToken = cancelSource.token
|
||||||
|
|
||||||
|
// Store the cancel token in the pending requests map
|
||||||
|
pendingRequests.set(requestKey, cancelSource)
|
||||||
|
|
||||||
if (config.data instanceof FormData) {
|
if (config.data instanceof FormData) {
|
||||||
config.headers['Content-Type'] = 'multipart/form-data'
|
config.headers['Content-Type'] = 'multipart/form-data'
|
||||||
}
|
}
|
||||||
@@ -15,6 +32,26 @@ axios.interceptors.request.use(
|
|||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
// Remove the request from the pending requests map
|
||||||
|
const requestKey = `${response.config.method}:${response.config.url}`
|
||||||
|
pendingRequests.delete(requestKey)
|
||||||
|
return response
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
// Handle duplicate request cancellation here if needed
|
||||||
|
console.warn(error.message)
|
||||||
|
} else {
|
||||||
|
// Remove the request from the pending requests map on error
|
||||||
|
const requestKey = `${error.config.method}:${error.config.url}`
|
||||||
|
pendingRequests.delete(requestKey)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const api = axios.create()
|
const api = axios.create()
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import api from './api'
|
import api from './api'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import Message from "@/store/modules/message"
|
import { push } from 'notivue'
|
||||||
|
|
||||||
export interface Msg {
|
export interface Msg {
|
||||||
success: boolean
|
success: boolean
|
||||||
@@ -10,18 +10,27 @@ export interface Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _handleMsg(msg: any): void {
|
function _handleMsg(msg: any): void {
|
||||||
const sb = Message()
|
|
||||||
if (!isMsg(msg)) {
|
if (!isMsg(msg)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(msg.msg){
|
if(msg.msg){
|
||||||
if (!msg.success && msg.msg == "Invalid login") {
|
if (!msg.success && msg.msg == "Invalid login") {
|
||||||
sb.showMessage(i18n.global.t('invalidLogin'),'error', 5000)
|
push.error({
|
||||||
|
title: i18n.global.t('invalidLogin'),
|
||||||
|
})
|
||||||
logout()
|
logout()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const message = msg.success ? i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg) : i18n.global.t('failed') + ": " + msg.msg
|
if (msg.success) {
|
||||||
sb.showMessage(message, msg.success ? 'success' : 'error', 5000)
|
push.success({
|
||||||
|
message: i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
push.error({
|
||||||
|
title: i18n.global.t('failed'),
|
||||||
|
message: msg.msg
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +57,7 @@ function _respToMsg(resp: any): Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isMsg(obj: any): obj is Msg {
|
function isMsg(obj: any): obj is Msg {
|
||||||
return 'success' in obj && 'msg' in obj && 'obj' in obj
|
return Object.hasOwn(obj,'success') && Object.hasOwn(obj,'msg') && Object.hasOwn(obj, 'obj')
|
||||||
}
|
}
|
||||||
|
|
||||||
const HttpUtils = {
|
const HttpUtils = {
|
||||||
@@ -76,4 +85,4 @@ const HttpUtils = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HttpUtils;
|
export default HttpUtils
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Addr {
|
||||||
|
server: string
|
||||||
|
server_port: number
|
||||||
|
tls?: boolean
|
||||||
|
insecure?: boolean
|
||||||
|
server_name?: string
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InData {
|
||||||
|
id: number
|
||||||
|
tag: string
|
||||||
|
addrs: Addr[]
|
||||||
|
outJson: any
|
||||||
|
}
|
||||||
+324
-95
@@ -1,5 +1,7 @@
|
|||||||
import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds"
|
import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds"
|
||||||
import { HTTP, WebSocket, QUIC, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport";
|
import { HTTP, WebSocket, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport"
|
||||||
|
import RandomUtil from "./randomUtil"
|
||||||
|
import { Client } from "@/types/clients"
|
||||||
|
|
||||||
export interface Link {
|
export interface Link {
|
||||||
type: "local" | "external" | "sub"
|
type: "local" | "external" | "sub"
|
||||||
@@ -8,56 +10,69 @@ export interface Link {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function utf8ToBase64(utf8String: string): string {
|
function utf8ToBase64(utf8String: string): string {
|
||||||
const encodedUtf8 = encodeURIComponent(utf8String).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)));
|
const encodedUtf8 = encodeURIComponent(utf8String).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)))
|
||||||
return btoa(encodedUtf8);
|
return btoa(encodedUtf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace LinkUtil {
|
export namespace LinkUtil {
|
||||||
export function linkGenerator(user: string, inbound: Inbound): string {
|
export function linkGenerator(user: Client, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] {
|
||||||
const addr = location.hostname
|
|
||||||
switch(inbound.type){
|
switch(inbound.type){
|
||||||
case InTypes.Shadowsocks:
|
case InTypes.Shadowsocks:
|
||||||
return shadowsocksLink(user,<Shadowsocks>inbound,addr)
|
return shadowsocksLink(user,<Shadowsocks>inbound, addrs)
|
||||||
case InTypes.Naive:
|
case InTypes.Naive:
|
||||||
return naiveLink(user,<Naive>inbound,addr)
|
return naiveLink(user,<Naive>inbound, addrs, tlsClient)
|
||||||
case InTypes.Hysteria:
|
case InTypes.Hysteria:
|
||||||
return hysteriaLink(user,<Hysteria>inbound,addr)
|
return hysteriaLink(user,<Hysteria>inbound, addrs, tlsClient)
|
||||||
case InTypes.Hysteria2:
|
case InTypes.Hysteria2:
|
||||||
return hysteria2Link(user,<Hysteria2>inbound,addr)
|
return hysteria2Link(user,<Hysteria2>inbound, addrs, tlsClient)
|
||||||
case InTypes.TUIC:
|
case InTypes.TUIC:
|
||||||
return tuicLink(user,<TUIC>inbound,addr)
|
return tuicLink(user,<TUIC>inbound, addrs, tlsClient)
|
||||||
case InTypes.VLESS:
|
case InTypes.VLESS:
|
||||||
return vlessLink(user,<VLESS>inbound,addr)
|
return vlessLink(user,<VLESS>inbound, addrs, tlsClient)
|
||||||
case InTypes.Trojan:
|
case InTypes.Trojan:
|
||||||
return trojanLink(user,<Trojan>inbound,addr)
|
return trojanLink(user,<Trojan>inbound, addrs, tlsClient)
|
||||||
case InTypes.VMess:
|
case InTypes.VMess:
|
||||||
return vmessLink(user,<VMess>inbound,addr)
|
return vmessLink(user,<VMess>inbound, addrs, tlsClient)
|
||||||
}
|
}
|
||||||
return ''
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
function shadowsocksLink(user: string, inbound: Shadowsocks, addr: string): string {
|
function shadowsocksLink(user: Client, inbound: Shadowsocks, addrs: any[]): string[] {
|
||||||
const userPass = inbound.users?.find(i => i.name == user)?.password
|
const userPass = inbound.method == "2022-blake3-aes-128-gcm" ? user.config.shadowsocks16?.password : user.config.shadowsocks?.password
|
||||||
const password = [userPass]
|
const password = [userPass]
|
||||||
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
tfo: inbound.tcp_fast_open? 1 : null,
|
tfo: inbound.tcp_fast_open? 1 : null,
|
||||||
network: inbound.network?? null
|
network: inbound.network?? null
|
||||||
}
|
|
||||||
|
|
||||||
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
return uri.toString()
|
let links = <string[]>[]
|
||||||
|
if (addrs.length == 0) {
|
||||||
|
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
function hysteriaLink(user: string, inbound: Hysteria, addr: string): string {
|
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tlsClient: any): string[] {
|
||||||
const auth = inbound.users.find(i => i.name == user)?.auth_str
|
const auth = user.config.hysteria.auth_str
|
||||||
const params = {
|
const params = {
|
||||||
upmbps: inbound.up_mbps?? null,
|
upmbps: inbound.up_mbps?? null,
|
||||||
downmbps: inbound.down_mbps?? null,
|
downmbps: inbound.down_mbps?? null,
|
||||||
@@ -65,20 +80,47 @@ export namespace LinkUtil {
|
|||||||
peer: inbound.tls.server_name?? null,
|
peer: inbound.tls.server_name?? null,
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
obfsParam: inbound.obfs?? null,
|
obfsParam: inbound.obfs?? null,
|
||||||
fastopen: inbound.tcp_fast_open? 1 : 0
|
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||||
|
insecure: tlsClient?.insecure ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`hysteria://${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`hysteria://${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`hysteria://${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('peer', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('insecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hysteria2Link(user: string, inbound: Hysteria2, addr: string): string {
|
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] {
|
||||||
const password = inbound.users.find(i => i.name == user)?.password
|
const password = user.config.hysteria2.password
|
||||||
const params = {
|
const params = {
|
||||||
upmbps: inbound.up_mbps?? null,
|
upmbps: inbound.up_mbps?? null,
|
||||||
downmbps: inbound.down_mbps?? null,
|
downmbps: inbound.down_mbps?? null,
|
||||||
@@ -86,51 +128,130 @@ export namespace LinkUtil {
|
|||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
obfs: inbound.obfs?.type?? null,
|
obfs: inbound.obfs?.type?? null,
|
||||||
'obfs-password': inbound.obfs?.password?? null,
|
'obfs-password': inbound.obfs?.password?? null,
|
||||||
fastopen: inbound.tcp_fast_open? 1 : 0
|
fastopen: inbound.tcp_fast_open? 1 : 0,
|
||||||
|
insecure: tlsClient?.insecure ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`hysteria2://${password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`hysteria2://${password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`hysteria2://${password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('insecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function naiveLink(user: string, inbound: Naive, addr: string): string {
|
function naiveLink(user: Client, inbound: Naive, addrs: any[], tlsClient: any): string[] {
|
||||||
const password = inbound.users.find(i => i.username == user)?.password
|
const password = user.config.naive.password
|
||||||
const params = {
|
|
||||||
padding: 1,
|
let links = <string[]>[]
|
||||||
peer: inbound.tls.server_name?? null,
|
if (addrs.length == 0) {
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
const params = {
|
||||||
tfo: inbound.tcp_fast_open? 1 : 0
|
padding: 1,
|
||||||
}
|
peer: inbound.tls.server_name?? null,
|
||||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + addr + ":" + inbound.listen_port)}`
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
const paramsArray = []
|
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||||
for (const [key, value] of Object.entries(params)){
|
allowInsecure: tlsClient?.insecure ? 1 : null
|
||||||
if (value) {
|
|
||||||
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
|
||||||
}
|
}
|
||||||
|
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}`
|
||||||
|
const paramsArray = []
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag)
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const params = {
|
||||||
|
padding: 1,
|
||||||
|
peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null,
|
||||||
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
|
tfo: inbound.tcp_fast_open? 1 : 0,
|
||||||
|
allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null
|
||||||
|
}
|
||||||
|
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}`
|
||||||
|
const paramsArray = []
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
function tuicLink(user: string, inbound: TUIC, addr: string): string {
|
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = user.config.tuic
|
||||||
const params = {
|
const params = {
|
||||||
sni: inbound.tls.server_name?? null,
|
sni: inbound.tls.server_name?? null,
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
alpn: inbound.tls.alpn?.join(',')?? null,
|
||||||
congestion_control: inbound.congestion_control?? null
|
congestion_control: inbound.congestion_control?? null,
|
||||||
|
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||||
|
disable_sni: tlsClient?.disable_sni ? 1 : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries(params)){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTransportParams(t:Transport): any {
|
function getTransportParams(t:Transport): any {
|
||||||
@@ -166,53 +287,132 @@ export namespace LinkUtil {
|
|||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
function vlessLink(user: string, inbound: VLESS, addr: string): string {
|
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = user.config.vless
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
const tParams = getTransportParams(transport)
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
type: transport?.type?? 'tcp',
|
type: transport?.type?? 'tcp',
|
||||||
security: inbound.tls?.enabled? 'tls' : null,
|
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||||
sni: inbound.tls?.server_name?? null,
|
sni: inbound.tls?.server_name?? null,
|
||||||
flow: inbound.tls?.enabled ? u?.flow?? null : null
|
flow: inbound.tls?.enabled ? u?.flow?? null : null,
|
||||||
|
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||||
|
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||||
|
pbk: tlsClient?.reality?.public_key?? null,
|
||||||
|
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`vless://${u?.uuid}@${addr}:${inbound.listen_port}`)
|
let links = <string[]>[]
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
if (addrs.length == 0) {
|
||||||
if (value) {
|
const uri = new URL(`vless://${u?.uuid}@${location.hostname}:${inbound.listen_port}`)
|
||||||
uri.searchParams.set(key, value.toString())
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`vless://${u?.uuid}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
uri.searchParams.set('security','tls')
|
||||||
|
} else {
|
||||||
|
uri.searchParams.delete('security')
|
||||||
|
uri.searchParams.delete('sni')
|
||||||
|
uri.searchParams.delete('alpn')
|
||||||
|
uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function trojanLink(user: string, inbound: Trojan, addr: string): string {
|
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = user.config.trojan
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
const tParams = getTransportParams(transport)
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
type: transport?.type?? 'tcp',
|
type: transport?.type?? 'tcp',
|
||||||
security: inbound.tls?.enabled? 'tls' : null,
|
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
||||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||||
sni: inbound.tls?.server_name?? null,
|
sni: inbound.tls?.server_name?? null,
|
||||||
|
allowInsecure: tlsClient?.insecure ? 1 : null,
|
||||||
|
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
||||||
|
pbk: tlsClient?.reality?.public_key?? null,
|
||||||
|
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
||||||
}
|
}
|
||||||
const uri = new URL(`trojan://${u?.password}@${addr}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
let links = <string[]>[]
|
||||||
if (value) {
|
if (addrs.length == 0) {
|
||||||
uri.searchParams.set(key, value.toString())
|
const uri = new URL(`trojan://${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
uri.hash = encodeURIComponent(inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
const uri = new URL(`trojan://${u?.password}@${a.server}:${a.server_port}`)
|
||||||
|
for (const [key, value] of Object.entries({...params, ...tParams})){
|
||||||
|
if (value) {
|
||||||
|
uri.searchParams.set(key, value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
uri.searchParams.set('security','tls')
|
||||||
|
} else {
|
||||||
|
uri.searchParams.delete('security')
|
||||||
|
uri.searchParams.delete('sni')
|
||||||
|
uri.searchParams.delete('alpn')
|
||||||
|
uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
uri.searchParams.set('sni', a.server_name)
|
||||||
|
} else {
|
||||||
|
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
uri.searchParams.set('allowInsecure', '1')
|
||||||
|
} else {
|
||||||
|
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
||||||
|
}
|
||||||
|
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
||||||
|
links.push(uri.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
return links
|
||||||
return uri.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function vmessLink(user: string, inbound: VMess, addr: string): string {
|
function vmessLink(user: Client, inbound: VMess, addrs: any[], tlsClient: any): string[] {
|
||||||
const u = inbound.users.find(i => i.name == user)
|
const u = user.config.vmess
|
||||||
const transport = <Transport>inbound.transport
|
const transport = <Transport>inbound.transport
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
const tParams = getTransportParams(transport)
|
||||||
@@ -220,17 +420,46 @@ export namespace LinkUtil {
|
|||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
v: 2,
|
v: 2,
|
||||||
add: addr,
|
add: location.hostname,
|
||||||
aid: u?.alterId,
|
aid: u?.alterId,
|
||||||
host: tParams.host,
|
host: tParams.host?? undefined,
|
||||||
id: u?.uuid,
|
id: u?.uuid,
|
||||||
net: transport?.type?? 'tcp',
|
net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
|
||||||
path: tParams.path,
|
type: transport?.type == 'http' ? 'http' : undefined,
|
||||||
|
path: tParams.path?? undefined,
|
||||||
port: inbound.listen_port,
|
port: inbound.listen_port,
|
||||||
ps: inbound.tag,
|
ps: inbound.tag,
|
||||||
sni: inbound.tls.server_name?? '',
|
sni: inbound.tls.server_name?? undefined,
|
||||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none'
|
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
||||||
|
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
||||||
}
|
}
|
||||||
return 'vmess://' + utf8ToBase64(JSON.stringify(params))
|
let links = <string[]>[]
|
||||||
|
if (addrs.length == 0) {
|
||||||
|
links.push('vmess://' + utf8ToBase64(JSON.stringify(params, null, 2)))
|
||||||
|
} else {
|
||||||
|
addrs.forEach(a => {
|
||||||
|
let newParams = {...params}
|
||||||
|
newParams.add = a.server
|
||||||
|
newParams.port = a.server_port
|
||||||
|
if (a.tls != undefined){
|
||||||
|
if (a.tls) {
|
||||||
|
newParams.tls = 'tls'
|
||||||
|
} else {
|
||||||
|
newParams.tls = 'none'
|
||||||
|
delete newParams.sni
|
||||||
|
delete newParams.allowInsecure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.server_name?.length>0) {
|
||||||
|
newParams.sni = a.server_name
|
||||||
|
}
|
||||||
|
if (a.insecure) {
|
||||||
|
newParams.allowInsecure = 1
|
||||||
|
}
|
||||||
|
newParams.ps = inbound.tag + (a.remark??'')
|
||||||
|
links.push('vmess://' + utf8ToBase64(JSON.stringify(newParams, null, 2)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return links
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { Hysteria, Hysteria2, Inbound, InTypes, Shadowsocks, Trojan, TUIC, VLESS, VMess, ShadowTLS } from "@/types/inbounds"
|
||||||
|
import { iTls } from "@/types/inTls"
|
||||||
|
import { oTls } from "@/types/outTls"
|
||||||
|
import RandomUtil from "./randomUtil"
|
||||||
|
|
||||||
|
export function fillData(out: any, inbound: Inbound, tlsClient: any) {
|
||||||
|
if (Object.hasOwn(inbound, 'tls')) {
|
||||||
|
const inb = <any>inbound
|
||||||
|
addTls(out,inb.tls,tlsClient)
|
||||||
|
} else {
|
||||||
|
delete out.tls
|
||||||
|
}
|
||||||
|
out.type = inbound.type
|
||||||
|
out.tag = inbound.tag
|
||||||
|
out.server = location.hostname
|
||||||
|
out.server_port = inbound.listen_port
|
||||||
|
switch(inbound.type){
|
||||||
|
case InTypes.HTTP || InTypes.SOCKS:
|
||||||
|
return
|
||||||
|
case InTypes.Shadowsocks:
|
||||||
|
shadowsocksOut(out, <Shadowsocks>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.ShadowTLS:
|
||||||
|
shadowTlsOut(out, <ShadowTLS>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Hysteria:
|
||||||
|
hysteriaOut(out, <Hysteria>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Hysteria2:
|
||||||
|
hysteria2Out(out, <Hysteria2>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.TUIC:
|
||||||
|
tuicOut(out, <TUIC>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.VLESS:
|
||||||
|
vlessOut(out, <VLESS>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.Trojan:
|
||||||
|
trojanOut(out, <Trojan>inbound)
|
||||||
|
return
|
||||||
|
case InTypes.VMess:
|
||||||
|
vmessOut(out, <VMess>inbound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Object.keys(out).forEach(key => delete out[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTls(out: any, tls: iTls, tlsClient: oTls){
|
||||||
|
out.tls = tlsClient
|
||||||
|
if(tls.enabled) out.tls.enabled = tls.enabled
|
||||||
|
if(tls.server_name) out.tls.server_name = tls.server_name
|
||||||
|
if(tls.alpn) out.tls.alpn = tls.alpn
|
||||||
|
if(tls.min_version) out.tls.min_version = tls.min_version
|
||||||
|
if(tls.max_version) out.tls.max_version = tls.max_version
|
||||||
|
if(tls.cipher_suites) out.tls.cipher_suites = tls.cipher_suites
|
||||||
|
if(tls.reality?.enabled){
|
||||||
|
out.tls.reality.enabled = true
|
||||||
|
out.tls.reality.short_id = tls.reality.short_id[RandomUtil.randomInt(tls.reality.short_id.length)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowsocksOut(out: any, inbound: Shadowsocks) {
|
||||||
|
out.method = inbound.method
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadowTlsOut(out: any, inbound: ShadowTLS) {
|
||||||
|
if (inbound.version == 3) {
|
||||||
|
out.version = 3
|
||||||
|
} else {
|
||||||
|
Object.keys(out).forEach(key => delete out[key])
|
||||||
|
}
|
||||||
|
out.tls = { enabled: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteriaOut(out: any, inbound: Hysteria) {
|
||||||
|
out.up_mbps = inbound.down_mbps
|
||||||
|
out.down_mbps = inbound.up_mbps
|
||||||
|
out.obfs = inbound.obfs
|
||||||
|
out.recv_window_conn = inbound.recv_window_conn
|
||||||
|
out.disable_mtu_discovery = inbound.disable_mtu_discovery
|
||||||
|
}
|
||||||
|
|
||||||
|
function hysteria2Out(out: any, inbound: Hysteria2) {
|
||||||
|
out.up_mbps = inbound.down_mbps
|
||||||
|
out.down_mbps = inbound.up_mbps
|
||||||
|
out.obfs = inbound.obfs
|
||||||
|
}
|
||||||
|
|
||||||
|
function tuicOut(out: any, inbound: TUIC) {
|
||||||
|
out.congestion_control = inbound.congestion_control?? "cubic"
|
||||||
|
out.zero_rtt_handshake = inbound.zero_rtt_handshake
|
||||||
|
out.heartbeat = inbound.heartbeat
|
||||||
|
}
|
||||||
|
|
||||||
|
function vlessOut(out: any, inbound: VLESS) {
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
function trojanOut(out: any, inbound: Trojan) {
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
function vmessOut(out: any, inbound: VMess) {
|
||||||
|
out.transport = inbound.transport
|
||||||
|
}
|
||||||
@@ -35,14 +35,14 @@ const RandomUtil = {
|
|||||||
return btoa(String.fromCharCode(...array))
|
return btoa(String.fromCharCode(...array))
|
||||||
},
|
},
|
||||||
randomShortId(): string[] {
|
randomShortId(): string[] {
|
||||||
let shortIds = ['','','','']
|
let shortIds = new Array(24).fill('')
|
||||||
for (var ii = 0; ii < 4; ii++) {
|
for (var ii = 1; ii < 24; ii++) {
|
||||||
for (var jj = 0; jj < this.randomInt(8); jj++){
|
for (var jj = 0; jj <= this.randomInt(7); jj++){
|
||||||
let randomNum = this.randomInt(256)
|
let randomNum = this.randomInt(256)
|
||||||
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
|
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shortIds
|
return shortIds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ export const FindDiff = {
|
|||||||
|
|
||||||
return differences
|
return differences
|
||||||
},
|
},
|
||||||
Clients(value1: any[], value2: any[]): any {
|
ArrObj(value1: any[], value2: any[], key: string): any {
|
||||||
const differences: any[] = []
|
const differences: any[] = []
|
||||||
value1.forEach((v1,index) => {
|
value1.forEach((v1,index) => {
|
||||||
if(index >= value2.length) differences.push({key: "clients", action: "new", obj: v1})
|
if(index >= value2.length) differences.push({key: key, action: "new", obj: v1})
|
||||||
else if(!this.deepCompare(v1,value2[index])) differences.push({key: "clients", action: "edit", obj: v1})
|
else if(!this.deepCompare(v1,value2[index])) differences.push({key: key, action: "edit", obj: v1})
|
||||||
})
|
})
|
||||||
return differences
|
return differences
|
||||||
},
|
},
|
||||||
@@ -76,8 +76,8 @@ export const FindDiff = {
|
|||||||
|
|
||||||
// Check if both objects are plain objects
|
// Check if both objects are plain objects
|
||||||
if (typeof obj1 === 'object' && typeof obj2 === 'object' && obj1 !== null && obj2 !== null) {
|
if (typeof obj1 === 'object' && typeof obj2 === 'object' && obj1 !== null && obj2 !== null) {
|
||||||
const keys1 = Object.keys(obj1)
|
const keys1 = Object.keys(obj1).filter(key => obj1[key] !== undefined)
|
||||||
const keys2 = Object.keys(obj2)
|
const keys2 = Object.keys(obj2).filter(key => obj2[key] !== undefined)
|
||||||
|
|
||||||
if (keys1.length !== keys2.length) {
|
if (keys1.length !== keys2.length) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import '@mdi/font/css/materialdesignicons.css'
|
|||||||
import 'vuetify/styles'
|
import 'vuetify/styles'
|
||||||
|
|
||||||
import colors from 'vuetify/util/colors'
|
import colors from 'vuetify/util/colors'
|
||||||
import { fa, en } from 'vuetify/locale'
|
import { fa, en, vi, zhHans, zhHant, ru } from 'vuetify/locale'
|
||||||
|
|
||||||
// Composables
|
// Composables
|
||||||
import { createVuetify } from 'vuetify'
|
import { createVuetify } from 'vuetify'
|
||||||
@@ -53,6 +53,6 @@ export default createVuetify({
|
|||||||
locale: {
|
locale: {
|
||||||
locale: localStorage.getItem("locale") ?? 'en',
|
locale: localStorage.getItem("locale") ?? 'en',
|
||||||
fallback: 'en',
|
fallback: 'en',
|
||||||
messages: { en, fa },
|
messages: { en, fa, vi, zhHans, zhHant, ru },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ const routes = [
|
|||||||
name: 'pages.rules',
|
name: 'pages.rules',
|
||||||
component: () => import('@/views/Rules.vue'),
|
component: () => import('@/views/Rules.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tls',
|
||||||
|
name: 'pages.tls',
|
||||||
|
component: () => import('@/views/Tls.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/basics',
|
path: '/basics',
|
||||||
name: 'pages.basics',
|
name: 'pages.basics',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { FindDiff } from '@/plugins/utils'
|
import { FindDiff } from '@/plugins/utils'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { onMounted } from 'vue'
|
import { push } from 'notivue'
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
|
||||||
const Data = defineStore('Data', {
|
const Data = defineStore('Data', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -9,9 +10,11 @@ const Data = defineStore('Data', {
|
|||||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||||
subURI: "",
|
subURI: "",
|
||||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||||
oldData: <{config: any, clients: any[]}>{},
|
oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{},
|
||||||
config: {},
|
config: <any>{},
|
||||||
clients: [],
|
clients: [],
|
||||||
|
tlsConfigs: [],
|
||||||
|
inData: [],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async loadData() {
|
async loadData() {
|
||||||
@@ -20,37 +23,71 @@ const Data = defineStore('Data', {
|
|||||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||||
|
|
||||||
// Set new data
|
// Set new data
|
||||||
const data = JSON.parse(msg.obj)
|
if (msg.obj.config) this.oldData.config = msg.obj.config
|
||||||
if (data.config) this.config = data.config
|
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
||||||
if (data.clients) this.clients = data.clients
|
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
||||||
if (data.subURI) this.subURI = data.subURI
|
if (msg.obj.inData) this.oldData.inData = msg.obj.inData
|
||||||
this.onlines = data.onlines
|
this.onlines = msg.obj.onlines
|
||||||
|
if (msg.obj.lastLog) {
|
||||||
// To avoid ref copy
|
push.error({
|
||||||
if (data.config) this.oldData.config = { ...JSON.parse(msg.obj).config }
|
title: i18n.global.t('error.core'),
|
||||||
if (data.clients) this.oldData.clients = [ ...JSON.parse(msg.obj).clients ]
|
duration: 5000,
|
||||||
|
message: msg.obj.lastLog
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.obj.config) {
|
||||||
|
// To avoid ref copy
|
||||||
|
const data = JSON.parse(JSON.stringify(msg.obj))
|
||||||
|
if (data.subURI) this.subURI = data.subURI
|
||||||
|
if (data.config) this.config = data.config
|
||||||
|
if (data.clients) this.clients = data.clients
|
||||||
|
if (data.tls) this.tlsConfigs = data.tls
|
||||||
|
if (data.inData) this.inData = data.inData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async pushData() {
|
async pushData() {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2),
|
||||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||||
|
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||||
|
inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2),
|
||||||
}
|
}
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
const msg = await HttpUtils.post('api/save',diff)
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
|
this.lastLoad = 0
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async delInbound(index: number) {
|
async delInbound(index: number) {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
||||||
clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)),
|
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
||||||
|
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
||||||
|
inData: <string|undefined> undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate inData
|
||||||
|
let invalidInData = <any[]>[]
|
||||||
|
this.inData.forEach((d:any) => {
|
||||||
|
const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag)
|
||||||
|
if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null})
|
||||||
|
})
|
||||||
|
if (invalidInData.length>0) {
|
||||||
|
diff.inData = JSON.stringify(invalidInData)
|
||||||
}
|
}
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
const msg = await HttpUtils.post('api/save',diff)
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async delInData(id: number) {
|
||||||
|
const diff = {
|
||||||
|
inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}])
|
||||||
|
}
|
||||||
|
await HttpUtils.post('api/save',diff)
|
||||||
|
},
|
||||||
async delOutbound(index: number) {
|
async delOutbound(index: number) {
|
||||||
const diff = {
|
const diff = {
|
||||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||||
@@ -69,6 +106,15 @@ const Data = defineStore('Data', {
|
|||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async delTls(id: number) {
|
||||||
|
const diff = {
|
||||||
|
tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
|
||||||
|
}
|
||||||
|
const msg = await HttpUtils.post('api/save',diff)
|
||||||
|
if(msg.success) {
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
const Message = defineStore('msg', {
|
|
||||||
state: () => ({
|
|
||||||
showMsg: false,
|
|
||||||
snackbar: {
|
|
||||||
message: '',
|
|
||||||
timeout: 5000,
|
|
||||||
color: '',
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
showMessage(message:string, color='success',timeout=5000) {
|
|
||||||
this.snackbar.message = message
|
|
||||||
this.snackbar.color = color
|
|
||||||
this.snackbar.timeout = timeout
|
|
||||||
this.showMsg = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default Message
|
|
||||||
@@ -1,30 +1,33 @@
|
|||||||
|
import { Link } from "@/plugins/link"
|
||||||
import RandomUtil from "@/plugins/randomUtil"
|
import RandomUtil from "@/plugins/randomUtil"
|
||||||
|
|
||||||
export interface Client {
|
export interface Client {
|
||||||
id?: number
|
id?: number
|
||||||
enable: boolean
|
enable: boolean
|
||||||
name: string
|
name: string
|
||||||
config: string
|
config: Config
|
||||||
inbounds: string
|
inbounds: string[]
|
||||||
links: string
|
links: Link[]
|
||||||
volume: number
|
volume: number
|
||||||
expiry: number
|
expiry: number
|
||||||
up: number
|
up: number
|
||||||
down: number
|
down: number
|
||||||
desc: string
|
desc: string
|
||||||
|
group: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultClient: Client = {
|
const defaultClient: Client = {
|
||||||
enable: true,
|
enable: true,
|
||||||
name: "",
|
name: "",
|
||||||
config: "[]",
|
config: {},
|
||||||
inbounds: "",
|
inbounds: [],
|
||||||
links: "[]",
|
links: [],
|
||||||
volume: 0,
|
volume: 0,
|
||||||
expiry: 0,
|
expiry: 0,
|
||||||
up: 0,
|
up: 0,
|
||||||
down: 0,
|
down: 0,
|
||||||
desc: "",
|
desc: "",
|
||||||
|
group: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
@@ -35,12 +38,11 @@ type Config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateConfigs(configs: string, newUserName: string): string {
|
export function updateConfigs(configs: Config, newUserName: string): Config {
|
||||||
const updatedConfigs: Config = JSON.parse(configs)
|
|
||||||
|
|
||||||
for (const key in updatedConfigs) {
|
for (const key in configs) {
|
||||||
if (updatedConfigs.hasOwnProperty(key)) {
|
if (configs.hasOwnProperty(key)) {
|
||||||
const config = updatedConfigs[key]
|
const config = configs[key]
|
||||||
if (config.hasOwnProperty("name")) {
|
if (config.hasOwnProperty("name")) {
|
||||||
config.name = newUserName
|
config.name = newUserName
|
||||||
} else if (config.hasOwnProperty("username")) {
|
} else if (config.hasOwnProperty("username")) {
|
||||||
@@ -49,12 +51,13 @@ export function updateConfigs(configs: string, newUserName: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify(updatedConfigs)
|
return configs
|
||||||
}
|
}
|
||||||
|
|
||||||
export function randomConfigs(user: string): Config {
|
export function randomConfigs(user: string): Config {
|
||||||
const mixedPassword = RandomUtil.randomSeq(10)
|
const mixedPassword = RandomUtil.randomSeq(10)
|
||||||
const ssPassword = RandomUtil.randomShadowsocksPassword(32)
|
const ssPassword16 = RandomUtil.randomShadowsocksPassword(16)
|
||||||
|
const ssPassword32 = RandomUtil.randomShadowsocksPassword(32)
|
||||||
const uuid = RandomUtil.randomUUID()
|
const uuid = RandomUtil.randomUUID()
|
||||||
return {
|
return {
|
||||||
mixed: {
|
mixed: {
|
||||||
@@ -71,11 +74,15 @@ export function randomConfigs(user: string): Config {
|
|||||||
},
|
},
|
||||||
shadowsocks: {
|
shadowsocks: {
|
||||||
name: user,
|
name: user,
|
||||||
password: ssPassword,
|
password: ssPassword32,
|
||||||
|
},
|
||||||
|
shadowsocks16: {
|
||||||
|
name: user,
|
||||||
|
password: ssPassword16,
|
||||||
},
|
},
|
||||||
shadowtls: {
|
shadowtls: {
|
||||||
name: user,
|
name: user,
|
||||||
password: ssPassword,
|
password: ssPassword32,
|
||||||
},
|
},
|
||||||
vmess: {
|
vmess: {
|
||||||
name: user,
|
name: user,
|
||||||
@@ -114,5 +121,9 @@ export function randomConfigs(user: string): Config {
|
|||||||
export function createClient<T extends Client>(json?: Partial<T>): Client {
|
export function createClient<T extends Client>(json?: Partial<T>): Client {
|
||||||
defaultClient.name = RandomUtil.randomSeq(8)
|
defaultClient.name = RandomUtil.randomSeq(8)
|
||||||
const defaultObject: Client = { ...defaultClient, ...(json || {}) }
|
const defaultObject: Client = { ...defaultClient, ...(json || {}) }
|
||||||
|
|
||||||
|
// Add missing config
|
||||||
|
defaultObject.config = { ...randomConfigs(defaultObject.name), ...defaultObject.config }
|
||||||
|
|
||||||
return defaultObject
|
return defaultObject
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Dial } from "./dial"
|
||||||
|
|
||||||
export interface iTls {
|
export interface iTls {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
server_name?: string
|
server_name?: string
|
||||||
@@ -9,6 +11,50 @@ export interface iTls {
|
|||||||
certificate_path?: string
|
certificate_path?: string
|
||||||
key?: string[]
|
key?: string[]
|
||||||
key_path?: string
|
key_path?: string
|
||||||
|
acme?: acme
|
||||||
|
ech?: ech
|
||||||
|
reality?: reality
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface acme {
|
||||||
|
domain: string[]
|
||||||
|
data_directory?: string
|
||||||
|
default_server_name?: string
|
||||||
|
email?: string
|
||||||
|
provider?: string
|
||||||
|
disable_http_challenge?: boolean
|
||||||
|
disable_tls_alpn_challenge?: boolean
|
||||||
|
alternative_http_port?: number
|
||||||
|
alternative_tls_port?: number
|
||||||
|
external_account?: {
|
||||||
|
key_id: string
|
||||||
|
mac_key: string
|
||||||
|
}
|
||||||
|
dns01_challenge?: {
|
||||||
|
provider: string
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ech {
|
||||||
|
enabled: boolean
|
||||||
|
pq_signature_schemes_enabled?: boolean
|
||||||
|
dynamic_record_sizing_disabled?: boolean
|
||||||
|
key?: string[]
|
||||||
|
key_path?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface realityHanshake extends Dial {
|
||||||
|
server: string
|
||||||
|
server_port: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface reality {
|
||||||
|
enabled: boolean
|
||||||
|
handshake: realityHanshake
|
||||||
|
private_key: string
|
||||||
|
short_id: string[]
|
||||||
|
max_time_difference?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultInTls: iTls = {
|
export const defaultInTls: iTls = {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const InTypes = {
|
|||||||
TUIC: 'tuic',
|
TUIC: 'tuic',
|
||||||
Hysteria2: 'hysteria2',
|
Hysteria2: 'hysteria2',
|
||||||
VLESS: 'vless',
|
VLESS: 'vless',
|
||||||
// Tun: 'tun',
|
Tun: 'tun',
|
||||||
Redirect: 'redirect',
|
Redirect: 'redirect',
|
||||||
TProxy: 'tproxy',
|
TProxy: 'tproxy',
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,31 @@ export interface Hysteria2 extends InboundBasics {
|
|||||||
brutal_debug?: boolean
|
brutal_debug?: boolean
|
||||||
}
|
}
|
||||||
export interface Tun extends InboundBasics {
|
export interface Tun extends InboundBasics {
|
||||||
[otherProperties: string]: any
|
interface_name?: string
|
||||||
|
address?: string[]
|
||||||
|
mtu?: number
|
||||||
|
endpoint_independent_nat?: boolean
|
||||||
|
udp_timeout?: string
|
||||||
|
stack?: string
|
||||||
|
auto_route?: boolean
|
||||||
|
// gso?: boolean
|
||||||
|
// strict_route?: boolean
|
||||||
|
// iproute2_table_index?: number
|
||||||
|
// iproute2_rule_index?: number
|
||||||
|
// auto_redirect?: boolean
|
||||||
|
// auto_redirect_input_mark?: string
|
||||||
|
// auto_redirect_output_mark?: string
|
||||||
|
// route_address?: string[]
|
||||||
|
// route_exclude_address?: string[]
|
||||||
|
// include_interface?: string[]
|
||||||
|
// exclude_interface?: string[]
|
||||||
|
// include_uid?: string[]
|
||||||
|
// include_uid_range?: string[]
|
||||||
|
// exclude_uid?: number[]
|
||||||
|
// exclude_uid_range?: string[]
|
||||||
|
// include_android_user?: number[]
|
||||||
|
// include_package?: string[]
|
||||||
|
// exclude_package?: string[]
|
||||||
}
|
}
|
||||||
export interface Redirect extends InboundBasics {}
|
export interface Redirect extends InboundBasics {}
|
||||||
export interface TProxy extends InboundBasics {
|
export interface TProxy extends InboundBasics {
|
||||||
@@ -187,7 +211,7 @@ type InterfaceMap = {
|
|||||||
tuic: TUIC
|
tuic: TUIC
|
||||||
hysteria2: Hysteria2
|
hysteria2: Hysteria2
|
||||||
vless: VLESS
|
vless: VLESS
|
||||||
// tun: Tun
|
tun: Tun
|
||||||
redirect: Redirect
|
redirect: Redirect
|
||||||
tproxy: TProxy
|
tproxy: TProxy
|
||||||
}
|
}
|
||||||
@@ -228,7 +252,7 @@ const defaultValues: Record<InType, Inbound> = {
|
|||||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls: { enabled: true } },
|
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls: { enabled: true } },
|
||||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls: { enabled: true } },
|
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls: { enabled: true } },
|
||||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
||||||
// tun: <Tun>{ type: InTypes.Tun },
|
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
||||||
redirect: <Redirect>{ type: InTypes.Redirect },
|
redirect: <Redirect>{ type: InTypes.Redirect },
|
||||||
tproxy: <TProxy>{ type: InTypes.TProxy },
|
tproxy: <TProxy>{ type: InTypes.TProxy },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ export interface WgPeer {
|
|||||||
export interface Direct extends OutboundBasics, Dial {
|
export interface Direct extends OutboundBasics, Dial {
|
||||||
override_address?: string
|
override_address?: string
|
||||||
override_port?: number
|
override_port?: number
|
||||||
proxy_protocol?: 0 | 1 | 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Block extends OutboundBasics {}
|
export interface Block extends OutboundBasics {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user