Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ed6d49d61 | |||
| 8e369535bf | |||
| 6a5e0a940b | |||
| 3b24819309 | |||
| b5920cdc07 | |||
| cf620962bb | |||
| 17f1126c23 | |||
| 16203fdece | |||
| 12fe21906e | |||
| 1b9d5e9378 | |||
| c152a977c6 | |||
| 341baf69de | |||
| dedd4b3ee3 | |||
| bfbf9777e9 | |||
| 2cabf0aefb | |||
| c994f4b24a | |||
| f136229539 | |||
| 40fbb22b74 | |||
| 9547038164 | |||
| aca870e78f | |||
| dbf01c2086 | |||
| c3debcec5a | |||
| c179bf8a37 | |||
| 21add1f3ce | |||
| 9968f3885f | |||
| 2ac13ef8f4 | |||
| 4900c14295 | |||
| 55a6d78114 | |||
| caa115bbe3 | |||
| e3be3be9d9 | |||
| 988675a7a7 | |||
| 458f0c20da | |||
| f8fbc3c329 | |||
| 89bc3b5b23 | |||
| edfe0c86e7 | |||
| 6865c8b49d | |||
| 07947c9665 | |||
| 09616b6fac | |||
| 15105710bc | |||
| c88fb45e27 | |||
| 4f7a90b7dd | |||
| cc45e8de20 | |||
| 5805e397af | |||
| db36756515 | |||
| 609132a5b1 | |||
| c0aef193ea | |||
| 7d39252fec | |||
| b4fcec9477 | |||
| 8e2023ee66 | |||
| 8b6cd88625 | |||
| 8272285fe5 | |||
| cb606828ff | |||
| 70c986662f | |||
| bf7b42ec20 | |||
| 43d1aecec7 | |||
| 9a02e6593c | |||
| abb869c75b | |||
| fa922291ea | |||
| ae7fa7285f | |||
| a0e437a549 | |||
| 3d263d500f | |||
| c1c05c4863 | |||
| af2861f2c9 | |||
| 39ad029e20 | |||
| f545bcb30c | |||
| 819d74cf10 | |||
| 68ed8d120f | |||
| 5b804eb149 | |||
| 6a6d7d7c1a | |||
| 5726e64b9f | |||
| 52db4e5332 | |||
| b673fd9032 | |||
| e80519eac0 | |||
| 5371227fb6 | |||
| b9a7cefc7c | |||
| 13b750f965 | |||
| 4ced272d9a | |||
| 8b0cd4b89d | |||
| de43f9779c | |||
| d39df72896 | |||
| cfa5f38177 | |||
| abcf0bca99 | |||
| 5de5d6c3d0 | |||
| 944f671d96 | |||
| 00ee69e541 | |||
| f5b609204e | |||
| edc1038623 | |||
| c687a42d7e | |||
| bf1759ceda | |||
| e404342e2c | |||
| 4a3917ea5d | |||
| 037dbbd7a0 | |||
| 541e54c9a4 | |||
| 2cc78da07f | |||
| 63dc779f68 | |||
| 9ba611649c | |||
| b0817e9c05 | |||
| 8a2407fbd3 | |||
| 204e0d30e1 | |||
| 19a6053098 | |||
| e91d8038ad | |||
| 840b7ba1b1 | |||
| 233c27be23 | |||
| c54d9c15bc | |||
| 30c9ed6aa7 | |||
| 1bdf4e9c4c | |||
| 2f0d7a8502 | |||
| 99b8cbc75f |
@@ -0,0 +1 @@
|
|||||||
|
github: alireza0
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: Sing-box Docker Image CI
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get latest release
|
||||||
|
id: get_release
|
||||||
|
run: |
|
||||||
|
latest_release=$(curl -Ls "https://api.github.com/repos/sagernet/sing-box/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
|
echo "latest_release: $latest_release"
|
||||||
|
echo "latest_release=$latest_release" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
alireza7/s-ui-singbox
|
||||||
|
ghcr.io/alireza0/s-ui-singbox
|
||||||
|
tags: |
|
||||||
|
type=sha
|
||||||
|
type=pep440,pattern=${{ steps.get_release.outputs.latest_release }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Login to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: core/
|
||||||
|
push: true
|
||||||
|
build-args: SINGBOX_VER=${{ steps.get_release.outputs.latest_release }}
|
||||||
|
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -11,8 +11,6 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -50,6 +48,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64/v8
|
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -10,16 +10,21 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [amd64, arm64, arm]
|
platform:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- armv7
|
||||||
|
- 386
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
cache: false
|
||||||
|
go-version: '1.22'
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
@@ -27,13 +32,15 @@ jobs:
|
|||||||
node-version: '20'
|
node-version: '20'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install dependencies for arm64 and arm
|
- name: Install dependencies
|
||||||
if: matrix.platform == 'arm64' || matrix.platform == 'arm'
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
if [ "${{ matrix.platform }}" == "arm" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
sudo apt install gcc-arm-linux-gnueabihf
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
sudo apt install gcc-i686-linux-gnu
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
@@ -44,26 +51,33 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
mv frontend/dist backend/web/html
|
mv frontend/dist backend/web/html
|
||||||
|
|
||||||
- name: Set evironments
|
- name: Build s-ui & singbox
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
export GOARCH=${{ matrix.platform }}
|
export GOARCH=${{ matrix.platform }}
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
|
export GOARCH=arm64
|
||||||
export CC=aarch64-linux-gnu-gcc
|
export CC=aarch64-linux-gnu-gcc
|
||||||
elif [ "${{ matrix.platform }}" == "arm" ]; then
|
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||||
|
export GOARCH=arm
|
||||||
|
export GOARM=7
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
export CC=arm-linux-gnueabihf-gcc
|
||||||
|
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||||
|
export GOARCH=386
|
||||||
|
export CC=i686-linux-gnu-gcc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build sing-box
|
#### Build Sing-Box
|
||||||
run: |
|
export VERSION=v1.8.14
|
||||||
git clone -b v1.8.5 https://github.com/SagerNet/sing-box
|
git clone -b $VERSION https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box
|
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 \
|
||||||
|
-v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' -s -w -buildid=" \
|
||||||
|
-o sing-box ./cmd/sing-box
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- name: Build s-ui
|
### Build s-ui
|
||||||
run: |
|
|
||||||
cd backend
|
cd backend
|
||||||
go build -o ../sui main.go
|
go build -o ../sui main.go
|
||||||
cd ..
|
cd ..
|
||||||
@@ -74,13 +88,13 @@ jobs:
|
|||||||
cp sing-box.service s-ui/
|
cp sing-box.service s-ui/
|
||||||
mkdir s-ui/bin
|
mkdir s-ui/bin
|
||||||
cp sing-box/sing-box s-ui/bin/
|
cp sing-box/sing-box s-ui/bin/
|
||||||
cp runSingbox.sh s-ui/bin/
|
cp core/runSingbox.sh s-ui/bin/
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
|
|||||||
+6
-5
@@ -1,19 +1,20 @@
|
|||||||
FROM node:alpine as front-builder
|
FROM --platform=$BUILDPLATFORM node:alpine as front-builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
RUN npm install && npm run build
|
RUN npm install && npm run build
|
||||||
|
|
||||||
FROM golang:1.21-alpine AS backend-builder
|
FROM golang:1.22-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 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
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
**An Advanced Web Panel • Built on SagerNet/Sing-Box**
|
**An Advanced Web Panel • Built on SagerNet/Sing-Box**
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
|
[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
@@ -26,6 +27,13 @@
|
|||||||
| Dark/Light Theme | :heavy_check_mark: |
|
| Dark/Light Theme | :heavy_check_mark: |
|
||||||
|
|
||||||
|
|
||||||
|
## Default Installation Informarion
|
||||||
|
- Panel Port: 2095
|
||||||
|
- Panel Path: /app/
|
||||||
|
- Subscription Port: 2096
|
||||||
|
- Subscription Path: /sub/
|
||||||
|
- User/Passowrd: admin
|
||||||
|
|
||||||
## Install & Upgrade to Latest Version
|
## Install & Upgrade to Latest Version
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -40,6 +48,19 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.s
|
|||||||
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) 0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Uninstall S-UI
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl disable sing-box --now
|
||||||
|
systemctl disable s-ui --now
|
||||||
|
|
||||||
|
rm -f /etc/systemd/system/s-ui.service
|
||||||
|
rm -f /etc/systemd/system/sing-box.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
rm -fr /usr/local/s-ui
|
||||||
|
```
|
||||||
|
|
||||||
## Install using Docker
|
## Install using Docker
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -55,10 +76,20 @@ curl -fsSL https://get.docker.com | sh
|
|||||||
|
|
||||||
**Step 2:** Install S-UI
|
**Step 2:** Install S-UI
|
||||||
|
|
||||||
|
> Docker compose method
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mkdir s-ui && cd s-ui
|
||||||
|
wget -q https://raw.githubusercontent.com/alireza0/s-ui/main/docker-compose.yml
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use docker for s-ui only
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
mkdir s-ui && cd s-ui
|
mkdir s-ui && cd s-ui
|
||||||
docker run -itd \
|
docker run -itd \
|
||||||
-p 2095:2095 -p 443:443 -p 80:80 \
|
-p 2095:2095 -p 2096:2096 -p 443:443 -p 80:80 \
|
||||||
-v $PWD/db/:/usr/local/s-ui/db/ \
|
-v $PWD/db/:/usr/local/s-ui/db/ \
|
||||||
-v $PWD/cert/:/root/cert/ \
|
-v $PWD/cert/:/root/cert/ \
|
||||||
--name s-ui --restart=unless-stopped \
|
--name s-ui --restart=unless-stopped \
|
||||||
@@ -77,6 +108,9 @@ docker build -t s-ui .
|
|||||||
|
|
||||||
- English
|
- English
|
||||||
- Farsi
|
- Farsi
|
||||||
|
- Vietnamese
|
||||||
|
- Chinese (Simplified)
|
||||||
|
- Chinese (Traditional)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -135,5 +169,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)
|
|
||||||
|
|||||||
+61
-7
@@ -1,7 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -15,6 +14,7 @@ type APIHandler struct {
|
|||||||
service.UserService
|
service.UserService
|
||||||
service.ConfigService
|
service.ConfigService
|
||||||
service.ClientService
|
service.ClientService
|
||||||
|
service.TlsService
|
||||||
service.PanelService
|
service.PanelService
|
||||||
service.StatsService
|
service.StatsService
|
||||||
service.ServerService
|
service.ServerService
|
||||||
@@ -27,7 +27,8 @@ func NewAPIHandler(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
func (a *APIHandler) initRouter(g *gin.RouterGroup) {
|
||||||
g.Use(func(c *gin.Context) {
|
g.Use(func(c *gin.Context) {
|
||||||
if c.Request.URL.Path != "/api/login" && c.Request.URL.Path != "/api/logout" {
|
path := c.Request.URL.Path
|
||||||
|
if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") {
|
||||||
checkLogin(c)
|
checkLogin(c)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -62,9 +63,26 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = SetLoginUser(c, loginUser)
|
err = SetLoginUser(c, loginUser)
|
||||||
|
if err == nil {
|
||||||
logger.Info("user ", loginUser, " login success")
|
logger.Info("user ", loginUser, " login success")
|
||||||
|
} else {
|
||||||
|
logger.Warning("login failed: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
jsonMsg(c, "", nil)
|
jsonMsg(c, "", nil)
|
||||||
|
case "changePass":
|
||||||
|
id := c.Request.FormValue("id")
|
||||||
|
oldPass := c.Request.FormValue("oldPass")
|
||||||
|
newUsername := c.Request.FormValue("newUsername")
|
||||||
|
newPass := c.Request.FormValue("newPass")
|
||||||
|
err = a.UserService.ChangePass(id, oldPass, newUsername, newPass)
|
||||||
|
if err == nil {
|
||||||
|
logger.Info("change user credentials success")
|
||||||
|
jsonMsg(c, "save", nil)
|
||||||
|
} else {
|
||||||
|
logger.Warning("change user credentials failed:", err)
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
}
|
||||||
case "save":
|
case "save":
|
||||||
loginUser := GetLoginUser(c)
|
loginUser := GetLoginUser(c)
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
@@ -99,6 +117,13 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, data, nil)
|
jsonObj(c, data, nil)
|
||||||
|
case "users":
|
||||||
|
users, err := a.UserService.GetUsers()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, *users, nil)
|
||||||
case "setting":
|
case "setting":
|
||||||
data, err := a.SettingService.GetAllSetting()
|
data, err := a.SettingService.GetAllSetting()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,19 +151,40 @@ 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)
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -151,13 +197,21 @@ 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
|
||||||
|
}
|
||||||
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["subURI"] = subURI
|
||||||
|
data["onlines"] = onlines
|
||||||
} else {
|
} else {
|
||||||
data = fmt.Sprintf(`{"onlines": %s}`, onlines)
|
data["onlines"] = onlines
|
||||||
}
|
}
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
sessions "github.com/Calidity/gin-sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func pureJsonMsg(c *gin.Context, success bool, msg string) {
|
|||||||
func checkLogin(c *gin.Context) {
|
func checkLogin(c *gin.Context) {
|
||||||
if !IsLogin(c) {
|
if !IsLogin(c) {
|
||||||
if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
|
if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
|
||||||
pureJsonMsg(c, false, "Not authorized")
|
pureJsonMsg(c, false, "Invalid login")
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -51,7 +51,13 @@ func (a *APP) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = a.cronJob.Start(loc)
|
|
||||||
|
trafficAge, err := a.SettingService.GetTrafficAge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.cronJob.Start(loc, trafficAge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resetAdmin() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userService := service.UserService{}
|
||||||
|
err = userService.UpdateFirstUser("admin", "admin")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset admin credentials failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset admin credentials success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAdmin(username string, password string) {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != "" || password != "" {
|
||||||
|
userService := service.UserService{}
|
||||||
|
err := userService.UpdateFirstUser(username, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset admin credentials failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset admin credentials success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showAdmin() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userService := service.UserService{}
|
||||||
|
userModel, err := userService.GetFirstUser()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current user info failed,error info:", err)
|
||||||
|
}
|
||||||
|
username := userModel.Username
|
||||||
|
userpasswd := userModel.Password
|
||||||
|
if (username == "") || (userpasswd == "") {
|
||||||
|
fmt.Println("current username or password is empty")
|
||||||
|
}
|
||||||
|
fmt.Println("First admin credentials:")
|
||||||
|
fmt.Println("\tUsername:\t", username)
|
||||||
|
fmt.Println("\tPassword:\t", userpasswd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseCmd() {
|
||||||
|
var showVersion bool
|
||||||
|
flag.BoolVar(&showVersion, "v", false, "show version")
|
||||||
|
|
||||||
|
adminCmd := flag.NewFlagSet("admin", flag.ExitOnError)
|
||||||
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
|
|
||||||
|
var username string
|
||||||
|
var password string
|
||||||
|
var port int
|
||||||
|
var path string
|
||||||
|
var subPort int
|
||||||
|
var subPath string
|
||||||
|
var reset bool
|
||||||
|
var show bool
|
||||||
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
|
settingCmd.StringVar(&path, "path", "", "set panel path")
|
||||||
|
settingCmd.IntVar(&subPort, "subPort", 0, "set sub port")
|
||||||
|
settingCmd.StringVar(&subPath, "subPath", "", "set sub path")
|
||||||
|
|
||||||
|
adminCmd.BoolVar(&show, "show", false, "show first admin credentials")
|
||||||
|
adminCmd.BoolVar(&reset, "reset", false, "reset first admin credentials")
|
||||||
|
adminCmd.StringVar(&username, "username", "", "set login username")
|
||||||
|
adminCmd.StringVar(&password, "password", "", "set login password")
|
||||||
|
|
||||||
|
oldUsage := flag.Usage
|
||||||
|
flag.Usage = func() {
|
||||||
|
oldUsage()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Commands:")
|
||||||
|
fmt.Println(" admin set/reset/show first admin credentials")
|
||||||
|
fmt.Println(" migrate migrate form older version")
|
||||||
|
fmt.Println(" setting set/reset/show settings")
|
||||||
|
fmt.Println()
|
||||||
|
adminCmd.Usage()
|
||||||
|
fmt.Println()
|
||||||
|
settingCmd.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
if showVersion {
|
||||||
|
fmt.Println(config.GetVersion())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "admin":
|
||||||
|
err := adminCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case show:
|
||||||
|
showAdmin()
|
||||||
|
case reset:
|
||||||
|
resetAdmin()
|
||||||
|
default:
|
||||||
|
updateAdmin(username, password)
|
||||||
|
showAdmin()
|
||||||
|
}
|
||||||
|
|
||||||
|
case "migrate":
|
||||||
|
migrateDb()
|
||||||
|
|
||||||
|
case "setting":
|
||||||
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case show:
|
||||||
|
showSetting()
|
||||||
|
case reset:
|
||||||
|
resetSetting()
|
||||||
|
default:
|
||||||
|
updateSetting(port, path, subPort, subPath)
|
||||||
|
showSetting()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("Invalid subcommands")
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func migrateDb() {
|
||||||
|
err := database.OpenDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
db := database.GetDB()
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"s-ui/config"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resetSetting() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
err = settingService.ResetSettings()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("reset setting failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("reset setting success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSetting(port int, path string, subPort int, subPath string) {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
|
||||||
|
if port > 0 {
|
||||||
|
err := settingService.SetPort(port)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set port failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set port success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path != "" {
|
||||||
|
err := settingService.SetWebPath(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set path failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set path success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subPort > 0 {
|
||||||
|
err := settingService.SetSubPort(subPort)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set sub port failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set sub port success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subPath != "" {
|
||||||
|
err := settingService.SetSubPath(subPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("set sub path failed:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("set sub path success")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showSetting() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
allSetting, err := settingService.GetAllSetting()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("get current port failed,error info:", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Current panel settings:")
|
||||||
|
fmt.Println("\tPanel port:\t", (*allSetting)["webPort"])
|
||||||
|
fmt.Println("\tPanel path:\t", (*allSetting)["webPath"])
|
||||||
|
if (*allSetting)["webListen"] != "" {
|
||||||
|
fmt.Println("\tPanel IP:\t", (*allSetting)["webListen"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["webDomain"] != "" {
|
||||||
|
fmt.Println("\tPanel Domain:\t", (*allSetting)["webDomain"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["webURI"] != "" {
|
||||||
|
fmt.Println("\tPanel URI:\t", (*allSetting)["webURI"])
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Current subscription settings:")
|
||||||
|
fmt.Println("\tSub port:\t", (*allSetting)["subPort"])
|
||||||
|
fmt.Println("\tSub path:\t", (*allSetting)["subPath"])
|
||||||
|
if (*allSetting)["subListen"] != "" {
|
||||||
|
fmt.Println("\tSub IP:\t", (*allSetting)["subListen"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["subDomain"] != "" {
|
||||||
|
fmt.Println("\tSub Domain:\t", (*allSetting)["subDomain"])
|
||||||
|
}
|
||||||
|
if (*allSetting)["subURI"] != "" {
|
||||||
|
fmt.Println("\tSub URI:\t", (*allSetting)["subURI"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ func GetBinFolderPath() string {
|
|||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath == "" {
|
||||||
dbFolderPath = "db"
|
dbFolderPath = "/usr/local/s-ui/db"
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,10 @@ func GetDBPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultConfig() string {
|
func GetDefaultConfig() string {
|
||||||
|
apiEnv := GetEnvApi()
|
||||||
|
if len(apiEnv) > 0 {
|
||||||
|
return strings.Replace(defaultConfig, "127.0.0.1:1080", apiEnv, 1)
|
||||||
|
}
|
||||||
return defaultConfig
|
return defaultConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.0.1
|
0.0.5
|
||||||
@@ -14,7 +14,7 @@ func NewCronJob() *CronJob {
|
|||||||
return &CronJob{}
|
return &CronJob{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CronJob) Start(loc *time.Location) error {
|
func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
||||||
c.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds())
|
c.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds())
|
||||||
c.cron.Start()
|
c.cron.Start()
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ func (c *CronJob) Start(loc *time.Location) error {
|
|||||||
// Start expiry job
|
// Start expiry job
|
||||||
c.cron.AddJob("@every 1m", NewDepleteJob())
|
c.cron.AddJob("@every 1m", NewDepleteJob())
|
||||||
// Start deleting old stats
|
// Start deleting old stats
|
||||||
c.cron.AddJob("@daily", NewDelStatsJob())
|
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -7,16 +7,20 @@ import (
|
|||||||
|
|
||||||
type DelStatsJob struct {
|
type DelStatsJob struct {
|
||||||
service.StatsService
|
service.StatsService
|
||||||
|
trafficAge int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDelStatsJob() *DelStatsJob {
|
func NewDelStatsJob(ta int) *DelStatsJob {
|
||||||
return &DelStatsJob{}
|
return &DelStatsJob{
|
||||||
|
trafficAge: ta,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DelStatsJob) Run() {
|
func (s *DelStatsJob) Run() {
|
||||||
err := s.StatsService.DelOldStats(30)
|
err := s.StatsService.DelOldStats(s.trafficAge)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Deleting old statistics failed: ", err)
|
logger.Warning("Deleting old statistics failed: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Debug("Stats older than ", s.trafficAge, " days were deleted")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 @@ 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.User{},
|
&model.User{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ 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 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"`
|
||||||
@@ -19,13 +27,14 @@ 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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
|
|||||||
+18
-21
@@ -1,23 +1,23 @@
|
|||||||
module s-ui
|
module s-ui
|
||||||
|
|
||||||
go 1.21.5
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-contrib/sessions v0.0.5
|
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
|
github.com/v2fly/v2ray-core/v5 v5.13.0
|
||||||
gorm.io/driver/sqlite v1.5.5
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.7
|
gorm.io/gorm v1.25.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.4.0 // indirect
|
github.com/adrg/xdg v0.4.0 // indirect
|
||||||
github.com/bytedance/sonic v1.10.2 // indirect
|
github.com/bytedance/sonic v1.11.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // 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/goccy/go-json v0.10.2 // indirect
|
||||||
@@ -26,42 +26,39 @@ require (
|
|||||||
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.2.2 // 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.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // 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-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/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/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // 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.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.7.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.3 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/arch v0.7.0 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/crypto v0.18.0 // indirect
|
golang.org/x/crypto v0.21.0 // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/net v0.23.0 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/sys v0.18.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.17.0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.17.0 // indirect
|
github.com/go-playground/validator/v10 v10.18.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.1
|
github.com/shirou/gopsutil/v3 v3.24.1
|
||||||
github.com/v2fly/v2ray-core/v5 v5.13.0
|
google.golang.org/grpc v1.62.0
|
||||||
google.golang.org/grpc v1.61.0
|
|
||||||
)
|
)
|
||||||
|
|||||||
+38
-29
@@ -1,3 +1,5 @@
|
|||||||
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
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=
|
||||||
@@ -12,8 +14,8 @@ github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28
|
|||||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn4=
|
||||||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
@@ -37,8 +39,6 @@ github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXb
|
|||||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
|
||||||
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
@@ -48,8 +48,9 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
|||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-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/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.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=
|
||||||
@@ -60,8 +61,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
|||||||
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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
|
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
|
||||||
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
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.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
@@ -105,8 +106,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/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=
|
||||||
@@ -121,8 +122,9 @@ 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.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20240226150601-1dcf7310316a h1:3Bm7EwfUQUvhNeKIkUct/gl9eod1TcXuj8stxvi/GoI=
|
||||||
|
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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
@@ -165,8 +167,9 @@ github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP
|
|||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||||
@@ -188,6 +191,7 @@ github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5
|
|||||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
@@ -200,10 +204,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
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 v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
@@ -224,8 +230,9 @@ github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2Rer
|
|||||||
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 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
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/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.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||||
@@ -236,15 +243,15 @@ golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUu
|
|||||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
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 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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=
|
||||||
@@ -254,12 +261,14 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-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.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.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -268,18 +277,18 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
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=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
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-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.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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
+11
-1
@@ -5,10 +5,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"s-ui/app"
|
"s-ui/app"
|
||||||
|
"s-ui/cmd"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func runApp() {
|
||||||
app := app.NewApp()
|
app := app.NewApp()
|
||||||
|
|
||||||
err := app.Init()
|
err := app.Init()
|
||||||
@@ -36,3 +37,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
runApp()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
cmd.ParseCmd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+81
-40
@@ -6,14 +6,18 @@ 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
|
||||||
|
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
ClientService
|
ClientService
|
||||||
|
TlsService
|
||||||
singbox.Controller
|
singbox.Controller
|
||||||
SettingService
|
SettingService
|
||||||
}
|
}
|
||||||
@@ -51,27 +55,44 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) GetConfig() (*[]byte, error) {
|
return s.RefreshApiAddr(&singboxConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 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["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 +122,12 @@ 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(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 +139,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 {
|
||||||
@@ -146,18 +169,15 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
case "outbounds":
|
case "outbounds":
|
||||||
if change.Action == "edit" {
|
if change.Action == "edit" {
|
||||||
newConfig.Outbounds[change.Index] = rawObject
|
newConfig.Outbounds[change.Index] = rawObject
|
||||||
|
} else if change.Action == "del" {
|
||||||
|
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
|
||||||
} else {
|
} else {
|
||||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
@@ -165,7 +185,7 @@ 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(append(clientChanges, settingChanges...), append(configChanges, tlsChanges...)...)
|
||||||
for index := range allChanges {
|
for index := range allChanges {
|
||||||
allChanges[index].DateTime = dt
|
allChanges[index].DateTime = dt
|
||||||
allChanges[index].Actor = loginUser
|
allChanges[index].Actor = loginUser
|
||||||
@@ -175,20 +195,30 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
return err
|
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
|
||||||
}
|
}
|
||||||
|
if LastUpdate == 0 {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var count int64
|
var count int64
|
||||||
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
|
err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error
|
||||||
|
if err == nil {
|
||||||
|
LastUpdate = time.Now().Unix()
|
||||||
|
}
|
||||||
return count > 0, err
|
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) {
|
||||||
@@ -200,35 +230,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"`
|
||||||
@@ -255,12 +286,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)) {
|
||||||
@@ -299,13 +325,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
|
||||||
}
|
}
|
||||||
@@ -320,3 +343,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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -89,12 +92,12 @@ func (s *ServerService) GetNetInfo() map[string]interface{} {
|
|||||||
|
|
||||||
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
||||||
info := make(map[string]interface{}, 0)
|
info := make(map[string]interface{}, 0)
|
||||||
if s.SingBoxService.IsRunning() {
|
sysStats, err := s.SingBoxService.GetSysStats()
|
||||||
|
if err == nil {
|
||||||
info["running"] = true
|
info["running"] = true
|
||||||
sysStats, _ := s.SingBoxService.GetSysStats()
|
|
||||||
info["stats"] = sysStats
|
info["stats"] = sysStats
|
||||||
} else {
|
} else {
|
||||||
info["running"] = false
|
info["running"] = s.SingBoxService.IsRunning()
|
||||||
}
|
}
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
@@ -135,3 +138,24 @@ 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)
|
||||||
|
var lines []string
|
||||||
|
if service == "sing-box" {
|
||||||
|
cmdArgs := []string{"journalctl", "-u", service, "--no-pager", "-n", count, "-p", level}
|
||||||
|
// 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 run journalctl command!"}
|
||||||
|
}
|
||||||
|
lines = strings.Split(out.String(), "\n")
|
||||||
|
} else {
|
||||||
|
lines = logger.GetLogs(c, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ var defaultValueMap = map[string]string{
|
|||||||
"webListen": "",
|
"webListen": "",
|
||||||
"webDomain": "",
|
"webDomain": "",
|
||||||
"webPort": "2095",
|
"webPort": "2095",
|
||||||
"webSecret": common.Random(32),
|
"secret": common.Random(32),
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
|
"webPath": "/app/",
|
||||||
|
"webURI": "",
|
||||||
"sessionMaxAge": "0",
|
"sessionMaxAge": "0",
|
||||||
|
"trafficAge": "30",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
"subListen": "",
|
"subListen": "",
|
||||||
"subPort": "2096",
|
"subPort": "2096",
|
||||||
@@ -67,6 +70,11 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
|||||||
return &allSetting, nil
|
return &allSetting, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) ResetSettings() error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Where("1 = 1").Delete(model.Setting{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
setting := &model.Setting{}
|
setting := &model.Setting{}
|
||||||
@@ -158,12 +166,36 @@ func (s *SettingService) GetKeyFile() (string, error) {
|
|||||||
return s.getString("webKeyFile")
|
return s.getString("webKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecret() ([]byte, error) {
|
func (s *SettingService) GetWebPath() (string, error) {
|
||||||
secret, err := s.getString("webSecret")
|
webPath, err := s.getString("webPath")
|
||||||
if secret == defaultValueMap["webSecret"] {
|
|
||||||
err := s.saveSetting("webSecret", secret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("save webSecret failed:", err)
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(webPath, "/") {
|
||||||
|
webPath = "/" + webPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(webPath, "/") {
|
||||||
|
webPath += "/"
|
||||||
|
}
|
||||||
|
return webPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetWebPath(webPath string) error {
|
||||||
|
if !strings.HasPrefix(webPath, "/") {
|
||||||
|
webPath = "/" + webPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(webPath, "/") {
|
||||||
|
webPath += "/"
|
||||||
|
}
|
||||||
|
return s.setString("webPath", webPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecret() ([]byte, error) {
|
||||||
|
secret, err := s.getString("secret")
|
||||||
|
if secret == defaultValueMap["secret"] {
|
||||||
|
err := s.saveSetting("secret", secret)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("save secret failed:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []byte(secret), err
|
return []byte(secret), err
|
||||||
@@ -173,6 +205,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) {
|
|||||||
return s.getInt("sessionMaxAge")
|
return s.getInt("sessionMaxAge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTrafficAge() (int, error) {
|
||||||
|
return s.getInt("trafficAge")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
||||||
l, err := s.getString("timeLocation")
|
l, err := s.getString("timeLocation")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,6 +231,10 @@ func (s *SettingService) GetSubPort() (int, error) {
|
|||||||
return s.getInt("subPort")
|
return s.getInt("subPort")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubPort(subPort int) error {
|
||||||
|
return s.setInt("subPort", subPort)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubPath() (string, error) {
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
subPath, err := s.getString("subPath")
|
subPath, err := s.getString("subPath")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -209,6 +249,16 @@ func (s *SettingService) GetSubPath() (string, error) {
|
|||||||
return subPath, nil
|
return subPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetSubPath(subPath string) error {
|
||||||
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return s.setString("subPath", subPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
return s.getString("subDomain")
|
return s.getString("subDomain")
|
||||||
}
|
}
|
||||||
@@ -268,16 +318,27 @@ func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|||||||
json.Unmarshal(change.Obj, &obj)
|
json.Unmarshal(change.Obj, &obj)
|
||||||
|
|
||||||
// Secure file existance check
|
// Secure file existance check
|
||||||
if key == "webCertFile" ||
|
if obj != "" && (key == "webCertFile" ||
|
||||||
key == "webKeyFile" ||
|
key == "webKeyFile" ||
|
||||||
key == "subCertFile" ||
|
key == "subCertFile" ||
|
||||||
key == "subKeyFile" {
|
key == "subKeyFile") {
|
||||||
err = s.fileExists(obj)
|
err = s.fileExists(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError(" -> ", obj, " is not exists")
|
return common.NewError(" -> ", obj, " is not exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Correct Pathes start and ends with `/`
|
||||||
|
if key == "webPath" ||
|
||||||
|
key == "subPath" {
|
||||||
|
if !strings.HasPrefix(obj, "/") {
|
||||||
|
obj = "/" + obj
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(obj, "/") {
|
||||||
|
obj += "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
|
err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ func (s *SingBoxService) GetStats() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SingBoxService) GetSysStats() (*map[string]interface{}, error) {
|
func (s *SingBoxService) GetSysStats() (*map[string]interface{}, error) {
|
||||||
s.V2rayAPI.Init(ApiAddr)
|
err := s.V2rayAPI.Init(ApiAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
defer s.V2rayAPI.Close()
|
defer s.V2rayAPI.Close()
|
||||||
resp, err := s.V2rayAPI.GetSysStats()
|
resp, err := s.V2rayAPI.GetSysStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+57
-1
@@ -13,6 +13,40 @@ import (
|
|||||||
type UserService struct {
|
type UserService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetFirstUser() (*model.User, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).
|
||||||
|
First(user).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) UpdateFirstUser(username string, password string) error {
|
||||||
|
if username == "" {
|
||||||
|
return common.NewError("username can not be empty")
|
||||||
|
} else if password == "" {
|
||||||
|
return common.NewError("password can not be empty")
|
||||||
|
}
|
||||||
|
db := database.GetDB()
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).First(user).Error
|
||||||
|
if database.IsNotFound(err) {
|
||||||
|
user.Username = username
|
||||||
|
user.Password = password
|
||||||
|
return db.Model(model.User{}).Create(user).Error
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Username = username
|
||||||
|
user.Password = password
|
||||||
|
return db.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
|
func (s *UserService) Login(username string, password string, remoteIP string) (string, error) {
|
||||||
user := s.CheckUser(username, password, remoteIP)
|
user := s.CheckUser(username, password, remoteIP)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
@@ -36,7 +70,7 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + "-" + remoteIP
|
lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + " " + remoteIP
|
||||||
err = db.Model(model.User{}).
|
err = db.Model(model.User{}).
|
||||||
Where("username = ?", username).
|
Where("username = ?", username).
|
||||||
Update("last_logins", &lastLoginTxt).Error
|
Update("last_logins", &lastLoginTxt).Error
|
||||||
@@ -45,3 +79,25 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin
|
|||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *UserService) GetUsers() (*[]model.User, error) {
|
||||||
|
var users []model.User
|
||||||
|
db := database.GetDB()
|
||||||
|
err := db.Model(model.User{}).Select("id,username,last_logins").Scan(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserService) ChangePass(id string, oldPass string, newUser string, newPass string) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
user := &model.User{}
|
||||||
|
err := db.Model(model.User{}).Where("id = ? AND password = ?", id, oldPass).First(user).Error
|
||||||
|
if err != nil || database.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.Username = newUser
|
||||||
|
user.Password = newPass
|
||||||
|
return db.Save(user).Error
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,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)
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-13
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"embed"
|
"embed"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
@@ -17,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,6 +54,19 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
|
// Load the HTML template
|
||||||
|
t := template.New("").Funcs(engine.FuncMap)
|
||||||
|
template, err := t.ParseFS(content, "html/index.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
engine.SetHTMLTemplate(template)
|
||||||
|
|
||||||
|
base_url, err := s.settingService.GetWebPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
webDomain, err := s.settingService.GetWebDomain()
|
webDomain, err := s.settingService.GetWebDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -68,10 +82,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
engine.Use(gzip.Gzip(gzip.DefaultCompression))
|
engine.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||||
assetsBasePath := "/assets/"
|
assetsBasePath := base_url + "assets/"
|
||||||
|
|
||||||
store := cookie.NewStore(secret)
|
store := cookie.NewStore(secret)
|
||||||
engine.Use(sessions.Sessions("session", store))
|
engine.Use(sessions.Sessions("s-ui", store))
|
||||||
|
|
||||||
engine.Use(func(c *gin.Context) {
|
engine.Use(func(c *gin.Context) {
|
||||||
uri := c.Request.RequestURI
|
uri := c.Request.RequestURI
|
||||||
if strings.HasPrefix(uri, assetsBasePath) {
|
if strings.HasPrefix(uri, assetsBasePath) {
|
||||||
@@ -87,26 +102,29 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
engine.StaticFS(assetsBasePath, http.FS(assetsFS))
|
engine.StaticFS(assetsBasePath, http.FS(assetsFS))
|
||||||
|
|
||||||
group_api := engine.Group("/api")
|
group_api := engine.Group(base_url + "api")
|
||||||
api.NewAPIHandler(group_api)
|
api.NewAPIHandler(group_api)
|
||||||
|
|
||||||
// Serve index.html as the entry point
|
// Serve index.html as the entry point
|
||||||
// Handle all other routes by serving index.html
|
// Handle all other routes by serving index.html
|
||||||
engine.NoRoute(func(c *gin.Context) {
|
engine.NoRoute(func(c *gin.Context) {
|
||||||
if c.Request.URL.Path != "/login" && !api.IsLogin(c) {
|
if c.Request.URL.Path == strings.TrimSuffix(base_url, "/") {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/login")
|
c.Redirect(http.StatusTemporaryRedirect, base_url)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Request.URL.Path == "/login" && api.IsLogin(c) {
|
if !strings.HasPrefix(c.Request.URL.Path, base_url) {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, "/")
|
c.String(404, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := content.ReadFile("html/index.html")
|
if c.Request.URL.Path != base_url+"login" && !api.IsLogin(c) {
|
||||||
if err != nil {
|
c.Redirect(http.StatusTemporaryRedirect, base_url+"login")
|
||||||
c.String(http.StatusInternalServerError, "Internal Server Error")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Data(http.StatusOK, "text/html", data)
|
if c.Request.URL.Path == base_url+"login" && api.IsLogin(c) {
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, base_url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.HTML(http.StatusOK, "index.html", gin.H{"BASE_URL": base_url})
|
||||||
})
|
})
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS singbox-builder
|
||||||
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
|
WORKDIR /app
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
|
ARG SINGBOX_VER=v1.8.13
|
||||||
|
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=""
|
||||||
|
ENV GOPROXY ${GOPROXY}
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
ENV GOOS=$TARGETOS
|
||||||
|
ENV GOARCH=$TARGETARCH
|
||||||
|
RUN apk --no-cache --update add build-base gcc wget unzip git
|
||||||
|
RUN set -ex \
|
||||||
|
&& git clone --depth 1 --branch $SINGBOX_VER https://github.com/SagerNet/sing-box.git \
|
||||||
|
&& cd sing-box \
|
||||||
|
&& go build -v -trimpath -tags \
|
||||||
|
$SINGBOX_TAGS \
|
||||||
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \
|
||||||
|
./cmd/sing-box
|
||||||
|
|
||||||
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
|
LABEL maintainer="Alireza <alireza7@gmail.com>"
|
||||||
|
ENV TZ=Asia/Tehran
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apk add --no-cache --update ca-certificates tzdata bash
|
||||||
|
COPY --from=singbox-builder /app/sing-box/sing-box .
|
||||||
|
COPY runSingbox.sh .
|
||||||
|
ENTRYPOINT [ "./runSingbox.sh" ]
|
||||||
@@ -42,4 +42,12 @@ do
|
|||||||
;;
|
;;
|
||||||
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..."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
services:
|
||||||
|
s-ui:
|
||||||
|
image: alireza7/s-ui
|
||||||
|
container_name: s-ui
|
||||||
|
hostname: "S-UI docker"
|
||||||
|
volumes:
|
||||||
|
- "singbox:/app/bin"
|
||||||
|
- "$PWD/db:/app/db"
|
||||||
|
- "$PWD/cert:/app/cert"
|
||||||
|
environment:
|
||||||
|
SINGBOX_API: "sing-box:1080"
|
||||||
|
SUI_DB_FOLDER: "db"
|
||||||
|
tty: true
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "2095:2095"
|
||||||
|
- "2096:2096"
|
||||||
|
networks:
|
||||||
|
- s-ui
|
||||||
|
entrypoint: "./sui"
|
||||||
|
|
||||||
|
sing-box:
|
||||||
|
image: alireza7/s-ui-singbox
|
||||||
|
container_name: sing-box
|
||||||
|
volumes:
|
||||||
|
- "singbox:/app/"
|
||||||
|
- "$PWD/cert:/cert"
|
||||||
|
networks:
|
||||||
|
- s-ui
|
||||||
|
ports:
|
||||||
|
- "443:443"
|
||||||
|
- "1443:1443"
|
||||||
|
- "2443:2443"
|
||||||
|
- "3443:3443"
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- s-ui
|
||||||
|
|
||||||
|
networks:
|
||||||
|
s-ui:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
singbox:
|
||||||
@@ -5,6 +5,12 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="assets/favicon.ico" />
|
<link rel="icon" href="assets/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script>
|
||||||
|
window.BASE_URL = "{{ .BASE_URL }}"
|
||||||
|
|
||||||
|
// Dev Mode
|
||||||
|
if (window.BASE_URL.charAt(0) === '{') window.BASE_URL = "/app/"
|
||||||
|
</script>
|
||||||
<title>S-UI</title>
|
<title>S-UI</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
Generated
+603
-1917
File diff suppressed because it is too large
Load Diff
+23
-21
@@ -3,40 +3,42 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.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.0.96",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.7.2",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.3",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"core-js": "^3.29.0",
|
"core-js": "^3.37.1",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"notivue": "^2.4.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "^0.10.0",
|
||||||
"vue": "^3.2.0",
|
"vue": "^3.2.0",
|
||||||
"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.3.2",
|
||||||
"vue3-persian-datetime-picker": "^1.2.2",
|
"vue3-persian-datetime-picker": "^1.2.2",
|
||||||
"vuetify": "^3.0.0"
|
"vuetify": "^3.6.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.21.4",
|
"@babel/types": "^7.24.5",
|
||||||
"@types/node": "^18.15.0",
|
"@types/node": "^18.19.33",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.6.2",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.26.0",
|
||||||
"material-design-icons-iconfont": "^6.7.0",
|
"material-design-icons-iconfont": "^6.7.0",
|
||||||
"sass": "^1.60.0",
|
"sass": "^1.77.2",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.4.5",
|
||||||
"unplugin-fonts": "^1.0.3",
|
"unplugin-fonts": "^1.1.1",
|
||||||
"vite": "^4.2.0",
|
"vite": "^4.5.3",
|
||||||
"vite-plugin-vuetify": "^1.0.0",
|
"vite-plugin-vuetify": "^1.0.2",
|
||||||
"vue-tsc": "^1.2.0"
|
"vue-tsc": "^1.8.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>{{ $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>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
v-model="Input"
|
v-model="Input"
|
||||||
@input="Input=$event"
|
@input="Input=$event"
|
||||||
:locale="$i18n.locale"
|
:locale="locale"
|
||||||
element="expiry"
|
element="expiry"
|
||||||
compact-time
|
compact-time
|
||||||
type="datetime">
|
type="datetime">
|
||||||
@@ -42,6 +42,8 @@
|
|||||||
<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/zh-cn'
|
||||||
|
import 'moment/locale/zh-tw'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['expiry'],
|
props: ['expiry'],
|
||||||
@@ -54,10 +56,14 @@ export default {
|
|||||||
},
|
},
|
||||||
components: { DatePicker },
|
components: { DatePicker },
|
||||||
computed: {
|
computed: {
|
||||||
|
locale() {
|
||||||
|
const l = i18n.global.locale.value
|
||||||
|
return l.replace('zh', 'zh-')
|
||||||
|
},
|
||||||
dateFormatted() {
|
dateFormatted() {
|
||||||
if (this.expDate == 0) return i18n.global.t('unlimited')
|
if (this.expDate == 0) return i18n.global.t('unlimited')
|
||||||
const date = new Date(this.expDate*1000)
|
const date = new Date(this.expDate*1000)
|
||||||
return date.toLocaleString(i18n.global.locale.value)
|
return date.toLocaleString(this.locale)
|
||||||
},
|
},
|
||||||
expDate() {
|
expDate() {
|
||||||
return parseInt(this.expiry?? 0)
|
return parseInt(this.expiry?? 0)
|
||||||
@@ -99,18 +105,18 @@ export default {
|
|||||||
<style>
|
<style>
|
||||||
.vpd-addon-list,
|
.vpd-addon-list,
|
||||||
.vpd-addon-list-item {
|
.vpd-addon-list-item {
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: rgb(var(--v-theme-background)) !important;
|
||||||
border-color: rgb(var(--v-theme-background));
|
border-color: rgb(var(--v-theme-background)) !important;
|
||||||
}
|
}
|
||||||
.vpd-content {
|
.vpd-content {
|
||||||
background-color: rgb(var(--v-theme-background));
|
background-color: rgb(var(--v-theme-background)) !important;
|
||||||
}
|
}
|
||||||
.vpd-addon-list-item.vpd-selected,
|
.vpd-addon-list-item.vpd-selected,
|
||||||
.vpd-addon-list-item:hover {
|
.vpd-addon-list-item:hover {
|
||||||
background-color: rgb(var(--v-theme-primary));
|
background-color: rgb(var(--v-theme-primary)) !important;
|
||||||
}
|
}
|
||||||
.vpd-close-addon {
|
.vpd-close-addon {
|
||||||
color: rgb(var(--v-theme-on-surface));
|
color: rgb(var(--v-theme-on-surface)) !important;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
.vpd-controls {
|
.vpd-controls {
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Dial" style="background-color: inherit;">
|
<v-card :subtitle="$t('objects.dial')" style="background-color: inherit;">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||||
<v-text-field
|
<v-select
|
||||||
label="Forward to Outbound tag"
|
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.detour"></v-text-field>
|
:label="$t('dial.detourText')"
|
||||||
|
:items="outTags"
|
||||||
|
v-model="dial.detour">
|
||||||
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionBind">
|
<v-col cols="12" sm="6" md="4" v-if="optionBind">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to Network Interface"
|
:label="$t('dial.bindIf')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.bind_interface"></v-text-field>
|
v-model="dial.bind_interface"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -17,13 +19,13 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to IPv4"
|
:label="$t('dial.bindIp4')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.inet4_bind_address"></v-text-field>
|
v-model="dial.inet4_bind_address"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Bind to IPv6"
|
:label="$t('dial.bindIp6')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="dial.inet6_bind_address"></v-text-field>
|
v-model="dial.inet6_bind_address"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
v-model.number="routingMark"></v-text-field>
|
v-model.number="routingMark"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
||||||
<v-switch v-model="dial.reuse_addr" color="primary" label="Reuse listener address" hide-details></v-switch>
|
<v-switch v-model="dial.reuse_addr" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionTCP">
|
<v-row v-if="optionTCP">
|
||||||
@@ -55,11 +57,11 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Connection Timeout"
|
:label="$t('dial.connTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
suffix="s"
|
:suffix="$t('date.s')"
|
||||||
v-model.number="connectTimeout"></v-text-field>
|
v-model.number="connectTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -67,22 +69,19 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
clearable
|
:label="$t('listen.domainStrategy')"
|
||||||
@click:clear="delete dial.domain_strategy"
|
|
||||||
width="100"
|
|
||||||
label="Domain to IP Strategy"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||||
v-model="dial.domain_strategy">
|
v-model="dial.domain_strategy">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Fallback Timeout"
|
:label="$t('dial.fbTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="50"
|
min="50"
|
||||||
step="50"
|
step="50"
|
||||||
suffix="ms"
|
:suffix="$t('date.ms')"
|
||||||
v-model.number="fallbackDelay"></v-text-field>
|
v-model.number="fallbackDelay"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -90,39 +89,39 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Dial Options</v-btn>
|
<v-btn v-bind="props" hide-details>{{ $t('dial.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDetour" color="primary" label="Detour" hide-details></v-switch>
|
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionBind" color="primary" label="Bind Interface" hide-details></v-switch>
|
<v-switch v-model="optionBind" color="primary" :label="$t('dial.bindIf')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionIPV4" color="primary" label="Bind to IPv4" hide-details></v-switch>
|
<v-switch v-model="optionIPV4" color="primary" :label="$t('dial.bindIp4')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionIPV6" color="primary" label="Bind to IPv6" hide-details></v-switch>
|
<v-switch v-model="optionIPV6" color="primary" :label="$t('dial.bindIp6')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
<v-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionRA" color="primary" label="Reuse Address" hide-details></v-switch>
|
<v-switch v-model="optionRA" color="primary" :label="$t('dial.reuseAddr')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionTCP" color="primary" label="TCP Options" hide-details></v-switch>
|
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionUDP" color="primary" label="UDP Options" hide-details></v-switch>
|
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionCT" color="primary" label="Connection Timeout" hide-details></v-switch>
|
<v-switch v-model="optionCT" color="primary" :label="$t('dial.connTimeout')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDS" color="primary" label="Domain Strategy" hide-details></v-switch>
|
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -133,7 +132,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['dial'],
|
props: ['dial', 'outTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false
|
menu: false
|
||||||
@@ -154,7 +153,7 @@ export default {
|
|||||||
},
|
},
|
||||||
optionDetour: {
|
optionDetour: {
|
||||||
get(): boolean { return this.$props.dial.detour != undefined },
|
get(): boolean { return this.$props.dial.detour != undefined },
|
||||||
set(v:boolean) { v ? this.$props.dial.detour = '' : delete this.$props.dial.detour }
|
set(v:boolean) { v ? this.$props.dial.detour = this.outTags[0]?? '' : delete this.$props.dial.detour }
|
||||||
},
|
},
|
||||||
optionBind: {
|
optionBind: {
|
||||||
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<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-row>
|
||||||
|
<v-row v-if="useEchPath == 0">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<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" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.key')"
|
||||||
|
hide-details
|
||||||
|
v-model="echKeyText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<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 { ech } from '@/types/inTls'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['iTls','oTls'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
useEchPath: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ $t('objects.headers') }}
|
||||||
|
<v-icon @click="add_header" icon="mdi-plus"></v-icon>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-row v-for="(header, index) in hdrs">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('objects.key')"
|
||||||
|
hide-details
|
||||||
|
@input="update_key(index,$event.target.value)"
|
||||||
|
v-model="header.name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('objects.value')"
|
||||||
|
hide-details
|
||||||
|
@input="update_value(index,$event.target.value)"
|
||||||
|
append-icon="mdi-delete"
|
||||||
|
@click:append="del_header(index)"
|
||||||
|
v-model="header.value">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
type Header = {
|
||||||
|
name: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
add_header() {
|
||||||
|
this.hdrs = [...this.hdrs, {name: "Host", value: ""}]
|
||||||
|
},
|
||||||
|
del_header(i:number) {
|
||||||
|
let h = this.hdrs
|
||||||
|
h.splice(i,1)
|
||||||
|
this.hdrs = h
|
||||||
|
},
|
||||||
|
update_key(i:number,k:string) {
|
||||||
|
let h = this.hdrs
|
||||||
|
h[i].name = k
|
||||||
|
this.hdrs = h
|
||||||
|
},
|
||||||
|
update_value(i:number,v:string) {
|
||||||
|
let h = this.hdrs
|
||||||
|
h[i].value = v
|
||||||
|
this.hdrs = h
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hdrs: {
|
||||||
|
get() :Header[] {
|
||||||
|
let headers: Header[] = []
|
||||||
|
const h = this.$props.data.headers
|
||||||
|
if (h) {
|
||||||
|
Object.keys(h).forEach(key => {
|
||||||
|
if (Array.isArray(h[key])){
|
||||||
|
h[key].forEach((v:string) => headers.push({ name: key, value: v }))
|
||||||
|
} else {
|
||||||
|
headers.push({ name: key, value: h[key] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
},
|
||||||
|
set(v:Header[]) {
|
||||||
|
if (v.length>0) {
|
||||||
|
let headers:any = {}
|
||||||
|
v.forEach((h:Header) => {
|
||||||
|
if (headers[h.name]) {
|
||||||
|
if (Array.isArray(headers[h.name])) {
|
||||||
|
headers[h.name].push(h.value)
|
||||||
|
} else {
|
||||||
|
headers[h.name] = [headers[h.name], h.value]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
headers[h.name] = h.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.$props.data.headers = headers
|
||||||
|
} else {
|
||||||
|
this.$props.data.headers = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-card :subtitle="$t('in.multiplex')">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch color="primary" label="Enable Multiplex" v-model="muxEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
|
||||||
<v-switch color="primary" label="Reject Non-Padded" v-model="mux.padding" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
|
||||||
<v-switch color="primary" label="Enable Brutal" v-model="burtalEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="mux.brutal?.enabled">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Uplink Bandwidth"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
suffix="Mbps"
|
|
||||||
v-model.number="up_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
label="Downlink Bandwidth"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
suffix="Mbps"
|
|
||||||
min="0"
|
|
||||||
v-model.number="down_mbps">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { iMultiplex } from '@/types/inMultiplex'
|
|
||||||
export default {
|
|
||||||
props: ['inbound'],
|
|
||||||
data() {
|
|
||||||
return {}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mux(): iMultiplex {
|
|
||||||
return <iMultiplex> this.$props.inbound.multiplex
|
|
||||||
},
|
|
||||||
muxEnable: {
|
|
||||||
get(): boolean { return this.$props.inbound.multiplex ? this.mux.enabled : false },
|
|
||||||
set(newValue:boolean) { this.$props.inbound.multiplex = newValue ? { enabled: newValue } : {} }
|
|
||||||
},
|
|
||||||
burtalEnable: {
|
|
||||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
|
||||||
set(newValue:boolean) { this.mux.brutal = { enabled: newValue, up_mbps: 100, down_mbps: 100 } }
|
|
||||||
},
|
|
||||||
down_mbps: {
|
|
||||||
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
|
||||||
set(newValue:any) {
|
|
||||||
if (this.mux.brutal){
|
|
||||||
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
up_mbps: {
|
|
||||||
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
|
||||||
set(newValue:any) {
|
|
||||||
if (this.mux.brutal){
|
|
||||||
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('in.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="Preset"
|
||||||
|
: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"
|
||||||
@@ -77,7 +86,7 @@
|
|||||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Minimum Version"
|
:label="$t('tls.minVer')"
|
||||||
:items="tlsVersions"
|
:items="tlsVersions"
|
||||||
v-model="tls.min_version">
|
v-model="tls.min_version">
|
||||||
</v-select>
|
</v-select>
|
||||||
@@ -85,7 +94,7 @@
|
|||||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Maximum Version"
|
:label="$t('tls.maxVer')"
|
||||||
:items="tlsVersions"
|
:items="tlsVersions"
|
||||||
v-model="tls.max_version">
|
v-model="tls.max_version">
|
||||||
</v-select>
|
</v-select>
|
||||||
@@ -95,7 +104,7 @@
|
|||||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Cipher Suites"
|
:label="$t('tls.cs')"
|
||||||
multiple
|
multiple
|
||||||
:items="cipher_suites"
|
:items="cipher_suites"
|
||||||
v-model="tls.cipher_suites">
|
v-model="tls.cipher_suites">
|
||||||
@@ -103,11 +112,11 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
<v-card-actions>
|
<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>TLS Options</v-btn>
|
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
@@ -118,13 +127,13 @@
|
|||||||
<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="Min Version" 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="Max Version" 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="Cipher Suites" 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>
|
||||||
@@ -136,20 +145,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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: 'HTTP/3' },
|
{ title: "H3", value: 'h3' },
|
||||||
{ title: "H2", value: 'HTTP/2' },
|
{ title: "H2", value: 'h2' },
|
||||||
{ title: "Http1.1", value: 'HTTP/1.1' },
|
{ title: "Http/1.1", value: 'http/1.1' },
|
||||||
],
|
],
|
||||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
||||||
cipher_suites: [
|
cipher_suites: [
|
||||||
{ title: "Automatic", value: "" },
|
|
||||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
{ 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-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-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
||||||
@@ -174,8 +182,15 @@ export default {
|
|||||||
tls(): iTls {
|
tls(): iTls {
|
||||||
return <iTls> this.$props.inbound.tls
|
return <iTls> this.$props.inbound.tls
|
||||||
},
|
},
|
||||||
|
tlsItems(): any[] {
|
||||||
|
return [ { title: '', 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 } : {} }
|
||||||
},
|
},
|
||||||
tlsOptional(): boolean {
|
tlsOptional(): boolean {
|
||||||
@@ -191,23 +206,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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Listen">
|
<v-card :subtitle="$t('objects.listen')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@@ -20,27 +20,29 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||||
<v-text-field
|
<v-select
|
||||||
label="Forward to Inbound tag"
|
:label="$t('listen.detourText')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.detour"></v-text-field>
|
:items="inTags"
|
||||||
|
v-model="inbound.detour">
|
||||||
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch v-model="inbound.sniff" color="primary" :label="$t('in.sniffing')" hide-details></v-switch>
|
<v-switch v-model="inbound.sniff" color="primary" :label="$t('listen.sniffing')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="inbound.sniff">
|
<v-row v-if="inbound.sniff">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch v-model="inbound.sniff_override_destination" color="primary" label="Override Sniffed Domain" hide-details></v-switch>
|
<v-switch v-model="inbound.sniff_override_destination" color="primary" :label="$t('listen.sniffingOverride')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Sniffing Timeout"
|
:label="$t('listen.sniffingTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="50"
|
min="50"
|
||||||
step="50"
|
step="50"
|
||||||
suffix="ms"
|
:suffix="$t('date.ms')"
|
||||||
v-model.number="sniffTimeout"></v-text-field>
|
v-model.number="sniffTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -62,7 +64,7 @@
|
|||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
suffix="Min"
|
:suffix="$t('date.m')"
|
||||||
v-model.number="udpTimeout"></v-text-field>
|
v-model.number="udpTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -70,8 +72,7 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
width="100"
|
:label="$t('listen.domainStrategy')"
|
||||||
label="Domain to IP Strategy"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||||
v-model="inbound.domain_strategy">
|
v-model="inbound.domain_strategy">
|
||||||
</v-select>
|
</v-select>
|
||||||
@@ -81,21 +82,21 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Listen Options</v-btn>
|
<v-btn v-bind="props" hide-details>{{ $t('listen.options') }}</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionTCP" color="primary" label="TCP Options" hide-details></v-switch>
|
<v-switch v-model="optionDetour" color="primary" :label="$t('listen.detour')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionUDP" color="primary" label="UDP Options" hide-details></v-switch>
|
<v-switch v-model="optionTCP" color="primary" :label="$t('listen.tcpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDetour" color="primary" label="Detour" hide-details></v-switch>
|
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionDS" color="primary" label="Domain Strategy" hide-details></v-switch>
|
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['inbound', 'inTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false
|
menu: false
|
||||||
@@ -138,12 +139,12 @@ export default {
|
|||||||
},
|
},
|
||||||
set(v:boolean) {
|
set(v:boolean) {
|
||||||
this.$props.inbound.udp_fragment = v ? false : undefined
|
this.$props.inbound.udp_fragment = v ? false : undefined
|
||||||
this.$props.inbound.udp_timeout = v ? false : undefined
|
this.$props.inbound.udp_timeout = v ? '5m' : undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
optionDetour: {
|
optionDetour: {
|
||||||
get(): boolean { return this.$props.inbound.detour != undefined },
|
get(): boolean { return this.$props.inbound.detour != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.detour = v ? '' : undefined }
|
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
||||||
},
|
},
|
||||||
optionDS: {
|
optionDS: {
|
||||||
get(): boolean { return this.$props.inbound.domain_strategy != undefined },
|
get(): boolean { return this.$props.inbound.domain_strategy != undefined },
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<LogVue
|
||||||
|
v-model="logModal.visible"
|
||||||
|
:visible="logModal.visible"
|
||||||
|
:logType="logModal.logType"
|
||||||
|
@close="closeLogs"
|
||||||
|
/>
|
||||||
<v-container class="fill-height">
|
<v-container class="fill-height">
|
||||||
<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">
|
||||||
@@ -45,9 +51,9 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
<v-col cols="12" sm="6" md="3" v-for="i in reloadItems" :key="i">
|
||||||
<v-card class="rounded-lg" variant="outlined" height="200px"
|
<v-card class="rounded-lg" variant="outlined" height="210px"
|
||||||
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
:title="menuItems.flatMap(cat => cat.value).find(m => m.value == i)?.title">
|
||||||
<v-card-text style="padding: 0 16px;">
|
<v-card-text style="padding: 0 16px;" align="center" justify="center">
|
||||||
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
<Gauge :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'g'" />
|
||||||
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
|
<History :tilesData="tilesData" :type="i" v-if="i.charAt(0) == 'h'" />
|
||||||
<template v-if="i == 'i-sys'">
|
<template v-if="i == 'i-sys'">
|
||||||
@@ -80,13 +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>
|
||||||
@@ -98,6 +110,12 @@
|
|||||||
<v-col cols="8">
|
<v-col cols="8">
|
||||||
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
||||||
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
||||||
|
<v-chip density="compact" color="transparent" 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-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,6 +166,7 @@ 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 menu = ref(false)
|
const menu = ref(false)
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
@@ -184,7 +203,7 @@ const reloadItems = computed({
|
|||||||
|
|
||||||
const reloadData = async () => {
|
const reloadData = async () => {
|
||||||
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
|
const request = [...new Set(reloadItems.value.map(r => r.split('-')[1]))]
|
||||||
const data = await HttpUtils.get('/api/status',{ r: request.join(',')})
|
const data = await HttpUtils.get('api/status',{ r: request.join(',')})
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
tilesData.value = data.obj
|
tilesData.value = data.obj
|
||||||
}
|
}
|
||||||
@@ -215,4 +234,19 @@ 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
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :subtitle="$t('objects.multiplex')">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('mux.enable')" v-model="muxEnable" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<template v-if="mux.enabled">
|
||||||
|
<template v-if="direction=='out'">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="[ 'smux', 'yamux', 'h2mux']"
|
||||||
|
:label="$t('protocol')"
|
||||||
|
clearable
|
||||||
|
@click:clear="mux.protocol=undefined"
|
||||||
|
v-model="mux.protocol">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('mux.maxConn')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
v-model.number="max_connections">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('mux.minStr')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
v-model.number="min_streams">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('mux.maxStr')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
:min="min_streams"
|
||||||
|
v-model.number="max_streams">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('mux.padding')" v-model="mux.padding" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('mux.enableBrutal')" v-model="burtalEnable" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</template>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="mux.brutal?.enabled">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('stats.upload')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
:suffix="$t('stats.Mbps')"
|
||||||
|
v-model.number="up_mbps">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('stats.download')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
:suffix="$t('stats.Mbps')"
|
||||||
|
min="0"
|
||||||
|
v-model.number="down_mbps">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { oMultiplex } from '@/types/multiplex'
|
||||||
|
export default {
|
||||||
|
props: ['data', 'direction'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mux(): oMultiplex {
|
||||||
|
return <oMultiplex> this.$props.data.multiplex
|
||||||
|
},
|
||||||
|
muxEnable: {
|
||||||
|
get(): boolean { return this.mux ? this.mux.enabled : false },
|
||||||
|
set(newValue:boolean) { this.$props.data.multiplex = newValue ? { enabled: newValue } : {} }
|
||||||
|
},
|
||||||
|
max_connections: {
|
||||||
|
get(): number { return this.mux.max_connections ? this.mux.max_connections : 0 },
|
||||||
|
set(newValue:number) { this.mux.max_connections = newValue > 0 ? newValue : undefined }
|
||||||
|
},
|
||||||
|
min_streams: {
|
||||||
|
get(): number { return this.mux.min_streams ? this.mux.min_streams : 0 },
|
||||||
|
set(newValue:number) { this.mux.min_streams = newValue > 0 ? newValue : undefined }
|
||||||
|
},
|
||||||
|
max_streams: {
|
||||||
|
get(): number { return this.mux.max_streams ? this.mux.max_streams : 0 },
|
||||||
|
set(newValue:number) { this.mux.max_streams = newValue > 0 ? newValue : undefined }
|
||||||
|
},
|
||||||
|
burtalEnable: {
|
||||||
|
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
||||||
|
set(newValue:boolean) { this.mux.brutal = newValue ? { enabled: newValue, up_mbps: 100, down_mbps: 100 } : undefined }
|
||||||
|
},
|
||||||
|
down_mbps: {
|
||||||
|
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
||||||
|
set(newValue:any) {
|
||||||
|
if (this.mux.brutal){
|
||||||
|
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
up_mbps: {
|
||||||
|
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
||||||
|
set(newValue:any) {
|
||||||
|
if (this.mux.brutal){
|
||||||
|
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
networks: [
|
networks: [
|
||||||
@@ -21,8 +21,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Network: {
|
Network: {
|
||||||
get():string { return this.$props.inbound.network?? '' },
|
get():string { return this.$props.data.network?? '' },
|
||||||
set(v:string) { this.$props.inbound.network = v != '' ? v : undefined }
|
set(v:string) { this.$props.data.network = v != '' ? v : undefined }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
<template>
|
||||||
|
<v-card :subtitle="$t('objects.tls')">
|
||||||
|
<v-row v-if="tlsOptional">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="tls.enabled">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" :label="$t('tls.disableSni')" v-model="disable_sni" 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 v-if="optionCert">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn-toggle v-model="usePath"
|
||||||
|
class="rounded-xl"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
shaped
|
||||||
|
mandatory>
|
||||||
|
<v-btn
|
||||||
|
@click="tls.certificate=undefined; tls.certificate_path=''"
|
||||||
|
>{{ $t('tls.usePath') }}</v-btn>
|
||||||
|
<v-btn
|
||||||
|
@click="tls.certificate_path=undefined; tls.certificate=''"
|
||||||
|
>{{ $t('tls.useText') }}</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="usePath == 0">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.certPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.certificate_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.cert')"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.certificate">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="tls.server_name != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="SNI"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.server_name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="tls.alpn">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="ALPN"
|
||||||
|
multiple
|
||||||
|
:items="alpn"
|
||||||
|
v-model="tls.alpn">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.minVer')"
|
||||||
|
:items="tlsVersions"
|
||||||
|
v-model="tls.min_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.maxVer')"
|
||||||
|
:items="tlsVersions"
|
||||||
|
v-model="tls.max_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="tls.cipher_suites != undefined">
|
||||||
|
<v-col cols="12" md="8">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('tls.cs')"
|
||||||
|
multiple
|
||||||
|
:items="cipher_suites"
|
||||||
|
v-model="tls.cipher_suites">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="tls.utls != undefined">
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Fingerprint"
|
||||||
|
:items="fingerprints"
|
||||||
|
v-model="tls.utls.fingerprint">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="tls.reality != undefined">
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.pubKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.reality.public_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="Short ID"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.reality.short_id">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="tls.ech != undefined">
|
||||||
|
<v-row>
|
||||||
|
<v-col class="v-card-subtitle">ECH</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" label="Post-Quantum Schemes" v-model="tls.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="tls.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 tls.ech?.config"
|
||||||
|
>{{ $t('tls.usePath') }}</v-btn>
|
||||||
|
<v-btn
|
||||||
|
@click="delete tls.ech?.config_path"
|
||||||
|
>{{ $t('tls.useText') }}</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="useEchPath == 0">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.certPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="tls.ech.config_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.cert')"
|
||||||
|
hide-details
|
||||||
|
v-model="echConfigText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<v-card-actions v-if="tls.enabled">
|
||||||
|
<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>{{ $t('tls.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionCert" color="primary" :label="$t('tls.cert')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<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>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionFP" color="primary" label="UTLS" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionReality" color="primary" label="Reality" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionEch" color="primary" label="ECH" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { oTls, defaultOutTls } from '@/types/outTls'
|
||||||
|
export default {
|
||||||
|
props: ['outbound'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
usePath: 0,
|
||||||
|
useEchPath: 0,
|
||||||
|
defaults: defaultOutTls,
|
||||||
|
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" },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tls(): oTls {
|
||||||
|
return <oTls> this.$props.outbound.tls
|
||||||
|
},
|
||||||
|
tlsEnable: {
|
||||||
|
get() { return Object.hasOwn(this.tls, 'enabled') ? this.tls.enabled : false },
|
||||||
|
set(newValue: boolean) { this.$props.outbound.tls = newValue ? { enabled: true } : {} }
|
||||||
|
},
|
||||||
|
disable_sni: {
|
||||||
|
get() { return this.tls.disable_sni ?? false },
|
||||||
|
set(newValue: boolean) { this.$props.outbound.tls.disable_sni = newValue ? true : undefined }
|
||||||
|
},
|
||||||
|
insecure: {
|
||||||
|
get() { return this.tls.insecure ?? false },
|
||||||
|
set(newValue: boolean) { this.$props.outbound.tls.insecure = newValue ? true : undefined }
|
||||||
|
},
|
||||||
|
tlsOptional(): boolean {
|
||||||
|
return !['hysteria','hysteria2','tuic','shadowtls'].includes(this.$props.outbound.type)
|
||||||
|
},
|
||||||
|
echConfigText: {
|
||||||
|
get(): string { return this.tls.ech?.config ? this.tls.ech.config.join('\n') : '' },
|
||||||
|
set(newValue:string) { if (this.tls.ech) this.tls.ech.config = newValue.split('\n') }
|
||||||
|
},
|
||||||
|
optionCert: {
|
||||||
|
get(): boolean { return this.tls.certificate != undefined || this.tls.certificate_path != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
this.usePath = 0
|
||||||
|
if (v) {
|
||||||
|
this.$props.outbound.tls.certificate_path = ""
|
||||||
|
} else {
|
||||||
|
delete this.$props.outbound.tls.certificate_path
|
||||||
|
delete this.$props.outbound.tls.certificate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSNI: {
|
||||||
|
get(): boolean { return this.tls.server_name != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.server_name = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionALPN: {
|
||||||
|
get(): boolean { return this.tls.alpn != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.alpn = v ? defaultOutTls.alpn : undefined }
|
||||||
|
},
|
||||||
|
optionMinV: {
|
||||||
|
get(): boolean { return this.tls.min_version != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.min_version = v ? defaultOutTls.min_version : undefined }
|
||||||
|
},
|
||||||
|
optionMaxV: {
|
||||||
|
get(): boolean { return this.tls.max_version != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.max_version = v ? defaultOutTls.max_version : undefined }
|
||||||
|
},
|
||||||
|
optionCS: {
|
||||||
|
get(): boolean { return this.tls.cipher_suites != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.cipher_suites = v ? defaultOutTls.cipher_suites : undefined }
|
||||||
|
},
|
||||||
|
optionFP: {
|
||||||
|
get(): boolean { return this.tls.utls != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.utls = v ? defaultOutTls.utls : undefined }
|
||||||
|
},
|
||||||
|
optionReality: {
|
||||||
|
get(): boolean { return this.tls.reality != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.reality = v ? defaultOutTls.reality : undefined }
|
||||||
|
},
|
||||||
|
optionEch: {
|
||||||
|
get(): boolean { return this.tls.ech != undefined },
|
||||||
|
set(v:boolean) { this.$props.outbound.tls.ech = v ? defaultOutTls.ech : undefined }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
<template>
|
||||||
|
<v-card style="background-color: inherit;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" v-if="optionInbound">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.inbound"
|
||||||
|
:items="inTags"
|
||||||
|
:label="$t('pages.inbounds')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" v-if="optionClient">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.auth_user"
|
||||||
|
:items="clients"
|
||||||
|
:label="$t('pages.clients')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionIPver">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('rule.ipVer')"
|
||||||
|
:items="[4,6]"
|
||||||
|
v-model.number="rule.ip_version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="optionProtocol">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.protocol"
|
||||||
|
:items="['http','tls', 'quic', 'stun', 'dns']"
|
||||||
|
:label="$t('protocol')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionDomain">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="domainKeys"
|
||||||
|
@update:model-value="updateDomainOption($event)"
|
||||||
|
v-model="domainOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.domain') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="domain"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_suffix != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.domainSufix') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_suffix"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_keyword != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.domainKw') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_keyword"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.domain_regex != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.domainRgx') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="domain_regex"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.ip_cidr != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.ip') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="ip_cidr"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.ip_is_private != undefined">
|
||||||
|
<v-switch v-model="rule.ip_is_private" color="primary" :label="$t('rule.privateIp')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionPort">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="portKeys"
|
||||||
|
@update:model-value="updatePortOption($event)"
|
||||||
|
v-model="portOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.port != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.port') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="port"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.port_range != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.portRange') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="port_range"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionSrcIP">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="srcIPKeys"
|
||||||
|
@update:model-value="updateSrcIPOption($event)"
|
||||||
|
v-model="srcIPOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_ip_cidr != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.srcIp') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="source_ip_cidr"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_ip_is_private != undefined">
|
||||||
|
<v-switch v-model="rule.source_ip_is_private" color="primary" :label="$t('rule.srcPrivateIp')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionSrcPort">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="srcPortKeys"
|
||||||
|
@update:model-value="updateSrcPortOption($event)"
|
||||||
|
v-model="srcPortOption">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_port != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.srcPort') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="source_port"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" v-if="rule.source_port_range != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.srcPortRange') + ' ' + $t('commaSeparated')"
|
||||||
|
hide-details
|
||||||
|
v-model="source_port_range"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="optionRuleSet">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-combobox
|
||||||
|
v-model="rule.rule_set"
|
||||||
|
:items="rsTags"
|
||||||
|
:label="$t('rule.ruleset')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-switch v-model="rule.rule_set_ipcidr_match_source" color="primary" :label="$t('rule.rulesetMatchSrc')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('rule.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionInbound" color="primary" :label="$t('pages.inbounds')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionClient" color="primary" :label="$t('pages.clients')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionIPver" color="primary" :label="$t('rule.ipVer')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionProtocol" color="primary" :label="$t('protocol')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionDomain" color="primary" :label="$t('rule.domainRules')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionPort" color="primary" :label="$t('in.port')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionSrcIP" color="primary" :label="$t('rule.srcIpRules')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionSrcPort" color="primary" :label="$t('rule.srcPortRules')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionRuleSet" color="primary" :label="$t('rule.ruleset')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['rule', 'clients', 'inTags', 'rsTags', 'deleteable'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
domainKeys: ['domain', 'domain_suffix', 'domain_keyword', 'domain_regex', 'ip_cidr', 'ip_is_private'],
|
||||||
|
portKeys: ['port', 'port_range'],
|
||||||
|
srcIPKeys: ['source_ip_cidr', 'source_ip_is_private'],
|
||||||
|
srcPortKeys: ['source_port', 'source_port_range'],
|
||||||
|
domainOption: 'domain',
|
||||||
|
portOption: 'port',
|
||||||
|
srcIPOption: 'source_ip_cidr',
|
||||||
|
srcPortOption: 'source_port',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateDomainOption(option:string) {
|
||||||
|
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = option == 'ip_is_private' ? false : []
|
||||||
|
},
|
||||||
|
updatePortOption(option:string) {
|
||||||
|
this.portKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = []
|
||||||
|
},
|
||||||
|
updateSrcIPOption(option:string) {
|
||||||
|
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = option == 'source_ip_is_private' ? false : []
|
||||||
|
},
|
||||||
|
updateSrcPortOption(option:string) {
|
||||||
|
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
this.$props.rule[option] = []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionInbound: {
|
||||||
|
get() { return this.$props.rule.inbound != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.inbound = v ? [] : undefined }
|
||||||
|
},
|
||||||
|
optionClient: {
|
||||||
|
get() { return this.$props.rule.auth_user != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.auth_user = v ? [] : undefined }
|
||||||
|
},
|
||||||
|
optionIPver: {
|
||||||
|
get() { return this.$props.rule.ip_version != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.ip_version = v ? 4 : undefined }
|
||||||
|
},
|
||||||
|
optionProtocol: {
|
||||||
|
get() { return this.$props.rule.protocol != undefined },
|
||||||
|
set(v:boolean) { this.$props.rule.protocol = v ? ['http'] : undefined }
|
||||||
|
},
|
||||||
|
optionDomain: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.domainKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.domain = []
|
||||||
|
} else {
|
||||||
|
this.domainKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.domainOption = 'domain'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionPort: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.portKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.port = []
|
||||||
|
} else {
|
||||||
|
this.portKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.portOption = 'port'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSrcIP: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.srcIPKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.source_ip_cidr = []
|
||||||
|
} else {
|
||||||
|
this.srcIPKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.srcIPOption = 'source_ip_cidr'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionSrcPort: {
|
||||||
|
get() { return Object.keys(this.$props.rule).some(r => this.srcPortKeys.includes(r)) },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.source_port = []
|
||||||
|
} else {
|
||||||
|
this.srcPortKeys.forEach(k => delete this.$props.rule[k])
|
||||||
|
}
|
||||||
|
this.srcPortOption = 'source_port'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionRuleSet: {
|
||||||
|
get() { return this.$props.rule.rule_set != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.$props.rule.rule_set = []
|
||||||
|
this.$props.rule.rule_set_ipcidr_match_source = false
|
||||||
|
} else {
|
||||||
|
delete this.$props.rule.rule_set
|
||||||
|
delete this.$props.rule.rule_set_ipcidr_match_source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
domain: {
|
||||||
|
get() { return this.$props.rule.domain?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_suffix: {
|
||||||
|
get() { return this.$props.rule.domain_suffix?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_suffix = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_keyword: {
|
||||||
|
get() { return this.$props.rule.domain_keyword?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_keyword = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
domain_regex: {
|
||||||
|
get() { return this.$props.rule.domain_regex?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.domain_regex = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
ip_cidr: {
|
||||||
|
get() { return this.$props.rule.ip_cidr?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.ip_cidr = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
get() { return this.$props.rule.port?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.rule.port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port_range: {
|
||||||
|
get() { return this.$props.rule.port_range?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.port_range = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
source_ip_cidr: {
|
||||||
|
get() { return this.$props.rule.source_ip_cidr?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.source_ip_cidr = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
source_port: {
|
||||||
|
get() { return this.$props.rule.source_port?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.rule.source_port = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
source_port_range: {
|
||||||
|
get() { return this.$props.rule.source_port_range?.join(',') },
|
||||||
|
set(v:string) { this.$props.rule.source_port_range = v.length>0 ? v.split(',') : [] }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const ruleKeys = Object.keys(this.$props.rule)
|
||||||
|
if (this.optionDomain) {
|
||||||
|
const enabledOption = this.domainKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.domainOption = enabledOption.length>0 ? enabledOption[0] : 'domain'
|
||||||
|
}
|
||||||
|
if (this.optionPort) {
|
||||||
|
const enabledOption = this.portKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.portOption = enabledOption.length>0 ? enabledOption[0] : 'port'
|
||||||
|
}
|
||||||
|
if (this.optionSrcIP) {
|
||||||
|
const enabledOption = this.srcIPKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.srcIPOption = enabledOption.length>0 ? enabledOption[0] : 'source_ip_cidr'
|
||||||
|
}
|
||||||
|
if (this.optionSrcPort) {
|
||||||
|
const enabledOption = this.srcPortKeys.filter(k => ruleKeys.includes(k))
|
||||||
|
this.srcPortOption = enabledOption.length>0 ? enabledOption[0] : 'source_port'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('in.transport')">
|
<v-card :subtitle="$t('objects.transport')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
||||||
@@ -7,7 +7,6 @@
|
|||||||
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
width="100"
|
|
||||||
:label="$t('type')"
|
:label="$t('type')"
|
||||||
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
||||||
v-model="transportType">
|
v-model="transportType">
|
||||||
@@ -28,7 +27,7 @@ import WebSocket from './transports/WebSocket.vue'
|
|||||||
import GRPC from './transports/gRPC.vue'
|
import GRPC from './transports/gRPC.vue'
|
||||||
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
trspTypes: TrspTypes
|
trspTypes: TrspTypes
|
||||||
@@ -36,15 +35,15 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Transport() {
|
Transport() {
|
||||||
return <Transport>this.$props.inbound.transport
|
return <Transport>this.$props.data.transport
|
||||||
},
|
},
|
||||||
tpEnable: {
|
tpEnable: {
|
||||||
get() { return Object.hasOwn(this.$props.inbound.transport, 'type') },
|
get() { return Object.hasOwn(this.$props.data.transport, 'type') },
|
||||||
set(newValue: boolean) { this.$props.inbound.transport = newValue ? { type: 'http' } : {} }
|
set(newValue: boolean) { this.$props.data.transport = newValue ? { type: 'http' } : {} }
|
||||||
},
|
},
|
||||||
transportType: {
|
transportType: {
|
||||||
get() { return this.Transport.type },
|
get() { return this.Transport.type },
|
||||||
set(newValue: string) { this.$props.inbound.transport = { type: newValue } }
|
set(newValue: string) { this.$props.data.transport = { type: newValue } }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
components: { Http, WebSocket, GRPC, HttpUpgrade }
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<template>
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="UDP over TCP"
|
||||||
|
:items="versions"
|
||||||
|
v-model="udp_over_tcp">
|
||||||
|
</v-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
versions: [
|
||||||
|
{ title: this.$t('disable'), value: 0 },
|
||||||
|
{ title: "1", value: 1 },
|
||||||
|
{ title: "2", value: 2 },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
udp_over_tcp: {
|
||||||
|
get():number { return this.$props.data.udp_over_tcp?.version?? 0 },
|
||||||
|
set(v:number) { this.$props.data.udp_over_tcp = v > 0 ? { enabled: true, version: v } : undefined }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Clients">
|
<v-card :subtitle="$t('pages.clients')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch
|
<v-switch
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.addr')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.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="data.server_port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="data.public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="allowed_ips" :label="$t('types.wg.allowedIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
allowed_ips: {
|
||||||
|
get() { return this.$props.data.allowed_ips?.join(',') },
|
||||||
|
set(v:string) { this.$props.data.allowed_ips = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
reserved: {
|
||||||
|
get() { return this.$props.data.reserved?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.data.reserved = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,18 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-snackbar
|
<Notivue v-slot="item">
|
||||||
v-model="sb.showMsg"
|
<NotivueSwipe :item="item">
|
||||||
location="top"
|
<Notification
|
||||||
:color="snackbar.color"
|
:item="item"
|
||||||
:timeout="snackbar.timeout">
|
:theme="theme"
|
||||||
{{ snackbar.message }}
|
:dir="direction"
|
||||||
</v-snackbar>
|
:icons="outlinedIcons"
|
||||||
|
:hideClose="true"
|
||||||
|
@click="item.clear"
|
||||||
|
/>
|
||||||
|
</NotivueSwipe>
|
||||||
|
</Notivue>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { Notivue, Notification, NotivueSwipe, outlinedIcons, pastelTheme, darkTheme } from 'notivue'
|
||||||
import Message from '@/store/modules/message'
|
import { computed } from 'vue'
|
||||||
|
import { useTheme } from 'vuetify'
|
||||||
|
import vuetify from '@/plugins/vuetify';
|
||||||
|
|
||||||
const sb = Message()
|
const Theme = useTheme()
|
||||||
|
|
||||||
const snackbar = ref(sb.snackbar)
|
const theme = computed(() =>{
|
||||||
|
return Theme.global.name.value == "light" ? pastelTheme : darkTheme
|
||||||
|
})
|
||||||
|
|
||||||
|
const direction = computed(() => {
|
||||||
|
return vuetify.locale.current.value == 'fa' ? 'rtl' : 'ltr'
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--nv-z: 10020;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Direct">
|
<v-card subtitle="Direct">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Override Address"
|
:label="$t('types.direct.overrideAddr')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.override_address">
|
v-model="data.override_address">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Override Port"
|
:label="$t('types.direct.overridePort')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="override_port">
|
v-model.number="override_port">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -28,14 +28,14 @@
|
|||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction','data'],
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
override_port: {
|
override_port: {
|
||||||
get() { return this.$props.inbound.override_port ? this.$props.inbound.override_port : ''; },
|
get() { return this.$props.data.override_port ? this.$props.data.override_port : ''; },
|
||||||
set(newValue: any) { this.$props.inbound.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
set(newValue: any) { this.$props.data.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Network }
|
components: { Network }
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="HTTP">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.un')"
|
||||||
|
hide-details
|
||||||
|
v-model="username">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
v-model="password">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('transport.path')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<Headers :data="data" />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Headers from '@/components/Headers.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
username: {
|
||||||
|
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
||||||
|
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
||||||
|
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Headers }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -3,19 +3,19 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Uplink Limit"
|
:label="$t('stats.upload')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
v-model.number="up_mbps">
|
v-model.number="up_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Downlink Limit"
|
:label="$t('stats.download')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
min="0"
|
min="0"
|
||||||
v-model.number="down_mbps">
|
v-model.number="down_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
@@ -24,40 +24,136 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="obfs Password"
|
:label="$t('types.hy.obfs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.obfs">
|
v-model="data.obfs">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.hy.auth')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.auth_str">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.disable_mtu_discovery" color="primary" label="Disable MTU discovery" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_conn != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Recv window conn"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="data.recv_window_conn">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.recv_window != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Recv window"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="data.recv_window">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_client != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Recv window client"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="data.recv_window_client">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.max_conn_client != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="Max conn client"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
v-model.number="data.max_conn_client">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details>{{ $t('types.hy.hyOptions') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionRsvConn" color="primary" label="Recv window conn" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="direction=='out'">
|
||||||
|
<v-switch v-model="optionRsvWin" color="primary" label="Recv window" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="direction=='in'">
|
||||||
|
<v-switch v-model="optionRsvClnt" color="primary" label="Recv window client" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item v-if="direction=='in'">
|
||||||
|
<v-switch v-model="optionMaxConn" color="primary" label="Max conn client" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction','data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
menu: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
optionRsvConn: {
|
||||||
|
get(): boolean { return this.$props.data.recv_window_conn != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.recv_window_conn = v ? 15728640 : undefined }
|
||||||
|
},
|
||||||
|
optionRsvWin: {
|
||||||
|
get(): boolean { return this.$props.data.recv_window != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.recv_window = v ? 67108864 : undefined }
|
||||||
|
},
|
||||||
|
optionRsvClnt: {
|
||||||
|
get(): boolean { return this.$props.data.recv_window_client != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.recv_window_client = v ? 67108864 : undefined }
|
||||||
|
},
|
||||||
|
optionMaxConn: {
|
||||||
|
get(): boolean { return this.$props.data.max_conn_client != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.max_conn_client = v ? 1024 : undefined }
|
||||||
|
},
|
||||||
down_mbps: {
|
down_mbps: {
|
||||||
get() { return this.$props.inbound.down_mbps ? this.$props.inbound.down_mbps : 0 },
|
get() { return this.$props.data.down_mbps ? this.$props.data.down_mbps : 0 },
|
||||||
set(newValue:any) {
|
set(newValue:any) {
|
||||||
if (newValue.length != 0 ){
|
if (newValue.length != 0 ){
|
||||||
this.$props.inbound.down_mbps = newValue
|
this.$props.data.down_mbps = newValue
|
||||||
this.$props.inbound.down = "" + newValue + " Mbps"
|
this.$props.data.down = "" + newValue + " Mbps"
|
||||||
} else {
|
} else {
|
||||||
this.$props.inbound.down_mbps = 0
|
this.$props.data.down_mbps = 0
|
||||||
this.$props.inbound.down = "0 Mbps"
|
this.$props.data.down = "0 Mbps"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
up_mbps: {
|
up_mbps: {
|
||||||
get() { return this.$props.inbound.up_mbps ? this.$props.inbound.up_mbps : 0 },
|
get() { return this.$props.data.up_mbps ? this.$props.data.up_mbps : 0 },
|
||||||
set(newValue:number) { this.$props.inbound.up_mbps = newValue > 0 ? newValue : 0 }
|
set(newValue:number) { this.$props.data.up_mbps = newValue > 0 ? newValue : 0 }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
components: { Network }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,43 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Hysteria2">
|
<v-card subtitle="Hysteria2">
|
||||||
<v-row>
|
<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="Masquerade"
|
label="HTTP3 server on auth fail"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="hysteria2.masquerade"></v-text-field>
|
v-model="data.masquerade">
|
||||||
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch v-model="hysteria2.ignore_client_bandwidth" color="primary" label="Ignore Client Bandwidth" hide-details></v-switch>
|
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="!hysteria2.ignore_client_bandwidth">
|
<v-row v-else>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Uplink Limit"
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.password">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="!data.ignore_client_bandwidth">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('stats.upload')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
|
min="0"
|
||||||
v-model.number="up_mbps">
|
v-model.number="up_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Downlink Limit"
|
:label="$t('stats.download')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="Mbps"
|
:suffix="$t('stats.Mbps')"
|
||||||
min="0"
|
min="0"
|
||||||
v-model.number="down_mbps">
|
v-model.number="down_mbps">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="hysteria2.obfs">
|
<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="obfs Password"
|
:label="$t('types.hy.obfs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="hysteria2.obfs.password">
|
v-model="data.obfs.password">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -45,12 +59,15 @@
|
|||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
<template v-slot:activator="{ props }">
|
<template v-slot:activator="{ props }">
|
||||||
<v-btn v-bind="props" hide-details>Options</v-btn>
|
<v-btn v-bind="props" hide-details>{{ $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="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-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -60,32 +77,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Hysteria2, createInbound } from '@/types/inbounds'
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction', 'data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
hysteria2: <Hysteria2> createInbound("hysteria2",{ "tag": "" }),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
down_mbps: {
|
down_mbps: {
|
||||||
get() { return this.hysteria2.down_mbps ? this.hysteria2.down_mbps : 0 },
|
get() { return this.$props.data.down_mbps?? 0 },
|
||||||
set(newValue:any) { this.hysteria2.down_mbps = newValue.length == 0 ? undefined : this.hysteria2.down_mbps }
|
set(newValue:number) { this.$props.data.down_mbps = newValue>0 ? newValue : undefined }
|
||||||
},
|
},
|
||||||
up_mbps: {
|
up_mbps: {
|
||||||
get() { return this.hysteria2.up_mbps ? this.hysteria2.up_mbps : 0 },
|
get() { return this.$props.data.up_mbps?? 0 },
|
||||||
set(newValue:any) { this.hysteria2.up_mbps = newValue.length == 0 ? undefined : this.hysteria2.up_mbps }
|
set(newValue:number) { this.$props.data.up_mbps = newValue>0 ? newValue : undefined }
|
||||||
},
|
},
|
||||||
optionObfs: {
|
optionObfs: {
|
||||||
get(): boolean { return this.hysteria2.obfs != undefined },
|
get(): boolean { return this.$props.data.obfs != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.obfs = v ? { type: "salamander", password: ""} : undefined }
|
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
||||||
|
},
|
||||||
|
optionMasq: {
|
||||||
|
get(): boolean { return this.$props.data.masquerade != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.masquerade = v ? "" : undefined }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
components: { Network }
|
||||||
this.hysteria2 = <Hysteria2> this.$props.inbound
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-card subtitle="Naive">
|
<v-card subtitle="Naive">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="inbound" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="ShadowTls">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="[1,2,3]"
|
||||||
|
:label="$t('version')"
|
||||||
|
v-model="version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.version > 1">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.password">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
version: {
|
||||||
|
get() { return this.$props.data.version ?? 3 },
|
||||||
|
set(v: number) {
|
||||||
|
this.$props.data.version = v
|
||||||
|
if (v==1) {
|
||||||
|
delete this.$props.data.password
|
||||||
|
} else if (this.$props.data.password === undefined ) {
|
||||||
|
this.$props.data.password = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Selector">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-combobox
|
||||||
|
v-model="data.outbounds"
|
||||||
|
:items="tags"
|
||||||
|
:label="$t('pages.outbounds')"
|
||||||
|
multiple
|
||||||
|
@update:model-value="updateDefault"
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-combobox
|
||||||
|
v-model="data.default"
|
||||||
|
:items="data.outbounds"
|
||||||
|
:label="$t('types.lb.defaultOut')"
|
||||||
|
clearable
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-switch v-model="data.interrupt_exist_connections" color="primary" :label="$t('types.lb.interruptConn')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data','tags'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateDefault() {
|
||||||
|
if (!this.$props.data.outbounds?.includes(this.$props.data.default)) {
|
||||||
|
delete this.$props.data.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -5,29 +5,29 @@
|
|||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
:items="[1,2,3]"
|
:items="[1,2,3]"
|
||||||
label="Version"
|
:label="$t('version')"
|
||||||
v-model="version">
|
v-model="version">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="inbound.password != undefined">
|
<v-col cols="12" sm="6" md="4" v-if="data.password != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Password"
|
:label="$t('types.pw')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inbound.password">
|
v-model="data.password">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Handshake Server"
|
:label="$t('types.shdwTls.hs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="Inbound.handshake.server">
|
v-model="Inbound.handshake.server">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Server Port"
|
:label="$t('out.port')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Dial :dial="Inbound.handshake" />
|
<Dial :dial="Inbound.handshake" :outTags="outTags" />
|
||||||
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Add Hanshake Server"
|
:label="$t('types.shdwTls.addHS')"
|
||||||
hide-details
|
hide-details
|
||||||
append-icon="mdi-plus"
|
append-icon="mdi-plus"
|
||||||
@click:append="addHandshakeServer()"
|
@click:append="addHandshakeServer()"
|
||||||
@@ -67,14 +67,14 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Handshake Server"
|
:label="$t('types.shdwTls.hs')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="value.server">
|
v-model="value.server">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Server Port"
|
:label="$t('out.port')"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Dial :dial="value" />
|
<Dial :dial="value" :outTags="outTags" />
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -92,7 +92,7 @@ import { ShadowTLS } from '@/types/inbounds'
|
|||||||
import Dial from '../Dial.vue'
|
import Dial from '../Dial.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['data', 'outTags'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
handshake_server: ''
|
handshake_server: ''
|
||||||
@@ -100,7 +100,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addHandshakeServer() {
|
addHandshakeServer() {
|
||||||
this.inbound.handshake_for_server_name[this.handshake_server] = {}
|
this.data.handshake_for_server_name[this.handshake_server] = {}
|
||||||
// Clear the input field after adding the server
|
// Clear the input field after adding the server
|
||||||
this.handshake_server = ''
|
this.handshake_server = ''
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Inbound(): ShadowTLS {
|
Inbound(): ShadowTLS {
|
||||||
return <ShadowTLS>this.$props.inbound;
|
return <ShadowTLS>this.$props.data;
|
||||||
},
|
},
|
||||||
server_port: {
|
server_port: {
|
||||||
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
get() { return this.Inbound.handshake.server_port ? this.Inbound.handshake.server_port : 443; },
|
||||||
|
|||||||
@@ -4,16 +4,19 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Method"
|
:label="$t('in.ssMethod')"
|
||||||
:items="ssMethods"
|
:items="ssMethods"
|
||||||
v-model="inbound.method">
|
v-model="data.method">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="inbound.password" label="Password" hide-details></v-text-field>
|
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
||||||
|
<UoT :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -21,9 +24,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
import UoT from '@/components/UoT.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction','data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ssMethods: [
|
ssMethods: [
|
||||||
@@ -39,6 +43,6 @@ export default {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Network }
|
components: { Network, UoT }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="SOCKS">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.un')"
|
||||||
|
hide-details
|
||||||
|
v-model="username">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
v-model="password">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:items="['4','4a','5']"
|
||||||
|
:label="$t('version')"
|
||||||
|
v-model="data.version">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<UoT :data="data" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
import UoT from '@/components/UoT.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
username: {
|
||||||
|
get(): string { return this.data.username?.length > 0 ? this.data.username : '' },
|
||||||
|
set(v:string) { this.data.username = v.length > 0 ? v : undefined },
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
get(): string { return this.data.password?.length > 0 ? this.data.password : '' },
|
||||||
|
set(v:string) { this.data.password = v.length > 0 ? v : undefined },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network, UoT }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="SSH">
|
||||||
|
<template v-if="optionKey">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="auto">
|
||||||
|
<v-btn-toggle v-model="usePath"
|
||||||
|
class="rounded-xl"
|
||||||
|
density="compact"
|
||||||
|
variant="outlined"
|
||||||
|
shaped
|
||||||
|
mandatory>
|
||||||
|
<v-btn
|
||||||
|
@click="data.private_key=undefined; data.private_key_path=''"
|
||||||
|
>{{ $t('tls.usePath') }}</v-btn>
|
||||||
|
<v-btn
|
||||||
|
@click="data.private_key_path=undefined; data.private_key=''"
|
||||||
|
>{{ $t('tls.useText') }}</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="usePath == 0">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.keyPath')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.private_key_path">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.key')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.private_key">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.ssh.passphrase')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.private_key_passphrase">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.user" :label="$t('types.un')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<v-row v-if="optionHostKey">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('types.ssh.hostKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="host_key">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.host_key_algorithms != undefined">
|
||||||
|
<v-text-field v-model="algorithms" :label="$t('types.ssh.algorithm') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.client_version != undefined">
|
||||||
|
<v-text-field v-model="data.client_version" :label="$t('types.ssh.clientVer')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details>{{ $t('types.ssh.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionKey" color="primary" label="SSH Key" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionHostKey" color="primary" :label="$t('types.ssh.hostKey')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionAlgorithms" color="primary" :label="$t('types.ssh.algorithm')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionVer" color="primary" :label="$t('types.ssh.clientVer')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
usePath: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionKey: {
|
||||||
|
get(): boolean { return this.data.private_key != undefined || this.data.private_key_path != undefined },
|
||||||
|
set(v:boolean) {
|
||||||
|
this.usePath = 0
|
||||||
|
if (v) {
|
||||||
|
this.$props.data.private_key_path = ""
|
||||||
|
delete this.$props.data.user
|
||||||
|
delete this.$props.data.password
|
||||||
|
} else {
|
||||||
|
delete this.$props.data.private_key_path
|
||||||
|
delete this.$props.data.private_key
|
||||||
|
delete this.$props.data.private_key_passphrase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optionHostKey: {
|
||||||
|
get(): boolean { return this.data.host_key != undefined },
|
||||||
|
set(v:boolean) { this.data.host_key = v ? '' : undefined }
|
||||||
|
},
|
||||||
|
optionAlgorithms: {
|
||||||
|
get(): boolean { return this.data.host_key_algorithms != undefined },
|
||||||
|
set(v:boolean) { this.data.host_key_algorithms = v ? [] : undefined }
|
||||||
|
},
|
||||||
|
optionVer: {
|
||||||
|
get(): boolean { return this.data.client_version != undefined },
|
||||||
|
set(v:boolean) { this.data.client_version = v ? 'SSH-2.0-OpenSSH_7.4p1' : undefined }
|
||||||
|
},
|
||||||
|
host_key: {
|
||||||
|
get(): string { return this.$props.data.host_key ? this.$props.data.host_key.join('\n') : '' },
|
||||||
|
set(v:string) { this.$props.data.host_key = v.split('\n') }
|
||||||
|
},
|
||||||
|
algorithms: {
|
||||||
|
get() { return this.$props.data.host_key_algorithms ? this.$props.data.host_key_algorithms.join(',') : '' },
|
||||||
|
set(v:string) { this.$props.data.host_key_algorithms = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-card subtitle="TProxy">
|
<v-card subtitle="TProxy">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :inbound="inbound" />
|
<Network :data="inbound" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Tor">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.executable_path" :label="$t('types.tor.execPath')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.data_directory" :label="$t('types.tor.dataDir')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="extra_args" :label="$t('types.tor.extArgs') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
extra_args: {
|
||||||
|
get() { return this.$props.data.extra_args?.join(',') },
|
||||||
|
set(v:string) { this.$props.data.extra_args = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Trojan">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
components: { Network }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,35 +1,59 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="TUIC">
|
<v-card subtitle="TUIC">
|
||||||
<v-row>
|
<v-row v-if="direction == 'out'">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="data.password" :label="$t('types.pw')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
label="Congestion Control"
|
label="UDP Relay Mode"
|
||||||
:items="congestion_controls"
|
:items="['native', 'quic']"
|
||||||
v-model="inbound.congestion_control">
|
clearable
|
||||||
|
@click:clear="delete data.udp_relay_mode"
|
||||||
|
v-model="data.udp_relay_mode">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" label="Zero-RTT Handshake" v-model="inbound.zero_rtt_handshake" hide-details></v-switch>
|
<v-switch color="primary" label="UDP Over Stream" v-model="data.udp_over_stream" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.tuic.congControl')"
|
||||||
|
:items="congestion_controls"
|
||||||
|
v-model="data.congestion_control">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" label="Zero-RTT Handshake" v-model="data.zero_rtt_handshake" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Authentication Timeout"
|
:label="$t('types.tuic.authTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
:suffix="$t('date.s')"
|
||||||
min="1"
|
min="1"
|
||||||
v-model.number="auth_timeout">
|
v-model.number="auth_timeout">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Heartbeat"
|
:label="$t('types.tuic.hb')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
:suffix="$t('date.s')"
|
||||||
min="1"
|
min="1"
|
||||||
v-model.number="heartbeat">
|
v-model.number="heartbeat">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
@@ -39,9 +63,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TUIC } from '@/types/inbounds'
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound'],
|
props: ['direction', 'data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
congestion_controls: [
|
congestion_controls: [
|
||||||
@@ -50,17 +75,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
Inbound(): TUIC {
|
|
||||||
return <TUIC> this.$props.inbound
|
|
||||||
},
|
|
||||||
auth_timeout: {
|
auth_timeout: {
|
||||||
get() { return this.Inbound.auth_timeout ? parseInt(this.Inbound.auth_timeout.replace('s','')) : '' },
|
get() { return this.$props.data.auth_timeout ? parseInt(this.$props.data.auth_timeout.replace('s','')) : '' },
|
||||||
set(newValue:number) { this.$props.inbound.auth_timeout = newValue ? newValue + 's' : '' }
|
set(newValue:number) { this.$props.data.auth_timeout = newValue ? newValue + 's' : '' }
|
||||||
},
|
},
|
||||||
heartbeat: {
|
heartbeat: {
|
||||||
get() { return this.Inbound.heartbeat ? parseInt(this.Inbound.heartbeat.replace('s','')) : '' },
|
get() { return this.$props.data.heartbeat ? parseInt(this.$props.data.heartbeat.replace('s','')) : '' },
|
||||||
set(newValue:number) { this.$props.inbound.heartbeat = newValue ? newValue + 's' : '' }
|
set(newValue:number) { this.$props.data.heartbeat = newValue ? newValue + 's' : '' }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
components: { Network }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="URL Test">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-combobox
|
||||||
|
v-model="data.outbounds"
|
||||||
|
:items="tags"
|
||||||
|
:label="$t('pages.outbounds')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" v-if="optionUrl">
|
||||||
|
<v-text-field v-model="data.url" :label="$t('types.lb.testUrl')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionInterval">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.lb.interval')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="3"
|
||||||
|
:suffix="$t('date.s')"
|
||||||
|
v-model.number="interval"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionTolerance">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.lb.tolerance')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
:suffix="$t('date.ms')"
|
||||||
|
v-model.number="tolerance"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="optionIdle">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('transport.idleTimeout')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
:suffix="$t('date.m')"
|
||||||
|
v-model.number="idle_timeout"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-switch v-model="data.interrupt_exist_connections" color="primary" :label="$t('types.lb.interruptConn')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-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>{{ $t('types.lb.urlTestOptions') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionUrl" color="primary" :label="$t('types.lb.testUrl')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionInterval" color="primary" :label="$t('types.lb.interval')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionTolerance" color="primary" :label="$t('types.lb.tolerance')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionIdle" color="primary" :label="$t('transport.idleTimeout')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data', 'tags'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionUrl: {
|
||||||
|
get(): boolean { return this.$props.data.url != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.url = v ? 'https://www.gstatic.com/generate_204' : undefined }
|
||||||
|
},
|
||||||
|
optionInterval: {
|
||||||
|
get(): boolean { return this.$props.data.interval != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.interval = v ? '3s' : undefined }
|
||||||
|
},
|
||||||
|
optionTolerance: {
|
||||||
|
get(): boolean { return this.$props.data.tolerance != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.tolerance = v ? 50 : undefined }
|
||||||
|
},
|
||||||
|
optionIdle: {
|
||||||
|
get(): boolean { return this.$props.data.idle_timeout != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.idle_timeout = v ? '30m' : undefined }
|
||||||
|
},
|
||||||
|
interval: {
|
||||||
|
get() { return this.$props.data.interval ? parseInt(this.$props.data.interval.replace('s','')) : 3 },
|
||||||
|
set(v:number) { this.$props.data.interval = v > 0 ? v + 's' : '3s' }
|
||||||
|
},
|
||||||
|
tolerance: {
|
||||||
|
get() { return this.$props.data.tolerance ? parseInt(this.$props.data.tolerance) : 0 },
|
||||||
|
set(v:number) { this.$props.data.tolerance = v > 0 ? v : 0 }
|
||||||
|
},
|
||||||
|
idle_timeout: {
|
||||||
|
get() { return this.$props.data.idle_timeout ? parseInt(this.$props.data.idle_timeout.replace('m','')) : 30 },
|
||||||
|
set(v:number) { this.$props.data.idle_timeout = v > 0 ? v + 'm' : '0m' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="VLESS">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vless.flow')"
|
||||||
|
:items="['','xtls-rprx-vision']"
|
||||||
|
v-model="data.flow">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vless.udpEnc')"
|
||||||
|
:items="['none','packetaddr','xudp']"
|
||||||
|
v-model="packet_encoding">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
packet_encoding: {
|
||||||
|
get() { return this.$props.data.packet_encoding != undefined ? this.$props.data.packet_encoding : 'none'; },
|
||||||
|
set(newValue:string) { this.$props.data.packet_encoding = newValue != "none" ? newValue : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="VMESS">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field v-model="data.uuid" label="UUID" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="Alter ID"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
v-model.number="data.alter_id">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vmess.security')"
|
||||||
|
:items="securities"
|
||||||
|
v-model="data.security">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('types.vless.udpEnc')"
|
||||||
|
:items="['none','packetaddr','xudp']"
|
||||||
|
v-model="packet_encoding">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.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="data.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
securities: [
|
||||||
|
"auto",
|
||||||
|
"none",
|
||||||
|
"zero",
|
||||||
|
"aes-128-gcm",
|
||||||
|
"aes-128-ctr",
|
||||||
|
"chacha20-poly1305",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
packet_encoding: {
|
||||||
|
get() { return this.$props.data.packet_encoding != undefined ? this.$props.data.packet_encoding : 'none'; },
|
||||||
|
set(newValue:string) { this.$props.data.packet_encoding = newValue != "none" ? newValue : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<template>
|
||||||
|
<v-card subtitle="Wireguard">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field v-model="data.private_key" :label="$t('types.wg.privKey')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field v-model="data.peer_public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8" v-if="data.pre_shared_key != undefined">
|
||||||
|
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field v-model="local_ips" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.reserved != undefined">
|
||||||
|
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.wg.worker')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=1
|
||||||
|
v-model.number="data.workers">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.mtu != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="MTU"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
v-model.number="data.mtu">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.interface_name != undefined">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.wg.ifName')"
|
||||||
|
hide-details
|
||||||
|
v-model.number="data.interface_name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.system_interface" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.gso" color="primary" :label="$t('types.wg.gso')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn v-bind="props" hide-details>{{ $t('types.wg.options') }}</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-card>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionPsk" color="primary" :label="$t('types.wg.psk')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionRsrv" color="primary" label="Reserved" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionWorker" color="primary" :label="$t('types.wg.worker')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionMtu" color="primary" label="MTU" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionInterface" color="primary" :label="$t('types.wg.ifName')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionPeers" color="primary" :label="$t('types.wg.multiPeer')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-card v-if="data.peers != undefined">
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ $t('types.wg.peers') }} <v-icon @click="addPeer" icon="mdi-plus" />
|
||||||
|
</v-card-subtitle>
|
||||||
|
<template v-for="(p, index) in data.peers">
|
||||||
|
<v-card style="margin-top: 1rem;">
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" />
|
||||||
|
</v-card-subtitle>
|
||||||
|
<Peer :data="p" />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Network from '@/components/Network.vue'
|
||||||
|
import Peer from '@/components/WgPeer.vue'
|
||||||
|
import { WgPeer } from '@/types/outbounds'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['data'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menu: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addPeer() {
|
||||||
|
this.$props.data.peers.push({server: '', port: ''})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
optionPsk: {
|
||||||
|
get(): boolean { return this.$props.data.pre_shared_key != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.pre_shared_key = v ? "" : undefined }
|
||||||
|
},
|
||||||
|
optionRsrv: {
|
||||||
|
get(): boolean { return this.$props.data.reserved != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.reserved = v ? [0,0,0] : undefined }
|
||||||
|
},
|
||||||
|
optionWorker: {
|
||||||
|
get(): boolean { return this.$props.data.workers != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.workers = v ? 2 : undefined }
|
||||||
|
},
|
||||||
|
optionMtu: {
|
||||||
|
get(): boolean { return this.$props.data.mtu != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.mtu = v ? 1408 : undefined }
|
||||||
|
},
|
||||||
|
optionInterface: {
|
||||||
|
get(): boolean { return this.$props.data.interface_name != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.interface_name = v ? "" : undefined }
|
||||||
|
},
|
||||||
|
optionPeers: {
|
||||||
|
get(): boolean { return this.$props.data.peers != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.peers = v ? <WgPeer[]>[] : undefined }
|
||||||
|
},
|
||||||
|
local_ips: {
|
||||||
|
get() { return this.$props.data.local_address?.join(',') },
|
||||||
|
set(v:string) { this.$props.data.local_address = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
reserved: {
|
||||||
|
get() { return this.$props.data.reserved?.join(',') },
|
||||||
|
set(v:string) {
|
||||||
|
if(!v.endsWith(',')) {
|
||||||
|
this.$props.data.reserved = v.length > 0 ? v.split(',').map(str => parseInt(str, 10)) : []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Network, Peer }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -15,17 +15,20 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-select
|
||||||
label="Method"
|
:label="$t('transport.httpMethod')"
|
||||||
hide-details
|
hide-details
|
||||||
|
clearable
|
||||||
|
@click:clear="delete transport.method"
|
||||||
|
:items="methodList"
|
||||||
v-model="transport.method">
|
v-model="transport.method">
|
||||||
</v-text-field>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Idle Timeout"
|
:label="$t('transport.idleTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
suffix="s"
|
||||||
@@ -35,7 +38,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Ping Timeout"
|
:label="$t('transport.pingTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
suffix="s"
|
||||||
@@ -44,14 +47,17 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<Headers :data="transport" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { HTTP } from '../../types/transport'
|
import { HTTP } from '../../types/transport'
|
||||||
|
import Headers from '../Headers.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['transport'],
|
props: ['transport'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
methodList: ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -70,6 +76,7 @@ export default {
|
|||||||
get() { return this.Http.ping_timeout ? parseInt(this.Http.ping_timeout.replace('s','')) : '' },
|
get() { return this.Http.ping_timeout ? parseInt(this.Http.ping_timeout.replace('s','')) : '' },
|
||||||
set(newValue:number) { this.$props.transport.ping_timeout = newValue ? newValue + 's' : '' }
|
set(newValue:number) { this.$props.transport.ping_timeout = newValue ? newValue + 's' : '' }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
components: { Headers }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -15,14 +15,17 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<Headers :data="transport" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Headers from '../Headers.vue';
|
||||||
export default {
|
export default {
|
||||||
props: ['transport'],
|
props: ['transport'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
components: { Headers }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -7,6 +7,15 @@
|
|||||||
v-model="transport.path">
|
v-model="transport.path">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('transport.host')"
|
||||||
|
hide-details
|
||||||
|
v-model="host">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Max Early Data"
|
label="Max Early Data"
|
||||||
@@ -24,10 +33,12 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<Headers :data="transport" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { WebSocket } from '../../types/transport'
|
import { WebSocket } from '../../types/transport'
|
||||||
|
import Headers from '../Headers.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['transport'],
|
props: ['transport'],
|
||||||
data() {
|
data() {
|
||||||
@@ -42,10 +53,17 @@ export default {
|
|||||||
get() { return this.WS.max_early_data ? this.WS.max_early_data : '' },
|
get() { return this.WS.max_early_data ? this.WS.max_early_data : '' },
|
||||||
set(newValue:number) { this.$props.transport.max_early_data = newValue != 0 ? newValue : undefined }
|
set(newValue:number) { this.$props.transport.max_early_data = newValue != 0 ? newValue : undefined }
|
||||||
},
|
},
|
||||||
|
host: {
|
||||||
|
get() { return this.WS.headers?.Host ? this.WS.headers.Host : '' },
|
||||||
|
set(newValue:string) {
|
||||||
|
this.$props.transport.headers = newValue != "" ? { Host: newValue } : undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.WS.early_data_header_name = 'Sec-WebSocket-Protocol'
|
this.WS.early_data_header_name ??= 'Sec-WebSocket-Protocol'
|
||||||
this.WS.path = '/'
|
this.WS.path ??= '/'
|
||||||
}
|
},
|
||||||
|
components: { Headers }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Service Name"
|
:label="$t('transport.grpcServiceName')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="transport.service_name">
|
v-model="transport.service_name">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<v-switch
|
<v-switch
|
||||||
color="primary"
|
color="primary"
|
||||||
v-model="transport.permit_without_stream"
|
v-model="transport.permit_without_stream"
|
||||||
label="Permit Without Stream"
|
:label="$t('transport.grpcPws')"
|
||||||
hide-details>
|
hide-details>
|
||||||
</v-switch>
|
</v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Idle Timeout"
|
:label="$t('transport.idleTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
suffix="s"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Ping Timeout"
|
:label="$t('transport.pingTimeout')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
suffix="s"
|
suffix="s"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<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>
|
||||||
<v-app-bar-title :text="$t(<string>$router.currentRoute.value.name)" class="align-center text-center " />
|
<v-app-bar-title :text="$t(<string>$router.currentRoute.value.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>
|
||||||
@@ -8,7 +9,7 @@
|
|||||||
</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"
|
||||||
@@ -31,11 +32,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}
|
||||||
})
|
})
|
||||||
|
|
||||||
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}
|
||||||
})
|
})
|
||||||
|
|
||||||
const stateChange = computed((): any => {
|
const stateChange = computed((): any => {
|
||||||
|
|||||||
@@ -25,3 +25,11 @@ const isMobile = computed( ():boolean =>{
|
|||||||
return smAndDown.value
|
return smAndDown.value
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.v-card-subtitle {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<v-list-item prepend-icon="mdi-logout" :title="$t('menu.logout')" @click="logout"></v-list-item>
|
<v-list-item prepend-icon="mdi-logout" :title="$t('menu.logout')" @click="Logout"></v-list-item>
|
||||||
</template>
|
</template>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
</template>
|
</template>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import HttpUtil from '@/plugins/httputil'
|
import { logout } from '@/plugins/httputil'
|
||||||
|
|
||||||
const props = defineProps(['isMobile','displayDrawer'])
|
const props = defineProps(['isMobile','displayDrawer'])
|
||||||
|
|
||||||
@@ -54,14 +54,13 @@ 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.settings', icon: 'mdi-cog', path: '/settings' },
|
{ title: 'pages.settings', icon: 'mdi-cog', path: '/settings' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const logout = async () => {
|
const Logout = async () => {
|
||||||
const response = await HttpUtil.get('/api/logout')
|
logout()
|
||||||
if(response.success){
|
|
||||||
router.push('/login')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="400">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('admin.changeCred') + " " + user.username }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field v-model="newData.oldPass" :label="$t('admin.oldPass')" :rules="passwordRules" type="password" required></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field v-model="newData.newUsername" :label="$t('admin.newUname')" :rules="usernameRules" required></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-text-field v-model="newData.newPass" :label="$t('admin.newPass')" :rules="passwordRules" type="password" required></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</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"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'user'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
newData: {
|
||||||
|
id: 0,
|
||||||
|
oldPass: "",
|
||||||
|
newUsername: "",
|
||||||
|
newPass: ""
|
||||||
|
},
|
||||||
|
usernameRules: [
|
||||||
|
(value: string) => {
|
||||||
|
if (value?.length > 0) return true
|
||||||
|
return i18n.global.t('login.unRules')
|
||||||
|
},
|
||||||
|
],
|
||||||
|
passwordRules: [
|
||||||
|
(value: string) => {
|
||||||
|
if (value?.length > 0) return true
|
||||||
|
return i18n.global.t('login.pwRules')
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetData() {
|
||||||
|
this.newData.id = this.$props.user.id
|
||||||
|
this.newData.oldPass = ""
|
||||||
|
this.newData.newUsername = ""
|
||||||
|
this.newData.newPass = ""
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.resetData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
if (this.newData.oldPass == '' || this.newData.newUsername == '' || this.newData.newPass == '') return
|
||||||
|
this.$emit('save', this.newData)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.resetData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
<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
|
||||||
|
return l.replace('zh', 'zh-')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
this.changes = []
|
||||||
|
this.user = this.$props.actor
|
||||||
|
this.key = ''
|
||||||
|
this.chngCount = 10
|
||||||
|
if (newValue) {
|
||||||
|
this.loadData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -26,6 +26,9 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="client.name" :label="$t('client.name')" hide-details></v-text-field>
|
<v-text-field v-model="client.name" :label="$t('client.name')" hide-details></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="client.desc" :label="$t('client.desc')" hide-details></v-text-field>
|
||||||
|
</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">
|
||||||
@@ -139,6 +142,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
color="blue-darken-1"
|
color="blue-darken-1"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
|
:loading="loading"
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
@@ -160,6 +164,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
client: createClient(),
|
client: createClient(),
|
||||||
title: "add",
|
title: "add",
|
||||||
|
loading: false,
|
||||||
clientStats: false,
|
clientStats: false,
|
||||||
tab: "t1",
|
tab: "t1",
|
||||||
clientConfig: <any>[],
|
clientConfig: <any>[],
|
||||||
@@ -174,7 +179,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()
|
||||||
@@ -182,10 +187,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() {
|
||||||
@@ -193,12 +197,14 @@ export default {
|
|||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.client.config = updateConfigs(JSON.stringify(this.clientConfig), this.client.name)
|
this.loading = true
|
||||||
this.client.links = JSON.stringify([
|
this.client.config = updateConfigs(this.clientConfig, this.client.name)
|
||||||
|
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
|
||||||
},
|
},
|
||||||
setDate(newDate:number){
|
setDate(newDate:number){
|
||||||
this.client.expiry = newDate
|
this.client.expiry = newDate
|
||||||
@@ -206,8 +212,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
clientInbounds: {
|
clientInbounds: {
|
||||||
get() { return this.client.inbounds == "" ? [] : this.client.inbounds.split(',') },
|
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},
|
||||||
@@ -219,7 +225,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newValue) { if (newValue) {
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
this.updateData()
|
this.updateData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
width="100"
|
|
||||||
:label="$t('type')"
|
:label="$t('type')"
|
||||||
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
||||||
v-model="inbound.type"
|
v-model="inbound.type"
|
||||||
@@ -18,22 +17,22 @@
|
|||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="inbound.tag" :label="$t('in.tag')" hide-details></v-text-field>
|
<v-text-field v-model="inbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Listen :inbound="inbound" />
|
<Listen :inbound="inbound" :inTags="inTags" />
|
||||||
<Direct v-if="inbound.type == inTypes.Direct" :inbound="inbound" />
|
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
||||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" :inbound="inbound" />
|
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" :inbound="inbound" />
|
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" :inbound="inbound" />
|
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||||
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
||||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" :inbound="inbound" />
|
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
||||||
<Tuic v-if="inbound.type == inTypes.TUIC" :inbound="inbound" />
|
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
||||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :inbound="inbound" />
|
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" :id="id" />
|
||||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" />
|
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
||||||
<InMulitiplex v-if="Object.hasOwn(inbound,'multiplex')" :inbound="inbound" />
|
<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-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -48,6 +47,7 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
color="blue-darken-1"
|
color="blue-darken-1"
|
||||||
variant="text"
|
variant="text"
|
||||||
|
:loading="loading"
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
</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 Listen from '@/components/Listen.vue'
|
import Listen from '@/components/Listen.vue'
|
||||||
@@ -72,17 +71,19 @@ import Tuic from '@/components/protocols/Tuic.vue'
|
|||||||
import InTls from '@/components/InTLS.vue'
|
import InTls from '@/components/InTLS.vue'
|
||||||
import TProxy from '@/components/protocols/TProxy.vue'
|
import TProxy from '@/components/protocols/TProxy.vue'
|
||||||
import RandomUtil from '@/plugins/randomUtil'
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
import InMulitiplex from '@/components/InMulitiplex.vue'
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'id', 'stats'],
|
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inbound: createInbound("direct",{ "tag": "" }),
|
inbound: createInbound("direct",{ "tag": "" }),
|
||||||
title: "add",
|
title: "add",
|
||||||
|
loading: false,
|
||||||
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],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -91,17 +92,21 @@ export default {
|
|||||||
if (this.$props.id != -1) {
|
if (this.$props.id != -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
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
this.inbound = createInbound("mixed",{ tag: "in-"+port ,listen: "::", listen_port: port })
|
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
}
|
}
|
||||||
this.inboundStats = this.$props.stats
|
this.inboundStats = this.$props.stats
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
const prevConfig = { tag: this.inbound.tag ,listen: this.inbound.listen, listen_port: this.inbound.listen_port }
|
// Tag change only in add outbound
|
||||||
|
const tag = this.$props.id != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||||
|
// Use previous data
|
||||||
|
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, prevConfig)
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
@@ -109,7 +114,9 @@ export default {
|
|||||||
this.$emit('close')
|
this.$emit('close')
|
||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.$emit('save', this.inbound, this.inboundStats)
|
this.loading = true
|
||||||
|
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value)
|
||||||
|
this.loading = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -119,13 +126,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, InMulitiplex, Tuic, Transport }
|
components: { Listen, InTls, Hysteria2, Naive, Direct, Shadowsocks, Users, Hysteria, ShadowTls, TProxy, Multiplex, Tuic, Transport }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.v-card-subtitle {
|
|
||||||
text-align: center;
|
|
||||||
border-bottom: 1px solid gray;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " " + $t('objects.outbound') }}
|
||||||
|
</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('type')"
|
||||||
|
:items="Object.keys(outTypes).map((key,index) => ({title: key, value: Object.values(outTypes)[index]}))"
|
||||||
|
v-model="outbound.type"
|
||||||
|
@update:modelValue="changeType">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="outbound.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="!NoServer.includes(outbound.type)">
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.addr')"
|
||||||
|
hide-details
|
||||||
|
v-model="outbound.server">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('out.port')"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
hide-details
|
||||||
|
v-model.number="outbound.server_port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<Direct v-if="outbound.type == outTypes.Direct" direction="out" :data="outbound" />
|
||||||
|
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
||||||
|
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
||||||
|
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
||||||
|
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
||||||
|
<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" />
|
||||||
|
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
||||||
|
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
||||||
|
<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-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 { OutTypes, createOutbound } from '@/types/outbounds'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
import Dial from '@/components/Dial.vue'
|
||||||
|
import Multiplex from '@/components/Multiplex.vue'
|
||||||
|
import Transport from '@/components/Transport.vue'
|
||||||
|
import OutTLS from '@/components/OutTLS.vue'
|
||||||
|
import Direct from '@/components/protocols/Direct.vue'
|
||||||
|
import Socks from '@/components/protocols/Socks.vue'
|
||||||
|
import Http from '@/components/protocols/Http.vue'
|
||||||
|
import Shadowsocks from '@/components/protocols/Shadowsocks.vue'
|
||||||
|
import Vmess from '@/components/protocols/Vmess.vue'
|
||||||
|
import Trojan from '@/components/protocols/Trojan.vue'
|
||||||
|
import Wireguard from '@/components/protocols/Wireguard.vue'
|
||||||
|
import Hysteria from '@/components/protocols/Hysteria.vue'
|
||||||
|
import ShadowTls from '@/components/protocols/OutShadowTls.vue'
|
||||||
|
import Vless from '@/components/protocols/Vless.vue'
|
||||||
|
import Tuic from '@/components/protocols/Tuic.vue'
|
||||||
|
import Hysteria2 from '@/components/protocols/Hysteria2.vue'
|
||||||
|
import Tor from '@/components/protocols/Tor.vue'
|
||||||
|
import Ssh from '@/components/protocols/Ssh.vue'
|
||||||
|
import Selector from '@/components/protocols/Selector.vue'
|
||||||
|
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
outbound: createOutbound("direct",{ "tag": "" }),
|
||||||
|
title: "add",
|
||||||
|
loading: false,
|
||||||
|
outTypes: OutTypes,
|
||||||
|
outboundStats: false,
|
||||||
|
NoDial: [OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest],
|
||||||
|
NoServer: [OutTypes.Direct, OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest, OutTypes.Tor],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.id != -1) {
|
||||||
|
const newData = JSON.parse(this.$props.data)
|
||||||
|
this.outbound = createOutbound(newData.type, newData)
|
||||||
|
this.title = "edit"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.outbound = createOutbound("direct",{ tag: "direct-" + RandomUtil.randomSeq(3) })
|
||||||
|
this.title = "add"
|
||||||
|
}
|
||||||
|
this.outboundStats = this.$props.stats
|
||||||
|
},
|
||||||
|
changeType() {
|
||||||
|
// Tag change only in add outbound
|
||||||
|
const tag = this.$props.id != -1 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||||
|
// Use previous data
|
||||||
|
const prevConfig = { tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||||
|
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.updateData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
this.$emit('save', this.outbound, this.outboundStats)
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Dial, Multiplex, Transport, OutTLS,
|
||||||
|
Direct, Socks, Http, Shadowsocks, Vmess, Trojan,
|
||||||
|
Wireguard, Hysteria, ShadowTls, Vless, Tuic,
|
||||||
|
Hysteria2, Tor, Ssh, Selector, UrlTest }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<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>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-for="l in clientLinks">
|
<v-row v-for="l in clientLinks">
|
||||||
<v-col style="text-align: center;" @click="copyToClipboard(l.uri)">
|
<v-col style="text-align: center;" @click="copyToClipboard(l.uri)">
|
||||||
<v-chip>{{ l.remark }}</v-chip><br />
|
<v-chip>{{ l.remark?? "-" }}</v-chip><br />
|
||||||
<QrcodeVue :value="l.uri" :size="300" :margin="1" style="border-radius: 1rem;" />
|
<QrcodeVue :value="l.uri" :size="300" :margin="1" style="border-radius: 1rem;" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -31,39 +31,45 @@
|
|||||||
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'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
msg: Message(),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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: {
|
||||||
@@ -76,7 +82,7 @@ export default {
|
|||||||
return Data().subURI + this.client.name
|
return Data().subURI + this.client.name
|
||||||
},
|
},
|
||||||
clientLinks() {
|
clientLinks() {
|
||||||
return JSON.parse(this.client.links?? "[]")
|
return this.client.links?? []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { QrcodeVue }
|
components: { QrcodeVue }
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " " + $t('objects.rule') }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" v-model="logical" :label="$t('rule.logical')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-col cols="auto" v-if="logical" justify="center" align="center">
|
||||||
|
<v-btn color="primary" @click="ruleData.rules.push({})" hide-details>{{ $t('actions.add') + " " + $t('objects.rule') }}</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-card style="background-color: inherit; margin-bottom: 5px;" v-for="(r, index) in ruleData.rules" v-if="ruleData.type == 'logical'">
|
||||||
|
<v-card-subtitle>{{ $t('objects.rule') + ' ' + (index+1) }}
|
||||||
|
<v-icon @click="ruleData.rules.splice(index,1)" icon="mdi-delete" v-if="ruleData.rules.length>1" />
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text style="padding: 0;">
|
||||||
|
<RuleOptions
|
||||||
|
:rule="r"
|
||||||
|
:clients="clients"
|
||||||
|
:inTags="inTags"
|
||||||
|
:rsTags="rsTags" />
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<RuleOptions
|
||||||
|
v-else
|
||||||
|
:rule="ruleData.rules[0]"
|
||||||
|
:clients="clients"
|
||||||
|
:inTags="inTags"
|
||||||
|
:rsTags="rsTags" />
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-combobox
|
||||||
|
v-model="ruleData.outbound"
|
||||||
|
:items="outTags"
|
||||||
|
:label="$t('objects.outbound')"
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="logical">
|
||||||
|
<v-combobox
|
||||||
|
v-model="ruleData.mode"
|
||||||
|
:items="['and', 'or']"
|
||||||
|
:label="$t('rule.mode')"
|
||||||
|
hide-details
|
||||||
|
></v-combobox>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch color="primary" v-model="ruleData.invert" :label="$t('rule.invert')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</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 { logicalRule, rule } from '@/types/rules'
|
||||||
|
import RuleOptions from '@/components/Rule.vue'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'index', 'clients', 'inTags', 'outTags', 'rsTags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'add',
|
||||||
|
loading: false,
|
||||||
|
ruleData: <logicalRule>{
|
||||||
|
type: 'logical',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{}],
|
||||||
|
invert: false,
|
||||||
|
outbound: 'direct',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.index != -1) {
|
||||||
|
const newData = JSON.parse(this.$props.data)
|
||||||
|
if (newData.type) {
|
||||||
|
this.ruleData = newData
|
||||||
|
} else {
|
||||||
|
this.ruleData = <logicalRule>{
|
||||||
|
type: 'simple',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{...newData}],
|
||||||
|
invert: newData.invert,
|
||||||
|
outbound: newData.outbound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.title = 'edit'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.ruleData = <logicalRule>{
|
||||||
|
type: 'simple',
|
||||||
|
mode: 'and',
|
||||||
|
rules: <rule[]>[{}],
|
||||||
|
invert: false,
|
||||||
|
outbound: this.$props.outTags[0]?? 'direct',
|
||||||
|
}
|
||||||
|
this.title = 'add'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.updateData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
if (this.ruleData.type == 'simple'){
|
||||||
|
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
||||||
|
this.ruleData.rules[0].invert = this.ruleData.invert
|
||||||
|
}
|
||||||
|
this.$emit('save', this.ruleData)
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
deleteRule(index:number) {
|
||||||
|
this.ruleData.rules.splice(index,1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
logical: {
|
||||||
|
get() { return this.ruleData.type == 'logical' },
|
||||||
|
set(v:boolean) {
|
||||||
|
if (v) {
|
||||||
|
this.ruleData.type = 'logical'
|
||||||
|
this.ruleData.outbound = this.ruleData.rules[0].outbound?? this.$props.outTags[0]
|
||||||
|
delete this.ruleData.rules[0].outbound
|
||||||
|
} else {
|
||||||
|
this.ruleData.type = 'simple'
|
||||||
|
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { RuleOptions }
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " Ruleset" }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('type')"
|
||||||
|
:items="[{title: $t('ruleset.local'), value: 'local'},{ title: $t('ruleset.remote'), value: 'remote'}]"
|
||||||
|
@update:model-value="updateType($event)"
|
||||||
|
v-model="rule_set.type">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="rule_set.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('ruleset.format')"
|
||||||
|
:items="['source', 'binary']"
|
||||||
|
v-model="rule_set.format">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="rule_set.type == 'local'">
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field v-model="rule_set.path" :label="$t('transport.path')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-else>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field v-model="rule_set.url" label="URL" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('objects.outbound')"
|
||||||
|
:items="outTags"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete rule_set.download_detour"
|
||||||
|
v-model="rule_set.download_detour">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model.number="update_intervals" :suffix="$t('date.d')" type="number" min="0" :label="$t('ruleset.interval')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</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 RandomUtil from '@/plugins/randomUtil'
|
||||||
|
import { ruleset } from '@/types/rules'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'index', 'outTags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: "add",
|
||||||
|
loading: false,
|
||||||
|
rule_set: <ruleset>{},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData() {
|
||||||
|
if (this.$props.index != -1) {
|
||||||
|
this.title = "edit"
|
||||||
|
this.rule_set = <ruleset>JSON.parse(this.$props.data)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.title = "add"
|
||||||
|
this.rule_set = <ruleset>{type: 'local', tag: "rs-" + RandomUtil.randomSeq(3), format: 'binary'}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateType(t:string) {
|
||||||
|
if (t == 'local') {
|
||||||
|
delete this.rule_set.url
|
||||||
|
delete this.rule_set.download_detour
|
||||||
|
delete this.rule_set.update_interval
|
||||||
|
} else {
|
||||||
|
delete this.rule_set.path
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
this.$emit('save', this.rule_set)
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
update_intervals: {
|
||||||
|
get() { return this.rule_set.update_interval != undefined ? parseInt(this.rule_set.update_interval.replace('d','')) : 0 },
|
||||||
|
set(v:number) { this.rule_set.update_interval = v>0 ? v + 'd' : undefined }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async loadData(limit: number) {
|
async loadData(limit: number) {
|
||||||
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: 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"
|
||||||
|
|||||||
@@ -0,0 +1,443 @@
|
|||||||
|
<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>
|
||||||
|
<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-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" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.cert')"
|
||||||
|
hide-details
|
||||||
|
v-model="certText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-textarea
|
||||||
|
:label="$t('tls.key')"
|
||||||
|
hide-details
|
||||||
|
v-model="keyText">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="outTls.utls != undefined">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
label="Fingerprint"
|
||||||
|
:items="fingerprints"
|
||||||
|
v-model="outTls.utls.fingerprint">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<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-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.privKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="inTls.reality.private_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('tls.pubKey')"
|
||||||
|
hide-details
|
||||||
|
v-model="outTls.reality.public_key">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="Short IDs"
|
||||||
|
hide-details
|
||||||
|
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-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>{{ $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>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionFP" color="primary" label="UTLS" 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>
|
||||||
|
</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/Acme.vue'
|
||||||
|
import EchVue from '@/components/Ech.vue'
|
||||||
|
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 } }, 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
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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>
|
||||||
+236
-6
@@ -11,13 +11,23 @@ export default {
|
|||||||
unlimited: "infinite",
|
unlimited: "infinite",
|
||||||
remained: "Remained",
|
remained: "Remained",
|
||||||
type: "Type",
|
type: "Type",
|
||||||
|
protocol: "Protocol",
|
||||||
submit: "Submit",
|
submit: "Submit",
|
||||||
reset: "Reset",
|
reset: "Reset",
|
||||||
now: "Now",
|
now: "Now",
|
||||||
network: "Network",
|
network: "Network",
|
||||||
copyToClipboard: "Copy to clipboard",
|
copyToClipboard: "Copy to clipboard",
|
||||||
noData: "No data!",
|
noData: "No data!",
|
||||||
|
invalidLogin: "Invalid Login!",
|
||||||
online: "Online",
|
online: "Online",
|
||||||
|
version: "Version",
|
||||||
|
email: "Email",
|
||||||
|
commaSeparated: "(comma separated)",
|
||||||
|
count: "Count",
|
||||||
|
error: {
|
||||||
|
dplData: "Duplicate Data",
|
||||||
|
core: "Sing-Box Error",
|
||||||
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "Login",
|
login: "Login",
|
||||||
home: "Home",
|
home: "Home",
|
||||||
@@ -25,7 +35,9 @@ export default {
|
|||||||
outbounds: "Outbounds",
|
outbounds: "Outbounds",
|
||||||
clients: "Clients",
|
clients: "Clients",
|
||||||
rules: "Rules",
|
rules: "Rules",
|
||||||
|
tls: "TLS Settings",
|
||||||
basics: "Basics",
|
basics: "Basics",
|
||||||
|
admins: "Admins",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
@@ -61,18 +73,33 @@ export default {
|
|||||||
outbound: "Outbound",
|
outbound: "Outbound",
|
||||||
rule: "Rule",
|
rule: "Rule",
|
||||||
user: "User",
|
user: "User",
|
||||||
|
tag: "Tag",
|
||||||
|
listen: "Listen",
|
||||||
|
dial: "Dial",
|
||||||
|
tls: "TLS",
|
||||||
|
multiplex: "Multiplex",
|
||||||
|
transport: "Transport",
|
||||||
|
method: "Method",
|
||||||
|
headers: "Headers",
|
||||||
|
key: "Key",
|
||||||
|
value: "Value",
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
action: "Action",
|
action: "Action",
|
||||||
add: "Add",
|
add: "Add",
|
||||||
|
new: "Add",
|
||||||
edit: "Edit",
|
edit: "Edit",
|
||||||
del: "Delete",
|
del: "Delete",
|
||||||
save: "Save",
|
save: "Save",
|
||||||
update: "Update",
|
update: "Update",
|
||||||
|
submit: "Submit",
|
||||||
|
set: "Set",
|
||||||
|
disable: "Disable",
|
||||||
close: "Close",
|
close: "Close",
|
||||||
restartApp: "Restart App",
|
restartApp: "Restart App",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
|
title: "Login",
|
||||||
username: "Username",
|
username: "Username",
|
||||||
unRules: "Username can not be empty",
|
unRules: "Username can not be empty",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
@@ -81,15 +108,31 @@ export default {
|
|||||||
menu: {
|
menu: {
|
||||||
logout: "Logout",
|
logout: "Logout",
|
||||||
},
|
},
|
||||||
|
admin: {
|
||||||
|
changeCred: "Change credentials",
|
||||||
|
oldPass: "Current Password",
|
||||||
|
newUname: "New Username",
|
||||||
|
newPass: "New Password",
|
||||||
|
lastLogin: "Last login",
|
||||||
|
date: "Date",
|
||||||
|
time: "Time",
|
||||||
|
changes: "Changes",
|
||||||
|
actor: "Actor",
|
||||||
|
key: "Key",
|
||||||
|
action: "Action",
|
||||||
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "Interface",
|
interface: "Interface",
|
||||||
sub: "Subscription",
|
sub: "Subscription",
|
||||||
addr: "Address",
|
addr: "Address",
|
||||||
port: "Port",
|
port: "Port",
|
||||||
|
webPath: "Base URI",
|
||||||
domain: "Domain",
|
domain: "Domain",
|
||||||
sslKey: "SSL Key Path",
|
sslKey: "SSL Key Path",
|
||||||
sslCert: "SSL Certificate Path",
|
sslCert: "SSL Certificate Path",
|
||||||
|
webUri: "Panel URI",
|
||||||
sessionAge: "Session Maximum Age",
|
sessionAge: "Session Maximum Age",
|
||||||
|
trafficAge: "Traffic Maximum Age",
|
||||||
timeLoc: "Timezone Location",
|
timeLoc: "Timezone Location",
|
||||||
subEncode: "Enable Encoding",
|
subEncode: "Enable Encoding",
|
||||||
subInfo: "Enable Client Info",
|
subInfo: "Enable Client Info",
|
||||||
@@ -99,6 +142,7 @@ export default {
|
|||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "Name",
|
name: "Name",
|
||||||
|
desc: "Description",
|
||||||
inboundTags: "Inbound Tags",
|
inboundTags: "Inbound Tags",
|
||||||
basics: "Basics",
|
basics: "Basics",
|
||||||
config: "Config",
|
config: "Config",
|
||||||
@@ -106,21 +150,179 @@ export default {
|
|||||||
external: "External Link",
|
external: "External Link",
|
||||||
sub: "External Subscription",
|
sub: "External Subscription",
|
||||||
},
|
},
|
||||||
|
types: {
|
||||||
|
un: "Username",
|
||||||
|
pw: "Password",
|
||||||
|
direct: {
|
||||||
|
overrideAddr: "Override Address",
|
||||||
|
overridePort: "Override Port",
|
||||||
|
},
|
||||||
|
hy: {
|
||||||
|
obfs: "Obfuscated Password",
|
||||||
|
auth: "Authentication Password",
|
||||||
|
hyOptions: "Hysteria Options",
|
||||||
|
hy2Options: "Hysteria2 Options",
|
||||||
|
ignoreBw: "Ignore Client Bandwidth",
|
||||||
|
},
|
||||||
|
shdwTls: {
|
||||||
|
hs: "Handshake Server",
|
||||||
|
addHS: "Add Handshake Server",
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
passphrase: "Passphrase",
|
||||||
|
hostKey: "Host Keys",
|
||||||
|
algorithm: "Key Algorithms",
|
||||||
|
clientVer: "Client Version",
|
||||||
|
options: "SSH Options",
|
||||||
|
},
|
||||||
|
tor: {
|
||||||
|
execPath: "Executable File Path",
|
||||||
|
dataDir: "Data Directory",
|
||||||
|
extArgs: "Extra Args",
|
||||||
|
},
|
||||||
|
tuic: {
|
||||||
|
congControl: "Congestion Control",
|
||||||
|
authTimeout: "Authentication Timeout",
|
||||||
|
hb: "Heartbeat",
|
||||||
|
},
|
||||||
|
vless: {
|
||||||
|
flow: "Flow",
|
||||||
|
udpEnc: "UDP Packet Encoding",
|
||||||
|
},
|
||||||
|
vmess: {
|
||||||
|
security: "Security",
|
||||||
|
globalPadding: "Global Padding",
|
||||||
|
authLen: "Encryptrd Length",
|
||||||
|
},
|
||||||
|
wg: {
|
||||||
|
privKey: "Private Key",
|
||||||
|
pubKey: "Peer Public Key",
|
||||||
|
psk: "Pre-Shared Key",
|
||||||
|
localIp: "Local IPs",
|
||||||
|
worker: "Workers",
|
||||||
|
ifName: "Interface Name",
|
||||||
|
sysIf: "System Interface",
|
||||||
|
gso: "Segmentation Offload",
|
||||||
|
options: "Wireguard Options",
|
||||||
|
multiPeer: "Multi Peer",
|
||||||
|
allowedIp: "Allowed IPs",
|
||||||
|
peer: "Peer",
|
||||||
|
peers: "Peers",
|
||||||
|
},
|
||||||
|
lb: {
|
||||||
|
defaultOut: "Default Outbound",
|
||||||
|
interruptConn: "Interrupt exist connections",
|
||||||
|
testUrl: "Test URL",
|
||||||
|
interval: "Interval",
|
||||||
|
tolerance: "Tolerance",
|
||||||
|
urlTestOptions: "URLTest Options"
|
||||||
|
}
|
||||||
|
},
|
||||||
in: {
|
in: {
|
||||||
tag: "Tag",
|
|
||||||
addr: "Address",
|
addr: "Address",
|
||||||
port: "Port",
|
port: "Port",
|
||||||
sniffing: "Sniffing",
|
|
||||||
tls: "TLS",
|
|
||||||
clients: "Enable Clients",
|
clients: "Enable Clients",
|
||||||
multiplex: "Multiplex",
|
ssMethod: "Method",
|
||||||
transport: "Transport",
|
},
|
||||||
|
listen: {
|
||||||
|
sniffing: "Sniffing",
|
||||||
|
sniffingTimeout: "Sniffing Timeout",
|
||||||
|
sniffingOverride: "Override Destation",
|
||||||
|
options: "Listen Options",
|
||||||
|
tcpOptions: "TCP Options",
|
||||||
|
udpOptions: "UDP Options",
|
||||||
|
detour: "Detour",
|
||||||
|
detourText: "Forward to inbound",
|
||||||
|
domainStrategy: "Domain Strategy",
|
||||||
|
},
|
||||||
|
dial: {
|
||||||
|
bindIf: "Bind to Network Interface",
|
||||||
|
bindIp4: "Bind to IPv4",
|
||||||
|
bindIp6: "Bind to IPv6",
|
||||||
|
reuseAddr: "Reuse Listener Address",
|
||||||
|
connTimeout: "Connection Timeout",
|
||||||
|
fbTimeout: "Fallback Timeout",
|
||||||
|
options: "Dial Options",
|
||||||
|
detourText: "Forward to outbound",
|
||||||
},
|
},
|
||||||
transport: {
|
transport: {
|
||||||
enable: "Enable Transport",
|
enable: "Enable Transport",
|
||||||
host: "Host",
|
host: "Host",
|
||||||
hosts: "Hosts",
|
hosts: "Hosts",
|
||||||
path: "Path",
|
path: "Path",
|
||||||
|
httpMethod: "Request Method",
|
||||||
|
idleTimeout: "Idle Timeout",
|
||||||
|
pingTimeout: "Ping Timeout",
|
||||||
|
grpcServiceName: "Service Name",
|
||||||
|
grpcPws: "Permit Without Stream",
|
||||||
|
},
|
||||||
|
mux: {
|
||||||
|
enable: "Enable Multiplex",
|
||||||
|
maxConn: "Max Connections",
|
||||||
|
minStr: "Min Streams",
|
||||||
|
maxStr: "Max Streams",
|
||||||
|
padding: "Only padding",
|
||||||
|
enableBrutal: "Enable Brutal",
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
addr: "Server Address",
|
||||||
|
port: "Server Port",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
add: "Add Rule",
|
||||||
|
simple: "Simple",
|
||||||
|
logical: "Logical",
|
||||||
|
mode: "Mode",
|
||||||
|
invert: "Invert",
|
||||||
|
ipVer: "IP Version",
|
||||||
|
domain: "Domains",
|
||||||
|
domainSufix: "Domain Suffixes",
|
||||||
|
domainKw: "Domain Keywords",
|
||||||
|
domainRgx: "Domain Regexes",
|
||||||
|
ip: "IP CIDRs",
|
||||||
|
privateIp: "Invalid IP Ranges",
|
||||||
|
port: "Ports",
|
||||||
|
portRange: "Port Ranges",
|
||||||
|
srcCidr: "Source IP CIDRs",
|
||||||
|
srcPrivateIp: "Invalid Source IPs",
|
||||||
|
srcPort: "Source Ports",
|
||||||
|
srcPortRange: "Source Port Ranges",
|
||||||
|
ruleset: "Rulesets",
|
||||||
|
rulesetMatchSrc: "Ruleset IPcidr Match Source",
|
||||||
|
options: "Rule Options",
|
||||||
|
domainRules: "Domain/IP",
|
||||||
|
srcIpRules: "Source IP",
|
||||||
|
srcPortRules: "Source Port",
|
||||||
|
},
|
||||||
|
ruleset: {
|
||||||
|
add: "Add Ruleset",
|
||||||
|
format: "Data Format",
|
||||||
|
interval: "Update Intervals",
|
||||||
|
remote: "Remote",
|
||||||
|
local: "Local",
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
log: {
|
||||||
|
title: "Logs",
|
||||||
|
level: "Level",
|
||||||
|
output: "Output",
|
||||||
|
timestamp: "Enable Timestamp",
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
final: "Final",
|
||||||
|
server: "Server",
|
||||||
|
firstServer: "First Server",
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
title: "Routing",
|
||||||
|
defaultOut: "Default Outbound",
|
||||||
|
defaultIf: "Default NIC",
|
||||||
|
defaultRm: "Default Routing Mark",
|
||||||
|
autoBind: "Auto Bind NIC",
|
||||||
|
},
|
||||||
|
exp: {
|
||||||
|
storeFakeIp: "Store Fake IP",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
enable: "Enable TLS",
|
enable: "Enable TLS",
|
||||||
@@ -130,6 +332,30 @@ export default {
|
|||||||
keyPath: "Key File Path",
|
keyPath: "Key File Path",
|
||||||
cert: "Certificate",
|
cert: "Certificate",
|
||||||
key: "Key",
|
key: "Key",
|
||||||
|
options: "TLS Options",
|
||||||
|
minVer: "Minimum Version",
|
||||||
|
maxVer: "Maximum Version",
|
||||||
|
cs: "Cipher suits",
|
||||||
|
privKey: "Private Key",
|
||||||
|
pubKey: "Public Key",
|
||||||
|
disableSni: "Disable SNI",
|
||||||
|
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",
|
||||||
@@ -148,6 +374,9 @@ export default {
|
|||||||
Kp: "Kp",
|
Kp: "Kp",
|
||||||
Mp: "Mp",
|
Mp: "Mp",
|
||||||
Gb: "Gb",
|
Gb: "Gb",
|
||||||
|
bps: "bps",
|
||||||
|
Kbps: "Kbps",
|
||||||
|
Mbps: "Mbps",
|
||||||
},
|
},
|
||||||
date: {
|
date: {
|
||||||
expiry: "Expiry",
|
expiry: "Expiry",
|
||||||
@@ -156,5 +385,6 @@ export default {
|
|||||||
h: "h",
|
h: "h",
|
||||||
m: "m",
|
m: "m",
|
||||||
s: "s",
|
s: "s",
|
||||||
}
|
ms: "ms",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+234
-5
@@ -11,13 +11,23 @@ export default {
|
|||||||
unlimited: "نامحدود",
|
unlimited: "نامحدود",
|
||||||
remained: "باقیمانده",
|
remained: "باقیمانده",
|
||||||
type: "مدل",
|
type: "مدل",
|
||||||
|
protocol: "پروتکل",
|
||||||
submit: "تایید",
|
submit: "تایید",
|
||||||
reset: "ریست",
|
reset: "ریست",
|
||||||
now: "اکنون",
|
now: "اکنون",
|
||||||
network: "شبکه",
|
network: "شبکه",
|
||||||
copyToClipboard: "کپی در حافظه",
|
copyToClipboard: "کپی در حافظه",
|
||||||
noData: "بدون داده!",
|
noData: "بدون داده!",
|
||||||
|
invalidLogin: "ورود نامعتبر!",
|
||||||
online: "آنلاین",
|
online: "آنلاین",
|
||||||
|
version: "نسخه",
|
||||||
|
email: "ایمیل",
|
||||||
|
commaSeparated: "(جداشده با کاما)",
|
||||||
|
count: "تعداد",
|
||||||
|
error: {
|
||||||
|
dplData: "داده تکراری",
|
||||||
|
core: "خطا در سینگباکس",
|
||||||
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "ورود",
|
login: "ورود",
|
||||||
home: "خانه",
|
home: "خانه",
|
||||||
@@ -25,7 +35,9 @@ export default {
|
|||||||
outbounds: "خروجیها",
|
outbounds: "خروجیها",
|
||||||
clients: "کاربران",
|
clients: "کاربران",
|
||||||
rules: "قوانین",
|
rules: "قوانین",
|
||||||
|
tls: "رمزنگاریها",
|
||||||
basics: "ترازها",
|
basics: "ترازها",
|
||||||
|
admins: "ادمینها",
|
||||||
settings: "پیکربندی",
|
settings: "پیکربندی",
|
||||||
},
|
},
|
||||||
main: {
|
main: {
|
||||||
@@ -61,18 +73,32 @@ export default {
|
|||||||
outbound: "خروجی",
|
outbound: "خروجی",
|
||||||
rule: "قانون",
|
rule: "قانون",
|
||||||
user: "کاربر",
|
user: "کاربر",
|
||||||
|
tag: "برچسب",
|
||||||
|
listen: "گوشدادن",
|
||||||
|
dial: "تماس",
|
||||||
|
tls: "رمزنگاری",
|
||||||
|
multiplex: "تسهیم",
|
||||||
|
transport: "انتقال",
|
||||||
|
headers: "سربرگها",
|
||||||
|
key: "نام",
|
||||||
|
value: "مقدار",
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
action: "فرمان",
|
action: "فرمان",
|
||||||
add: "ایجاد",
|
add: "ایجاد",
|
||||||
|
new: "ایجاد",
|
||||||
edit: "ویرایش",
|
edit: "ویرایش",
|
||||||
del: "حذف",
|
del: "حذف",
|
||||||
save: "ذخیره",
|
save: "ذخیره",
|
||||||
update: "بروزرسانی",
|
update: "بروزرسانی",
|
||||||
|
submit: "ارسال",
|
||||||
|
set: "تنظیم",
|
||||||
|
disable: "غیرفعال",
|
||||||
close: "بستن",
|
close: "بستن",
|
||||||
restartApp: "ریستارت پنل",
|
restartApp: "ریستارت پنل",
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
|
title: "ورود",
|
||||||
username: "نام کاربری",
|
username: "نام کاربری",
|
||||||
unRules: "نام کاربری نمیتواند خالی باشد",
|
unRules: "نام کاربری نمیتواند خالی باشد",
|
||||||
password: "کلمه عبور",
|
password: "کلمه عبور",
|
||||||
@@ -81,15 +107,31 @@ export default {
|
|||||||
menu: {
|
menu: {
|
||||||
logout: "خروج",
|
logout: "خروج",
|
||||||
},
|
},
|
||||||
|
admin: {
|
||||||
|
changeCred: "ویرایش دادهها",
|
||||||
|
oldPass: "رمز کنونی",
|
||||||
|
newUname: "نام کاربری جدید",
|
||||||
|
newPass: "رمز جدید",
|
||||||
|
lastLogin: "آخرین ورود",
|
||||||
|
date: "تاریخ",
|
||||||
|
time: "ساعت",
|
||||||
|
changes: "تغییرات",
|
||||||
|
actor: "مجری",
|
||||||
|
key: "کلید",
|
||||||
|
action: "عمل",
|
||||||
|
},
|
||||||
setting: {
|
setting: {
|
||||||
interface: "نما",
|
interface: "نما",
|
||||||
sub: "سابسکریپشن",
|
sub: "سابسکریپشن",
|
||||||
addr: "آدرس",
|
addr: "آدرس",
|
||||||
port: "پورت",
|
port: "پورت",
|
||||||
|
webPath: "مسیر پایه",
|
||||||
domain: "دامنه",
|
domain: "دامنه",
|
||||||
sslKey: "مسیر فایل کلید",
|
sslKey: "مسیر فایل کلید",
|
||||||
sslCert: "مسیر فایل گواهی",
|
sslCert: "مسیر فایل گواهی",
|
||||||
|
webUri: "آدرس نهایی پنل",
|
||||||
sessionAge: "بیشینه زمان لاگین ماندن",
|
sessionAge: "بیشینه زمان لاگین ماندن",
|
||||||
|
trafficAge: "بیشینه زمان ذخیره ترافیک",
|
||||||
timeLoc: "منطقه زمانی",
|
timeLoc: "منطقه زمانی",
|
||||||
subEncode: "رمزگذاری",
|
subEncode: "رمزگذاری",
|
||||||
subInfo: "نمایش اطلاعات کاربر",
|
subInfo: "نمایش اطلاعات کاربر",
|
||||||
@@ -99,6 +141,7 @@ export default {
|
|||||||
},
|
},
|
||||||
client: {
|
client: {
|
||||||
name: "نام",
|
name: "نام",
|
||||||
|
desc: "شرح",
|
||||||
inboundTags: "برچسبهای ورودی",
|
inboundTags: "برچسبهای ورودی",
|
||||||
basics: "پایه",
|
basics: "پایه",
|
||||||
config: "تنظیم",
|
config: "تنظیم",
|
||||||
@@ -106,21 +149,179 @@ export default {
|
|||||||
external: "لینک خارجی",
|
external: "لینک خارجی",
|
||||||
sub: "سابسکریپشن خارجی",
|
sub: "سابسکریپشن خارجی",
|
||||||
},
|
},
|
||||||
|
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: "ضربان قلب",
|
||||||
|
},
|
||||||
|
vless: {
|
||||||
|
flow: "جریان",
|
||||||
|
udpEnc: "کدگذاری بسته UDP",
|
||||||
|
},
|
||||||
|
vmess: {
|
||||||
|
security: "امنیت",
|
||||||
|
globalPadding: "لایه بندی کلی",
|
||||||
|
authLen: "رمزگذاری اندازه بسته",
|
||||||
|
},
|
||||||
|
wg: {
|
||||||
|
privKey: "کلید خصوصی",
|
||||||
|
pubKey: "کلید عمومی همتا",
|
||||||
|
psk: "کلید مشترک",
|
||||||
|
localIp: "آدرسهای محلی",
|
||||||
|
worker: "عملگرها",
|
||||||
|
ifName: "نام اینترفیس",
|
||||||
|
sysIf: "استفاده از اینترفیس سیستم",
|
||||||
|
gso: "بارگذاری تقسیمبندی عمومی",
|
||||||
|
options: "گزینههای Wireguard",
|
||||||
|
multiPeer: "چند همتایی",
|
||||||
|
allowedIp: "آدرسهای مجاز",
|
||||||
|
peer: "همتا",
|
||||||
|
peers: "همتاها",
|
||||||
|
},
|
||||||
|
lb: {
|
||||||
|
defaultOut: "خروجی پیشفرض",
|
||||||
|
interruptConn: "قطع ارتباط موجود",
|
||||||
|
testUrl: "URL تست",
|
||||||
|
interval: "فاصله زمانی",
|
||||||
|
tolerance: "تحمل",
|
||||||
|
urlTestOptions: "گزینههای URLTest"
|
||||||
|
}
|
||||||
|
},
|
||||||
in: {
|
in: {
|
||||||
tag: "برچسب",
|
|
||||||
addr: "آدرس",
|
addr: "آدرس",
|
||||||
port: "پورت",
|
port: "پورت",
|
||||||
sniffing: "مبدل آدرس",
|
|
||||||
tls: "رمزنگاری",
|
|
||||||
clients: "فعالسازی کاربران",
|
clients: "فعالسازی کاربران",
|
||||||
multiplex: "تسهیم",
|
ssMethod: "روش",
|
||||||
transport: "انتقال",
|
},
|
||||||
|
listen: {
|
||||||
|
sniffing: "شنود آدرس",
|
||||||
|
sniffingTimeout: "مهلت شنود آدرس",
|
||||||
|
sniffingOverride: "جایگزینی مقصد",
|
||||||
|
options: "گزینههای گوشدادن",
|
||||||
|
tcpOptions: "گزینههای TCP",
|
||||||
|
udpOptions: "گزینههای UDP",
|
||||||
|
detour: "انحراف مسیر",
|
||||||
|
detourText: "ارسال به ورودی دیگر",
|
||||||
|
domainStrategy: "استراتژی دامنه",
|
||||||
|
},
|
||||||
|
dial: {
|
||||||
|
bindIf: "اتصال به کارت شبکه",
|
||||||
|
bindIp4: "اتصال به IPv4",
|
||||||
|
bindIp6: "اتصال به IPv6",
|
||||||
|
reuseAddr: "استفاده مجدد از آدرس",
|
||||||
|
connTimeout: "مهلت ارتباط",
|
||||||
|
fbTimeout: "مهلت فالبک",
|
||||||
|
options: "گزینههای تماس",
|
||||||
|
detourText: "ارسال به خروجی دیگر",
|
||||||
},
|
},
|
||||||
transport: {
|
transport: {
|
||||||
enable: "فعالسازی انتقال",
|
enable: "فعالسازی انتقال",
|
||||||
host: "دامنه",
|
host: "دامنه",
|
||||||
hosts: "دامنهها",
|
hosts: "دامنهها",
|
||||||
path: "مسیر",
|
path: "مسیر",
|
||||||
|
httpMethod: "متد درخواست",
|
||||||
|
idleTimeout: "مهلت بیکاری",
|
||||||
|
pingTimeout: "مهلت پینگ",
|
||||||
|
grpcServiceName: "نام سرویس",
|
||||||
|
grpcPws: "حفظ ارتباط بدون دیتا",
|
||||||
|
},
|
||||||
|
mux: {
|
||||||
|
enable: "فعالسازی تسهیم",
|
||||||
|
maxConn: "بیشینه ارتباطات",
|
||||||
|
minStr: "کمینه استریم",
|
||||||
|
maxStr: "بیشینه استریم",
|
||||||
|
padding: "فقط با پدینگ",
|
||||||
|
enableBrutal: "فعالسازی شدت",
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
addr: "آدرس سرور",
|
||||||
|
port: "پورت سرور",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
add: "ایجاد قانون",
|
||||||
|
simple: "ساده",
|
||||||
|
logical: "منطقی",
|
||||||
|
mode: "حالت",
|
||||||
|
invert: "برعکس",
|
||||||
|
ipVer: "نسخه IP",
|
||||||
|
domain: "دامنهها",
|
||||||
|
domainSufix: "پسوندهای دامنه",
|
||||||
|
domainKw: "کلمات کلیدی دامنه",
|
||||||
|
domainRgx: "رجکس دامنه",
|
||||||
|
ip: "محدودههای IP",
|
||||||
|
privateIp: "آدرس های IP نامعتبر",
|
||||||
|
port: "پورتها",
|
||||||
|
portRange: "محدودههای پورت",
|
||||||
|
srcIp: "محدودههای آدرس IP مبدا",
|
||||||
|
srcPrivateIp: "آدرسهای IP مبدا نامعتبر",
|
||||||
|
srcPort: "پورتهای مبدا",
|
||||||
|
srcPortRange: "محدوده پورتهای منبع",
|
||||||
|
ruleset: "مجموعهها",
|
||||||
|
rulesetMatchSrc: "تطابق آدرسهای مبدا با مجموعه قوانین",
|
||||||
|
options: "گزینههای قوانین",
|
||||||
|
domainRules: "دامنه/آدرس",
|
||||||
|
srcIpRules: "آدرس مبدا",
|
||||||
|
srcPortRules: "پورت مبدا",
|
||||||
|
},
|
||||||
|
ruleset: {
|
||||||
|
add: "ایجاد مجموعه",
|
||||||
|
format: "فرمت دادهها",
|
||||||
|
interval: "بازه بروزرسانیها",
|
||||||
|
remote: "راه دور",
|
||||||
|
local: "محلی",
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
log: {
|
||||||
|
title: "گزارشها",
|
||||||
|
level: "سطح",
|
||||||
|
output: "خروجی",
|
||||||
|
timestamp: "فعالسازی ثبت زمان",
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
final: "سرور نهایی",
|
||||||
|
server: "سرور",
|
||||||
|
firstServer: "سرور نخست",
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
title: "مسیریابی",
|
||||||
|
defaultOut: "خروجی پیشفرض",
|
||||||
|
defaultIf: "کارت شبکه پیشفرض",
|
||||||
|
defaultRm: "Routing Mark پیشفرض",
|
||||||
|
autoBind: "انتخاب اتوماتیک کارت شبکه",
|
||||||
|
},
|
||||||
|
exp: {
|
||||||
|
storeFakeIp: "ذخیره آدرسهای نامعتبر",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tls: {
|
tls: {
|
||||||
enable: "فعالسازی رمزنگاری",
|
enable: "فعالسازی رمزنگاری",
|
||||||
@@ -130,6 +331,30 @@ export default {
|
|||||||
keyPath: "مسیر فایل کلید",
|
keyPath: "مسیر فایل کلید",
|
||||||
cert: "گواهی",
|
cert: "گواهی",
|
||||||
key: "کلید",
|
key: "کلید",
|
||||||
|
options: "گزینههای رمزنگاری",
|
||||||
|
minVer: "کمینه نسخه",
|
||||||
|
maxVer: "بیشینه نسخه",
|
||||||
|
cs: "مدلهای رمزنگاری",
|
||||||
|
privKey: "کلید خصوصی",
|
||||||
|
pubKey: "کلید عمومی",
|
||||||
|
disableSni: "غیرفعالسازی SNI",
|
||||||
|
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: "آپلود",
|
||||||
@@ -148,6 +373,9 @@ export default {
|
|||||||
Kp: "کپ",
|
Kp: "کپ",
|
||||||
Mp: "مپ",
|
Mp: "مپ",
|
||||||
Gp: "گپ",
|
Gp: "گپ",
|
||||||
|
bps: "ب/ث",
|
||||||
|
Kbps: "کب/ث",
|
||||||
|
Mbps: "مب/ث",
|
||||||
},
|
},
|
||||||
date: {
|
date: {
|
||||||
expiry: "انقضا",
|
expiry: "انقضا",
|
||||||
@@ -156,5 +384,6 @@ export default {
|
|||||||
h: "س",
|
h: "س",
|
||||||
m: "د",
|
m: "د",
|
||||||
s: "ث",
|
s: "ث",
|
||||||
|
ms: "مث",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,27 @@
|
|||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import en from './en'
|
import en from './en'
|
||||||
import fa from './fa'
|
import fa from './fa'
|
||||||
|
import vi from './vi'
|
||||||
|
import zhcn from './zhcn'
|
||||||
|
import zhtw from './zhtw'
|
||||||
|
|
||||||
export const i18n = createI18n({
|
export const i18n = createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: localStorage.getItem("locale") ?? 'en',
|
locale: localStorage.getItem("locale") ?? 'en',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
messages: {
|
messages: {
|
||||||
en,
|
en: en,
|
||||||
fa,
|
fa: fa,
|
||||||
|
vi: vi,
|
||||||
|
zhcn: zhcn,
|
||||||
|
zhtw: zhtw
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const languages = [
|
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: '简体中文', value: 'zhcn' },
|
||||||
|
{ title: '繁體中文', value: 'zhtw' },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
export default {
|
||||||
|
message: "Chào mừng OHB",
|
||||||
|
success: "Thành công",
|
||||||
|
failed: "Thất bại",
|
||||||
|
enable: "Kích hoạt",
|
||||||
|
disable: "Vô hiệu hóa",
|
||||||
|
loading: "Đang tải...",
|
||||||
|
confirm: "Bạn chắc chắn chứ?",
|
||||||
|
yes: "có",
|
||||||
|
no: "không",
|
||||||
|
unlimited: "vô hạn",
|
||||||
|
remained: "Còn lại",
|
||||||
|
type: "Loại",
|
||||||
|
protocol: "Giao thức",
|
||||||
|
submit: "Gửi",
|
||||||
|
reset: "Đặt lại",
|
||||||
|
now: "Hiện tại",
|
||||||
|
network: "Mạng",
|
||||||
|
copyToClipboard: "Sao chép vào clipboard",
|
||||||
|
noData: "Không có dữ liệu!",
|
||||||
|
invalidLogin: "Đăng nhập không hợp lệ!",
|
||||||
|
online: "Trực tuyến",
|
||||||
|
version: "Phiên bản",
|
||||||
|
email: "Email",
|
||||||
|
commaSeparated: "(được phân tách bằng dấu phẩy)",
|
||||||
|
count: "Đếm",
|
||||||
|
error: {
|
||||||
|
dplData: "Dữ liệu trùng lặp",
|
||||||
|
core: "Lỗi Sing-Box",
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
login: "Đăng nhập",
|
||||||
|
home: "Trang chủ",
|
||||||
|
inbounds: "Đầu Vào",
|
||||||
|
outbounds: "Đầu ra",
|
||||||
|
clients: "Khách hàng",
|
||||||
|
rules: "Quy tắc",
|
||||||
|
tls: "Cài đặt TLS",
|
||||||
|
basics: "Cơ bản",
|
||||||
|
admins: "Quản trị viên",
|
||||||
|
settings: "Cài đặt",
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
tiles: "OHB",
|
||||||
|
gauges: "Đồng hồ đo",
|
||||||
|
charts: "Biểu đồ",
|
||||||
|
infos: "Thông tin",
|
||||||
|
gauge: {
|
||||||
|
cpu: "Đồng hồ CPU",
|
||||||
|
mem: "Đồng hồ RAM",
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
cpu: "Máy theo dõi CPU",
|
||||||
|
mem: "Máy theo dõi RAM",
|
||||||
|
net: "Băng thông mạng",
|
||||||
|
pnet: "Gói mạng",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
sys: "Thông tin hệ thống",
|
||||||
|
sbd: "Thông tin Sing-Box",
|
||||||
|
host: "Máy chủ",
|
||||||
|
cpu: "CPU",
|
||||||
|
core: "Nhân",
|
||||||
|
uptime: "Thời gian hoạt động",
|
||||||
|
threads: "Luồng",
|
||||||
|
memory: "Bộ nhớ",
|
||||||
|
running: "Đang chạy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
objects: {
|
||||||
|
inbound: "Đầu Vào",
|
||||||
|
client: "Máy Khách hàng",
|
||||||
|
outbound: "Đầu Ra",
|
||||||
|
rule: "Quy tắc",
|
||||||
|
user: "Người dùng",
|
||||||
|
tag: "Thẻ",
|
||||||
|
listen: "Nghe",
|
||||||
|
dial: "Quay số",
|
||||||
|
tls: "TLS",
|
||||||
|
multiplex: "Ghép đa truyền thông ",
|
||||||
|
transport: "Giao thông",
|
||||||
|
method: "Phương pháp",
|
||||||
|
headers: "Tiêu đề",
|
||||||
|
key: "Chìa khóa",
|
||||||
|
value: "Giá trị",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
action: "Hành động",
|
||||||
|
add: "Thêm",
|
||||||
|
new: "Thêm",
|
||||||
|
edit: "Chỉnh sửa",
|
||||||
|
del: "Xóa",
|
||||||
|
save: "Lưu",
|
||||||
|
update: "Cập nhật",
|
||||||
|
submit: "Gửi",
|
||||||
|
set: "Đặt",
|
||||||
|
disable: "Vô hiệu hóa",
|
||||||
|
close: "Đóng",
|
||||||
|
restartApp: "Khởi động lại ứng dụng",
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
title: "Đăng nhập",
|
||||||
|
username: "Tên người dùng",
|
||||||
|
unRules: "Tên người dùng không thể trống",
|
||||||
|
password: "Mật khẩu",
|
||||||
|
pwRules: "Mật khẩu không thể trống",
|
||||||
|
},
|
||||||
|
menu: {
|
||||||
|
logout: "Đăng xuất",
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
changeCred: "Thay đổi thông tin đăng nhập",
|
||||||
|
oldPass: "Mật khẩu hiện tại",
|
||||||
|
newUname: "Tên người dùng mới",
|
||||||
|
newPass: "Mật khẩu mới",
|
||||||
|
lastLogin: "Lân đăng nhập cuôi",
|
||||||
|
date: "Ngày",
|
||||||
|
time: "Thời gian",
|
||||||
|
changes: "Thay đổi",
|
||||||
|
actor: "Diễn viên",
|
||||||
|
key: "Khóa",
|
||||||
|
action: "Hành động",
|
||||||
|
},
|
||||||
|
setting: {
|
||||||
|
interface: "Giao diện",
|
||||||
|
sub: "Đăng ký",
|
||||||
|
addr: "Địa chỉ",
|
||||||
|
port: "Cổng",
|
||||||
|
webPath: "Đường dẫn gốc",
|
||||||
|
domain: "Miền",
|
||||||
|
sslKey: "Đường dẫn khóa SSL",
|
||||||
|
sslCert: "Đường dẫn chứng chỉ SSL",
|
||||||
|
webUri: "URI bảng điều khiển",
|
||||||
|
sessionAge: "Tuổi tối đa của phiên",
|
||||||
|
trafficAge: "Tuổi lưu thông tối đa",
|
||||||
|
timeLoc: "Vị trí múi giờ",
|
||||||
|
subEncode: "Kích hoạt mã hóa",
|
||||||
|
subInfo: "Kích hoạt thông tin khách hàng",
|
||||||
|
path: "Đường dẫn mặc định",
|
||||||
|
update: "Thời gian cập nhật tự động",
|
||||||
|
subUri: "URI đăng ký",
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
name: "Tên",
|
||||||
|
desc: "Mô tả",
|
||||||
|
inboundTags: "Thẻ đầu vào",
|
||||||
|
basics: "Cơ bản",
|
||||||
|
config: "Cấu hình",
|
||||||
|
links: "Liên kết",
|
||||||
|
external: "Liên kết bên ngoài",
|
||||||
|
sub: "Đăng ký bên ngoài",
|
||||||
|
},
|
||||||
|
types: {
|
||||||
|
un: "Tên người dùng",
|
||||||
|
pw: "Mật khẩu",
|
||||||
|
direct: {
|
||||||
|
overrideAddr: "Ghi đè Địa chỉ",
|
||||||
|
overridePort: "Ghi đè Cổng",
|
||||||
|
},
|
||||||
|
hy: {
|
||||||
|
obfs: "Mật khẩu đã được Ẩn",
|
||||||
|
auth: "Mật khẩu Xác thực",
|
||||||
|
hyOptions: "Tùy chọn Hysteria",
|
||||||
|
hy2Options: "Tùy chọn Hysteria2",
|
||||||
|
ignoreBw: "Bỏ qua Băng thông của Client",
|
||||||
|
},
|
||||||
|
shdwTls: {
|
||||||
|
hs: "Máy chủ Handshake",
|
||||||
|
addHS: "Thêm Máy chủ Handshake",
|
||||||
|
},
|
||||||
|
ssh: {
|
||||||
|
passphrase: "Cụm từ mật khẩu",
|
||||||
|
hostKey: "Khóa Máy chủ",
|
||||||
|
algorithm: "Thuật toán Khóa",
|
||||||
|
clientVer: "Phiên bản Client",
|
||||||
|
options: "Tùy chọn SSH",
|
||||||
|
},
|
||||||
|
tor: {
|
||||||
|
execPath: "Đường dẫn File thực thi",
|
||||||
|
dataDir: "Thư mục Dữ liệu",
|
||||||
|
extArgs: "Đối số Bổ sung",
|
||||||
|
},
|
||||||
|
tuic: {
|
||||||
|
congControl: "Kiểm soát Tắc nghẽn",
|
||||||
|
authTimeout: "Thời gian chờ Xác thực",
|
||||||
|
hb: "Nhịp tim",
|
||||||
|
},
|
||||||
|
vless: {
|
||||||
|
flow: "Luồng",
|
||||||
|
udpEnc: "Mã hóa Gói UDP",
|
||||||
|
},
|
||||||
|
vmess: {
|
||||||
|
security: "Bảo mật",
|
||||||
|
globalPadding: "Đệm Toàn cầu",
|
||||||
|
authLen: "Chiều dài Mã hóa",
|
||||||
|
},
|
||||||
|
wg: {
|
||||||
|
privKey: "Khóa Riêng tư",
|
||||||
|
pubKey: "Khóa Công khai của Đối tác",
|
||||||
|
psk: "Khóa được Chia sẻ trước",
|
||||||
|
localIp: "IPs Cục bộ",
|
||||||
|
worker: "Công nhân",
|
||||||
|
ifName: "Tên Giao diện",
|
||||||
|
sysIf: "Giao diện Hệ thống",
|
||||||
|
gso: "Giao Thức GSO",
|
||||||
|
options: "Tùy chọn Wireguard",
|
||||||
|
multiPeer: "Nhiều Đối tác",
|
||||||
|
allowedIp: "IPs được Phép",
|
||||||
|
peer: "Đối tác",
|
||||||
|
peers: "Đối tác",
|
||||||
|
},
|
||||||
|
lb: {
|
||||||
|
defaultOut: "Đầu ra Mặc định",
|
||||||
|
interruptConn: "Ngắt kết nối hiện tại",
|
||||||
|
testUrl: "URL Kiểm tra",
|
||||||
|
interval: "Khoảng thời gian",
|
||||||
|
tolerance: "Sự dung hòa",
|
||||||
|
urlTestOptions: "Tùy chọn Kiểm tra URL",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
in: {
|
||||||
|
addr: "Địa chỉ",
|
||||||
|
port: "Cổng",
|
||||||
|
sniffing: "Đang Sniffing",
|
||||||
|
clients: "Kích hoạt khách hàng",
|
||||||
|
ssMethod: "Phương thức",
|
||||||
|
},
|
||||||
|
listen: {
|
||||||
|
sniffing: "Đang Sniffing",
|
||||||
|
sniffingTimeout: "Thời gian Chờ Sniffing",
|
||||||
|
sniffingOverride: "Ghi đè Đích",
|
||||||
|
options: "Tùy chọn Nghe",
|
||||||
|
tcpOptions: "Tùy chọn TCP",
|
||||||
|
udpOptions: "Tùy chọn UDP",
|
||||||
|
detour: "Lạc đạo",
|
||||||
|
detourText: "Chuyển tiếp tới đầu vào",
|
||||||
|
domainStrategy: "Chiến lược Domain",
|
||||||
|
},
|
||||||
|
dial: {
|
||||||
|
bindIf: "Ràng buộc tới Giao diện Mạng",
|
||||||
|
bindIp4: "Ràng buộc tới IPv4",
|
||||||
|
bindIp6: "Ràng buộc tới IPv6",
|
||||||
|
reuseAddr: "Sử dụng lại Địa chỉ Nghe",
|
||||||
|
connTimeout: "Thời gian Chờ Kết nối",
|
||||||
|
fbTimeout: "Thời gian Chờ Fallback",
|
||||||
|
options: "Tùy chọn Gọi",
|
||||||
|
detourText: "Chuyển tiếp tới thư đi",
|
||||||
|
},
|
||||||
|
transport: {
|
||||||
|
enable: "Kích hoạt vận chuyển",
|
||||||
|
host: "Máy chủ",
|
||||||
|
hosts: "Máy chủ",
|
||||||
|
path: "Đường dẫn",
|
||||||
|
httpMethod: "Phương thức Yêu cầu",
|
||||||
|
idleTimeout: "Thời gian Chờ Chờ đợi",
|
||||||
|
pingTimeout: "Thời gian Chờ Ping",
|
||||||
|
grpcServiceName: "Tên Dịch vụ",
|
||||||
|
grpcPws: "Cho phép mà không Có Luồng",
|
||||||
|
},
|
||||||
|
mux: {
|
||||||
|
enable: "Bật Multiplex",
|
||||||
|
maxConn: "Số kết nối Tối đa",
|
||||||
|
minStr: "Số Luồng Tối thiểu",
|
||||||
|
maxStr: "Số Luồng Tối đa",
|
||||||
|
padding: "Chỉ đệm",
|
||||||
|
enableBrutal: "Bật Brutal",
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
addr: "Địa chỉ Máy chủ",
|
||||||
|
port: "Cổng Máy chủ",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
add: "Thêm Quy tắc",
|
||||||
|
simple: "Đơn giản",
|
||||||
|
logical: "Logic",
|
||||||
|
mode: "Chế độ",
|
||||||
|
invert: "Nghịch đảo",
|
||||||
|
ipVer: "Phiên bản IP",
|
||||||
|
domain: "Tên miền",
|
||||||
|
domainSufix: "Hậu tố Miền",
|
||||||
|
domainKw: "Từ khóa Miền",
|
||||||
|
domainRgx: "Regex Miền",
|
||||||
|
ip: "CIDRs IP",
|
||||||
|
privateIp: "Dải IP Không hợp lệ",
|
||||||
|
port: "Cổng",
|
||||||
|
portRange: "Dải Cổng",
|
||||||
|
srcCidr: "CIDRs IP Nguồn",
|
||||||
|
srcPrivateIp: "IP Nguồn Không hợp lệ",
|
||||||
|
srcPort: "Cổng Nguồn",
|
||||||
|
srcPortRange: "Dải Cổng Nguồn",
|
||||||
|
ruleset: "Bộ quy tắc",
|
||||||
|
rulesetMatchSrc: "Bộ quy tắc IPcidr Phù hợp Nguồn",
|
||||||
|
options: "Tùy chọn Quy tắc",
|
||||||
|
domainRules: "Tên miền/IP",
|
||||||
|
srcIpRules: "IP Nguồn",
|
||||||
|
srcPortRules: "Cổng Nguồn",
|
||||||
|
},
|
||||||
|
ruleset: {
|
||||||
|
add: "Thêm Bộ quy tắc",
|
||||||
|
format: "Định dạng Dữ liệu",
|
||||||
|
interval: "Khoảng cách Cập nhật",
|
||||||
|
remote: "Xa",
|
||||||
|
local: "Cục bộ",
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
log: {
|
||||||
|
title: "Nhật ký",
|
||||||
|
level: "Mức độ",
|
||||||
|
output: "Đầu ra",
|
||||||
|
timestamp: "Bật Dấu thời gian",
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
final: "Cuối cùng",
|
||||||
|
server: "Máy chủ",
|
||||||
|
firstServer: "Máy chủ Đầu tiên",
|
||||||
|
},
|
||||||
|
routing: {
|
||||||
|
title: "Định tuyến",
|
||||||
|
defaultOut: "Ra ngoài Mặc định",
|
||||||
|
defaultIf: "NIC Mặc định",
|
||||||
|
defaultRm: "Đánh dấu Định tuyến Mặc định",
|
||||||
|
autoBind: "Tự động Ràng buộc NIC",
|
||||||
|
},
|
||||||
|
exp: {
|
||||||
|
storeFakeIp: "Lưu IP Giả mạo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tls : {
|
||||||
|
enable: "Kích hoạt TLS",
|
||||||
|
usePath: "Sử dụng đường dẫn",
|
||||||
|
useText: "Sử dụng văn bản",
|
||||||
|
certPath: "Đường dẫn tệp chứng chỉ",
|
||||||
|
keyPath: "Đường dẫn tệp khóa",
|
||||||
|
cert: "Chứng chỉ",
|
||||||
|
key: "Khóa",
|
||||||
|
options: "Tùy chọn TLS",
|
||||||
|
minVer: "Phiên bản Tối thiểu",
|
||||||
|
maxVer: "Phiên bản Tối đa",
|
||||||
|
cs: "Các bộ mã hóa",
|
||||||
|
privKey: "Khóa riêng",
|
||||||
|
pubKey: "Khóa Công khai",
|
||||||
|
disableSni: "Tắt SNI",
|
||||||
|
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: {
|
||||||
|
upload: "Tải lên",
|
||||||
|
download: "Tải xuống",
|
||||||
|
volume: "Thể tích",
|
||||||
|
usage: "Sử dụng",
|
||||||
|
enable: "Kích hoạt thống kê",
|
||||||
|
graphTitle: "Biểu đồ lưu lượng",
|
||||||
|
B: "B",
|
||||||
|
KB: "KB",
|
||||||
|
MB: "MB",
|
||||||
|
GB: "GB",
|
||||||
|
TB: "TB",
|
||||||
|
PB: "PB",
|
||||||
|
p: "ph",
|
||||||
|
Kp: "Kph",
|
||||||
|
Mp: "Mph",
|
||||||
|
Gb: "Gb",
|
||||||
|
bps: "bps",
|
||||||
|
Kbps: "Kbps",
|
||||||
|
Mbps: "Mbps",
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
expiry: "Hết hạn",
|
||||||
|
expired: "Đã hết hạn",
|
||||||
|
d: "ng",
|
||||||
|
h: "g",
|
||||||
|
m: "p",
|
||||||
|
s: "s",
|
||||||
|
ms: "ms",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
export default {
|
||||||
|
message: "欢迎",
|
||||||
|
success: "成功",
|
||||||
|
failed: "失败",
|
||||||
|
enable: "启用",
|
||||||
|
disable: "禁用",
|
||||||
|
loading: "加载中...",
|
||||||
|
confirm: "是否确定?",
|
||||||
|
yes: "确认",
|
||||||
|
no: "取消",
|
||||||
|
unlimited: "无限",
|
||||||
|
remained: "剩余",
|
||||||
|
type: "类型",
|
||||||
|
protocol: "协议",
|
||||||
|
submit: "提交",
|
||||||
|
reset: "重置",
|
||||||
|
now: "当前",
|
||||||
|
network: "网络",
|
||||||
|
copyToClipboard: "复制到剪贴板",
|
||||||
|
noData: "无数据!",
|
||||||
|
invalidLogin: "登录无效!",
|
||||||
|
online: "在线",
|
||||||
|
version: "版本",
|
||||||
|
email: "电子邮件",
|
||||||
|
commaSeparated: "(逗号分隔)",
|
||||||
|
count: "计数",
|
||||||
|
error: {
|
||||||
|
dplData: "重复数据",
|
||||||
|
core: "Sing-Box 错误",
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
login: "登录",
|
||||||
|
home: "主页",
|
||||||
|
inbounds: "入站管理",
|
||||||
|
outbounds: "出站管理",
|
||||||
|
clients: "用户管理",
|
||||||
|
rules: "路由列表",
|
||||||
|
tls: "TLS 设置",
|
||||||
|
basics: "基础信息",
|
||||||
|
admins: "管理员",
|
||||||
|
settings: "设置",
|
||||||
|
},
|
||||||
|
main: {
|
||||||
|
tiles: "信息卡",
|
||||||
|
gauges: "仪表板",
|
||||||
|
charts: "图表",
|
||||||
|
infos: "信息",
|
||||||
|
gauge: {
|
||||||
|
cpu: "CPU 仪表",
|
||||||
|
mem: "RAM 仪表",
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
cpu: "CPU 监视器",
|
||||||
|
mem: "RAM 监视器",
|
||||||
|
net: "网络带宽",
|
||||||
|
pnet: "网络数据包",
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
sys: "系统信息",
|
||||||
|
sbd: "运行信息",
|
||||||
|
host: "主机",
|
||||||
|
cpu: "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: "删除",
|
||||||
|
save: "保存",
|
||||||
|
update: "更新",
|
||||||
|
submit: "提交",
|
||||||
|
set: "设置",
|
||||||
|
disable: "禁用",
|
||||||
|
close: "关闭",
|
||||||
|
restartApp: "重启面板",
|
||||||
|
},
|
||||||
|
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 密钥 (Key) 路径",
|
||||||
|
sslCert: "SSL 证书 (cert) 路径",
|
||||||
|
webUri: "面板 URI",
|
||||||
|
sessionAge: "会话最大连接数",
|
||||||
|
trafficAge: "流量最大年龄",
|
||||||
|
timeLoc: "时区",
|
||||||
|
subEncode: "启用编码",
|
||||||
|
subInfo: "启用用户信息",
|
||||||
|
path: "默认路径",
|
||||||
|
update: "自动更新时间",
|
||||||
|
subUri: "订阅 URL",
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
name: "名称",
|
||||||
|
desc: "描述",
|
||||||
|
inboundTags: "入站标签",
|
||||||
|
basics: "基础",
|
||||||
|
config: "配置",
|
||||||
|
links: "链接",
|
||||||
|
external: "外部链接",
|
||||||
|
sub: "外部订阅",
|
||||||
|
},
|
||||||
|
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: "心跳",
|
||||||
|
},
|
||||||
|
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: "端口",
|
||||||
|
sniffing: "嗅探",
|
||||||
|
clients: "启用客户端",
|
||||||
|
ssMethod: "方法",
|
||||||
|
},
|
||||||
|
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: "Ping超时",
|
||||||
|
grpcServiceName: "服务名称",
|
||||||
|
grpcPws: "允许无流",
|
||||||
|
},
|
||||||
|
mux: {
|
||||||
|
enable: "启用多路复用",
|
||||||
|
maxConn: "最大连接数",
|
||||||
|
minStr: "最小流数",
|
||||||
|
maxStr: "最大流数",
|
||||||
|
padding: "仅填充",
|
||||||
|
enableBrutal: "启用强力模式",
|
||||||
|
},
|
||||||
|
out: {
|
||||||
|
addr: "服务器地址",
|
||||||
|
port: "服务器端口",
|
||||||
|
},
|
||||||
|
rule: {
|
||||||
|
add: "添加规则",
|
||||||
|
simple: "简单",
|
||||||
|
logical: "逻辑",
|
||||||
|
mode: "模式",
|
||||||
|
invert: "反转",
|
||||||
|
ipVer: "IP 版本",
|
||||||
|
domain: "域名",
|
||||||
|
domainSufix: "域名后缀",
|
||||||
|
domainKw: "域名关键词",
|
||||||
|
domainRgx: "域名正则表达式",
|
||||||
|
ip: "IP CIDR",
|
||||||
|
privateIp: "无效 IP 范围",
|
||||||
|
port: "端口",
|
||||||
|
portRange: "端口范围",
|
||||||
|
srcCidr: "源 IP CIDR",
|
||||||
|
srcPrivateIp: "无效源 IP",
|
||||||
|
srcPort: "源端口",
|
||||||
|
srcPortRange: "源端口范围",
|
||||||
|
ruleset: "规则集",
|
||||||
|
rulesetMatchSrc: "规则集 IP CIDR 匹配源",
|
||||||
|
options: "规则选项",
|
||||||
|
domainRules: "域名/IP",
|
||||||
|
srcIpRules: "源 IP",
|
||||||
|
srcPortRules: "源端口",
|
||||||
|
},
|
||||||
|
ruleset: {
|
||||||
|
add: "添加规则集",
|
||||||
|
format: "数据格式",
|
||||||
|
interval: "更新间隔",
|
||||||
|
remote: "远程",
|
||||||
|
local: "本地",
|
||||||
|
},
|
||||||
|
basic: {
|
||||||
|
log: {
|
||||||
|
title: "日志",
|
||||||
|
level: "级别",
|
||||||
|
output: "输出",
|
||||||
|
timestamp: "启用时间戳",
|
||||||
|
},
|
||||||
|
dns: {
|
||||||
|
final: "最终",
|
||||||
|
server: "服务器",
|
||||||
|
firstServer: "首选服务器",
|
||||||
|
},
|
||||||
|
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: "B",
|
||||||
|
KB: "KB",
|
||||||
|
MB: "MB",
|
||||||
|
GB: "GB",
|
||||||
|
TB: "TB",
|
||||||
|
PB: "PB",
|
||||||
|
p: "p",
|
||||||
|
Kp: "Kp",
|
||||||
|
Mp: "Mp",
|
||||||
|
Gb: "Gb",
|
||||||
|
bps: "bps",
|
||||||
|
Kbps: "Kbps",
|
||||||
|
Mbps: "Mbps",
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
expiry: "到期",
|
||||||
|
expired: "已到期",
|
||||||
|
d: "天",
|
||||||
|
h: "时",
|
||||||
|
m: "分",
|
||||||
|
s: "秒",
|
||||||
|
ms: "毫秒",
|
||||||
|
},
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user