Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
@@ -50,6 +48,6 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get update && sudo apt-get install upx -yq
|
||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
sudo apt install gcc-aarch64-linux-gnu
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
@@ -69,15 +69,22 @@ jobs:
|
||||
fi
|
||||
|
||||
#### Build Sing-Box
|
||||
git clone -b v1.8.7 https://github.com/SagerNet/sing-box
|
||||
git clone -b v1.8.13 https://github.com/SagerNet/sing-box
|
||||
cd sing-box
|
||||
go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box
|
||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a \
|
||||
-tags='netgo osusergo static_build with_quic with_grpc with_wireguard with_ech with_utls with_reality_server with_acme with_v2ray_api with_clash_api with_gvisor' \
|
||||
-o sing-box ./cmd/sing-box
|
||||
upx --ultra-brute -9 -v --lzma --best --force sing-box
|
||||
cd ..
|
||||
|
||||
### Build s-ui
|
||||
cd backend
|
||||
go build -o ../sui main.go
|
||||
go build -v -gcflags=all="-l -B -C" -mod=mod -trimpath \
|
||||
-ldflags "-s -w -buildid= -extldflags '-static'" -a -tags='netgo osusergo static_build sqlite_omit_load_extension' \
|
||||
-o ../sui main.go
|
||||
cd ..
|
||||
upx --ultra-brute -9 -v --lzma --best --force sui
|
||||
|
||||
mkdir s-ui
|
||||
cp sui s-ui/
|
||||
@@ -85,7 +92,7 @@ jobs:
|
||||
cp sing-box.service s-ui/
|
||||
mkdir 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
|
||||
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
FROM node:alpine as front-builder
|
||||
FROM --platform=$BUILDPLATFORM node:alpine as front-builder
|
||||
WORKDIR /app
|
||||
COPY frontend/ ./
|
||||
RUN npm install && npm run build
|
||||
|
||||
FROM golang:1.22-alpine AS backend-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS backend-builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||
@@ -13,7 +13,7 @@ COPY backend/ ./
|
||||
COPY --from=front-builder /app/dist/ /app/web/html/
|
||||
RUN go build -o sui main.go
|
||||
|
||||
FROM alpine
|
||||
FROM --platform=$BUILDPLATFORM alpine
|
||||
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||
ENV TZ=Asia/Tehran
|
||||
WORKDIR /app
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
**An Advanced Web Panel • Built on SagerNet/Sing-Box**
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
@@ -28,7 +29,9 @@
|
||||
|
||||
## Default Installation Informarion
|
||||
- Panel Port: 2095
|
||||
- Panel Path: /app/
|
||||
- Subscription Port: 2096
|
||||
- Subscription Path: /sub/
|
||||
- User/Passowrd: admin
|
||||
|
||||
## Install & Upgrade to Latest Version
|
||||
@@ -73,10 +76,20 @@ curl -fsSL https://get.docker.com | sh
|
||||
|
||||
**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
|
||||
mkdir s-ui && cd s-ui
|
||||
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/cert/:/root/cert/ \
|
||||
--name s-ui --restart=unless-stopped \
|
||||
@@ -95,6 +108,9 @@ docker build -t s-ui .
|
||||
|
||||
- English
|
||||
- Farsi
|
||||
- Vietnamese
|
||||
- Chinese (Simplified)
|
||||
- Chinese (Traditional)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -153,5 +169,4 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
|
||||
</details>
|
||||
|
||||
## Stargazers over Time
|
||||
|
||||
[](https://starchart.cc/alireza0/s-ui)
|
||||
[](https://starchart.cc/alireza0/s-ui)
|
||||
|
||||
+7
-1
@@ -51,7 +51,13 @@ func (a *APP) Start() error {
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ func GetDBPath() string {
|
||||
}
|
||||
|
||||
func GetDefaultConfig() string {
|
||||
apiEnv := GetEnvApi()
|
||||
if len(apiEnv) > 0 {
|
||||
return strings.Replace(defaultConfig, "127.0.0.1:1080", apiEnv, 1)
|
||||
}
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.0.2
|
||||
0.0.3
|
||||
@@ -14,7 +14,7 @@ func NewCronJob() *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.Start()
|
||||
|
||||
@@ -24,7 +24,7 @@ func (c *CronJob) Start(loc *time.Location) error {
|
||||
// Start expiry job
|
||||
c.cron.AddJob("@every 1m", NewDepleteJob())
|
||||
// Start deleting old stats
|
||||
c.cron.AddJob("@daily", NewDelStatsJob())
|
||||
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||
}()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -7,14 +7,17 @@ import (
|
||||
|
||||
type DelStatsJob struct {
|
||||
service.StatsService
|
||||
trafficAge int
|
||||
}
|
||||
|
||||
func NewDelStatsJob() *DelStatsJob {
|
||||
return &DelStatsJob{}
|
||||
func NewDelStatsJob(ta int) *DelStatsJob {
|
||||
return &DelStatsJob{
|
||||
trafficAge: ta,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DelStatsJob) Run() {
|
||||
err := s.StatsService.DelOldStats(30)
|
||||
err := s.StatsService.DelOldStats(s.trafficAge)
|
||||
if err != nil {
|
||||
logger.Warning("Deleting old statistics failed: ", err)
|
||||
return
|
||||
|
||||
@@ -26,6 +26,7 @@ type Client struct {
|
||||
Expiry int64 `json:"expiry" form:"expiry"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Desc string `json:"desc" from:"desc"`
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
|
||||
+4
-4
@@ -41,12 +41,12 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.20.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
+8
-8
@@ -243,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/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
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/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
|
||||
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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -267,8 +267,8 @@ 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.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -287,8 +287,8 @@ google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJai
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -146,6 +146,8 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
||||
case "outbounds":
|
||||
if change.Action == "edit" {
|
||||
newConfig.Outbounds[change.Index] = rawObject
|
||||
} else if change.Action == "del" {
|
||||
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
|
||||
} else {
|
||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
||||
}
|
||||
|
||||
@@ -89,12 +89,12 @@ func (s *ServerService) GetNetInfo() map[string]interface{} {
|
||||
|
||||
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
||||
info := make(map[string]interface{}, 0)
|
||||
if s.SingBoxService.IsRunning() {
|
||||
sysStats, err := s.SingBoxService.GetSysStats()
|
||||
if err == nil {
|
||||
info["running"] = true
|
||||
sysStats, _ := s.SingBoxService.GetSysStats()
|
||||
info["stats"] = sysStats
|
||||
} else {
|
||||
info["running"] = false
|
||||
info["running"] = s.SingBoxService.IsRunning()
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ var defaultValueMap = map[string]string{
|
||||
"webPath": "/app/",
|
||||
"webURI": "",
|
||||
"sessionMaxAge": "0",
|
||||
"trafficAge": "30",
|
||||
"timeLocation": "Asia/Tehran",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
@@ -204,6 +205,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||
return s.getInt("sessionMaxAge")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTrafficAge() (int, error) {
|
||||
return s.getInt("trafficAge")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
||||
l, err := s.getString("timeLocation")
|
||||
if err != nil {
|
||||
|
||||
@@ -26,7 +26,10 @@ func (s *SingBoxService) GetStats() 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()
|
||||
resp, err := s.V2rayAPI.GetSysStats()
|
||||
if err != 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.10
|
||||
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=$BUILDPLATFORM 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" ]
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
version: "3"
|
||||
|
||||
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:
|
||||
Generated
+8
-7
@@ -13,6 +13,7 @@
|
||||
"chart.js": "^4.4.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.29.0",
|
||||
"moment": "^2.30.1",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"roboto-fontface": "*",
|
||||
@@ -34,7 +35,7 @@
|
||||
"sass": "^1.60.0",
|
||||
"typescript": "^5.0.0",
|
||||
"unplugin-fonts": "^1.0.3",
|
||||
"vite": "^4.2.0",
|
||||
"vite": "^4.5.3",
|
||||
"vite-plugin-vuetify": "^1.0.0",
|
||||
"vue-tsc": "^1.2.0"
|
||||
}
|
||||
@@ -2761,9 +2762,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -4647,9 +4648,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
|
||||
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
|
||||
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"chart.js": "^4.4.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"core-js": "^3.29.0",
|
||||
"moment": "^2.30.1",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode.vue": "^3.4.1",
|
||||
"roboto-fontface": "*",
|
||||
@@ -35,7 +36,7 @@
|
||||
"sass": "^1.60.0",
|
||||
"typescript": "^5.0.0",
|
||||
"unplugin-fonts": "^1.0.3",
|
||||
"vite": "^4.2.0",
|
||||
"vite": "^4.5.3",
|
||||
"vite-plugin-vuetify": "^1.0.0",
|
||||
"vue-tsc": "^1.2.0"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<DatePicker
|
||||
v-model="Input"
|
||||
@input="Input=$event"
|
||||
:locale="$i18n.locale"
|
||||
:locale="locale"
|
||||
element="expiry"
|
||||
compact-time
|
||||
type="datetime">
|
||||
@@ -42,6 +42,8 @@
|
||||
<script lang="ts">
|
||||
import DatePicker from 'vue3-persian-datetime-picker'
|
||||
import { i18n } from '@/locales'
|
||||
import 'moment/locale/zh-cn'
|
||||
import 'moment/locale/zh-tw'
|
||||
|
||||
export default {
|
||||
props: ['expiry'],
|
||||
@@ -54,10 +56,14 @@ export default {
|
||||
},
|
||||
components: { DatePicker },
|
||||
computed: {
|
||||
locale() {
|
||||
const l = i18n.global.locale.value
|
||||
return l.replace('zh', 'zh-')
|
||||
},
|
||||
dateFormatted() {
|
||||
if (this.expDate == 0) return i18n.global.t('unlimited')
|
||||
const date = new Date(this.expDate*1000)
|
||||
return date.toLocaleString(i18n.global.locale.value)
|
||||
return date.toLocaleString(this.locale)
|
||||
},
|
||||
expDate() {
|
||||
return parseInt(this.expiry?? 0)
|
||||
@@ -99,18 +105,18 @@ export default {
|
||||
<style>
|
||||
.vpd-addon-list,
|
||||
.vpd-addon-list-item {
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
border-color: rgb(var(--v-theme-background));
|
||||
background-color: rgb(var(--v-theme-background)) !important;
|
||||
border-color: rgb(var(--v-theme-background)) !important;
|
||||
}
|
||||
.vpd-content {
|
||||
background-color: rgb(var(--v-theme-background));
|
||||
background-color: rgb(var(--v-theme-background)) !important;
|
||||
}
|
||||
.vpd-addon-list-item.vpd-selected,
|
||||
.vpd-addon-list-item:hover {
|
||||
background-color: rgb(var(--v-theme-primary));
|
||||
background-color: rgb(var(--v-theme-primary)) !important;
|
||||
}
|
||||
.vpd-close-addon {
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
color: rgb(var(--v-theme-on-surface)) !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
.vpd-controls {
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<v-card subtitle="Dial" style="background-color: inherit;">
|
||||
<v-card :subtitle="$t('objects.dial')" style="background-color: inherit;">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||
<v-text-field
|
||||
label="Forward to Outbound tag"
|
||||
<v-select
|
||||
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 cols="12" sm="6" md="4" v-if="optionBind">
|
||||
<v-text-field
|
||||
label="Bind to Network Interface"
|
||||
:label="$t('dial.bindIf')"
|
||||
hide-details
|
||||
v-model="dial.bind_interface"></v-text-field>
|
||||
</v-col>
|
||||
@@ -17,13 +19,13 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV4">
|
||||
<v-text-field
|
||||
label="Bind to IPv4"
|
||||
:label="$t('dial.bindIp4')"
|
||||
hide-details
|
||||
v-model="dial.inet4_bind_address"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionIPV6">
|
||||
<v-text-field
|
||||
label="Bind to IPv6"
|
||||
:label="$t('dial.bindIp6')"
|
||||
hide-details
|
||||
v-model="dial.inet6_bind_address"></v-text-field>
|
||||
</v-col>
|
||||
@@ -38,7 +40,7 @@
|
||||
v-model.number="routingMark"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionRA">
|
||||
<v-switch v-model="dial.reuse_addr" color="primary" label="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-row>
|
||||
<v-row v-if="optionTCP">
|
||||
@@ -55,11 +57,11 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionCT">
|
||||
<v-text-field
|
||||
label="Connection Timeout"
|
||||
:label="$t('dial.connTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
min="1"
|
||||
suffix="s"
|
||||
:suffix="$t('date.s')"
|
||||
v-model.number="connectTimeout"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -67,22 +69,19 @@
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
clearable
|
||||
@click:clear="delete dial.domain_strategy"
|
||||
width="100"
|
||||
label="Domain to IP Strategy"
|
||||
:label="$t('listen.domainStrategy')"
|
||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||
v-model="dial.domain_strategy">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Fallback Timeout"
|
||||
:label="$t('dial.fbTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
min="50"
|
||||
step="50"
|
||||
suffix="ms"
|
||||
:suffix="$t('date.ms')"
|
||||
v-model.number="fallbackDelay"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -90,39 +89,39 @@
|
||||
<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>Dial Options</v-btn>
|
||||
<v-btn v-bind="props" hide-details>{{ $t('dial.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<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-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-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-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-switch v-model="optionRM" color="primary" label="Routing Mark" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRA" color="primary" label="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-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-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-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-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>
|
||||
</v-card>
|
||||
@@ -133,7 +132,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['dial'],
|
||||
props: ['dial', 'outTags'],
|
||||
data() {
|
||||
return {
|
||||
menu: false
|
||||
@@ -154,7 +153,7 @@ export default {
|
||||
},
|
||||
optionDetour: {
|
||||
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: {
|
||||
get(): boolean { return this.$props.dial.bind_interface != undefined },
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-subtitle>
|
||||
{{ $t('objects.headers') }}
|
||||
<v-icon @click="add_header" icon="mdi-plus"></v-icon>
|
||||
</v-card-subtitle>
|
||||
<v-row v-for="(header, index) in hdrs">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('objects.key')"
|
||||
hide-details
|
||||
@input="update_key(index,$event.target.value)"
|
||||
v-model="header.name">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
:label="$t('objects.value')"
|
||||
hide-details
|
||||
@input="update_value(index,$event.target.value)"
|
||||
append-icon="mdi-delete"
|
||||
@click:append="del_header(index)"
|
||||
v-model="header.value">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
type Header = {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
export default {
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
add_header() {
|
||||
this.hdrs = [...this.hdrs, {name: "Host", value: ""}]
|
||||
},
|
||||
del_header(i:number) {
|
||||
let h = this.hdrs
|
||||
h.splice(i,1)
|
||||
this.hdrs = h
|
||||
},
|
||||
update_key(i:number,k:string) {
|
||||
let h = this.hdrs
|
||||
h[i].name = k
|
||||
this.hdrs = h
|
||||
},
|
||||
update_value(i:number,v:string) {
|
||||
let h = this.hdrs
|
||||
h[i].value = v
|
||||
this.hdrs = h
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hdrs: {
|
||||
get() :Header[] {
|
||||
let headers: Header[] = []
|
||||
const h = this.$props.data.headers
|
||||
if (h) {
|
||||
Object.keys(h).forEach(key => {
|
||||
if (Array.isArray(h[key])){
|
||||
h[key].forEach((v:string) => headers.push({ name: key, value: v }))
|
||||
} else {
|
||||
headers.push({ name: key, value: h[key] })
|
||||
}
|
||||
})
|
||||
}
|
||||
return headers
|
||||
},
|
||||
set(v:Header[]) {
|
||||
if (v.length>0) {
|
||||
let headers:any = {}
|
||||
v.forEach((h:Header) => {
|
||||
if (headers[h.name]) {
|
||||
if (Array.isArray(headers[h.name])) {
|
||||
headers[h.name].push(h.value)
|
||||
} else {
|
||||
headers[h.name] = [headers[h.name], h.value]
|
||||
}
|
||||
} else {
|
||||
headers[h.name] = h.value
|
||||
}
|
||||
})
|
||||
this.$props.data.headers = headers
|
||||
} else {
|
||||
this.$props.data.headers = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('in.multiplex')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Enable Multiplex" v-model="muxEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
||||
<v-switch color="primary" label="Reject Non-Padded" v-model="mux.padding" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="mux.enabled">
|
||||
<v-switch color="primary" label="Enable Brutal" v-model="burtalEnable" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="mux.brutal?.enabled">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Uplink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
v-model.number="up_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Downlink Bandwidth"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
min="0"
|
||||
v-model.number="down_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { iMultiplex } from '@/types/inMultiplex'
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
mux(): iMultiplex {
|
||||
return <iMultiplex> this.$props.inbound.multiplex
|
||||
},
|
||||
muxEnable: {
|
||||
get(): boolean { return this.$props.inbound.multiplex ? this.mux.enabled : false },
|
||||
set(newValue:boolean) { this.$props.inbound.multiplex = newValue ? { enabled: newValue } : {} }
|
||||
},
|
||||
burtalEnable: {
|
||||
get(): boolean { return this.mux.brutal ? this.mux.brutal.enabled : false },
|
||||
set(newValue:boolean) { this.mux.brutal = { enabled: newValue, up_mbps: 100, down_mbps: 100 } }
|
||||
},
|
||||
down_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.down_mbps ? this.mux.brutal.down_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.down_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.mux.brutal && this.mux.brutal.up_mbps ? this.mux.brutal.up_mbps : 0 },
|
||||
set(newValue:any) {
|
||||
if (this.mux.brutal){
|
||||
this.mux.brutal.up_mbps = newValue.length != 0 ? newValue : 0
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card :subtitle="$t('in.tls')">
|
||||
<v-card :subtitle="$t('objects.tls')">
|
||||
<v-row v-if="tlsOptional">
|
||||
<v-col cols="auto">
|
||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
||||
@@ -77,7 +77,7 @@
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Minimum Version"
|
||||
:label="$t('tls.minVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.min_version">
|
||||
</v-select>
|
||||
@@ -85,7 +85,7 @@
|
||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Maximum Version"
|
||||
:label="$t('tls.maxVer')"
|
||||
:items="tlsVersions"
|
||||
v-model="tls.max_version">
|
||||
</v-select>
|
||||
@@ -95,7 +95,7 @@
|
||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Cipher Suites"
|
||||
:label="$t('tls.cs')"
|
||||
multiple
|
||||
:items="cipher_suites"
|
||||
v-model="tls.cipher_suites">
|
||||
@@ -103,11 +103,11 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-card-actions>
|
||||
<v-card-actions v-if="tls.enabled">
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>TLS Options</v-btn>
|
||||
<v-btn v-bind="props" hide-details>{{ $t('tls.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
@@ -118,13 +118,13 @@
|
||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionMinV" color="primary" label="Min Version" hide-details></v-switch>
|
||||
<v-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="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-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>
|
||||
</v-card>
|
||||
@@ -149,7 +149,6 @@ export default {
|
||||
],
|
||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
||||
cipher_suites: [
|
||||
{ title: "Automatic", value: "" },
|
||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
||||
{ title: "RSA-AES256-CBC-SHA", value: "TLS_RSA_WITH_AES_256_CBC_SHA" },
|
||||
{ title: "RSA-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card subtitle="Listen">
|
||||
<v-card :subtitle="$t('objects.listen')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
@@ -20,27 +20,29 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="optionDetour">
|
||||
<v-text-field
|
||||
label="Forward to Inbound tag"
|
||||
<v-select
|
||||
:label="$t('listen.detourText')"
|
||||
hide-details
|
||||
v-model="inbound.detour"></v-text-field>
|
||||
:items="inTags"
|
||||
v-model="inbound.detour">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<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-row>
|
||||
<v-row v-if="inbound.sniff">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="inbound.sniff_override_destination" color="primary" label="Override Sniffed Domain" hide-details></v-switch>
|
||||
<v-switch v-model="inbound.sniff_override_destination" color="primary" :label="$t('listen.sniffingOverride')" hide-details></v-switch>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Sniffing Timeout"
|
||||
:label="$t('listen.sniffingTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
min="50"
|
||||
step="50"
|
||||
suffix="ms"
|
||||
:suffix="$t('date.ms')"
|
||||
v-model.number="sniffTimeout"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -62,7 +64,7 @@
|
||||
hide-details
|
||||
type="number"
|
||||
min="1"
|
||||
suffix="Min"
|
||||
:suffix="$t('date.m')"
|
||||
v-model.number="udpTimeout"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -70,8 +72,7 @@
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
width="100"
|
||||
label="Domain to IP Strategy"
|
||||
:label="$t('listen.domainStrategy')"
|
||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||
v-model="inbound.domain_strategy">
|
||||
</v-select>
|
||||
@@ -81,21 +82,21 @@
|
||||
<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>Listen Options</v-btn>
|
||||
<v-btn v-bind="props" hide-details>{{ $t('listen.options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<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-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-switch v-model="optionDetour" color="primary" label="Detour" hide-details></v-switch>
|
||||
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionDS" color="primary" label="Domain Strategy" hide-details></v-switch>
|
||||
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
@@ -106,7 +107,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['inbound', 'inTags'],
|
||||
data() {
|
||||
return {
|
||||
menu: false
|
||||
@@ -138,12 +139,12 @@ export default {
|
||||
},
|
||||
set(v:boolean) {
|
||||
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: {
|
||||
get(): boolean { return this.$props.inbound.detour != undefined },
|
||||
set(v:boolean) { this.$props.inbound.detour = v ? '' : undefined }
|
||||
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
||||
},
|
||||
optionDS: {
|
||||
get(): boolean { return this.$props.inbound.domain_strategy != undefined },
|
||||
|
||||
@@ -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">
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
networks: [
|
||||
@@ -21,8 +21,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
Network: {
|
||||
get():string { return this.$props.inbound.network?? '' },
|
||||
set(v:string) { this.$props.inbound.network = v != '' ? v : undefined }
|
||||
get():string { return this.$props.data.network?? '' },
|
||||
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>
|
||||
<v-card :subtitle="$t('in.transport')">
|
||||
<v-card :subtitle="$t('objects.transport')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" :label="$t('transport.enable')" v-model="tpEnable" hide-details></v-switch>
|
||||
@@ -7,7 +7,6 @@
|
||||
<v-col cols="12" sm="6" md="4" v-if="tpEnable">
|
||||
<v-select
|
||||
hide-details
|
||||
width="100"
|
||||
:label="$t('type')"
|
||||
:items="Object.keys(trspTypes).map((key,index) => ({title: key, value: Object.values(trspTypes)[index]}))"
|
||||
v-model="transportType">
|
||||
@@ -28,7 +27,7 @@ import WebSocket from './transports/WebSocket.vue'
|
||||
import GRPC from './transports/gRPC.vue'
|
||||
import HttpUpgrade from './transports/HttpUpgrade.vue'
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['data'],
|
||||
data() {
|
||||
return {
|
||||
trspTypes: TrspTypes
|
||||
@@ -36,15 +35,15 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
Transport() {
|
||||
return <Transport>this.$props.inbound.transport
|
||||
return <Transport>this.$props.data.transport
|
||||
},
|
||||
tpEnable: {
|
||||
get() { return Object.hasOwn(this.$props.inbound.transport, 'type') },
|
||||
set(newValue: boolean) { this.$props.inbound.transport = newValue ? { type: 'http' } : {} }
|
||||
get() { return Object.hasOwn(this.$props.data.transport, 'type') },
|
||||
set(newValue: boolean) { this.$props.data.transport = newValue ? { type: 'http' } : {} }
|
||||
},
|
||||
transportType: {
|
||||
get() { return this.Transport.type },
|
||||
set(newValue: string) { this.$props.inbound.transport = { type: newValue } }
|
||||
set(newValue: string) { this.$props.data.transport = { type: newValue } }
|
||||
}
|
||||
},
|
||||
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>
|
||||
<v-card subtitle="Clients">
|
||||
<v-card :subtitle="$t('pages.clients')">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<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,25 +1,35 @@
|
||||
<template>
|
||||
<v-card subtitle="Direct">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Override Address"
|
||||
:label="$t('types.direct.overrideAddr')"
|
||||
hide-details
|
||||
v-model="inbound.override_address">
|
||||
v-model="data.override_address">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Override Port"
|
||||
:label="$t('types.direct.overridePort')"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
v-model="override_port">
|
||||
v-model.number="override_port">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'out'">
|
||||
<v-select
|
||||
:label="$t('types.direct.proxyProtocol')"
|
||||
:items="[1,2]"
|
||||
hide-details
|
||||
clearable
|
||||
@click:clear="delete data.proxy_protocol"
|
||||
v-model.number="data.proxy_protocol">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -28,14 +38,14 @@
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
override_port: {
|
||||
get() { return this.$props.inbound.override_port ? this.$props.inbound.override_port : ''; },
|
||||
set(newValue: any) { this.$props.inbound.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||
get() { return this.$props.data.override_port ? this.$props.data.override_port : ''; },
|
||||
set(newValue: any) { this.$props.data.override_port = newValue.length == 0 || newValue == 0 ? undefined : parseInt(newValue); }
|
||||
},
|
||||
},
|
||||
components: { Network }
|
||||
|
||||
@@ -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-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Uplink Limit"
|
||||
:label="$t('stats.upload')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
: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="Downlink Limit"
|
||||
:label="$t('stats.download')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
:suffix="$t('stats.Mbps')"
|
||||
min="0"
|
||||
v-model.number="down_mbps">
|
||||
</v-text-field>
|
||||
@@ -24,40 +24,136 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="obfs Password"
|
||||
:label="$t('types.hy.obfs')"
|
||||
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-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction=='out'">
|
||||
<Network :data="data" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="data.disable_mtu_discovery" color="primary" label="Disable MTU discovery" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_conn != undefined">
|
||||
<v-text-field
|
||||
label="Recv window conn"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window_conn">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window != undefined">
|
||||
<v-text-field
|
||||
label="Recv window"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.recv_window_client != undefined">
|
||||
<v-text-field
|
||||
label="Recv window client"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.recv_window_client">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4" v-if="data.max_conn_client != undefined">
|
||||
<v-text-field
|
||||
label="Max conn client"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
v-model.number="data.max_conn_client">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" hide-details>{{ $t('types.hy.hyOptions') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-switch v-model="optionRsvConn" color="primary" label="Recv window conn" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='out'">
|
||||
<v-switch v-model="optionRsvWin" color="primary" label="Recv window" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='in'">
|
||||
<v-switch v-model="optionRsvClnt" color="primary" label="Recv window client" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="direction=='in'">
|
||||
<v-switch v-model="optionMaxConn" color="primary" label="Max conn client" hide-details></v-switch>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
optionRsvConn: {
|
||||
get(): boolean { return this.$props.data.recv_window_conn != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window_conn = v ? 15728640 : undefined }
|
||||
},
|
||||
optionRsvWin: {
|
||||
get(): boolean { return this.$props.data.recv_window != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window = v ? 67108864 : undefined }
|
||||
},
|
||||
optionRsvClnt: {
|
||||
get(): boolean { return this.$props.data.recv_window_client != undefined },
|
||||
set(v:boolean) { this.$props.data.recv_window_client = v ? 67108864 : undefined }
|
||||
},
|
||||
optionMaxConn: {
|
||||
get(): boolean { return this.$props.data.max_conn_client != undefined },
|
||||
set(v:boolean) { this.$props.data.max_conn_client = v ? 1024 : undefined }
|
||||
},
|
||||
down_mbps: {
|
||||
get() { return this.$props.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) {
|
||||
if (newValue.length != 0 ){
|
||||
this.$props.inbound.down_mbps = newValue
|
||||
this.$props.inbound.down = "" + newValue + " Mbps"
|
||||
this.$props.data.down_mbps = newValue
|
||||
this.$props.data.down = "" + newValue + " Mbps"
|
||||
} else {
|
||||
this.$props.inbound.down_mbps = 0
|
||||
this.$props.inbound.down = "0 Mbps"
|
||||
this.$props.data.down_mbps = 0
|
||||
this.$props.data.down = "0 Mbps"
|
||||
}
|
||||
}
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.$props.inbound.up_mbps ? this.$props.inbound.up_mbps : 0 },
|
||||
set(newValue:number) { this.$props.inbound.up_mbps = newValue > 0 ? newValue : 0 }
|
||||
get() { return this.$props.data.up_mbps ? this.$props.data.up_mbps : 0 },
|
||||
set(newValue:number) { this.$props.data.up_mbps = newValue > 0 ? newValue : 0 }
|
||||
},
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -1,43 +1,57 @@
|
||||
<template>
|
||||
<v-card subtitle="Hysteria2">
|
||||
<v-row>
|
||||
<v-row v-if="direction == 'in'">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Masquerade"
|
||||
label="HTTP3 server on auth fail"
|
||||
hide-details
|
||||
v-model="hysteria2.masquerade"></v-text-field>
|
||||
v-model="data.masquerade">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch v-model="hysteria2.ignore_client_bandwidth" color="primary" label="Ignore Client Bandwidth" hide-details></v-switch>
|
||||
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="!hysteria2.ignore_client_bandwidth">
|
||||
<v-row v-else>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<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
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
:suffix="$t('stats.Mbps')"
|
||||
min="0"
|
||||
v-model.number="up_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Downlink Limit"
|
||||
:label="$t('stats.download')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="Mbps"
|
||||
:suffix="$t('stats.Mbps')"
|
||||
min="0"
|
||||
v-model.number="down_mbps">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="hysteria2.obfs">
|
||||
<v-row v-if="data.obfs">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="obfs Password"
|
||||
:label="$t('types.hy.obfs')"
|
||||
hide-details
|
||||
v-model="hysteria2.obfs.password">
|
||||
v-model="data.obfs.password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -45,12 +59,12 @@
|
||||
<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>Options</v-btn>
|
||||
<v-btn v-bind="props" hide-details>{{ $t('types.hy.hy2Options') }}</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<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>
|
||||
</v-card>
|
||||
@@ -60,32 +74,29 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Hysteria2, createInbound } from '@/types/inbounds'
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction', 'data'],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
hysteria2: <Hysteria2> createInbound("hysteria2",{ "tag": "" }),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
down_mbps: {
|
||||
get() { return this.hysteria2.down_mbps ? this.hysteria2.down_mbps : 0 },
|
||||
set(newValue:any) { this.hysteria2.down_mbps = newValue.length == 0 ? undefined : this.hysteria2.down_mbps }
|
||||
get() { return this.$props.data.down_mbps?? 0 },
|
||||
set(newValue:number) { this.$props.data.down_mbps = newValue>0 ? newValue : undefined }
|
||||
},
|
||||
up_mbps: {
|
||||
get() { return this.hysteria2.up_mbps ? this.hysteria2.up_mbps : 0 },
|
||||
set(newValue:any) { this.hysteria2.up_mbps = newValue.length == 0 ? undefined : this.hysteria2.up_mbps }
|
||||
get() { return this.$props.data.up_mbps?? 0 },
|
||||
set(newValue:number) { this.$props.data.up_mbps = newValue>0 ? newValue : undefined }
|
||||
},
|
||||
optionObfs: {
|
||||
get(): boolean { return this.hysteria2.obfs != undefined },
|
||||
set(v:boolean) { this.$props.inbound.obfs = v ? { type: "salamander", password: ""} : undefined }
|
||||
get(): boolean { return this.$props.data.obfs != undefined },
|
||||
set(v:boolean) { this.$props.data.obfs = v ? { type: "salamander", password: "" } : undefined }
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.hysteria2 = <Hysteria2> this.$props.inbound
|
||||
}
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-card subtitle="Naive">
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<Network :data="inbound" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</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
|
||||
hide-details
|
||||
:items="[1,2,3]"
|
||||
label="Version"
|
||||
:label="$t('version')"
|
||||
v-model="version">
|
||||
</v-select>
|
||||
</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
|
||||
label="Password"
|
||||
:label="$t('types.pw')"
|
||||
hide-details
|
||||
v-model="inbound.password">
|
||||
v-model="data.password">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Handshake Server"
|
||||
:label="$t('types.shdwTls.hs')"
|
||||
hide-details
|
||||
v-model="Inbound.handshake.server">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Port"
|
||||
:label="$t('out.port')"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
@@ -35,11 +35,11 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Dial :dial="Inbound.handshake" />
|
||||
<Dial :dial="Inbound.handshake" :outTags="outTags" />
|
||||
<v-row v-if="Inbound.handshake_for_server_name != undefined">
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Add Hanshake Server"
|
||||
:label="$t('types.shdwTls.adHS')"
|
||||
hide-details
|
||||
append-icon="mdi-plus"
|
||||
@click:append="addHandshakeServer()"
|
||||
@@ -67,14 +67,14 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Handshake Server"
|
||||
:label="$t('types.shdwTls.hs')"
|
||||
hide-details
|
||||
v-model="value.server">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Server Port"
|
||||
:label="$t('out.port')"
|
||||
type="number"
|
||||
min="0"
|
||||
hide-details
|
||||
@@ -82,7 +82,7 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Dial :dial="value" />
|
||||
<Dial :dial="value" :outTags="outTags" />
|
||||
</v-card>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -92,7 +92,7 @@ import { ShadowTLS } from '@/types/inbounds'
|
||||
import Dial from '../Dial.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['data', 'outTags'],
|
||||
data() {
|
||||
return {
|
||||
handshake_server: ''
|
||||
@@ -100,7 +100,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
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
|
||||
this.handshake_server = ''
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
}
|
||||
},
|
||||
Inbound(): ShadowTLS {
|
||||
return <ShadowTLS>this.$props.inbound;
|
||||
return <ShadowTLS>this.$props.data;
|
||||
},
|
||||
server_port: {
|
||||
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-select
|
||||
hide-details
|
||||
label="Method"
|
||||
:label="$t('in.ssMethod')"
|
||||
:items="ssMethods"
|
||||
v-model="inbound.method">
|
||||
v-model="data.method">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<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 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-row>
|
||||
</v-card>
|
||||
@@ -21,9 +24,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Network from '@/components/Network.vue'
|
||||
import UoT from '@/components/UoT.vue';
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction','data'],
|
||||
data() {
|
||||
return {
|
||||
ssMethods: [
|
||||
@@ -39,6 +43,6 @@ export default {
|
||||
]
|
||||
}
|
||||
},
|
||||
components: { Network }
|
||||
components: { Network, UoT }
|
||||
}
|
||||
</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-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<Network :inbound="inbound" />
|
||||
<Network :data="inbound" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</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>
|
||||
<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-select
|
||||
hide-details
|
||||
label="Congestion Control"
|
||||
:items="congestion_controls"
|
||||
v-model="inbound.congestion_control">
|
||||
label="UDP Relay Mode"
|
||||
:items="['native', 'quic']"
|
||||
clearable
|
||||
@click:clear="delete data.udp_relay_mode"
|
||||
v-model="data.udp_relay_mode">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="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-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('types.tuic.congControl')"
|
||||
:items="congestion_controls"
|
||||
v-model="data.congestion_control">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-switch color="primary" label="Zero-RTT Handshake" v-model="data.zero_rtt_handshake" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
||||
<v-text-field
|
||||
label="Authentication Timeout"
|
||||
:label="$t('types.tuic.authTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
:suffix="$t('date.s')"
|
||||
min="1"
|
||||
v-model.number="auth_timeout">
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Heartbeat"
|
||||
:label="$t('types.tuic.hb')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
:suffix="$t('date.s')"
|
||||
min="1"
|
||||
v-model.number="heartbeat">
|
||||
</v-text-field>
|
||||
@@ -39,9 +63,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { TUIC } from '@/types/inbounds'
|
||||
import Network from '@/components/Network.vue'
|
||||
|
||||
export default {
|
||||
props: ['inbound'],
|
||||
props: ['direction', 'data'],
|
||||
data() {
|
||||
return {
|
||||
congestion_controls: [
|
||||
@@ -50,17 +75,15 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
Inbound(): TUIC {
|
||||
return <TUIC> this.$props.inbound
|
||||
},
|
||||
auth_timeout: {
|
||||
get() { return this.Inbound.auth_timeout ? parseInt(this.Inbound.auth_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.inbound.auth_timeout = newValue ? newValue + 's' : '' }
|
||||
get() { return this.$props.data.auth_timeout ? parseInt(this.$props.data.auth_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.data.auth_timeout = newValue ? newValue + 's' : '' }
|
||||
},
|
||||
heartbeat: {
|
||||
get() { return this.Inbound.heartbeat ? parseInt(this.Inbound.heartbeat.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.inbound.heartbeat = newValue ? newValue + 's' : '' }
|
||||
}
|
||||
get() { return this.$props.data.heartbeat ? parseInt(this.$props.data.heartbeat.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.data.heartbeat = newValue ? newValue + 's' : '' }
|
||||
}
|
||||
},
|
||||
components: { Network }
|
||||
}
|
||||
</script>
|
||||
@@ -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>
|
||||
@@ -15,17 +15,20 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Method"
|
||||
<v-select
|
||||
:label="$t('transport.httpMethod')"
|
||||
hide-details
|
||||
clearable
|
||||
@click:clear="delete transport.method"
|
||||
:items="methodList"
|
||||
v-model="transport.method">
|
||||
</v-text-field>
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Idle Timeout"
|
||||
:label="$t('transport.idleTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
@@ -35,7 +38,7 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Ping Timeout"
|
||||
:label="$t('transport.pingTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
@@ -44,14 +47,17 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { HTTP } from '../../types/transport'
|
||||
import Headers from '../Headers.vue'
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
return {
|
||||
methodList: ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -70,6 +76,7 @@ export default {
|
||||
get() { return this.Http.ping_timeout ? parseInt(this.Http.ping_timeout.replace('s','')) : '' },
|
||||
set(newValue:number) { this.$props.transport.ping_timeout = newValue ? newValue + 's' : '' }
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -15,14 +15,17 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Headers from '../Headers.vue';
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -33,10 +33,12 @@
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Headers :data="transport" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { WebSocket } from '../../types/transport'
|
||||
import Headers from '../Headers.vue'
|
||||
export default {
|
||||
props: ['transport'],
|
||||
data() {
|
||||
@@ -61,6 +63,7 @@ export default {
|
||||
mounted() {
|
||||
this.WS.early_data_header_name ??= 'Sec-WebSocket-Protocol'
|
||||
this.WS.path ??= '/'
|
||||
}
|
||||
},
|
||||
components: { Headers }
|
||||
}
|
||||
</script>
|
||||
@@ -2,7 +2,7 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Service Name"
|
||||
:label="$t('transport.grpcServiceName')"
|
||||
hide-details
|
||||
v-model="transport.service_name">
|
||||
</v-text-field>
|
||||
@@ -11,7 +11,7 @@
|
||||
<v-switch
|
||||
color="primary"
|
||||
v-model="transport.permit_without_stream"
|
||||
label="Permit Without Stream"
|
||||
:label="$t('transport.grpcPws')"
|
||||
hide-details>
|
||||
</v-switch>
|
||||
</v-col>
|
||||
@@ -19,7 +19,7 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Idle Timeout"
|
||||
:label="$t('transport.idleTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
@@ -29,7 +29,7 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
label="Ping Timeout"
|
||||
:label="$t('transport.pingTimeout')"
|
||||
hide-details
|
||||
type="number"
|
||||
suffix="s"
|
||||
|
||||
@@ -30,5 +30,6 @@ const isMobile = computed( ():boolean =>{
|
||||
.v-card-subtitle {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid gray;
|
||||
min-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -87,7 +87,8 @@ export default {
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) { if (newValue) {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.resetData()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
<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-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-col cols="12" sm="6" md="4">
|
||||
@@ -139,6 +142,7 @@
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="tonal"
|
||||
:loading="loading"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
@@ -160,6 +164,7 @@ export default {
|
||||
return {
|
||||
client: createClient(),
|
||||
title: "add",
|
||||
loading: false,
|
||||
clientStats: false,
|
||||
tab: "t1",
|
||||
clientConfig: <any>[],
|
||||
@@ -193,12 +198,14 @@ export default {
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.client.config = updateConfigs(JSON.stringify(this.clientConfig), this.client.name)
|
||||
this.client.links = JSON.stringify([
|
||||
...this.links,
|
||||
...this.extLinks.filter(l => l.uri != ''),
|
||||
...this.subLinks.filter(l => l.uri != '')])
|
||||
this.$emit('save', this.client, this.clientStats)
|
||||
this.loading = false
|
||||
},
|
||||
setDate(newDate:number){
|
||||
this.client.expiry = newDate
|
||||
@@ -206,7 +213,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
clientInbounds: {
|
||||
get() { return this.client.inbounds == "" ? [] : this.client.inbounds.split(',') },
|
||||
get() { return this.client.inbounds == "" ? [] : this.client.inbounds.split(',').filter(i => this.inboundTags.includes(i)) },
|
||||
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? "" : newValue.join(',') }
|
||||
},
|
||||
expDate: {
|
||||
@@ -219,7 +226,8 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(newValue) { if (newValue) {
|
||||
visible(newValue) {
|
||||
if (newValue) {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-select
|
||||
hide-details
|
||||
width="100"
|
||||
:label="$t('type')"
|
||||
:items="Object.keys(inTypes).map((key,index) => ({title: key, value: Object.values(inTypes)[index]}))"
|
||||
v-model="inbound.type"
|
||||
@@ -18,22 +17,22 @@
|
||||
</v-select>
|
||||
</v-col>
|
||||
<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-row>
|
||||
<Listen :inbound="inbound" />
|
||||
<Direct v-if="inbound.type == inTypes.Direct" :inbound="inbound" />
|
||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" :inbound="inbound" />
|
||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" :inbound="inbound" />
|
||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" :inbound="inbound" />
|
||||
<Listen :inbound="inbound" :inTags="inTags" />
|
||||
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||
<Naive v-if="inbound.type == inTypes.Naive" :inbound="inbound" />
|
||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" :inbound="inbound" />
|
||||
<Tuic v-if="inbound.type == inTypes.TUIC" :inbound="inbound" />
|
||||
<ShadowTls v-if="inbound.type == inTypes.ShadowTLS" direction="in" :data="inbound" :outTags="outTags" />
|
||||
<Tuic v-if="inbound.type == inTypes.TUIC" direction="in" :data="inbound" />
|
||||
<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" />
|
||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" />
|
||||
<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-card-text>
|
||||
<v-card-actions>
|
||||
@@ -48,6 +47,7 @@
|
||||
<v-btn
|
||||
color="blue-darken-1"
|
||||
variant="text"
|
||||
:loading="loading"
|
||||
@click="saveChanges"
|
||||
>
|
||||
{{ $t('actions.save') }}
|
||||
@@ -72,15 +72,16 @@ import Tuic from '@/components/protocols/Tuic.vue'
|
||||
import InTls from '@/components/InTLS.vue'
|
||||
import TProxy from '@/components/protocols/TProxy.vue'
|
||||
import RandomUtil from '@/plugins/randomUtil'
|
||||
import InMulitiplex from '@/components/InMulitiplex.vue'
|
||||
import Multiplex from '@/components/Multiplex.vue'
|
||||
import Transport from '@/components/Transport.vue'
|
||||
export default {
|
||||
props: ['visible', 'data', 'id', 'stats'],
|
||||
props: ['visible', 'data', 'id', 'stats', 'inTags', 'outTags'],
|
||||
emits: ['close', 'save'],
|
||||
data() {
|
||||
return {
|
||||
inbound: createInbound("direct",{ "tag": "" }),
|
||||
title: "add",
|
||||
loading: false,
|
||||
inTypes: InTypes,
|
||||
inboundStats: false,
|
||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||
@@ -95,13 +96,16 @@ export default {
|
||||
}
|
||||
else {
|
||||
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.inboundStats = this.$props.stats
|
||||
},
|
||||
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)
|
||||
},
|
||||
closeModal() {
|
||||
@@ -109,7 +113,9 @@ export default {
|
||||
this.$emit('close')
|
||||
},
|
||||
saveChanges() {
|
||||
this.loading = true
|
||||
this.$emit('save', this.inbound, this.inboundStats)
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
@@ -119,13 +125,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>
|
||||
|
||||
<style>
|
||||
.v-card-subtitle {
|
||||
text-align: center;
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
</style>
|
||||
@@ -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.addr')"
|
||||
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>
|
||||
@@ -43,7 +43,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(txt:string) {
|
||||
|
||||
const clipboard = new Clipboard('.clipboard-btn', {
|
||||
text: () => txt
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
+197
-6
@@ -11,6 +11,7 @@ export default {
|
||||
unlimited: "infinite",
|
||||
remained: "Remained",
|
||||
type: "Type",
|
||||
protocol: "Protocol",
|
||||
submit: "Submit",
|
||||
reset: "Reset",
|
||||
now: "Now",
|
||||
@@ -19,6 +20,11 @@ export default {
|
||||
noData: "No data!",
|
||||
invalidLogin: "Invalid Login!",
|
||||
online: "Online",
|
||||
version: "Version",
|
||||
commaSeparated: "(comma separated)",
|
||||
error: {
|
||||
dplData: "Duplicate Data",
|
||||
},
|
||||
pages: {
|
||||
login: "Login",
|
||||
home: "Home",
|
||||
@@ -63,6 +69,16 @@ export default {
|
||||
outbound: "Outbound",
|
||||
rule: "Rule",
|
||||
user: "User",
|
||||
tag: "Tag",
|
||||
listen: "Listen",
|
||||
dial: "Dial",
|
||||
tls: "TLS",
|
||||
multiplex: "Multiplex",
|
||||
transport: "Transport",
|
||||
method: "Method",
|
||||
headers: "Headers",
|
||||
key: "Key",
|
||||
value: "Value",
|
||||
},
|
||||
actions: {
|
||||
action: "Action",
|
||||
@@ -90,6 +106,9 @@ export default {
|
||||
oldPass: "Current Password",
|
||||
newUname: "New Username",
|
||||
newPass: "New Password",
|
||||
lastLogin: "Last login",
|
||||
date: "Date",
|
||||
time: "Time",
|
||||
},
|
||||
setting: {
|
||||
interface: "Interface",
|
||||
@@ -102,6 +121,7 @@ export default {
|
||||
sslCert: "SSL Certificate Path",
|
||||
webUri: "Panel URI",
|
||||
sessionAge: "Session Maximum Age",
|
||||
trafficAge: "Traffic Maximum Age",
|
||||
timeLoc: "Timezone Location",
|
||||
subEncode: "Enable Encoding",
|
||||
subInfo: "Enable Client Info",
|
||||
@@ -111,6 +131,7 @@ export default {
|
||||
},
|
||||
client: {
|
||||
name: "Name",
|
||||
desc: "Description",
|
||||
inboundTags: "Inbound Tags",
|
||||
basics: "Basics",
|
||||
config: "Config",
|
||||
@@ -118,21 +139,180 @@ export default {
|
||||
external: "External Link",
|
||||
sub: "External Subscription",
|
||||
},
|
||||
types: {
|
||||
un: "Username",
|
||||
pw: "Password",
|
||||
direct: {
|
||||
overrideAddr: "Override Address",
|
||||
overridePort: "Override Port",
|
||||
proxyProtocol: "Proxy Protocol",
|
||||
},
|
||||
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: {
|
||||
tag: "Tag",
|
||||
addr: "Address",
|
||||
port: "Port",
|
||||
sniffing: "Sniffing",
|
||||
tls: "TLS",
|
||||
clients: "Enable Clients",
|
||||
multiplex: "Multiplex",
|
||||
transport: "Transport",
|
||||
ssMethod: "Method",
|
||||
},
|
||||
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: {
|
||||
enable: "Enable Transport",
|
||||
host: "Host",
|
||||
hosts: "Hosts",
|
||||
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: {
|
||||
enable: "Enable TLS",
|
||||
@@ -142,6 +322,13 @@ export default {
|
||||
keyPath: "Key File Path",
|
||||
cert: "Certificate",
|
||||
key: "Key",
|
||||
options: "TLS Options",
|
||||
minVer: "Minimum Version",
|
||||
maxVer: "Maximum Version",
|
||||
cs: "Cipher suits",
|
||||
pubKey: "Public Key",
|
||||
disableSni: "Disable SNI",
|
||||
insecure: "Allow Insecure",
|
||||
},
|
||||
stats: {
|
||||
upload: "Upload",
|
||||
@@ -160,6 +347,9 @@ export default {
|
||||
Kp: "Kp",
|
||||
Mp: "Mp",
|
||||
Gb: "Gb",
|
||||
bps: "bps",
|
||||
Kbps: "Kbps",
|
||||
Mbps: "Mbps",
|
||||
},
|
||||
date: {
|
||||
expiry: "Expiry",
|
||||
@@ -168,5 +358,6 @@ export default {
|
||||
h: "h",
|
||||
m: "m",
|
||||
s: "s",
|
||||
}
|
||||
ms: "ms",
|
||||
},
|
||||
}
|
||||
+195
-5
@@ -11,6 +11,7 @@ export default {
|
||||
unlimited: "نامحدود",
|
||||
remained: "باقیمانده",
|
||||
type: "مدل",
|
||||
protocol: "پروتکل",
|
||||
submit: "تایید",
|
||||
reset: "ریست",
|
||||
now: "اکنون",
|
||||
@@ -19,6 +20,11 @@ export default {
|
||||
noData: "بدون داده!",
|
||||
invalidLogin: "ورود نامعتبر!",
|
||||
online: "آنلاین",
|
||||
version: "نسخه",
|
||||
commaSeparated: "(جداشده با کاما)",
|
||||
error: {
|
||||
dplData: "داده تکراری",
|
||||
},
|
||||
pages: {
|
||||
login: "ورود",
|
||||
home: "خانه",
|
||||
@@ -63,6 +69,15 @@ export default {
|
||||
outbound: "خروجی",
|
||||
rule: "قانون",
|
||||
user: "کاربر",
|
||||
tag: "برچسب",
|
||||
listen: "گوشدادن",
|
||||
dial: "تماس",
|
||||
tls: "رمزنگاری",
|
||||
multiplex: "تسهیم",
|
||||
transport: "انتقال",
|
||||
headers: "سربرگها",
|
||||
key: "نام",
|
||||
value: "مقدار",
|
||||
},
|
||||
actions: {
|
||||
action: "فرمان",
|
||||
@@ -90,6 +105,9 @@ export default {
|
||||
oldPass: "رمز کنونی",
|
||||
newUname: "نام کاربری جدید",
|
||||
newPass: "رمز جدید",
|
||||
lastLogin: "آخرین ورود",
|
||||
date: "تاریخ",
|
||||
time: "ساعت",
|
||||
},
|
||||
setting: {
|
||||
interface: "نما",
|
||||
@@ -102,6 +120,7 @@ export default {
|
||||
sslCert: "مسیر فایل گواهی",
|
||||
webUri: "آدرس نهایی پنل",
|
||||
sessionAge: "بیشینه زمان لاگین ماندن",
|
||||
trafficAge: "بیشینه زمان ذخیره ترافیک",
|
||||
timeLoc: "منطقه زمانی",
|
||||
subEncode: "رمزگذاری",
|
||||
subInfo: "نمایش اطلاعات کاربر",
|
||||
@@ -111,6 +130,7 @@ export default {
|
||||
},
|
||||
client: {
|
||||
name: "نام",
|
||||
desc: "شرح",
|
||||
inboundTags: "برچسبهای ورودی",
|
||||
basics: "پایه",
|
||||
config: "تنظیم",
|
||||
@@ -118,21 +138,180 @@ export default {
|
||||
external: "لینک خارجی",
|
||||
sub: "سابسکریپشن خارجی",
|
||||
},
|
||||
types: {
|
||||
un: "نام کاربری",
|
||||
pw: "رمز",
|
||||
direct: {
|
||||
overrideAddr: "جایگزین آدرس",
|
||||
overridePort: "جایگزین پورت",
|
||||
proxyProtocol: "پروتکل پراکسی",
|
||||
},
|
||||
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: {
|
||||
tag: "برچسب",
|
||||
addr: "آدرس",
|
||||
port: "پورت",
|
||||
sniffing: "مبدل آدرس",
|
||||
tls: "رمزنگاری",
|
||||
clients: "فعالسازی کاربران",
|
||||
multiplex: "تسهیم",
|
||||
transport: "انتقال",
|
||||
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: "مهلت پینگ",
|
||||
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: {
|
||||
enable: "فعالسازی رمزنگاری",
|
||||
@@ -142,6 +321,13 @@ export default {
|
||||
keyPath: "مسیر فایل کلید",
|
||||
cert: "گواهی",
|
||||
key: "کلید",
|
||||
options: "گزینههای رمزنگاری",
|
||||
minVer: "کمینه نسخه",
|
||||
maxVer: "بیشینه نسخه",
|
||||
cs: "مدلهای رمزنگاری",
|
||||
pubKey: "کلید عمومی",
|
||||
disableSni: "غیرفعالسازی SNI",
|
||||
insecure: "تایید ارتباط ناامن",
|
||||
},
|
||||
stats: {
|
||||
upload: "آپلود",
|
||||
@@ -160,6 +346,9 @@ export default {
|
||||
Kp: "کپ",
|
||||
Mp: "مپ",
|
||||
Gp: "گپ",
|
||||
bps: "ب/ث",
|
||||
Kbps: "کب/ث",
|
||||
Mbps: "مب/ث",
|
||||
},
|
||||
date: {
|
||||
expiry: "انقضا",
|
||||
@@ -168,5 +357,6 @@ export default {
|
||||
h: "س",
|
||||
m: "د",
|
||||
s: "ث",
|
||||
ms: "مث",
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,27 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import en from './en'
|
||||
import fa from './fa'
|
||||
import vi from './vi'
|
||||
import zhcn from './zhcn'
|
||||
|
||||
import zhtw from './zhtw'
|
||||
|
||||
export const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: localStorage.getItem("locale") ?? 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
en,
|
||||
fa,
|
||||
zhcn
|
||||
en: en,
|
||||
fa: fa,
|
||||
vi: vi,
|
||||
zhcn: zhcn,
|
||||
zhtw: zhtw
|
||||
},
|
||||
})
|
||||
|
||||
export const languages = [
|
||||
{ title: 'English', value: 'en' },
|
||||
{ title: 'فارسی', value: 'fa' },
|
||||
{ title: 'Tiếng Việt', value: 'vi' },
|
||||
{ title: '简体中文', value: 'zhcn' },
|
||||
{ title: '繁體中文', value: 'zhtw' },
|
||||
]
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
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",
|
||||
commaSeparated: "(được phân tách bằng dấu phẩy)",
|
||||
error: {
|
||||
dplData: "Dữ liệu trùng lặp",
|
||||
},
|
||||
pages: {
|
||||
login: "Đăng nhập",
|
||||
home: "Trang chủ",
|
||||
inbounds: "Đầu Vào",
|
||||
outbounds: "Đầu ra",
|
||||
clients: "Khách hàng",
|
||||
rules: "Quy tắc",
|
||||
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",
|
||||
edit: "Chỉnh sửa",
|
||||
del: "Xóa",
|
||||
save: "Lưu",
|
||||
update: "Cập nhật",
|
||||
submit: "Gửi",
|
||||
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",
|
||||
},
|
||||
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",
|
||||
proxyProtocol: "Giao thức Proxy",
|
||||
},
|
||||
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",
|
||||
pubKey: "Khóa Công khai",
|
||||
disableSni: "Tắt SNI",
|
||||
insecure: "Cho phép Không an toàn",
|
||||
},
|
||||
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",
|
||||
},
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
unlimited: "无限",
|
||||
remained: "剩余",
|
||||
type: "类型",
|
||||
protocol: "协议",
|
||||
submit: "提交",
|
||||
reset: "重置",
|
||||
now: "当前",
|
||||
@@ -19,6 +20,11 @@ export default {
|
||||
noData: "无数据!",
|
||||
invalidLogin: "登录无效!",
|
||||
online: "在线",
|
||||
version: "版本",
|
||||
commaSeparated: "(逗号分隔)",
|
||||
error: {
|
||||
dplData: "重复数据",
|
||||
},
|
||||
pages: {
|
||||
login: "登录",
|
||||
home: "主页",
|
||||
@@ -63,6 +69,16 @@ export default {
|
||||
outbound: "出站",
|
||||
rule: "规则",
|
||||
user: "用户",
|
||||
tag: "标签",
|
||||
listen: "听",
|
||||
dial: "拨号",
|
||||
tls: "TLS",
|
||||
multiplex: "多路复用",
|
||||
transport: "传输",
|
||||
method: "方法",
|
||||
headers: "标头",
|
||||
key: "钥匙",
|
||||
value: "价值",
|
||||
},
|
||||
actions: {
|
||||
action: "操作",
|
||||
@@ -90,6 +106,9 @@ export default {
|
||||
oldPass: "当前密码",
|
||||
newUname: "新用户名",
|
||||
newPass: "新密码",
|
||||
lastLogin: "上次登录",
|
||||
date: "日期",
|
||||
time: "时间",
|
||||
},
|
||||
setting: {
|
||||
interface: "界面",
|
||||
@@ -102,6 +121,7 @@ export default {
|
||||
sslCert: "SSL 证书 (cert) 路径",
|
||||
webUri: "面板 URI",
|
||||
sessionAge: "会话最大连接数",
|
||||
trafficAge: "流量最大年龄",
|
||||
timeLoc: "时区",
|
||||
subEncode: "启用编码",
|
||||
subInfo: "启用用户信息",
|
||||
@@ -111,6 +131,7 @@ export default {
|
||||
},
|
||||
client: {
|
||||
name: "名称",
|
||||
desc: "描述",
|
||||
inboundTags: "入站标签",
|
||||
basics: "基础",
|
||||
config: "配置",
|
||||
@@ -118,21 +139,181 @@ export default {
|
||||
external: "外部链接",
|
||||
sub: "外部订阅",
|
||||
},
|
||||
types: {
|
||||
un: "用户名",
|
||||
pw: "密码",
|
||||
direct: {
|
||||
overrideAddr: "覆盖地址",
|
||||
overridePort: "覆盖端口",
|
||||
proxyProtocol: "代理协议",
|
||||
},
|
||||
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: {
|
||||
tag: "标签",
|
||||
addr: "地址",
|
||||
port: "端口",
|
||||
sniffing: "嗅探",
|
||||
tls: "TLS",
|
||||
clients: "启用客户端",
|
||||
multiplex: "多路复用",
|
||||
transport: "传输",
|
||||
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",
|
||||
@@ -142,6 +323,13 @@ export default {
|
||||
keyPath: "私钥文件路径",
|
||||
cert: "证书文件内容",
|
||||
key: "私钥文件内容",
|
||||
options: "TLS 选项",
|
||||
minVer: "最低版本",
|
||||
maxVer: "最高版本",
|
||||
cs: "密码套件",
|
||||
pubKey: "公钥",
|
||||
disableSni: "禁用SNI",
|
||||
insecure: "允许不安全",
|
||||
},
|
||||
stats: {
|
||||
upload: "上传",
|
||||
@@ -160,6 +348,9 @@ export default {
|
||||
Kp: "Kp",
|
||||
Mp: "Mp",
|
||||
Gb: "Gb",
|
||||
bps: "bps",
|
||||
Kbps: "Kbps",
|
||||
Mbps: "Mbps",
|
||||
},
|
||||
date: {
|
||||
expiry: "到期",
|
||||
@@ -168,5 +359,6 @@ export default {
|
||||
h: "时",
|
||||
m: "分",
|
||||
s: "秒",
|
||||
}
|
||||
ms: "毫秒",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
export default {
|
||||
open: "打開",
|
||||
message: "歡迎",
|
||||
success: "成功",
|
||||
failed: "失敗",
|
||||
enable: "啟用",
|
||||
disable: "禁用",
|
||||
loading: "加載中...",
|
||||
confirm: "是否確定?",
|
||||
yes: "確認",
|
||||
no: "取消",
|
||||
unlimited: "無限",
|
||||
remained: "剩余",
|
||||
type: "類型",
|
||||
protocol: "協定",
|
||||
submit: "提交",
|
||||
reset: "重置",
|
||||
now: "當前",
|
||||
network: "網絡",
|
||||
copyToClipboard: "復製到剪貼板",
|
||||
noData: "無數據!",
|
||||
invalidLogin: "登錄無效!",
|
||||
online: "在線",
|
||||
version: "版本",
|
||||
commaSeparated: "(逗號分隔)",
|
||||
error: {
|
||||
dplData: "重複數據",
|
||||
},
|
||||
pages: {
|
||||
login: "登錄",
|
||||
home: "主頁",
|
||||
inbounds: "入站管理",
|
||||
outbounds: "出站管理",
|
||||
clients: "用戶管理",
|
||||
rules: "路由列表",
|
||||
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: "添加",
|
||||
edit: "編輯",
|
||||
del: "刪除",
|
||||
save: "保存",
|
||||
update: "更新",
|
||||
submit: "提交",
|
||||
close: "關閉",
|
||||
restartApp: "重啟面板",
|
||||
},
|
||||
login: {
|
||||
title: "登錄",
|
||||
username: "用戶名",
|
||||
unRules: "用戶名不能為空",
|
||||
password: "密碼",
|
||||
pwRules: "密碼不能為空",
|
||||
},
|
||||
menu: {
|
||||
logout: "退出登錄",
|
||||
},
|
||||
admin: {
|
||||
changeCred: "更改憑據",
|
||||
oldPass: "當前密碼",
|
||||
newUname: "新用戶名",
|
||||
newPass: "新密碼",
|
||||
lastLogin: "上次登入",
|
||||
date: "日期",
|
||||
time: "時間",
|
||||
},
|
||||
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: "覆蓋端口",
|
||||
proxyProtocol: "代理協議",
|
||||
},
|
||||
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 範圍匹配源",
|
||||
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: "加密套件",
|
||||
pubKey: "公鑰",
|
||||
disableSni: "停用 SNI",
|
||||
insecure: "允許不安全連線",
|
||||
},
|
||||
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: "毫秒",
|
||||
},
|
||||
}
|
||||
@@ -173,7 +173,7 @@ export namespace LinkUtil {
|
||||
const tParams = getTransportParams(transport)
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'none',
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
@@ -196,7 +196,7 @@ export namespace LinkUtil {
|
||||
const tParams = getTransportParams(transport)
|
||||
|
||||
const params = {
|
||||
type: transport?.type?? 'none',
|
||||
type: transport?.type?? 'tcp',
|
||||
security: inbound.tls?.enabled? 'tls' : null,
|
||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
||||
sni: inbound.tls?.server_name?? null,
|
||||
@@ -224,7 +224,7 @@ export namespace LinkUtil {
|
||||
aid: u?.alterId,
|
||||
host: tParams.host,
|
||||
id: u?.uuid,
|
||||
net: transport.type,
|
||||
net: transport?.type?? 'tcp',
|
||||
path: tParams.path,
|
||||
port: inbound.listen_port,
|
||||
ps: inbound.tag,
|
||||
|
||||
@@ -51,6 +51,15 @@ const Data = defineStore('Data', {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delOutbound(index: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
||||
}
|
||||
const msg = await HttpUtils.post('api/save',diff)
|
||||
if(msg.success) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
async delClient(id: number) {
|
||||
const diff = {
|
||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface Brutal {
|
||||
enabled: boolean
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export interface Client {
|
||||
expiry: number
|
||||
up: number
|
||||
down: number
|
||||
desc: string
|
||||
}
|
||||
|
||||
const defaultClient: Client = {
|
||||
@@ -23,6 +24,7 @@ const defaultClient: Client = {
|
||||
expiry: 0,
|
||||
up: 0,
|
||||
down: 0,
|
||||
desc: "",
|
||||
}
|
||||
|
||||
type Config = {
|
||||
@@ -110,6 +112,7 @@ export function randomConfigs(user: string): Config {
|
||||
}
|
||||
|
||||
export function createClient<T extends Client>(json?: Partial<T>): Client {
|
||||
defaultClient.name = RandomUtil.randomSeq(8)
|
||||
const defaultObject: Client = { ...defaultClient, ...(json || {}) }
|
||||
return defaultObject
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
interface Brutal {
|
||||
enabled: boolean
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
}
|
||||
|
||||
export interface iMultiplex{
|
||||
enabled: boolean
|
||||
padding?: boolean
|
||||
brutal?: Brutal
|
||||
}
|
||||
@@ -15,5 +15,5 @@ export const defaultInTls: iTls = {
|
||||
alpn: ['h3', 'h2', 'http/1.1'],
|
||||
min_version: "1.2",
|
||||
max_version: "1.3",
|
||||
cipher_suites: [""],
|
||||
cipher_suites: [],
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { iMultiplex } from "./inMultiplex"
|
||||
import { iMultiplex } from "./multiplex"
|
||||
import { iTls } from "./inTls"
|
||||
import { Dial } from "./outbounds"
|
||||
import { Transport } from "./transport"
|
||||
@@ -119,10 +119,7 @@ export interface Naive extends InboundBasics {
|
||||
export interface Hysteria extends InboundBasics {
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
obfs?: {
|
||||
type?: "salamander"
|
||||
password?: string
|
||||
}
|
||||
obfs?: string
|
||||
users: NameAuth[]
|
||||
recv_window_conn?: number
|
||||
recv_window_client?: number
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Brutal } from "./brutal"
|
||||
|
||||
export interface iMultiplex{
|
||||
enabled: boolean
|
||||
padding?: boolean
|
||||
brutal?: Brutal
|
||||
}
|
||||
|
||||
export interface oMultiplex extends iMultiplex{
|
||||
protocol?: "smux" | "yamux" | "h2mux"
|
||||
max_connections?: number
|
||||
min_streams?: number
|
||||
max_streams?: number
|
||||
}
|
||||
@@ -1,3 +1,50 @@
|
||||
export interface oTls {
|
||||
enabled?: boolean
|
||||
disable_sni?: boolean
|
||||
server_name?: string
|
||||
insecure?: boolean
|
||||
alpn?: string[]
|
||||
min_version?: string
|
||||
max_version?: string
|
||||
cipher_suites?: string[]
|
||||
certificate?: string
|
||||
certificate_path?: string
|
||||
ech?: {
|
||||
enabled: boolean
|
||||
pq_signature_schemes_enabled?: boolean
|
||||
dynamic_record_sizing_disabled?: boolean
|
||||
config?: string[],
|
||||
config_path?: string
|
||||
},
|
||||
utls?: {
|
||||
enabled: boolean
|
||||
fingerprint: string
|
||||
},
|
||||
reality?: {
|
||||
enabled: boolean
|
||||
public_key: string
|
||||
short_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultOutTls: oTls = {
|
||||
alpn: ['h3', 'h2', 'http/1.1'],
|
||||
min_version: "1.2",
|
||||
max_version: "1.3",
|
||||
cipher_suites: [],
|
||||
utls: {
|
||||
enabled: true,
|
||||
fingerprint: "chrome",
|
||||
},
|
||||
reality: {
|
||||
enabled: true,
|
||||
public_key: "",
|
||||
short_id: "",
|
||||
},
|
||||
ech: {
|
||||
enabled: true,
|
||||
pq_signature_schemes_enabled: false,
|
||||
dynamic_record_sizing_disabled: false,
|
||||
config_path: "",
|
||||
}
|
||||
}
|
||||
+208
-13
@@ -1,4 +1,6 @@
|
||||
import { oTls } from "./outTls"
|
||||
import { oMultiplex } from "./multiplex"
|
||||
import { Transport } from "./transport"
|
||||
|
||||
export const OutTypes = {
|
||||
Direct: 'direct',
|
||||
@@ -14,7 +16,7 @@ export const OutTypes = {
|
||||
ShadowTLS: 'shadowtls',
|
||||
TUIC: 'tuic',
|
||||
Hysteria2: 'hysteria2',
|
||||
Tur: 'tur',
|
||||
Tor: 'tor',
|
||||
SSH: 'ssh',
|
||||
DNS: 'dns',
|
||||
Selector: 'selector',
|
||||
@@ -43,12 +45,205 @@ interface OutboundBasics {
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface WgPeer {
|
||||
server: string
|
||||
server_port: number
|
||||
public_key: string
|
||||
pre_shared_key?: string
|
||||
allowed_ips?: string[]
|
||||
reserved?: number[]
|
||||
}
|
||||
|
||||
export interface Direct extends OutboundBasics, Dial {
|
||||
override_address?: string
|
||||
override_port?: number
|
||||
proxy_protocol?: 0 | 1 | 2
|
||||
}
|
||||
|
||||
export interface Block extends OutboundBasics {}
|
||||
|
||||
export interface SOCKS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
version?: "4" | "4a" | "5"
|
||||
username?: string
|
||||
password?: string
|
||||
network?: "udp" | "tcp"
|
||||
udp_over_tcp?: false | {
|
||||
enabled: true
|
||||
version?: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface HTTP extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
username?: string
|
||||
password?: string
|
||||
path?: string
|
||||
headers?: {
|
||||
[key: string]: string
|
||||
}
|
||||
tls?: oTls
|
||||
}
|
||||
|
||||
export interface Shadowsocks extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
method: string
|
||||
password: string
|
||||
network?: "udp" | "tcp"
|
||||
udp_over_tcp?: false | {
|
||||
enabled: true
|
||||
version?: number
|
||||
}
|
||||
multiplex?: oMultiplex
|
||||
}
|
||||
|
||||
export interface VMESS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
security?: string
|
||||
alter_id: 0
|
||||
global_padding?: boolean
|
||||
authenticated_length?: boolean
|
||||
network?: "udp" | "tcp"
|
||||
packet_encoding?: string
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface Trojan extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
password: string
|
||||
network?: "udp" | "tcp"
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface WireGuard extends OutboundBasics, Dial {
|
||||
server?: string
|
||||
server_port?: number
|
||||
system_interface?: boolean
|
||||
gso?: boolean
|
||||
interface_name?: string
|
||||
local_address: string[]
|
||||
private_key: string
|
||||
peers?: WgPeer[]
|
||||
peer_public_key?: string
|
||||
pre_shared_key?: string
|
||||
reserved?: number[]
|
||||
workers?: number
|
||||
mtu?: number
|
||||
network?: "udp" | "tcp"
|
||||
}
|
||||
|
||||
export interface Hysteria extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
up_mbps: number
|
||||
down_mbps: number
|
||||
obfs?: string
|
||||
auth_str?: string
|
||||
recv_window_conn?: number
|
||||
recv_window?: number
|
||||
disable_mtu_discovery?: boolean
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface ShadowTLS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
version: 1|2|3
|
||||
password?: string
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface VLESS extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
flow?: string
|
||||
network?: "udp" | "tcp"
|
||||
packet_encoding?: string
|
||||
tls?: oTls
|
||||
multiplex?: oMultiplex
|
||||
transport?: Transport
|
||||
}
|
||||
|
||||
export interface TUIC extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
uuid: string
|
||||
password?: string
|
||||
congestion_control?: "cubic"|"new_reno"|"bbr"
|
||||
udp_relay_mode?: "native" | "quic"
|
||||
udp_over_stream?: boolean
|
||||
zero_rtt_handshake?: boolean
|
||||
heartbeat?: string
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
}
|
||||
|
||||
export interface Hysteria2 extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port: number
|
||||
up_mbps?: number
|
||||
down_mbps?: number
|
||||
obfs?: {
|
||||
type?: "salamander"
|
||||
password: string
|
||||
}
|
||||
password?: string
|
||||
network?: "udp" | "tcp"
|
||||
tls: oTls
|
||||
brutal_debug?: boolean
|
||||
}
|
||||
|
||||
export interface Tor extends OutboundBasics, Dial {
|
||||
executable_path?: string
|
||||
extra_args?: string[]
|
||||
data_directory: string
|
||||
torrc?: {
|
||||
ClientOnly: 0 | 1
|
||||
}
|
||||
}
|
||||
|
||||
export interface SSH extends OutboundBasics, Dial {
|
||||
server: string
|
||||
server_port?: number
|
||||
user?: string
|
||||
password?: string
|
||||
private_key?: string
|
||||
private_key_path?: string
|
||||
private_key_passphrase?: string
|
||||
host_key?: string[]
|
||||
host_key_algorithms?: string[]
|
||||
client_version?: string
|
||||
}
|
||||
|
||||
export interface DNS extends OutboundBasics {}
|
||||
|
||||
export interface Selector extends OutboundBasics {
|
||||
outbounds: string[]
|
||||
url?: string
|
||||
interval?: string
|
||||
tolerance?: number
|
||||
idle_timeout?: string
|
||||
interrupt_exist_connections?: boolean
|
||||
}
|
||||
|
||||
export interface URLTest extends OutboundBasics {
|
||||
outbounds: string[]
|
||||
default?: string
|
||||
interrupt_exist_connections?: boolean
|
||||
}
|
||||
|
||||
// Create interfaces dynamically based on OutTypes keys
|
||||
type InterfaceMap = {
|
||||
[Key in keyof typeof OutTypes]: {
|
||||
@@ -64,18 +259,18 @@ export type Outbound = InterfaceMap[keyof InterfaceMap]
|
||||
const defaultValues: Record<OutType, Outbound> = {
|
||||
direct: { type: OutTypes.Direct },
|
||||
block: { type: OutTypes.Block },
|
||||
socks: { type: OutTypes.SOCKS },
|
||||
http: { type: OutTypes.HTTP },
|
||||
shadowsocks: { type: OutTypes.Shadowsocks },
|
||||
vmess: { type: OutTypes.VMess, tls: { enabled: true } },
|
||||
trojan: { type: OutTypes.Trojan },
|
||||
wireguard: { type: OutTypes.Wireguard },
|
||||
hysteria: { type: OutTypes.Hysteria },
|
||||
vless: { type: OutTypes.VLESS },
|
||||
shadowtls: { type: OutTypes.ShadowTLS },
|
||||
tuic: { type: OutTypes.TUIC },
|
||||
hysteria2: { type: OutTypes.Hysteria2, users: [], tls: {} },
|
||||
tur: { type: OutTypes.Tur },
|
||||
socks: { type: OutTypes.SOCKS, version: "5" },
|
||||
http: { type: OutTypes.HTTP, tls: {} },
|
||||
shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||
vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false },
|
||||
trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} },
|
||||
wireguard: { type: OutTypes.Wireguard, local_address: ['10.0.0.2/32','fe80::2/128'], private_key: '' },
|
||||
hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||
shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } },
|
||||
vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} },
|
||||
tuic: { type: OutTypes.TUIC, congestion_control: 'cubic', tls: { enabled: true } },
|
||||
hysteria2: { type: OutTypes.Hysteria2, tls: { enabled: true } },
|
||||
tor: { type: OutTypes.Tor, executable_path: './tor', data_directory: '$HOME/.cache/tor', torrc: { ClientOnly: 1 } },
|
||||
ssh: { type: OutTypes.SSH },
|
||||
dns: { type: OutTypes.DNS },
|
||||
selector: { type: OutTypes.Selector },
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
export interface logicalRule {
|
||||
type: 'logical' | 'simple'
|
||||
mode: 'and' | 'or'
|
||||
rules: rule[]
|
||||
invert: boolean
|
||||
outbound: string
|
||||
}
|
||||
|
||||
export interface rule {
|
||||
inbound?: string[]
|
||||
ip_version?: 4 | 6
|
||||
network?: string[]
|
||||
auth_user?: string[]
|
||||
protocol?: string[]
|
||||
domain?: string[]
|
||||
domain_suffix?: string[]
|
||||
domain_keyword?: string[]
|
||||
domain_regex?: string[]
|
||||
source_ip_cidr?: string[]
|
||||
source_ip_is_private?: boolean
|
||||
ip_cidr?: string[]
|
||||
ip_is_private?: boolean
|
||||
source_port?: number[]
|
||||
source_port_range?: string[]
|
||||
port?: number[]
|
||||
port_range?: string[]
|
||||
process_name?: string[]
|
||||
process_path?: string[]
|
||||
package_name?: string[]
|
||||
user?: string[]
|
||||
user_id?: number[]
|
||||
clash_mode?: string
|
||||
rule_set?: string[]
|
||||
rule_set_ipcidr_match_source?: boolean
|
||||
invert?: boolean
|
||||
outbound?: string
|
||||
}
|
||||
|
||||
export interface ruleset {
|
||||
type: 'local' | 'remote'
|
||||
tag: string
|
||||
format: 'source' | 'binary'
|
||||
path?: string
|
||||
url?: string
|
||||
download_detour?: string
|
||||
update_interval?: string
|
||||
}
|
||||
@@ -9,18 +9,18 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>users" :key="item.id">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.username">
|
||||
<v-card-subtitle>
|
||||
Last Login
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
{{ $t('admin.lastLogin') }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>Date</v-col>
|
||||
<v-col>{{ $t('admin.date') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.lastLogin.split(" ")[0]?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>Time</v-col>
|
||||
<v-col>{{ $t('admin.time') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.lastLogin.split(" ")[1]?? '-' }}
|
||||
</v-col>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel title="Log">
|
||||
<v-expansion-panel :title="$t('basic.log.title')">
|
||||
<v-expansion-panel-text>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
@@ -9,7 +9,7 @@
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Level"
|
||||
:label="$t('basic.log.level')"
|
||||
:items="levels"
|
||||
v-model="appConfig.log.level">
|
||||
</v-select>
|
||||
@@ -18,11 +18,11 @@
|
||||
<v-text-field
|
||||
v-model="appConfig.log.output"
|
||||
hide-details
|
||||
label="Output"
|
||||
:label="$t('basic.log.output')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-switch v-model="appConfig.log.timestamp" color="primary" label="Timestamp" hide-details></v-switch>
|
||||
<v-switch v-model="appConfig.log.timestamp" color="primary" :label="$t('basic.log.timestamp')" hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-text>
|
||||
@@ -33,15 +33,15 @@
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Final"
|
||||
:items="[ {title: 'First Server', value: ''}, ...dnsServersTags]"
|
||||
:label="$t('basic.dns.final')"
|
||||
:items="[ {title: $t('basic.dns.firstServer'), value: ''}, ...dnsServersTags]"
|
||||
v-model="finalDns">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Domain to IP Strategy"
|
||||
:label="$t('listen.domainStrategy')"
|
||||
clearable
|
||||
@click:clear="delete appConfig.dns.strategy"
|
||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||
@@ -50,12 +50,12 @@
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" align-self="center">
|
||||
<v-btn @click="addDnsServer" rounded>
|
||||
<v-icon icon="mdi-plus" />Server
|
||||
<v-icon icon="mdi-plus" />{{ $t('basic.dns.server') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-for="(s, index) in appConfig.dns.servers">
|
||||
Server {{ index+1 }} <v-icon icon="mdi-delete" @click="appConfig.dns.servers.splice(index,1)" />
|
||||
{{ $t('basic.dns.server') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="appConfig.dns.servers.splice(index,1)" />
|
||||
<v-divider></v-divider>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
@@ -64,20 +64,20 @@
|
||||
hide-details
|
||||
clearable
|
||||
@click:clear="delete s.tag"
|
||||
label="Tag"
|
||||
:label="$t('objects.tag')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
v-model="s.address"
|
||||
hide-details
|
||||
label="Address"
|
||||
:label="$t('out.addr')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Outbound"
|
||||
:label="$t('objects.outbound')"
|
||||
clearable
|
||||
@click:clear="delete s.detour"
|
||||
:items="outboundTags"
|
||||
@@ -87,7 +87,7 @@
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
label="Domain Strategy"
|
||||
:label="$t('listen.domainStrategy')"
|
||||
clearable
|
||||
@click:clear="delete s.strategy"
|
||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
||||
@@ -108,7 +108,7 @@
|
||||
<v-text-field
|
||||
v-model="appConfig.ntp.server"
|
||||
hide-details
|
||||
label="Server"
|
||||
:label="$t('out.addr')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" v-if="appConfig.ntp?.enabled">
|
||||
@@ -117,22 +117,64 @@
|
||||
hide-details
|
||||
type="number"
|
||||
clearable
|
||||
@click:clear="delete appConfig.ntp.server_port"
|
||||
label="Server Port"
|
||||
@click:clear="delete appConfig.ntp?.server_port"
|
||||
:label="$t('out.port')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" v-if="appConfig.ntp?.enabled">
|
||||
<v-text-field
|
||||
v-model="ntpInterval"
|
||||
hide-details
|
||||
suffix="m"
|
||||
:suffix="$t('date.m')"
|
||||
min="0"
|
||||
type="number"
|
||||
label="Interval"
|
||||
:label="$t('ruleset.interval')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<Dial :dial="appConfig.ntp" v-if="appConfig.ntp?.enabled" />
|
||||
<Dial :dial="appConfig.ntp" :outTags="outboundTags" v-if="appConfig.ntp?.enabled" />
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
<v-expansion-panel :title="$t('basic.routing.title')">
|
||||
<v-expansion-panel-text>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-select
|
||||
hide-details
|
||||
:label="$t('basic.routing.defaultOut')"
|
||||
clearable
|
||||
@click:clear="delete appConfig.route.final"
|
||||
:items="outboundTags"
|
||||
v-model="appConfig.route.final">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
v-model="appConfig.route.default_interface"
|
||||
hide-details
|
||||
clearable
|
||||
@click:clear="delete appConfig.route.default_interface"
|
||||
:label="$t('basic.routing.defaultIf')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-text-field
|
||||
v-model.number="routeMark"
|
||||
hide-details
|
||||
type="number"
|
||||
min="0"
|
||||
:label="$t('basic.routing.defaultRm')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
<v-switch
|
||||
v-model="appConfig.route.auto_detect_interface"
|
||||
color="primary"
|
||||
:label="$t('basic.routing.autoBind')"
|
||||
hide-details>
|
||||
</v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
<v-expansion-panel title="Experimental">
|
||||
@@ -147,7 +189,7 @@
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.cache_file.path"
|
||||
hide-details
|
||||
label="Path"
|
||||
:label="$t('transport.path')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3" v-if="appConfig.experimental.cache_file">
|
||||
@@ -160,7 +202,7 @@
|
||||
<v-col cols="12" sm="6" md="3" v-if="appConfig.experimental.cache_file">
|
||||
<v-switch v-model="appConfig.experimental.cache_file.store_fakeip"
|
||||
color="primary"
|
||||
label="Store Fake IP"
|
||||
:label="$t('basic.exp.storeFakeIp')"
|
||||
hide-details></v-switch>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -220,7 +262,7 @@
|
||||
<v-text-field
|
||||
v-model="appConfig.experimental.v2ray_api.listen"
|
||||
hide-details
|
||||
label="Listen"
|
||||
:label="$t('objects.listen')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="3">
|
||||
@@ -305,6 +347,11 @@ const addDnsServer = () => {
|
||||
appConfig.value.dns.servers.push({address: 'local'})
|
||||
}
|
||||
|
||||
const routeMark = computed({
|
||||
get() { return appConfig.value.route.default_mark?? 0 },
|
||||
set(v:number) { v>0 ? appConfig.value.route.default_mark = v : delete appConfig.value.route.default_mark }
|
||||
})
|
||||
|
||||
const enableNtp = computed({
|
||||
get() { return appConfig.value.ntp?.enabled?? false },
|
||||
set(v:boolean) {
|
||||
@@ -330,10 +377,6 @@ const enableCacheFile = computed({
|
||||
|
||||
const enableClashApi = computed({
|
||||
get() { return appConfig.value.experimental.clash_api != undefined },
|
||||
set(v:boolean) {
|
||||
if (v){
|
||||
appConfig.value.experimental.clash_api = {}
|
||||
} else { delete appConfig.value.experimental.clash_api }
|
||||
}
|
||||
set(v:boolean) { v ? appConfig.value.experimental.clash_api = {} : delete appConfig.value.experimental.clash_api }
|
||||
})
|
||||
</script>
|
||||
@@ -40,15 +40,19 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.desc }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom">
|
||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
||||
<span v-for="i in item.inbounds.split(',')">{{ i }}<br /></span>
|
||||
</v-tooltip>
|
||||
{{ item.inbounds.split(',').length }}
|
||||
{{ item.inbounds != '' ? item.inbounds.split(',').length : 0 }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
@@ -128,6 +132,8 @@ import { Config, V2rayApiStats } from '@/types/config'
|
||||
import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import { HumanReadable } from '@/plugins/utils'
|
||||
import Message from '@/store/modules/message'
|
||||
import { i18n } from '@/locales'
|
||||
|
||||
const clients = computed((): any[] => {
|
||||
return Data().clients
|
||||
@@ -146,12 +152,12 @@ const v2rayStats = computed((): V2rayApiStats => {
|
||||
})
|
||||
|
||||
const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value.inbounds
|
||||
return <Inbound[]> appConfig.value?.inbounds
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
if (!inbounds.value) return []
|
||||
return inbounds.value.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
|
||||
return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
@@ -173,6 +179,11 @@ const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:any, stats:boolean) => {
|
||||
if (clients.value.findIndex(c => c.name == data.name) != modal.value.index) {
|
||||
const sb = Message()
|
||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('client.name') ,'error', 5000)
|
||||
return
|
||||
}
|
||||
const inboundTags: string[] = data.inbounds.split(',')?? []
|
||||
let oldName:string = ""
|
||||
if(modal.value.index == -1) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
:id="modal.id"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:inTags="inTags"
|
||||
:outTags="outTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
@@ -23,7 +25,7 @@
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>inbounds" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
@@ -42,7 +44,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('in.tls') }}</v-col>
|
||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
||||
</v-col>
|
||||
@@ -106,6 +108,8 @@ import { computed, ref } from 'vue'
|
||||
import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
||||
import { Client } from '@/types/clients'
|
||||
import { Link, LinkUtil } from '@/plugins/link'
|
||||
import Message from '@/store/modules/message'
|
||||
import { i18n } from '@/locales'
|
||||
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
@@ -115,6 +119,14 @@ const inbounds = computed((): Inbound[] => {
|
||||
return <Inbound[]> appConfig.value.inbounds
|
||||
})
|
||||
|
||||
const inTags = computed((): string[] => {
|
||||
return inbounds.value?.map(i => i.tag)
|
||||
})
|
||||
|
||||
const outTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds?.map(i => i.tag)
|
||||
})
|
||||
|
||||
const clients = computed((): Client[] => {
|
||||
return <Client[]> Data().clients
|
||||
})
|
||||
@@ -124,7 +136,7 @@ const onlines = computed(() => {
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental.v2ray_api.stats
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
@@ -146,6 +158,11 @@ const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Inbound, stats: boolean) => {
|
||||
if (inbounds.value.findIndex(c => c.tag == data.tag) != modal.value.id) {
|
||||
const sb = Message()
|
||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
||||
return
|
||||
}
|
||||
// New or Edit
|
||||
if (modal.value.id == -1) {
|
||||
inbounds.value.push(data)
|
||||
|
||||
@@ -1,20 +1,89 @@
|
||||
<template>
|
||||
<OutboundVue
|
||||
v-model="modal.visible"
|
||||
:visible="modal.visible"
|
||||
:id="modal.id"
|
||||
:stats="modal.stats"
|
||||
:data="modal.data"
|
||||
:tags="outboundTags"
|
||||
@close="closeModal"
|
||||
@save="saveModal"
|
||||
/>
|
||||
<Stats
|
||||
v-model="stats.visible"
|
||||
:visible="stats.visible"
|
||||
:resource="stats.resource"
|
||||
:tag="stats.tag"
|
||||
@close="closeStats"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>outbounds" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||
<v-card-subtitle>
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>Server</v-col>
|
||||
<v-col>{{ $t('in.addr') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ (item.server ?? '') + ' ' + (item.server_port ?? '') }}
|
||||
{{ item.server?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('in.port') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.server_port?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('online') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
<template v-if="onlines[index]">
|
||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||
</template>
|
||||
<template v-else>-</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delOverlay[index]"
|
||||
contained
|
||||
class="align-center justify-center"
|
||||
>
|
||||
<v-card :title="$t('actions.del')" rounded="lg">
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delOutbound(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" />
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -22,13 +91,109 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed } from 'vue'
|
||||
import OutboundVue from '@/layouts/modals/Outbound.vue'
|
||||
import Stats from '@/layouts/modals/Stats.vue'
|
||||
import { Config, V2rayApiStats } from '@/types/config';
|
||||
import { Outbound } from '@/types/outbounds';
|
||||
import { computed, ref } from 'vue'
|
||||
import Message from '@/store/modules/message';
|
||||
import { i18n } from '@/locales';
|
||||
|
||||
const appConfig = Data().config
|
||||
const outbounds = computed((): any[] => {
|
||||
if (!appConfig || !('outbounds' in appConfig) || !Array.isArray(appConfig.outbounds)) {
|
||||
return []
|
||||
}
|
||||
return appConfig.outbounds
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const outbounds = computed((): Outbound[] => {
|
||||
return <Outbound[]> appConfig.value.outbounds
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return outbounds.value?.map((o:Outbound) => o.tag)
|
||||
})
|
||||
|
||||
const onlines = computed(() => {
|
||||
return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
|
||||
})
|
||||
|
||||
const v2rayStats = computed((): V2rayApiStats => {
|
||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
||||
})
|
||||
|
||||
const modal = ref({
|
||||
visible: false,
|
||||
id: -1,
|
||||
data: "",
|
||||
stats: false,
|
||||
})
|
||||
|
||||
let delOverlay = ref(new Array<boolean>)
|
||||
|
||||
const showModal = (id: number) => {
|
||||
modal.value.id = id
|
||||
modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
|
||||
modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
|
||||
modal.value.visible = true
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
modal.value.visible = false
|
||||
}
|
||||
const saveModal = (data:Outbound, stats: boolean) => {
|
||||
if (outbounds.value.findIndex(c => c.tag == data.tag) != modal.value.id) {
|
||||
const sb = Message()
|
||||
sb.showMessage(i18n.global.t('error.dplData') + ': ' + i18n.global.t('objects.tag') ,'error', 5000)
|
||||
return
|
||||
}
|
||||
// New or Edit
|
||||
if (modal.value.id == -1) {
|
||||
outbounds.value.push(data)
|
||||
if (stats && data.tag.length>0) {
|
||||
v2rayStats.value.outbounds.push(data.tag)
|
||||
}
|
||||
} else {
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
||||
|
||||
if (stats) {
|
||||
// Add if dos not exist
|
||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
|
||||
} else {
|
||||
// Delete if exists
|
||||
if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
|
||||
outbounds.value[modal.value.id] = data
|
||||
}
|
||||
modal.value.visible = false
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
visible: false,
|
||||
resource: "outbound",
|
||||
tag: "",
|
||||
})
|
||||
|
||||
const delOutbound = (index: number) => {
|
||||
const inb = outbounds.value[index]
|
||||
outbounds.value.splice(index,1)
|
||||
const tag = inb.tag
|
||||
|
||||
// Delete stats if exists and will be orphaned
|
||||
const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
|
||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
|
||||
if (tagCounts == 1 && sIndex != -1){
|
||||
v2rayStats.value.outbounds.splice(sIndex,1)
|
||||
}
|
||||
if (index < Data().oldData.config.outbounds.length){
|
||||
Data().delOutbound(index)
|
||||
}
|
||||
delOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const showStats = (tag: string) => {
|
||||
stats.value.tag = tag
|
||||
stats.value.visible = true
|
||||
}
|
||||
const closeStats = () => {
|
||||
stats.value.visible = false
|
||||
}
|
||||
</script>
|
||||
+218
-14
@@ -1,20 +1,98 @@
|
||||
<template>
|
||||
<RuleVue
|
||||
v-model="ruleModal.visible"
|
||||
:visible="ruleModal.visible"
|
||||
:index="ruleModal.index"
|
||||
:data="ruleModal.data"
|
||||
:clients="clients"
|
||||
:inTags="inboundTags"
|
||||
:outTags="outboundTags"
|
||||
:rsTags="rulesetTags"
|
||||
@close="closeRuleModal"
|
||||
@save="saveRuleModal"
|
||||
/>
|
||||
<RulesetVue
|
||||
v-model="rulesetModal.visible"
|
||||
:visible="rulesetModal.visible"
|
||||
:index="rulesetModal.index"
|
||||
:data="rulesetModal.data"
|
||||
:outTags="outboundTags"
|
||||
@close="closeRulesetModal"
|
||||
@save="saveRulesetModal"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rules" :key="item.name">
|
||||
<v-col cols="12" justify="center" align="center">
|
||||
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('rule.add') }}</v-btn>
|
||||
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('ruleset.add') }}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">{{ $t('rule.ruleset') }}</v-col>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rulesets" :key="item.tag">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="index">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ $t('ruleset.' + item.type) }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>Type</v-col>
|
||||
<v-col>{{ $t('objects.tag') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.type }}
|
||||
{{ item.tag }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>Mode</v-col>
|
||||
<v-col>{{ $t('ruleset.format') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.mode }}
|
||||
{{ item.format }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>{{ $t('actions.update') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.update_interval?? '-' }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showRulesetModal(index)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delRulesetOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delRulesetOverlay[index]"
|
||||
contained
|
||||
class="align-center justify-center"
|
||||
>
|
||||
<v-card :title="$t('actions.del')" rounded="lg">
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delRuleset(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delRulesetOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12">{{ $t('pages.rules') }}</v-col>
|
||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>rules">
|
||||
<v-card rounded="xl" elevation="5" min-width="200" :title="index">
|
||||
<v-card-subtitle style="margin-top: -20px;">
|
||||
<v-row>
|
||||
<v-col>{{ item.type != undefined ? $t('rule.logical') + ' (' + item.mode + ')' : $t('rule.simple') }}</v-col>
|
||||
</v-row>
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>{{ $t('objects.outbound') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
@@ -24,16 +102,41 @@
|
||||
<v-row>
|
||||
<v-col>{{ $t('pages.rules') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.rules ? item.rules.length : 0 }}
|
||||
{{ item.rules ? item.rules.length : Object.keys(item).filter(r => !["rule_set_ipcidr_match_source","invert","outbound"].includes(r)).length }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>Invert</v-col>
|
||||
<v-col>{{ $t('rule.invert') }}</v-col>
|
||||
<v-col dir="ltr">
|
||||
{{ item.invert }}
|
||||
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions style="padding: 0;">
|
||||
<v-btn icon="mdi-file-edit" @click="showRuleModal(index)">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delRuleOverlay[index] = true">
|
||||
<v-icon />
|
||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||
</v-btn>
|
||||
<v-overlay
|
||||
v-model="delRuleOverlay[index]"
|
||||
contained
|
||||
class="align-center justify-center"
|
||||
>
|
||||
<v-card :title="$t('actions.del')" rounded="lg">
|
||||
<v-divider></v-divider>
|
||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="error" variant="outlined" @click="delRule(index)">{{ $t('yes') }}</v-btn>
|
||||
<v-btn color="success" variant="outlined" @click="delRuleOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -42,21 +145,122 @@
|
||||
<script lang="ts" setup>
|
||||
import Data from '@/store/modules/data'
|
||||
import { computed, ref } from 'vue'
|
||||
import RuleVue from '@/layouts/modals/Rule.vue'
|
||||
import RulesetVue from '@/layouts/modals/Ruleset.vue'
|
||||
import { Config } from '@/types/config'
|
||||
import { logicalRule, ruleset } from '@/types/rules'
|
||||
|
||||
const appConfig = Data().config
|
||||
const appConfig = computed((): Config => {
|
||||
return <Config> Data().config
|
||||
})
|
||||
|
||||
const clients = computed((): string[] => {
|
||||
return Data().clients.map((c:any) => c.name)
|
||||
})
|
||||
|
||||
const route = computed((): any => {
|
||||
if (!appConfig || !('route' in appConfig)) {
|
||||
return []
|
||||
}
|
||||
return appConfig.route
|
||||
return appConfig.value.route
|
||||
})
|
||||
|
||||
const rules = computed((): any[] => {
|
||||
const data = route.value
|
||||
if (!route || !('rules' in data) || !Array.isArray(data.rules)){
|
||||
if (!data){
|
||||
return []
|
||||
}
|
||||
if (!('rules' in data) || !Array.isArray(data.rules)) {
|
||||
data.rules = []
|
||||
}
|
||||
return data.rules
|
||||
})
|
||||
|
||||
const rulesets = computed((): any[] => {
|
||||
const data = route.value
|
||||
if (!data){
|
||||
return []
|
||||
}
|
||||
if (!('rule_set' in data) || !Array.isArray(data.rule_set)) {
|
||||
data.rule_set = []
|
||||
}
|
||||
return data.rule_set
|
||||
})
|
||||
|
||||
const rulesetTags = computed((): any[] => {
|
||||
return rulesets.value.map((rs:any) => rs.tag)
|
||||
})
|
||||
|
||||
const outboundTags = computed((): string[] => {
|
||||
return appConfig.value.outbounds?.map((o:any) => o.tag)
|
||||
})
|
||||
|
||||
const inboundTags = computed((): string[] => {
|
||||
return appConfig.value.inbounds?.map((i:any) => i.tag)
|
||||
})
|
||||
|
||||
let delRuleOverlay = ref(new Array<boolean>)
|
||||
let delRulesetOverlay = ref(new Array<boolean>)
|
||||
|
||||
const ruleModal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const showRuleModal = (index: number) => {
|
||||
ruleModal.value.index = index
|
||||
ruleModal.value.data = index == -1 ? '' : JSON.stringify(rules.value[index])
|
||||
ruleModal.value.visible = true
|
||||
}
|
||||
|
||||
const closeRuleModal = () => {
|
||||
ruleModal.value.visible = false
|
||||
}
|
||||
|
||||
const saveRuleModal = (data:logicalRule) => {
|
||||
// Logical or simple
|
||||
const ruleData = data.type == 'logical' ? data : data.rules[0]
|
||||
|
||||
// New or Edit
|
||||
if (ruleModal.value.index == -1) {
|
||||
rules.value.push(ruleData)
|
||||
} else {
|
||||
rules.value[ruleModal.value.index] = ruleData
|
||||
}
|
||||
ruleModal.value.visible = false
|
||||
}
|
||||
|
||||
const delRule = (index: number) => {
|
||||
rules.value.splice(index,1)
|
||||
delRuleOverlay.value[index] = false
|
||||
}
|
||||
|
||||
const rulesetModal = ref({
|
||||
visible: false,
|
||||
index: -1,
|
||||
data: "",
|
||||
})
|
||||
|
||||
const showRulesetModal = (index: number) => {
|
||||
rulesetModal.value.index = index
|
||||
rulesetModal.value.data = index == -1 ? '' : JSON.stringify(rulesets.value[index])
|
||||
rulesetModal.value.visible = true
|
||||
}
|
||||
|
||||
const closeRulesetModal = () => {
|
||||
rulesetModal.value.visible = false
|
||||
}
|
||||
|
||||
const saveRulesetModal = (data:ruleset) => {
|
||||
// New or Edit
|
||||
if (rulesetModal.value.index == -1) {
|
||||
rulesets.value.push(data)
|
||||
} else {
|
||||
rulesets.value[rulesetModal.value.index] = data
|
||||
}
|
||||
rulesetModal.value.visible = false
|
||||
}
|
||||
|
||||
const delRuleset = (index: number) => {
|
||||
rulesets.value.splice(index,1)
|
||||
delRulesetOverlay.value[index] = false
|
||||
}
|
||||
</script>
|
||||
@@ -52,6 +52,16 @@
|
||||
type="number"
|
||||
v-model.number="sessionMaxAge"
|
||||
:label="$t('setting.sessionAge')"
|
||||
:suffix="$t('date.h')"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" md="4">
|
||||
<v-text-field
|
||||
type="number"
|
||||
v-model.number="trafficAge"
|
||||
:label="$t('setting.trafficAge')"
|
||||
:suffix="$t('date.d')"
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
@@ -152,6 +162,7 @@ const settings = ref({
|
||||
webPath: "/app/",
|
||||
webURI: "",
|
||||
sessionMaxAge: "0",
|
||||
trafficAge: "30",
|
||||
timeLocation: "Asia/Tehran",
|
||||
subListen: "",
|
||||
subPort: "2096",
|
||||
@@ -246,6 +257,11 @@ const sessionMaxAge = computed({
|
||||
set: (v:number) => { settings.value.sessionMaxAge = v.toString() }
|
||||
})
|
||||
|
||||
const trafficAge = computed({
|
||||
get: () => { return parseInt(settings.value.trafficAge) },
|
||||
set: (v:number) => { settings.value.trafficAge = v.toString() }
|
||||
})
|
||||
|
||||
const subPort = computed({
|
||||
get: () => { return parseInt(settings.value.subPort) },
|
||||
set: (v:number) => { settings.value.subPort = v.toString() }
|
||||
|
||||
+8
-4
@@ -79,12 +79,16 @@ config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):" config_port
|
||||
read -p "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):" config_path
|
||||
echo -e "Enter the ${yellow}panel port${plain} (leave blank for existing/default value):"
|
||||
read config_port
|
||||
echo -e "Enter the ${yellow}panel path${plain} (leave blank for existing/default value):"
|
||||
read config_path
|
||||
|
||||
# Sub configuration
|
||||
read -p "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):" config_subPort
|
||||
read -p "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):" config_subPath
|
||||
echo -e "Enter the ${yellow}subscription port${plain} (leave blank for existing/default value):"
|
||||
read config_subPort
|
||||
echo -e "Enter the ${yellow}subscription path${plain} (leave blank for existing/default value):"
|
||||
read config_subPath
|
||||
|
||||
# Set configs
|
||||
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||
|
||||
Reference in New Issue
Block a user