Merge branch 'MHSanaei:main' into main
|
|
@ -5,6 +5,7 @@ FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
|
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||||
|
|
||||||
RUN apk --no-cache --update add \
|
RUN apk --no-cache --update add \
|
||||||
build-base \
|
build-base \
|
||||||
|
|
|
||||||
10
README.md
|
|
@ -23,10 +23,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
|
|
||||||
# Install custom version
|
# Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.7.9`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v2.0.1`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.7.9
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
|
|
@ -311,6 +311,7 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset traffics of all clients in an inbound |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
|
| `POST` | `"/onlines"` | Get Online users ( list of emails ) |
|
||||||
|
|
||||||
\*- The field `clientId` should be filled by:
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
|
|
@ -318,7 +319,9 @@ Enter the user ID in input field number 4. The Telegram accounts with this id wi
|
||||||
- `client.password` for TROJAN
|
- `client.password` for TROJAN
|
||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
|
||||||
|
- [API Documentation](https://documenter.getpostman.com/view/16802678/2s9YkgD5jm)
|
||||||
|
- [<img src="https://run.pstmn.io/button.svg" alt="Run In Postman" style="width: 128px; height: 32px;">](https://app.getpostman.com/run-collection/16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415?action=collection%2Ffork&source=rip_markdown&collection-url=entityId%3D16802678-1a4c9270-ac77-40ed-959a-7aa56dc4a415%26entityType%3Dcollection%26workspaceId%3D2cd38c01-c851-4a15-a972-f181c23359d9)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
# Environment Variables
|
# Environment Variables
|
||||||
|
|
@ -368,6 +371,7 @@ XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Stargazers over time
|
## Stargazers over time
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.7.9
|
2.0.1
|
||||||
|
|
@ -112,3 +112,12 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
||||||
}
|
}
|
||||||
return bytes.Equal(buf, signature), nil
|
return bytes.Equal(buf, signature), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Checkpoint() error {
|
||||||
|
// Update WAL
|
||||||
|
err := db.Exec("PRAGMA wal_checkpoint;").Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,4 +83,5 @@ type Client struct {
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
TgID string `json:"tgId" form:"tgId"`
|
TgID string `json:"tgId" form:"tgId"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
version: "3.8"
|
version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
3x-ui:
|
3x-ui:
|
||||||
|
|
|
||||||
7
go.mod
|
|
@ -4,19 +4,19 @@ go 1.21.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/Workiva/go-datastructures v1.1.1
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.28.0
|
github.com/mymmrac/telego v0.28.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.3.0
|
github.com/nicksnyder/go-i18n/v2 v2.3.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.11
|
github.com/shirou/gopsutil/v3 v3.23.11
|
||||||
github.com/xtls/xray-core v1.8.6
|
github.com/xtls/xray-core v1.8.6
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.59.0
|
google.golang.org/grpc v1.60.0
|
||||||
gorm.io/driver/sqlite v1.5.4
|
gorm.io/driver/sqlite v1.5.4
|
||||||
gorm.io/gorm v1.25.5
|
gorm.io/gorm v1.25.5
|
||||||
)
|
)
|
||||||
|
|
@ -50,7 +50,6 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.2 // indirect
|
github.com/klauspost/compress v1.17.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
|
|
||||||
59
go.sum
|
|
@ -12,8 +12,6 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
|
|
||||||
github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
|
||||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
|
@ -56,8 +54,11 @@ github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67d
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||||
|
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
|
|
@ -67,16 +68,21 @@ github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
|
@ -137,6 +143,8 @@ github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/4
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
|
@ -144,6 +152,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
|
@ -151,6 +160,7 @@ github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0g
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
|
|
@ -180,11 +190,12 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWEr
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
@ -205,6 +216,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
|
@ -261,14 +274,14 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
|
|
@ -288,7 +301,6 @@ github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||||
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
|
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
|
||||||
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
|
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
|
|
@ -306,8 +318,7 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
|
@ -316,7 +327,6 @@ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUU
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -327,9 +337,7 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
|
@ -341,8 +349,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -350,11 +356,13 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -363,9 +371,11 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
|
@ -377,14 +387,9 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
||||||
|
|
@ -407,15 +412,18 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
google.golang.org/grpc v1.60.0 h1:6FQAR0kM31P6MRdeluor2w2gPaS4SVNrD/DNTxrQ15k=
|
||||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
@ -423,6 +431,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,15 @@ func InitLogger(level logging.Level) {
|
||||||
var format logging.Formatter
|
var format logging.Formatter
|
||||||
ppid := os.Getppid()
|
ppid := os.Getppid()
|
||||||
|
|
||||||
if ppid == 1 {
|
backend, err = logging.NewSyslogBackend("")
|
||||||
backend, err = logging.NewSyslogBackend("")
|
if err != nil {
|
||||||
format = logging.MustStringFormatter(
|
println(err)
|
||||||
`%{level} - %{message}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if err != nil || ppid != 1 {
|
|
||||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
format = logging.MustStringFormatter(
|
}
|
||||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
if ppid > 0 && err != nil {
|
||||||
)
|
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
||||||
|
} else {
|
||||||
|
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
|
|
|
||||||
BIN
media/1.png
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 150 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 214 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 138 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 52 KiB |
BIN
media/5.png
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 165 KiB |
BIN
media/6.png
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 88 KiB |
BIN
media/7.png
Normal file
|
After Width: | Height: | Size: 261 KiB |
|
|
@ -19,8 +19,9 @@ import (
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
showInfo bool
|
showInfo bool
|
||||||
|
remarkModel string
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
settingServics service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||||
|
|
@ -34,6 +35,10 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
s.remarkModel, err = s.settingService.GetRemarkModel()
|
||||||
|
if err != nil {
|
||||||
|
s.remarkModel = "-ieo"
|
||||||
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -53,6 +58,7 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
||||||
stream["security"] = masterStream["security"]
|
stream["security"] = masterStream["security"]
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
stream["externalProxy"] = masterStream["externalProxy"]
|
||||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
inbound.StreamSettings = string(modifiedStream)
|
inbound.StreamSettings = string(modifiedStream)
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +93,7 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
updateInterval, _ := s.settingServics.GetSubUpdates()
|
updateInterval, _ := s.settingService.GetSubUpdates()
|
||||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
headers = append(headers, subId)
|
headers = append(headers, subId)
|
||||||
return result, headers, nil
|
return result, headers, nil
|
||||||
|
|
@ -96,7 +102,14 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
|
||||||
|
SELECT DISTINCT inbounds.id
|
||||||
|
FROM inbounds,
|
||||||
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
|
WHERE
|
||||||
|
protocol in ('vmess','vless','trojan','shadowsocks')
|
||||||
|
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
|
||||||
|
)`, subId, true).Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -196,7 +209,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
|
@ -208,24 +220,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
obj["alpn"] = strings.Join(alpn, ",")
|
obj["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
obj["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
obj["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
obj["fp"], _ = fpValue.(string)
|
obj["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
obj["add"] = serverName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,16 +245,30 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
|
|
||||||
if len(domains) > 0 {
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
links := ""
|
links := ""
|
||||||
for index, d := range domains {
|
for index, externalProxy := range externalProxies {
|
||||||
domain := d.(map[string]interface{})
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
obj["add"] = domain["domain"].(string)
|
newObj := map[string]interface{}{}
|
||||||
|
for key, value := range obj {
|
||||||
|
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
||||||
|
newObj[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
newObj["add"] = ep["dest"].(string)
|
||||||
|
newObj["port"] = int(ep["port"].(float64))
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
newObj["tls"] = newSecurity
|
||||||
|
}
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
}
|
}
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(newObj, "", " ")
|
||||||
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
return links
|
return links
|
||||||
|
|
@ -323,7 +343,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
|
@ -335,11 +354,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
|
|
@ -348,19 +368,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = clients[clientIndex].Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if security == "reality" {
|
if security == "reality" {
|
||||||
|
|
@ -389,11 +401,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["spx"] = spx
|
params["spx"] = spx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
|
||||||
address = sname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
|
|
@ -412,7 +419,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok {
|
||||||
|
|
@ -423,25 +432,55 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = clients[clientIndex].Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := xtlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if security != "tls" && security != "reality" && security != "xtls" {
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
|
|
@ -453,20 +492,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
if len(domains) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, d := range domains {
|
|
||||||
domain := d.(map[string]interface{})
|
|
||||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
|
||||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
@ -534,7 +559,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
var domains []interface{}
|
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
|
@ -546,11 +570,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if sniValue, ok := searchKey(tlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
params["fp"], _ = fpValue.(string)
|
params["fp"], _ = fpValue.(string)
|
||||||
}
|
}
|
||||||
|
|
@ -559,14 +583,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
|
||||||
domains, _ = domainSettings.([]interface{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,11 +612,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["spx"] = spx
|
params["spx"] = spx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
|
||||||
address = sname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
|
|
@ -619,6 +630,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
if len(alpn) > 0 {
|
if len(alpn) > 0 {
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
}
|
}
|
||||||
|
if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
xtlsSettings, _ := searchKey(xtlsSetting, "settings")
|
||||||
if xtlsSetting != nil {
|
if xtlsSetting != nil {
|
||||||
|
|
@ -630,25 +644,55 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
params["flow"] = clients[clientIndex].Flow
|
params["flow"] = clients[clientIndex].Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := xtlsSetting["serverName"].(string)
|
|
||||||
if serverName != "" {
|
|
||||||
address = serverName
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if security != "tls" && security != "reality" && security != "xtls" {
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
params["security"] = "none"
|
params["security"] = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
|
|
@ -661,20 +705,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
if len(domains) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, d := range domains {
|
|
||||||
domain := d.(map[string]interface{})
|
|
||||||
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
|
||||||
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
@ -744,10 +774,78 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
security, _ := stream["security"].(string)
|
||||||
|
if security == "tls" {
|
||||||
|
params["security"] = "tls"
|
||||||
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
|
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||||
|
var alpn []string
|
||||||
|
for _, a := range alpns {
|
||||||
|
alpn = append(alpn, a.(string))
|
||||||
|
}
|
||||||
|
if len(alpn) > 0 {
|
||||||
|
params["alpn"] = strings.Join(alpn, ",")
|
||||||
|
}
|
||||||
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
|
params["sni"], _ = sniValue.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
|
if tlsSetting != nil {
|
||||||
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
params["fp"], _ = fpValue.(string)
|
||||||
|
}
|
||||||
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
|
if insecure.(bool) {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||||
if method[0] == '2' {
|
if method[0] == '2' {
|
||||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||||
|
|
||||||
|
if len(externalProxies) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, externalProxy := range externalProxies {
|
||||||
|
ep, _ := externalProxy.(map[string]interface{})
|
||||||
|
newSecurity, _ := ep["forceTls"].(string)
|
||||||
|
dest, _ := ep["dest"].(string)
|
||||||
|
port := int(ep["port"].(float64))
|
||||||
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
|
||||||
|
|
||||||
|
if newSecurity != "same" {
|
||||||
|
params["security"] = newSecurity
|
||||||
|
} else {
|
||||||
|
params["security"] = security
|
||||||
|
}
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
||||||
|
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
|
|
@ -758,22 +856,36 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
|
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||||
var remark []string
|
separationChar := string(s.remarkModel[0])
|
||||||
|
orderChars := s.remarkModel[1:]
|
||||||
|
orders := map[byte]string{
|
||||||
|
'i': "",
|
||||||
|
'e': "",
|
||||||
|
'o': "",
|
||||||
|
}
|
||||||
if len(email) > 0 {
|
if len(email) > 0 {
|
||||||
if len(inbound.Remark) > 0 {
|
orders['e'] = email
|
||||||
remark = append(remark, inbound.Remark)
|
}
|
||||||
|
if len(inbound.Remark) > 0 {
|
||||||
|
orders['i'] = inbound.Remark
|
||||||
|
}
|
||||||
|
if len(extra) > 0 {
|
||||||
|
orders['o'] = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
var remark []string
|
||||||
|
for i := 0; i < len(orderChars); i++ {
|
||||||
|
char := orderChars[i]
|
||||||
|
order, exists := orders[char]
|
||||||
|
if exists && order != "" {
|
||||||
|
remark = append(remark, order)
|
||||||
}
|
}
|
||||||
remark = append(remark, email)
|
|
||||||
if len(extra) > 0 {
|
|
||||||
remark = append(remark, extra)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return inbound.Remark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.showInfo {
|
if s.showInfo {
|
||||||
|
|
@ -790,7 +902,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||||
// Get remained days
|
// Get remained days
|
||||||
if statsExist {
|
if statsExist {
|
||||||
if !stats.Enable {
|
if !stats.Enable {
|
||||||
return fmt.Sprintf("⛔️N/A-%s", strings.Join(remark, "-"))
|
return fmt.Sprintf("⛔️N/A%s%s", separationChar, strings.Join(remark, separationChar))
|
||||||
}
|
}
|
||||||
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
||||||
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
||||||
|
|
@ -804,7 +916,7 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(remark, " : ")
|
return strings.Join(remark, separationChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
|
|
|
||||||
BIN
web/assets/Vazirmatn-UI-NL-Regular.woff2
Normal file
|
|
@ -1,2 +0,0 @@
|
||||||
@import "../lib/style/index.less";
|
|
||||||
@import "../lib/style/components.less";
|
|
||||||
4498
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
3
web/assets/ant-design-vue@1.7.8/antd-with-locales.min.js
vendored
Normal file
7
web/assets/ant-design-vue@1.7.8/antd.less
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
@import "../lib/style/index.less";
|
||||||
|
@import "../lib/style/components.less";
|
||||||
|
|
||||||
|
@green-6: #008771;
|
||||||
|
@primary-color: @green-6;
|
||||||
|
@border-radius-base: 1rem;
|
||||||
|
@progress-remaining-color: #EDEDED;
|
||||||
8
web/assets/ant-design-vue@1.7.8/antd.min.css
vendored
Normal file
3
web/assets/ant-design-vue@1.7.8/antd.min.js
vendored
Normal file
1
web/assets/ant-design-vue@1.7.8/antd.min.js.map
Normal file
344
web/assets/codemirror/codemirror.css
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: 300px;
|
||||||
|
color: black;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-line::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
|
||||||
|
.cm-fat-cursor .CodeMirror-line::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
|
||||||
|
.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
|
||||||
|
.cm-fat-cursor { caret-color: transparent; }
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -50px; margin-right: -50px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 50px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -50px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre.CodeMirror-line,
|
||||||
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line,
|
||||||
|
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background-color: #ffa;
|
||||||
|
background-color: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
||||||
9874
web/assets/codemirror/codemirror.js
Normal file
119
web/assets/codemirror/fold/brace-fold.js
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function bracketFolding(pairs) {
|
||||||
|
return function(cm, start) {
|
||||||
|
var line = start.line, lineText = cm.getLine(line);
|
||||||
|
|
||||||
|
function findOpening(pair) {
|
||||||
|
var tokenType;
|
||||||
|
for (var at = start.ch, pass = 0;;) {
|
||||||
|
var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1);
|
||||||
|
if (found == -1) {
|
||||||
|
if (pass == 1) break;
|
||||||
|
pass = 1;
|
||||||
|
at = lineText.length;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pass == 1 && found < start.ch) break;
|
||||||
|
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
|
||||||
|
if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair};
|
||||||
|
at = found - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRange(found) {
|
||||||
|
var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh
|
||||||
|
outer: for (var i = line; i <= lastLine; ++i) {
|
||||||
|
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
||||||
|
for (;;) {
|
||||||
|
var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos);
|
||||||
|
if (nextOpen < 0) nextOpen = text.length;
|
||||||
|
if (nextClose < 0) nextClose = text.length;
|
||||||
|
pos = Math.min(nextOpen, nextClose);
|
||||||
|
if (pos == text.length) break;
|
||||||
|
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) {
|
||||||
|
if (pos == nextOpen) ++count;
|
||||||
|
else if (!--count) { end = i; endCh = pos; break outer; }
|
||||||
|
}
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end == null || line == end) return null
|
||||||
|
return {from: CodeMirror.Pos(line, startCh),
|
||||||
|
to: CodeMirror.Pos(end, endCh)};
|
||||||
|
}
|
||||||
|
|
||||||
|
var found = []
|
||||||
|
for (var i = 0; i < pairs.length; i++) {
|
||||||
|
var open = findOpening(pairs[i])
|
||||||
|
if (open) found.push(open)
|
||||||
|
}
|
||||||
|
found.sort(function(a, b) { return a.ch - b.ch })
|
||||||
|
for (var i = 0; i < found.length; i++) {
|
||||||
|
var range = findRange(found[i])
|
||||||
|
if (range) return range
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]]));
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]]));
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
||||||
|
function hasImport(line) {
|
||||||
|
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||||
|
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||||
|
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||||
|
if (start.type != "keyword" || start.string != "import") return null;
|
||||||
|
// Now find closing semicolon, return its position
|
||||||
|
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
|
||||||
|
var text = cm.getLine(i), semi = text.indexOf(";");
|
||||||
|
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startLine = start.line, has = hasImport(startLine), prev;
|
||||||
|
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
|
||||||
|
return null;
|
||||||
|
for (var end = has.end;;) {
|
||||||
|
var next = hasImport(end.line + 1);
|
||||||
|
if (next == null) break;
|
||||||
|
end = next.end;
|
||||||
|
}
|
||||||
|
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
||||||
|
function hasInclude(line) {
|
||||||
|
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
||||||
|
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
||||||
|
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
||||||
|
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startLine = start.line, has = hasInclude(startLine);
|
||||||
|
if (has == null || hasInclude(startLine - 1) != null) return null;
|
||||||
|
for (var end = startLine;;) {
|
||||||
|
var next = hasInclude(end + 1);
|
||||||
|
if (next == null) break;
|
||||||
|
++end;
|
||||||
|
}
|
||||||
|
return {from: CodeMirror.Pos(startLine, has + 1),
|
||||||
|
to: cm.clipPos(CodeMirror.Pos(end))};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
159
web/assets/codemirror/fold/foldcode.js
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function doFold(cm, pos, options, force) {
|
||||||
|
if (options && options.call) {
|
||||||
|
var finder = options;
|
||||||
|
options = null;
|
||||||
|
} else {
|
||||||
|
var finder = getOption(cm, options, "rangeFinder");
|
||||||
|
}
|
||||||
|
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
||||||
|
var minSize = getOption(cm, options, "minFoldSize");
|
||||||
|
|
||||||
|
function getRange(allowFolded) {
|
||||||
|
var range = finder(cm, pos);
|
||||||
|
if (!range || range.to.line - range.from.line < minSize) return null;
|
||||||
|
if (force === "fold") return range;
|
||||||
|
|
||||||
|
var marks = cm.findMarksAt(range.from);
|
||||||
|
for (var i = 0; i < marks.length; ++i) {
|
||||||
|
if (marks[i].__isFold) {
|
||||||
|
if (!allowFolded) return null;
|
||||||
|
range.cleared = true;
|
||||||
|
marks[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
var range = getRange(true);
|
||||||
|
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
||||||
|
pos = CodeMirror.Pos(pos.line - 1, 0);
|
||||||
|
range = getRange(false);
|
||||||
|
}
|
||||||
|
if (!range || range.cleared || force === "unfold") return;
|
||||||
|
|
||||||
|
var myWidget = makeWidget(cm, options, range);
|
||||||
|
CodeMirror.on(myWidget, "mousedown", function(e) {
|
||||||
|
myRange.clear();
|
||||||
|
CodeMirror.e_preventDefault(e);
|
||||||
|
});
|
||||||
|
var myRange = cm.markText(range.from, range.to, {
|
||||||
|
replacedWith: myWidget,
|
||||||
|
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
||||||
|
__isFold: true
|
||||||
|
});
|
||||||
|
myRange.on("clear", function(from, to) {
|
||||||
|
CodeMirror.signal(cm, "unfold", cm, from, to);
|
||||||
|
});
|
||||||
|
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeWidget(cm, options, range) {
|
||||||
|
var widget = getOption(cm, options, "widget");
|
||||||
|
|
||||||
|
if (typeof widget == "function") {
|
||||||
|
widget = widget(range.from, range.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof widget == "string") {
|
||||||
|
var text = document.createTextNode(widget);
|
||||||
|
widget = document.createElement("span");
|
||||||
|
widget.appendChild(text);
|
||||||
|
widget.className = "CodeMirror-foldmarker";
|
||||||
|
} else if (widget) {
|
||||||
|
widget = widget.cloneNode(true)
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clumsy backwards-compatible interface
|
||||||
|
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
||||||
|
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
||||||
|
};
|
||||||
|
|
||||||
|
// New-style interface
|
||||||
|
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
||||||
|
doFold(this, pos, options, force);
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("isFolded", function(pos) {
|
||||||
|
var marks = this.findMarksAt(pos);
|
||||||
|
for (var i = 0; i < marks.length; ++i)
|
||||||
|
if (marks[i].__isFold) return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.commands.toggleFold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor());
|
||||||
|
};
|
||||||
|
CodeMirror.commands.fold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), null, "fold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfold = function(cm) {
|
||||||
|
cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold");
|
||||||
|
};
|
||||||
|
CodeMirror.commands.foldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
CodeMirror.commands.unfoldAll = function(cm) {
|
||||||
|
cm.operation(function() {
|
||||||
|
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
||||||
|
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "combine", function() {
|
||||||
|
var funcs = Array.prototype.slice.call(arguments, 0);
|
||||||
|
return function(cm, start) {
|
||||||
|
for (var i = 0; i < funcs.length; ++i) {
|
||||||
|
var found = funcs[i](cm, start);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
||||||
|
var helpers = cm.getHelpers(start, "fold");
|
||||||
|
for (var i = 0; i < helpers.length; i++) {
|
||||||
|
var cur = helpers[i](cm, start);
|
||||||
|
if (cur) return cur;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var defaultOptions = {
|
||||||
|
rangeFinder: CodeMirror.fold.auto,
|
||||||
|
widget: "\u2194",
|
||||||
|
minFoldSize: 0,
|
||||||
|
scanUp: false,
|
||||||
|
clearOnEnter: true
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldOptions", null);
|
||||||
|
|
||||||
|
function getOption(cm, options, name) {
|
||||||
|
if (options && options[name] !== undefined)
|
||||||
|
return options[name];
|
||||||
|
var editorOptions = cm.options.foldOptions;
|
||||||
|
if (editorOptions && editorOptions[name] !== undefined)
|
||||||
|
return editorOptions[name];
|
||||||
|
return defaultOptions[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("foldOption", function(options, name) {
|
||||||
|
return getOption(this, options, name);
|
||||||
|
});
|
||||||
|
});
|
||||||
20
web/assets/codemirror/fold/foldgutter.css
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
.CodeMirror-foldmarker {
|
||||||
|
color: blue;
|
||||||
|
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
||||||
|
font-family: arial;
|
||||||
|
line-height: .3;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter {
|
||||||
|
width: .7em;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open,
|
||||||
|
.CodeMirror-foldgutter-folded {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-open:after {
|
||||||
|
content: "\25BE";
|
||||||
|
}
|
||||||
|
.CodeMirror-foldgutter-folded:after {
|
||||||
|
content: "\25B8";
|
||||||
|
}
|
||||||
169
web/assets/codemirror/fold/foldgutter.js
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("./foldcode"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "./foldcode"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
||||||
|
cm.state.foldGutter = null;
|
||||||
|
cm.off("gutterClick", onGutterClick);
|
||||||
|
cm.off("changes", onChange);
|
||||||
|
cm.off("viewportChange", onViewportChange);
|
||||||
|
cm.off("fold", onFold);
|
||||||
|
cm.off("unfold", onFold);
|
||||||
|
cm.off("swapDoc", onChange);
|
||||||
|
cm.off("optionChange", optionChange);
|
||||||
|
}
|
||||||
|
if (val) {
|
||||||
|
cm.state.foldGutter = new State(parseOptions(val));
|
||||||
|
updateInViewport(cm);
|
||||||
|
cm.on("gutterClick", onGutterClick);
|
||||||
|
cm.on("changes", onChange);
|
||||||
|
cm.on("viewportChange", onViewportChange);
|
||||||
|
cm.on("fold", onFold);
|
||||||
|
cm.on("unfold", onFold);
|
||||||
|
cm.on("swapDoc", onChange);
|
||||||
|
cm.on("optionChange", optionChange);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function State(options) {
|
||||||
|
this.options = options;
|
||||||
|
this.from = this.to = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions(opts) {
|
||||||
|
if (opts === true) opts = {};
|
||||||
|
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
||||||
|
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
||||||
|
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isFolded(cm, line) {
|
||||||
|
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
||||||
|
for (var i = 0; i < marks.length; ++i) {
|
||||||
|
if (marks[i].__isFold) {
|
||||||
|
var fromPos = marks[i].find(-1);
|
||||||
|
if (fromPos && fromPos.line === line)
|
||||||
|
return marks[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function marker(spec) {
|
||||||
|
if (typeof spec == "string") {
|
||||||
|
var elt = document.createElement("div");
|
||||||
|
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
||||||
|
return elt;
|
||||||
|
} else {
|
||||||
|
return spec.cloneNode(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFoldInfo(cm, from, to) {
|
||||||
|
var opts = cm.state.foldGutter.options, cur = from - 1;
|
||||||
|
var minSize = cm.foldOption(opts, "minFoldSize");
|
||||||
|
var func = cm.foldOption(opts, "rangeFinder");
|
||||||
|
// we can reuse the built-in indicator element if its className matches the new state
|
||||||
|
var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
|
||||||
|
var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
|
||||||
|
cm.eachLine(from, to, function(line) {
|
||||||
|
++cur;
|
||||||
|
var mark = null;
|
||||||
|
var old = line.gutterMarkers;
|
||||||
|
if (old) old = old[opts.gutter];
|
||||||
|
if (isFolded(cm, cur)) {
|
||||||
|
if (clsFolded && old && clsFolded.test(old.className)) return;
|
||||||
|
mark = marker(opts.indicatorFolded);
|
||||||
|
} else {
|
||||||
|
var pos = Pos(cur, 0);
|
||||||
|
var range = func && func(cm, pos);
|
||||||
|
if (range && range.to.line - range.from.line >= minSize) {
|
||||||
|
if (clsOpen && old && clsOpen.test(old.className)) return;
|
||||||
|
mark = marker(opts.indicatorOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mark && !old) return;
|
||||||
|
cm.setGutterMarker(line, opts.gutter, mark);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// copied from CodeMirror/src/util/dom.js
|
||||||
|
function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
|
||||||
|
|
||||||
|
function updateInViewport(cm) {
|
||||||
|
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
cm.operation(function() {
|
||||||
|
updateFoldInfo(cm, vp.from, vp.to);
|
||||||
|
});
|
||||||
|
state.from = vp.from; state.to = vp.to;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGutterClick(cm, line, gutter) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
if (gutter != opts.gutter) return;
|
||||||
|
var folded = isFolded(cm, line);
|
||||||
|
if (folded) folded.clear();
|
||||||
|
else cm.foldCode(Pos(line, 0), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionChange(cm, option) {
|
||||||
|
if (option == "mode") onChange(cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
state.from = state.to = 0;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onViewportChange(cm) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var opts = state.options;
|
||||||
|
clearTimeout(state.changeUpdate);
|
||||||
|
state.changeUpdate = setTimeout(function() {
|
||||||
|
var vp = cm.getViewport();
|
||||||
|
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
||||||
|
updateInViewport(cm);
|
||||||
|
} else {
|
||||||
|
cm.operation(function() {
|
||||||
|
if (vp.from < state.from) {
|
||||||
|
updateFoldInfo(cm, vp.from, state.from);
|
||||||
|
state.from = vp.from;
|
||||||
|
}
|
||||||
|
if (vp.to > state.to) {
|
||||||
|
updateFoldInfo(cm, state.to, vp.to);
|
||||||
|
state.to = vp.to;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, opts.updateViewportTimeSpan || 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFold(cm, from) {
|
||||||
|
var state = cm.state.foldGutter;
|
||||||
|
if (!state) return;
|
||||||
|
var line = from.line;
|
||||||
|
if (line >= state.from && line < state.to)
|
||||||
|
updateFoldInfo(cm, line, line + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
162
web/assets/codemirror/hint/javascript-hint.js
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function forEach(arr, f) {
|
||||||
|
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayContains(arr, item) {
|
||||||
|
if (!Array.prototype.indexOf) {
|
||||||
|
var i = arr.length;
|
||||||
|
while (i--) {
|
||||||
|
if (arr[i] === item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return arr.indexOf(item) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scriptHint(editor, keywords, getToken, options) {
|
||||||
|
// Find the token at the cursor
|
||||||
|
var cur = editor.getCursor(), token = getToken(editor, cur);
|
||||||
|
if (/\b(?:string|comment)\b/.test(token.type)) return;
|
||||||
|
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
|
||||||
|
if (innerMode.mode.helperType === "json") return;
|
||||||
|
token.state = innerMode.state;
|
||||||
|
|
||||||
|
// If it's not a 'word-style' token, ignore the token.
|
||||||
|
if (!/^[\w$_]*$/.test(token.string)) {
|
||||||
|
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
|
||||||
|
type: token.string == "." ? "property" : null};
|
||||||
|
} else if (token.end > cur.ch) {
|
||||||
|
token.end = cur.ch;
|
||||||
|
token.string = token.string.slice(0, cur.ch - token.start);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tprop = token;
|
||||||
|
// If it is a property, find out what it is a property of.
|
||||||
|
while (tprop.type == "property") {
|
||||||
|
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||||
|
if (tprop.string != ".") return;
|
||||||
|
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||||
|
if (!context) var context = [];
|
||||||
|
context.push(tprop);
|
||||||
|
}
|
||||||
|
return {list: getCompletions(token, context, keywords, options),
|
||||||
|
from: Pos(cur.line, token.start),
|
||||||
|
to: Pos(cur.line, token.end)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function javascriptHint(editor, options) {
|
||||||
|
return scriptHint(editor, javascriptKeywords,
|
||||||
|
function (e, cur) {return e.getTokenAt(cur);},
|
||||||
|
options);
|
||||||
|
};
|
||||||
|
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
||||||
|
|
||||||
|
function getCoffeeScriptToken(editor, cur) {
|
||||||
|
// This getToken, it is for coffeescript, imitates the behavior of
|
||||||
|
// getTokenAt method in javascript.js, that is, returning "property"
|
||||||
|
// type and treat "." as independent token.
|
||||||
|
var token = editor.getTokenAt(cur);
|
||||||
|
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
|
||||||
|
token.end = token.start;
|
||||||
|
token.string = '.';
|
||||||
|
token.type = "property";
|
||||||
|
}
|
||||||
|
else if (/^\.[\w$_]*$/.test(token.string)) {
|
||||||
|
token.type = "property";
|
||||||
|
token.start++;
|
||||||
|
token.string = token.string.replace(/\./, '');
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function coffeescriptHint(editor, options) {
|
||||||
|
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
|
||||||
|
}
|
||||||
|
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
|
||||||
|
|
||||||
|
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
|
||||||
|
"toUpperCase toLowerCase split concat match replace search").split(" ");
|
||||||
|
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
|
||||||
|
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
|
||||||
|
var funcProps = "prototype apply call bind".split(" ");
|
||||||
|
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
|
||||||
|
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
|
||||||
|
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
||||||
|
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
||||||
|
|
||||||
|
function forAllProps(obj, callback) {
|
||||||
|
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||||
|
for (var name in obj) callback(name)
|
||||||
|
} else {
|
||||||
|
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
||||||
|
Object.getOwnPropertyNames(o).forEach(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompletions(token, context, keywords, options) {
|
||||||
|
var found = [], start = token.string, global = options && options.globalScope || window;
|
||||||
|
function maybeAdd(str) {
|
||||||
|
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
|
||||||
|
}
|
||||||
|
function gatherCompletions(obj) {
|
||||||
|
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
||||||
|
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
||||||
|
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
||||||
|
forAllProps(obj, maybeAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && context.length) {
|
||||||
|
// If this is a property, see if it belongs to some object we can
|
||||||
|
// find in the current environment.
|
||||||
|
var obj = context.pop(), base;
|
||||||
|
if (obj.type && obj.type.indexOf("variable") === 0) {
|
||||||
|
if (options && options.additionalContext)
|
||||||
|
base = options.additionalContext[obj.string];
|
||||||
|
if (!options || options.useGlobalScope !== false)
|
||||||
|
base = base || global[obj.string];
|
||||||
|
} else if (obj.type == "string") {
|
||||||
|
base = "";
|
||||||
|
} else if (obj.type == "atom") {
|
||||||
|
base = 1;
|
||||||
|
} else if (obj.type == "function") {
|
||||||
|
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
|
||||||
|
(typeof global.jQuery == 'function'))
|
||||||
|
base = global.jQuery();
|
||||||
|
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
|
||||||
|
base = global._();
|
||||||
|
}
|
||||||
|
while (base != null && context.length)
|
||||||
|
base = base[context.pop().string];
|
||||||
|
if (base != null) gatherCompletions(base);
|
||||||
|
} else {
|
||||||
|
// If not, just look in the global object, any local scope, and optional additional-context
|
||||||
|
// (reading into JS mode internals to get at the local and global variables)
|
||||||
|
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
|
||||||
|
for (var c = token.state.context; c; c = c.prev)
|
||||||
|
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
|
||||||
|
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
|
||||||
|
if (options && options.additionalContext != null)
|
||||||
|
for (var key in options.additionalContext)
|
||||||
|
maybeAdd(key);
|
||||||
|
if (!options || options.useGlobalScope !== false)
|
||||||
|
gatherCompletions(global);
|
||||||
|
forEach(keywords, maybeAdd);
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
});
|
||||||
960
web/assets/codemirror/javascript.js
Normal file
|
|
@ -0,0 +1,960 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||||
|
var indentUnit = config.indentUnit;
|
||||||
|
var statementIndent = parserConfig.statementIndent;
|
||||||
|
var jsonldMode = parserConfig.jsonld;
|
||||||
|
var jsonMode = parserConfig.json || jsonldMode;
|
||||||
|
var trackScope = parserConfig.trackScope !== false
|
||||||
|
var isTS = parserConfig.typescript;
|
||||||
|
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
||||||
|
|
||||||
|
// Tokenizer
|
||||||
|
|
||||||
|
var keywords = function(){
|
||||||
|
function kw(type) {return {type: type, style: "keyword"};}
|
||||||
|
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
|
||||||
|
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
||||||
|
|
||||||
|
return {
|
||||||
|
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
||||||
|
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
||||||
|
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||||
|
"function": kw("function"), "catch": kw("catch"),
|
||||||
|
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||||
|
"in": operator, "typeof": operator, "instanceof": operator,
|
||||||
|
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
||||||
|
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
|
||||||
|
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
|
||||||
|
"await": C
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
|
||||||
|
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
|
||||||
|
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
|
||||||
|
|
||||||
|
function readRegexp(stream) {
|
||||||
|
var escaped = false, next, inSet = false;
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (!escaped) {
|
||||||
|
if (next == "/" && !inSet) return;
|
||||||
|
if (next == "[") inSet = true;
|
||||||
|
else if (inSet && next == "]") inSet = false;
|
||||||
|
}
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used as scratch variables to communicate multiple values without
|
||||||
|
// consing up tons of objects.
|
||||||
|
var type, content;
|
||||||
|
function ret(tp, style, cont) {
|
||||||
|
type = tp; content = cont;
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
function tokenBase(stream, state) {
|
||||||
|
var ch = stream.next();
|
||||||
|
if (ch == '"' || ch == "'") {
|
||||||
|
state.tokenize = tokenString(ch);
|
||||||
|
return state.tokenize(stream, state);
|
||||||
|
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (ch == "." && stream.match("..")) {
|
||||||
|
return ret("spread", "meta");
|
||||||
|
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
||||||
|
return ret(ch);
|
||||||
|
} else if (ch == "=" && stream.eat(">")) {
|
||||||
|
return ret("=>", "operator");
|
||||||
|
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (/\d/.test(ch)) {
|
||||||
|
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
||||||
|
return ret("number", "number");
|
||||||
|
} else if (ch == "/") {
|
||||||
|
if (stream.eat("*")) {
|
||||||
|
state.tokenize = tokenComment;
|
||||||
|
return tokenComment(stream, state);
|
||||||
|
} else if (stream.eat("/")) {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return ret("comment", "comment");
|
||||||
|
} else if (expressionAllowed(stream, state, 1)) {
|
||||||
|
readRegexp(stream);
|
||||||
|
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
||||||
|
return ret("regexp", "string-2");
|
||||||
|
} else {
|
||||||
|
stream.eat("=");
|
||||||
|
return ret("operator", "operator", stream.current());
|
||||||
|
}
|
||||||
|
} else if (ch == "`") {
|
||||||
|
state.tokenize = tokenQuasi;
|
||||||
|
return tokenQuasi(stream, state);
|
||||||
|
} else if (ch == "#" && stream.peek() == "!") {
|
||||||
|
stream.skipToEnd();
|
||||||
|
return ret("meta", "meta");
|
||||||
|
} else if (ch == "#" && stream.eatWhile(wordRE)) {
|
||||||
|
return ret("variable", "property")
|
||||||
|
} else if (ch == "<" && stream.match("!--") ||
|
||||||
|
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
|
||||||
|
stream.skipToEnd()
|
||||||
|
return ret("comment", "comment")
|
||||||
|
} else if (isOperatorChar.test(ch)) {
|
||||||
|
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
|
||||||
|
if (stream.eat("=")) {
|
||||||
|
if (ch == "!" || ch == "=") stream.eat("=")
|
||||||
|
} else if (/[<>*+\-|&?]/.test(ch)) {
|
||||||
|
stream.eat(ch)
|
||||||
|
if (ch == ">") stream.eat(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch == "?" && stream.eat(".")) return ret(".")
|
||||||
|
return ret("operator", "operator", stream.current());
|
||||||
|
} else if (wordRE.test(ch)) {
|
||||||
|
stream.eatWhile(wordRE);
|
||||||
|
var word = stream.current()
|
||||||
|
if (state.lastType != ".") {
|
||||||
|
if (keywords.propertyIsEnumerable(word)) {
|
||||||
|
var kw = keywords[word]
|
||||||
|
return ret(kw.type, kw.style, word)
|
||||||
|
}
|
||||||
|
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
||||||
|
return ret("async", "keyword", word)
|
||||||
|
}
|
||||||
|
return ret("variable", "variable", word)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenString(quote) {
|
||||||
|
return function(stream, state) {
|
||||||
|
var escaped = false, next;
|
||||||
|
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
return ret("jsonld-keyword", "meta");
|
||||||
|
}
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (next == quote && !escaped) break;
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
if (!escaped) state.tokenize = tokenBase;
|
||||||
|
return ret("string", "string");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenComment(stream, state) {
|
||||||
|
var maybeEnd = false, ch;
|
||||||
|
while (ch = stream.next()) {
|
||||||
|
if (ch == "/" && maybeEnd) {
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maybeEnd = (ch == "*");
|
||||||
|
}
|
||||||
|
return ret("comment", "comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenQuasi(stream, state) {
|
||||||
|
var escaped = false, next;
|
||||||
|
while ((next = stream.next()) != null) {
|
||||||
|
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
|
||||||
|
state.tokenize = tokenBase;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
escaped = !escaped && next == "\\";
|
||||||
|
}
|
||||||
|
return ret("quasi", "string-2", stream.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
var brackets = "([{}])";
|
||||||
|
// This is a crude lookahead trick to try and notice that we're
|
||||||
|
// parsing the argument patterns for a fat-arrow function before we
|
||||||
|
// actually hit the arrow token. It only works if the arrow is on
|
||||||
|
// the same line as the arguments and there's no strange noise
|
||||||
|
// (comments) in between. Fallback is to only notice when we hit the
|
||||||
|
// arrow, and not declare the arguments as locals for the arrow
|
||||||
|
// body.
|
||||||
|
function findFatArrow(stream, state) {
|
||||||
|
if (state.fatArrowAt) state.fatArrowAt = null;
|
||||||
|
var arrow = stream.string.indexOf("=>", stream.start);
|
||||||
|
if (arrow < 0) return;
|
||||||
|
|
||||||
|
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
|
||||||
|
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
|
||||||
|
if (m) arrow = m.index
|
||||||
|
}
|
||||||
|
|
||||||
|
var depth = 0, sawSomething = false;
|
||||||
|
for (var pos = arrow - 1; pos >= 0; --pos) {
|
||||||
|
var ch = stream.string.charAt(pos);
|
||||||
|
var bracket = brackets.indexOf(ch);
|
||||||
|
if (bracket >= 0 && bracket < 3) {
|
||||||
|
if (!depth) { ++pos; break; }
|
||||||
|
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
||||||
|
} else if (bracket >= 3 && bracket < 6) {
|
||||||
|
++depth;
|
||||||
|
} else if (wordRE.test(ch)) {
|
||||||
|
sawSomething = true;
|
||||||
|
} else if (/["'\/`]/.test(ch)) {
|
||||||
|
for (;; --pos) {
|
||||||
|
if (pos == 0) return
|
||||||
|
var next = stream.string.charAt(pos - 1)
|
||||||
|
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
|
||||||
|
}
|
||||||
|
} else if (sawSomething && !depth) {
|
||||||
|
++pos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sawSomething && !depth) state.fatArrowAt = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
|
||||||
|
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
|
||||||
|
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
|
||||||
|
|
||||||
|
function JSLexical(indented, column, type, align, prev, info) {
|
||||||
|
this.indented = indented;
|
||||||
|
this.column = column;
|
||||||
|
this.type = type;
|
||||||
|
this.prev = prev;
|
||||||
|
this.info = info;
|
||||||
|
if (align != null) this.align = align;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inScope(state, varname) {
|
||||||
|
if (!trackScope) return false
|
||||||
|
for (var v = state.localVars; v; v = v.next)
|
||||||
|
if (v.name == varname) return true;
|
||||||
|
for (var cx = state.context; cx; cx = cx.prev) {
|
||||||
|
for (var v = cx.vars; v; v = v.next)
|
||||||
|
if (v.name == varname) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJS(state, style, type, content, stream) {
|
||||||
|
var cc = state.cc;
|
||||||
|
// Communicate our context to the combinators.
|
||||||
|
// (Less wasteful than consing up a hundred closures on every call.)
|
||||||
|
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
|
||||||
|
|
||||||
|
if (!state.lexical.hasOwnProperty("align"))
|
||||||
|
state.lexical.align = true;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
||||||
|
if (combinator(type, content)) {
|
||||||
|
while(cc.length && cc[cc.length - 1].lex)
|
||||||
|
cc.pop()();
|
||||||
|
if (cx.marked) return cx.marked;
|
||||||
|
if (type == "variable" && inScope(state, content)) return "variable-2";
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combinator utils
|
||||||
|
|
||||||
|
var cx = {state: null, column: null, marked: null, cc: null};
|
||||||
|
function pass() {
|
||||||
|
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
||||||
|
}
|
||||||
|
function cont() {
|
||||||
|
pass.apply(null, arguments);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function inList(name, list) {
|
||||||
|
for (var v = list; v; v = v.next) if (v.name == name) return true
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function register(varname) {
|
||||||
|
var state = cx.state;
|
||||||
|
cx.marked = "def";
|
||||||
|
if (!trackScope) return
|
||||||
|
if (state.context) {
|
||||||
|
if (state.lexical.info == "var" && state.context && state.context.block) {
|
||||||
|
// FIXME function decls are also not block scoped
|
||||||
|
var newContext = registerVarScoped(varname, state.context)
|
||||||
|
if (newContext != null) {
|
||||||
|
state.context = newContext
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (!inList(varname, state.localVars)) {
|
||||||
|
state.localVars = new Var(varname, state.localVars)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall through means this is global
|
||||||
|
if (parserConfig.globalVars && !inList(varname, state.globalVars))
|
||||||
|
state.globalVars = new Var(varname, state.globalVars)
|
||||||
|
}
|
||||||
|
function registerVarScoped(varname, context) {
|
||||||
|
if (!context) {
|
||||||
|
return null
|
||||||
|
} else if (context.block) {
|
||||||
|
var inner = registerVarScoped(varname, context.prev)
|
||||||
|
if (!inner) return null
|
||||||
|
if (inner == context.prev) return context
|
||||||
|
return new Context(inner, context.vars, true)
|
||||||
|
} else if (inList(varname, context.vars)) {
|
||||||
|
return context
|
||||||
|
} else {
|
||||||
|
return new Context(context.prev, new Var(varname, context.vars), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModifier(name) {
|
||||||
|
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combinators
|
||||||
|
|
||||||
|
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
|
||||||
|
function Var(name, next) { this.name = name; this.next = next }
|
||||||
|
|
||||||
|
var defaultVars = new Var("this", new Var("arguments", null))
|
||||||
|
function pushcontext() {
|
||||||
|
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
||||||
|
cx.state.localVars = defaultVars
|
||||||
|
}
|
||||||
|
function pushblockcontext() {
|
||||||
|
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
|
||||||
|
cx.state.localVars = null
|
||||||
|
}
|
||||||
|
pushcontext.lex = pushblockcontext.lex = true
|
||||||
|
function popcontext() {
|
||||||
|
cx.state.localVars = cx.state.context.vars
|
||||||
|
cx.state.context = cx.state.context.prev
|
||||||
|
}
|
||||||
|
popcontext.lex = true
|
||||||
|
function pushlex(type, info) {
|
||||||
|
var result = function() {
|
||||||
|
var state = cx.state, indent = state.indented;
|
||||||
|
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
||||||
|
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
|
||||||
|
indent = outer.indented;
|
||||||
|
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
||||||
|
};
|
||||||
|
result.lex = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function poplex() {
|
||||||
|
var state = cx.state;
|
||||||
|
if (state.lexical.prev) {
|
||||||
|
if (state.lexical.type == ")")
|
||||||
|
state.indented = state.lexical.indented;
|
||||||
|
state.lexical = state.lexical.prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
poplex.lex = true;
|
||||||
|
|
||||||
|
function expect(wanted) {
|
||||||
|
function exp(type) {
|
||||||
|
if (type == wanted) return cont();
|
||||||
|
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||||
|
else return cont(exp);
|
||||||
|
};
|
||||||
|
return exp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function statement(type, value) {
|
||||||
|
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
||||||
|
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
|
||||||
|
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
||||||
|
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
|
||||||
|
if (type == "debugger") return cont(expect(";"));
|
||||||
|
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
|
||||||
|
if (type == ";") return cont();
|
||||||
|
if (type == "if") {
|
||||||
|
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
||||||
|
cx.state.cc.pop()();
|
||||||
|
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
|
||||||
|
}
|
||||||
|
if (type == "function") return cont(functiondef);
|
||||||
|
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
|
||||||
|
if (type == "class" || (isTS && value == "interface")) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
|
||||||
|
}
|
||||||
|
if (type == "variable") {
|
||||||
|
if (isTS && value == "declare") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(statement)
|
||||||
|
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
if (value == "enum") return cont(enumdef);
|
||||||
|
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
|
||||||
|
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
|
||||||
|
} else if (isTS && value == "namespace") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(pushlex("form"), expression, statement, poplex)
|
||||||
|
} else if (isTS && value == "abstract") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(statement)
|
||||||
|
} else {
|
||||||
|
return cont(pushlex("stat"), maybelabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
|
||||||
|
block, poplex, poplex, popcontext);
|
||||||
|
if (type == "case") return cont(expression, expect(":"));
|
||||||
|
if (type == "default") return cont(expect(":"));
|
||||||
|
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
||||||
|
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
|
||||||
|
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
|
||||||
|
if (type == "async") return cont(statement)
|
||||||
|
if (value == "@") return cont(expression, statement)
|
||||||
|
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
||||||
|
}
|
||||||
|
function maybeCatchBinding(type) {
|
||||||
|
if (type == "(") return cont(funarg, expect(")"))
|
||||||
|
}
|
||||||
|
function expression(type, value) {
|
||||||
|
return expressionInner(type, value, false);
|
||||||
|
}
|
||||||
|
function expressionNoComma(type, value) {
|
||||||
|
return expressionInner(type, value, true);
|
||||||
|
}
|
||||||
|
function parenExpr(type) {
|
||||||
|
if (type != "(") return pass()
|
||||||
|
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
|
||||||
|
}
|
||||||
|
function expressionInner(type, value, noComma) {
|
||||||
|
if (cx.state.fatArrowAt == cx.stream.start) {
|
||||||
|
var body = noComma ? arrowBodyNoComma : arrowBody;
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
|
||||||
|
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||||
|
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||||
|
if (type == "function") return cont(functiondef, maybeop);
|
||||||
|
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
||||||
|
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
|
||||||
|
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
||||||
|
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
|
||||||
|
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
||||||
|
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
||||||
|
if (type == "quasi") return pass(quasi, maybeop);
|
||||||
|
if (type == "new") return cont(maybeTarget(noComma));
|
||||||
|
return cont();
|
||||||
|
}
|
||||||
|
function maybeexpression(type) {
|
||||||
|
if (type.match(/[;\}\)\],]/)) return pass();
|
||||||
|
return pass(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeoperatorComma(type, value) {
|
||||||
|
if (type == ",") return cont(maybeexpression);
|
||||||
|
return maybeoperatorNoComma(type, value, false);
|
||||||
|
}
|
||||||
|
function maybeoperatorNoComma(type, value, noComma) {
|
||||||
|
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
||||||
|
var expr = noComma == false ? expression : expressionNoComma;
|
||||||
|
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
||||||
|
if (type == "operator") {
|
||||||
|
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
|
||||||
|
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
|
||||||
|
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
|
||||||
|
if (value == "?") return cont(expression, expect(":"), expr);
|
||||||
|
return cont(expr);
|
||||||
|
}
|
||||||
|
if (type == "quasi") { return pass(quasi, me); }
|
||||||
|
if (type == ";") return;
|
||||||
|
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
||||||
|
if (type == ".") return cont(property, me);
|
||||||
|
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
||||||
|
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
|
||||||
|
if (type == "regexp") {
|
||||||
|
cx.state.lastType = cx.marked = "operator"
|
||||||
|
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
|
||||||
|
return cont(expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function quasi(type, value) {
|
||||||
|
if (type != "quasi") return pass();
|
||||||
|
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
||||||
|
return cont(maybeexpression, continueQuasi);
|
||||||
|
}
|
||||||
|
function continueQuasi(type) {
|
||||||
|
if (type == "}") {
|
||||||
|
cx.marked = "string-2";
|
||||||
|
cx.state.tokenize = tokenQuasi;
|
||||||
|
return cont(quasi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function arrowBody(type) {
|
||||||
|
findFatArrow(cx.stream, cx.state);
|
||||||
|
return pass(type == "{" ? statement : expression);
|
||||||
|
}
|
||||||
|
function arrowBodyNoComma(type) {
|
||||||
|
findFatArrow(cx.stream, cx.state);
|
||||||
|
return pass(type == "{" ? statement : expressionNoComma);
|
||||||
|
}
|
||||||
|
function maybeTarget(noComma) {
|
||||||
|
return function(type) {
|
||||||
|
if (type == ".") return cont(noComma ? targetNoComma : target);
|
||||||
|
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
||||||
|
else return pass(noComma ? expressionNoComma : expression);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function target(_, value) {
|
||||||
|
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
|
||||||
|
}
|
||||||
|
function targetNoComma(_, value) {
|
||||||
|
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
|
||||||
|
}
|
||||||
|
function maybelabel(type) {
|
||||||
|
if (type == ":") return cont(poplex, statement);
|
||||||
|
return pass(maybeoperatorComma, expect(";"), poplex);
|
||||||
|
}
|
||||||
|
function property(type) {
|
||||||
|
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||||
|
}
|
||||||
|
function objprop(type, value) {
|
||||||
|
if (type == "async") {
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(objprop);
|
||||||
|
} else if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property";
|
||||||
|
if (value == "get" || value == "set") return cont(getterSetter);
|
||||||
|
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
|
||||||
|
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
|
||||||
|
cx.state.fatArrowAt = cx.stream.pos + m[0].length
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (type == "number" || type == "string") {
|
||||||
|
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (type == "jsonld-keyword") {
|
||||||
|
return cont(afterprop);
|
||||||
|
} else if (isTS && isModifier(value)) {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(objprop)
|
||||||
|
} else if (type == "[") {
|
||||||
|
return cont(expression, maybetype, expect("]"), afterprop);
|
||||||
|
} else if (type == "spread") {
|
||||||
|
return cont(expressionNoComma, afterprop);
|
||||||
|
} else if (value == "*") {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(objprop);
|
||||||
|
} else if (type == ":") {
|
||||||
|
return pass(afterprop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getterSetter(type) {
|
||||||
|
if (type != "variable") return pass(afterprop);
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(functiondef);
|
||||||
|
}
|
||||||
|
function afterprop(type) {
|
||||||
|
if (type == ":") return cont(expressionNoComma);
|
||||||
|
if (type == "(") return pass(functiondef);
|
||||||
|
}
|
||||||
|
function commasep(what, end, sep) {
|
||||||
|
function proceed(type, value) {
|
||||||
|
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
||||||
|
var lex = cx.state.lexical;
|
||||||
|
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
||||||
|
return cont(function(type, value) {
|
||||||
|
if (type == end || value == end) return pass()
|
||||||
|
return pass(what)
|
||||||
|
}, proceed);
|
||||||
|
}
|
||||||
|
if (type == end || value == end) return cont();
|
||||||
|
if (sep && sep.indexOf(";") > -1) return pass(what)
|
||||||
|
return cont(expect(end));
|
||||||
|
}
|
||||||
|
return function(type, value) {
|
||||||
|
if (type == end || value == end) return cont();
|
||||||
|
return pass(what, proceed);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function contCommasep(what, end, info) {
|
||||||
|
for (var i = 3; i < arguments.length; i++)
|
||||||
|
cx.cc.push(arguments[i]);
|
||||||
|
return cont(pushlex(end, info), commasep(what, end), poplex);
|
||||||
|
}
|
||||||
|
function block(type) {
|
||||||
|
if (type == "}") return cont();
|
||||||
|
return pass(statement, block);
|
||||||
|
}
|
||||||
|
function maybetype(type, value) {
|
||||||
|
if (isTS) {
|
||||||
|
if (type == ":") return cont(typeexpr);
|
||||||
|
if (value == "?") return cont(maybetype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function maybetypeOrIn(type, value) {
|
||||||
|
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function mayberettype(type) {
|
||||||
|
if (isTS && type == ":") {
|
||||||
|
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
|
||||||
|
else return cont(typeexpr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isKW(_, value) {
|
||||||
|
if (value == "is") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function typeexpr(type, value) {
|
||||||
|
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
|
||||||
|
cx.marked = "keyword"
|
||||||
|
return cont(value == "typeof" ? expressionNoComma : typeexpr)
|
||||||
|
}
|
||||||
|
if (type == "variable" || value == "void") {
|
||||||
|
cx.marked = "type"
|
||||||
|
return cont(afterType)
|
||||||
|
}
|
||||||
|
if (value == "|" || value == "&") return cont(typeexpr)
|
||||||
|
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
|
||||||
|
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
|
||||||
|
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
|
||||||
|
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
|
||||||
|
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
|
||||||
|
if (type == "quasi") { return pass(quasiType, afterType); }
|
||||||
|
}
|
||||||
|
function maybeReturnType(type) {
|
||||||
|
if (type == "=>") return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function typeprops(type) {
|
||||||
|
if (type.match(/[\}\)\]]/)) return cont()
|
||||||
|
if (type == "," || type == ";") return cont(typeprops)
|
||||||
|
return pass(typeprop, typeprops)
|
||||||
|
}
|
||||||
|
function typeprop(type, value) {
|
||||||
|
if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property"
|
||||||
|
return cont(typeprop)
|
||||||
|
} else if (value == "?" || type == "number" || type == "string") {
|
||||||
|
return cont(typeprop)
|
||||||
|
} else if (type == ":") {
|
||||||
|
return cont(typeexpr)
|
||||||
|
} else if (type == "[") {
|
||||||
|
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
|
||||||
|
} else if (type == "(") {
|
||||||
|
return pass(functiondecl, typeprop)
|
||||||
|
} else if (!type.match(/[;\}\)\],]/)) {
|
||||||
|
return cont()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function quasiType(type, value) {
|
||||||
|
if (type != "quasi") return pass();
|
||||||
|
if (value.slice(value.length - 2) != "${") return cont(quasiType);
|
||||||
|
return cont(typeexpr, continueQuasiType);
|
||||||
|
}
|
||||||
|
function continueQuasiType(type) {
|
||||||
|
if (type == "}") {
|
||||||
|
cx.marked = "string-2";
|
||||||
|
cx.state.tokenize = tokenQuasi;
|
||||||
|
return cont(quasiType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function typearg(type, value) {
|
||||||
|
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
|
||||||
|
if (type == ":") return cont(typeexpr)
|
||||||
|
if (type == "spread") return cont(typearg)
|
||||||
|
return pass(typeexpr)
|
||||||
|
}
|
||||||
|
function afterType(type, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||||
|
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
|
||||||
|
if (type == "[") return cont(typeexpr, expect("]"), afterType)
|
||||||
|
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
|
||||||
|
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
|
||||||
|
}
|
||||||
|
function maybeTypeArgs(_, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
||||||
|
}
|
||||||
|
function typeparam() {
|
||||||
|
return pass(typeexpr, maybeTypeDefault)
|
||||||
|
}
|
||||||
|
function maybeTypeDefault(_, value) {
|
||||||
|
if (value == "=") return cont(typeexpr)
|
||||||
|
}
|
||||||
|
function vardef(_, value) {
|
||||||
|
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
|
||||||
|
return pass(pattern, maybetype, maybeAssign, vardefCont);
|
||||||
|
}
|
||||||
|
function pattern(type, value) {
|
||||||
|
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
|
||||||
|
if (type == "variable") { register(value); return cont(); }
|
||||||
|
if (type == "spread") return cont(pattern);
|
||||||
|
if (type == "[") return contCommasep(eltpattern, "]");
|
||||||
|
if (type == "{") return contCommasep(proppattern, "}");
|
||||||
|
}
|
||||||
|
function proppattern(type, value) {
|
||||||
|
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
|
||||||
|
register(value);
|
||||||
|
return cont(maybeAssign);
|
||||||
|
}
|
||||||
|
if (type == "variable") cx.marked = "property";
|
||||||
|
if (type == "spread") return cont(pattern);
|
||||||
|
if (type == "}") return pass();
|
||||||
|
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
||||||
|
return cont(expect(":"), pattern, maybeAssign);
|
||||||
|
}
|
||||||
|
function eltpattern() {
|
||||||
|
return pass(pattern, maybeAssign)
|
||||||
|
}
|
||||||
|
function maybeAssign(_type, value) {
|
||||||
|
if (value == "=") return cont(expressionNoComma);
|
||||||
|
}
|
||||||
|
function vardefCont(type) {
|
||||||
|
if (type == ",") return cont(vardef);
|
||||||
|
}
|
||||||
|
function maybeelse(type, value) {
|
||||||
|
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
||||||
|
}
|
||||||
|
function forspec(type, value) {
|
||||||
|
if (value == "await") return cont(forspec);
|
||||||
|
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
||||||
|
}
|
||||||
|
function forspec1(type) {
|
||||||
|
if (type == "var") return cont(vardef, forspec2);
|
||||||
|
if (type == "variable") return cont(forspec2);
|
||||||
|
return pass(forspec2)
|
||||||
|
}
|
||||||
|
function forspec2(type, value) {
|
||||||
|
if (type == ")") return cont()
|
||||||
|
if (type == ";") return cont(forspec2)
|
||||||
|
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
||||||
|
return pass(expression, forspec2)
|
||||||
|
}
|
||||||
|
function functiondef(type, value) {
|
||||||
|
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
|
||||||
|
if (type == "variable") {register(value); return cont(functiondef);}
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
|
||||||
|
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
|
||||||
|
}
|
||||||
|
function functiondecl(type, value) {
|
||||||
|
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
|
||||||
|
if (type == "variable") {register(value); return cont(functiondecl);}
|
||||||
|
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
|
||||||
|
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
|
||||||
|
}
|
||||||
|
function typename(type, value) {
|
||||||
|
if (type == "keyword" || type == "variable") {
|
||||||
|
cx.marked = "type"
|
||||||
|
return cont(typename)
|
||||||
|
} else if (value == "<") {
|
||||||
|
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function funarg(type, value) {
|
||||||
|
if (value == "@") cont(expression, funarg)
|
||||||
|
if (type == "spread") return cont(funarg);
|
||||||
|
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
|
||||||
|
if (isTS && type == "this") return cont(maybetype, maybeAssign)
|
||||||
|
return pass(pattern, maybetype, maybeAssign);
|
||||||
|
}
|
||||||
|
function classExpression(type, value) {
|
||||||
|
// Class expressions may have an optional name.
|
||||||
|
if (type == "variable") return className(type, value);
|
||||||
|
return classNameAfter(type, value);
|
||||||
|
}
|
||||||
|
function className(type, value) {
|
||||||
|
if (type == "variable") {register(value); return cont(classNameAfter);}
|
||||||
|
}
|
||||||
|
function classNameAfter(type, value) {
|
||||||
|
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
|
||||||
|
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
|
||||||
|
if (value == "implements") cx.marked = "keyword";
|
||||||
|
return cont(isTS ? typeexpr : expression, classNameAfter);
|
||||||
|
}
|
||||||
|
if (type == "{") return cont(pushlex("}"), classBody, poplex);
|
||||||
|
}
|
||||||
|
function classBody(type, value) {
|
||||||
|
if (type == "async" ||
|
||||||
|
(type == "variable" &&
|
||||||
|
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||||
|
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(classBody);
|
||||||
|
}
|
||||||
|
if (type == "variable" || cx.style == "keyword") {
|
||||||
|
cx.marked = "property";
|
||||||
|
return cont(classfield, classBody);
|
||||||
|
}
|
||||||
|
if (type == "number" || type == "string") return cont(classfield, classBody);
|
||||||
|
if (type == "[")
|
||||||
|
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
||||||
|
if (value == "*") {
|
||||||
|
cx.marked = "keyword";
|
||||||
|
return cont(classBody);
|
||||||
|
}
|
||||||
|
if (isTS && type == "(") return pass(functiondecl, classBody)
|
||||||
|
if (type == ";" || type == ",") return cont(classBody);
|
||||||
|
if (type == "}") return cont();
|
||||||
|
if (value == "@") return cont(expression, classBody)
|
||||||
|
}
|
||||||
|
function classfield(type, value) {
|
||||||
|
if (value == "!") return cont(classfield)
|
||||||
|
if (value == "?") return cont(classfield)
|
||||||
|
if (type == ":") return cont(typeexpr, maybeAssign)
|
||||||
|
if (value == "=") return cont(expressionNoComma)
|
||||||
|
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
|
||||||
|
return pass(isInterface ? functiondecl : functiondef)
|
||||||
|
}
|
||||||
|
function afterExport(type, value) {
|
||||||
|
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
|
||||||
|
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
||||||
|
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
||||||
|
return pass(statement);
|
||||||
|
}
|
||||||
|
function exportField(type, value) {
|
||||||
|
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
|
||||||
|
if (type == "variable") return pass(expressionNoComma, exportField);
|
||||||
|
}
|
||||||
|
function afterImport(type) {
|
||||||
|
if (type == "string") return cont();
|
||||||
|
if (type == "(") return pass(expression);
|
||||||
|
if (type == ".") return pass(maybeoperatorComma);
|
||||||
|
return pass(importSpec, maybeMoreImports, maybeFrom);
|
||||||
|
}
|
||||||
|
function importSpec(type, value) {
|
||||||
|
if (type == "{") return contCommasep(importSpec, "}");
|
||||||
|
if (type == "variable") register(value);
|
||||||
|
if (value == "*") cx.marked = "keyword";
|
||||||
|
return cont(maybeAs);
|
||||||
|
}
|
||||||
|
function maybeMoreImports(type) {
|
||||||
|
if (type == ",") return cont(importSpec, maybeMoreImports)
|
||||||
|
}
|
||||||
|
function maybeAs(_type, value) {
|
||||||
|
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
||||||
|
}
|
||||||
|
function maybeFrom(_type, value) {
|
||||||
|
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
|
||||||
|
}
|
||||||
|
function arrayLiteral(type) {
|
||||||
|
if (type == "]") return cont();
|
||||||
|
return pass(commasep(expressionNoComma, "]"));
|
||||||
|
}
|
||||||
|
function enumdef() {
|
||||||
|
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
||||||
|
}
|
||||||
|
function enummember() {
|
||||||
|
return pass(pattern, maybeAssign);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isContinuedStatement(state, textAfter) {
|
||||||
|
return state.lastType == "operator" || state.lastType == "," ||
|
||||||
|
isOperatorChar.test(textAfter.charAt(0)) ||
|
||||||
|
/[,.]/.test(textAfter.charAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
function expressionAllowed(stream, state, backUp) {
|
||||||
|
return state.tokenize == tokenBase &&
|
||||||
|
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
|
||||||
|
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface
|
||||||
|
|
||||||
|
return {
|
||||||
|
startState: function(basecolumn) {
|
||||||
|
var state = {
|
||||||
|
tokenize: tokenBase,
|
||||||
|
lastType: "sof",
|
||||||
|
cc: [],
|
||||||
|
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||||
|
localVars: parserConfig.localVars,
|
||||||
|
context: parserConfig.localVars && new Context(null, null, false),
|
||||||
|
indented: basecolumn || 0
|
||||||
|
};
|
||||||
|
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
||||||
|
state.globalVars = parserConfig.globalVars;
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
|
||||||
|
token: function(stream, state) {
|
||||||
|
if (stream.sol()) {
|
||||||
|
if (!state.lexical.hasOwnProperty("align"))
|
||||||
|
state.lexical.align = false;
|
||||||
|
state.indented = stream.indentation();
|
||||||
|
findFatArrow(stream, state);
|
||||||
|
}
|
||||||
|
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
|
||||||
|
var style = state.tokenize(stream, state);
|
||||||
|
if (type == "comment") return style;
|
||||||
|
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
||||||
|
return parseJS(state, style, type, content, stream);
|
||||||
|
},
|
||||||
|
|
||||||
|
indent: function(state, textAfter) {
|
||||||
|
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
|
||||||
|
if (state.tokenize != tokenBase) return 0;
|
||||||
|
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
|
||||||
|
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
||||||
|
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||||
|
var c = state.cc[i];
|
||||||
|
if (c == poplex) lexical = lexical.prev;
|
||||||
|
else if (c != maybeelse && c != popcontext) break;
|
||||||
|
}
|
||||||
|
while ((lexical.type == "stat" || lexical.type == "form") &&
|
||||||
|
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
|
||||||
|
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
|
||||||
|
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
|
||||||
|
lexical = lexical.prev;
|
||||||
|
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
||||||
|
lexical = lexical.prev;
|
||||||
|
var type = lexical.type, closing = firstChar == type;
|
||||||
|
|
||||||
|
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
|
||||||
|
else if (type == "form" && firstChar == "{") return lexical.indented;
|
||||||
|
else if (type == "form") return lexical.indented + indentUnit;
|
||||||
|
else if (type == "stat")
|
||||||
|
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
|
||||||
|
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
||||||
|
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
||||||
|
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
||||||
|
else return lexical.indented + (closing ? 0 : indentUnit);
|
||||||
|
},
|
||||||
|
|
||||||
|
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
||||||
|
blockCommentStart: jsonMode ? null : "/*",
|
||||||
|
blockCommentEnd: jsonMode ? null : "*/",
|
||||||
|
blockCommentContinue: jsonMode ? null : " * ",
|
||||||
|
lineComment: jsonMode ? null : "//",
|
||||||
|
fold: "brace",
|
||||||
|
closeBrackets: "()[]{}''\"\"``",
|
||||||
|
|
||||||
|
helperType: jsonMode ? "json" : "javascript",
|
||||||
|
jsonldMode: jsonldMode,
|
||||||
|
jsonMode: jsonMode,
|
||||||
|
|
||||||
|
expressionAllowed: expressionAllowed,
|
||||||
|
|
||||||
|
skipExpression: function(state) {
|
||||||
|
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
|
||||||
|
|
||||||
|
CodeMirror.defineMIME("text/javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||||
|
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
|
||||||
|
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
|
||||||
|
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
|
||||||
|
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
|
||||||
|
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
||||||
|
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
||||||
|
|
||||||
|
});
|
||||||
32076
web/assets/codemirror/jshint.js
Normal file
1
web/assets/codemirror/jsonlint.js
Normal file
65
web/assets/codemirror/lint/javascript-lint.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
// Depends on jshint.js from https://github.com/jshint/jshint
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
// declare global: JSHINT
|
||||||
|
|
||||||
|
function validator(text, options) {
|
||||||
|
if (!window.JSHINT) {
|
||||||
|
if (window.console) {
|
||||||
|
window.console.error("Error: window.JSHINT not defined, CodeMirror JavaScript linting cannot run.");
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!options.indent) // JSHint error.character actually is a column index, this fixes underlining on lines using tabs for indentation
|
||||||
|
options.indent = 1; // JSHint default value is 4
|
||||||
|
JSHINT(text, options, options.globals);
|
||||||
|
var errors = JSHINT.data().errors, result = [];
|
||||||
|
if (errors) parseErrors(errors, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("lint", "javascript", validator);
|
||||||
|
|
||||||
|
function parseErrors(errors, output) {
|
||||||
|
for ( var i = 0; i < errors.length; i++) {
|
||||||
|
var error = errors[i];
|
||||||
|
if (error) {
|
||||||
|
if (error.line <= 0) {
|
||||||
|
if (window.console) {
|
||||||
|
window.console.warn("Cannot display JSHint error (invalid line " + error.line + ")", error);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = error.character - 1, end = start + 1;
|
||||||
|
if (error.evidence) {
|
||||||
|
var index = error.evidence.substring(start).search(/.\b/);
|
||||||
|
if (index > -1) {
|
||||||
|
end += index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to format expected by validation service
|
||||||
|
var hint = {
|
||||||
|
message: error.reason,
|
||||||
|
severity: error.code ? (error.code.startsWith('W') ? "warning" : "error") : "error",
|
||||||
|
from: CodeMirror.Pos(error.line - 1, start),
|
||||||
|
to: CodeMirror.Pos(error.line - 1, end)
|
||||||
|
};
|
||||||
|
|
||||||
|
output.push(hint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
79
web/assets/codemirror/lint/lint.css
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
/* The lint marker gutter */
|
||||||
|
.CodeMirror-lint-markers {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-tooltip {
|
||||||
|
background-color: #ffd;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px 4px 4px 4px;
|
||||||
|
color: black;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10pt;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px 5px;
|
||||||
|
position: fixed;
|
||||||
|
white-space: pre;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 600px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .4s;
|
||||||
|
-moz-transition: opacity .4s;
|
||||||
|
-webkit-transition: opacity .4s;
|
||||||
|
-o-transition: opacity .4s;
|
||||||
|
-ms-transition: opacity .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark {
|
||||||
|
background-position: left bottom;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-mark-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker {
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-message {
|
||||||
|
padding-left: 18px;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-marker-multiple {
|
||||||
|
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right bottom;
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-line-error {
|
||||||
|
background-color: rgba(183, 76, 81, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-lint-line-warning {
|
||||||
|
background-color: rgba(255, 211, 0, 0.1);
|
||||||
|
}
|
||||||
288
web/assets/codemirror/lint/lint.js
Normal file
|
|
@ -0,0 +1,288 @@
|
||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
var GUTTER_ID = "CodeMirror-lint-markers";
|
||||||
|
var LINT_LINE_ID = "CodeMirror-lint-line-";
|
||||||
|
|
||||||
|
function showTooltip(cm, e, content) {
|
||||||
|
var tt = document.createElement("div");
|
||||||
|
tt.className = "CodeMirror-lint-tooltip cm-s-" + cm.options.theme;
|
||||||
|
tt.appendChild(content.cloneNode(true));
|
||||||
|
if (cm.state.lint.options.selfContain)
|
||||||
|
cm.getWrapperElement().appendChild(tt);
|
||||||
|
else
|
||||||
|
document.body.appendChild(tt);
|
||||||
|
|
||||||
|
function position(e) {
|
||||||
|
if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
|
||||||
|
var top = Math.max(0, e.clientY - tt.offsetHeight - 5);
|
||||||
|
var left = Math.max(0, Math.min(e.clientX + 5, tt.ownerDocument.defaultView.innerWidth - tt.offsetWidth));
|
||||||
|
tt.style.top = top + "px"
|
||||||
|
tt.style.left = left + "px";
|
||||||
|
}
|
||||||
|
CodeMirror.on(document, "mousemove", position);
|
||||||
|
position(e);
|
||||||
|
if (tt.style.opacity != null) tt.style.opacity = 1;
|
||||||
|
return tt;
|
||||||
|
}
|
||||||
|
function rm(elt) {
|
||||||
|
if (elt.parentNode) elt.parentNode.removeChild(elt);
|
||||||
|
}
|
||||||
|
function hideTooltip(tt) {
|
||||||
|
if (!tt.parentNode) return;
|
||||||
|
if (tt.style.opacity == null) rm(tt);
|
||||||
|
tt.style.opacity = 0;
|
||||||
|
setTimeout(function() { rm(tt); }, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltipFor(cm, e, content, node) {
|
||||||
|
var tooltip = showTooltip(cm, e, content);
|
||||||
|
function hide() {
|
||||||
|
CodeMirror.off(node, "mouseout", hide);
|
||||||
|
if (tooltip) { hideTooltip(tooltip); tooltip = null; }
|
||||||
|
}
|
||||||
|
var poll = setInterval(function() {
|
||||||
|
if (tooltip) for (var n = node;; n = n.parentNode) {
|
||||||
|
if (n && n.nodeType == 11) n = n.host;
|
||||||
|
if (n == document.body) return;
|
||||||
|
if (!n) { hide(); break; }
|
||||||
|
}
|
||||||
|
if (!tooltip) return clearInterval(poll);
|
||||||
|
}, 400);
|
||||||
|
CodeMirror.on(node, "mouseout", hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LintState(cm, conf, hasGutter) {
|
||||||
|
this.marked = [];
|
||||||
|
if (conf instanceof Function) conf = {getAnnotations: conf};
|
||||||
|
if (!conf || conf === true) conf = {};
|
||||||
|
this.options = {};
|
||||||
|
this.linterOptions = conf.options || {};
|
||||||
|
for (var prop in defaults) this.options[prop] = defaults[prop];
|
||||||
|
for (var prop in conf) {
|
||||||
|
if (defaults.hasOwnProperty(prop)) {
|
||||||
|
if (conf[prop] != null) this.options[prop] = conf[prop];
|
||||||
|
} else if (!conf.options) {
|
||||||
|
this.linterOptions[prop] = conf[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.timeout = null;
|
||||||
|
this.hasGutter = hasGutter;
|
||||||
|
this.onMouseOver = function(e) { onMouseOver(cm, e); };
|
||||||
|
this.waitingFor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
highlightLines: false,
|
||||||
|
tooltips: true,
|
||||||
|
delay: 500,
|
||||||
|
lintOnChange: true,
|
||||||
|
getAnnotations: null,
|
||||||
|
async: false,
|
||||||
|
selfContain: null,
|
||||||
|
formatAnnotation: null,
|
||||||
|
onUpdateLinting: null
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMarks(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (state.hasGutter) cm.clearGutter(GUTTER_ID);
|
||||||
|
if (state.options.highlightLines) clearErrorLines(cm);
|
||||||
|
for (var i = 0; i < state.marked.length; ++i)
|
||||||
|
state.marked[i].clear();
|
||||||
|
state.marked.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearErrorLines(cm) {
|
||||||
|
cm.eachLine(function(line) {
|
||||||
|
var has = line.wrapClass && /\bCodeMirror-lint-line-\w+\b/.exec(line.wrapClass);
|
||||||
|
if (has) cm.removeLineClass(line, "wrap", has[0]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMarker(cm, labels, severity, multiple, tooltips) {
|
||||||
|
var marker = document.createElement("div"), inner = marker;
|
||||||
|
marker.className = "CodeMirror-lint-marker CodeMirror-lint-marker-" + severity;
|
||||||
|
if (multiple) {
|
||||||
|
inner = marker.appendChild(document.createElement("div"));
|
||||||
|
inner.className = "CodeMirror-lint-marker CodeMirror-lint-marker-multiple";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
|
||||||
|
showTooltipFor(cm, e, labels, inner);
|
||||||
|
});
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxSeverity(a, b) {
|
||||||
|
if (a == "error") return a;
|
||||||
|
else return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupByLine(annotations) {
|
||||||
|
var lines = [];
|
||||||
|
for (var i = 0; i < annotations.length; ++i) {
|
||||||
|
var ann = annotations[i], line = ann.from.line;
|
||||||
|
(lines[line] || (lines[line] = [])).push(ann);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function annotationTooltip(ann) {
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
var tip = document.createElement("div");
|
||||||
|
tip.className = "CodeMirror-lint-message CodeMirror-lint-message-" + severity;
|
||||||
|
if (typeof ann.messageHTML != 'undefined') {
|
||||||
|
tip.innerHTML = ann.messageHTML;
|
||||||
|
} else {
|
||||||
|
tip.appendChild(document.createTextNode(ann.message));
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lintAsync(cm, getAnnotations) {
|
||||||
|
var state = cm.state.lint
|
||||||
|
var id = ++state.waitingFor
|
||||||
|
function abort() {
|
||||||
|
id = -1
|
||||||
|
cm.off("change", abort)
|
||||||
|
}
|
||||||
|
cm.on("change", abort)
|
||||||
|
getAnnotations(cm.getValue(), function(annotations, arg2) {
|
||||||
|
cm.off("change", abort)
|
||||||
|
if (state.waitingFor != id) return
|
||||||
|
if (arg2 && annotations instanceof CodeMirror) annotations = arg2
|
||||||
|
cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}, state.linterOptions, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLinting(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
var options = state.options;
|
||||||
|
/*
|
||||||
|
* Passing rules in `options` property prevents JSHint (and other linters) from complaining
|
||||||
|
* about unrecognized rules like `onUpdateLinting`, `delay`, `lintOnChange`, etc.
|
||||||
|
*/
|
||||||
|
var getAnnotations = options.getAnnotations || cm.getHelper(CodeMirror.Pos(0, 0), "lint");
|
||||||
|
if (!getAnnotations) return;
|
||||||
|
if (options.async || getAnnotations.async) {
|
||||||
|
lintAsync(cm, getAnnotations)
|
||||||
|
} else {
|
||||||
|
var annotations = getAnnotations(cm.getValue(), state.linterOptions, cm);
|
||||||
|
if (!annotations) return;
|
||||||
|
if (annotations.then) annotations.then(function(issues) {
|
||||||
|
cm.operation(function() {updateLinting(cm, issues)})
|
||||||
|
});
|
||||||
|
else cm.operation(function() {updateLinting(cm, annotations)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLinting(cm, annotationsNotSorted) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
var options = state.options;
|
||||||
|
clearMarks(cm);
|
||||||
|
|
||||||
|
var annotations = groupByLine(annotationsNotSorted);
|
||||||
|
|
||||||
|
for (var line = 0; line < annotations.length; ++line) {
|
||||||
|
var anns = annotations[line];
|
||||||
|
if (!anns) continue;
|
||||||
|
|
||||||
|
var maxSeverity = null;
|
||||||
|
var tipLabel = state.hasGutter && document.createDocumentFragment();
|
||||||
|
|
||||||
|
for (var i = 0; i < anns.length; ++i) {
|
||||||
|
var ann = anns[i];
|
||||||
|
var severity = ann.severity;
|
||||||
|
if (!severity) severity = "error";
|
||||||
|
maxSeverity = getMaxSeverity(maxSeverity, severity);
|
||||||
|
|
||||||
|
if (options.formatAnnotation) ann = options.formatAnnotation(ann);
|
||||||
|
if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
|
||||||
|
|
||||||
|
if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
|
||||||
|
className: "CodeMirror-lint-mark CodeMirror-lint-mark-" + severity,
|
||||||
|
__annotation: ann
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (state.hasGutter)
|
||||||
|
cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
|
||||||
|
options.tooltips));
|
||||||
|
|
||||||
|
if (options.highlightLines)
|
||||||
|
cm.addLineClass(line, "wrap", LINT_LINE_ID + maxSeverity);
|
||||||
|
}
|
||||||
|
if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm) {
|
||||||
|
var state = cm.state.lint;
|
||||||
|
if (!state) return;
|
||||||
|
clearTimeout(state.timeout);
|
||||||
|
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
function popupTooltips(cm, annotations, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
var tooltip = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
|
var ann = annotations[i];
|
||||||
|
tooltip.appendChild(annotationTooltip(ann));
|
||||||
|
}
|
||||||
|
showTooltipFor(cm, e, tooltip, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMouseOver(cm, e) {
|
||||||
|
var target = e.target || e.srcElement;
|
||||||
|
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||||
|
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||||
|
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||||
|
|
||||||
|
var annotations = [];
|
||||||
|
for (var i = 0; i < spans.length; ++i) {
|
||||||
|
var ann = spans[i].__annotation;
|
||||||
|
if (ann) annotations.push(ann);
|
||||||
|
}
|
||||||
|
if (annotations.length) popupTooltips(cm, annotations, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||||
|
if (old && old != CodeMirror.Init) {
|
||||||
|
clearMarks(cm);
|
||||||
|
if (cm.state.lint.options.lintOnChange !== false)
|
||||||
|
cm.off("change", onChange);
|
||||||
|
CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
|
||||||
|
clearTimeout(cm.state.lint.timeout);
|
||||||
|
delete cm.state.lint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val) {
|
||||||
|
var gutters = cm.getOption("gutters"), hasLintGutter = false;
|
||||||
|
for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
|
||||||
|
var state = cm.state.lint = new LintState(cm, val, hasLintGutter);
|
||||||
|
if (state.options.lintOnChange)
|
||||||
|
cm.on("change", onChange);
|
||||||
|
if (state.options.tooltips != false && state.options.tooltips != "gutter")
|
||||||
|
CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
|
||||||
|
|
||||||
|
startLinting(cm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("performLint", function() {
|
||||||
|
startLinting(this);
|
||||||
|
});
|
||||||
|
});
|
||||||
86
web/assets/codemirror/xq.css
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright (C) 2011 by MarkLogic Corporation
|
||||||
|
Author: Mike Brevoort <mike@brevoort.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
.cm-s-xq.CodeMirror { border-radius: 1.5rem; border: 1px solid #d9d9d9; height: auto; }
|
||||||
|
.cm-s-xq.CodeMirror:hover { background-color: rgb(232 244 242); border-color: #18947b; transition: all .3s; }
|
||||||
|
.cm-s-xq .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: rgb(221 221 221 / 20%); white-space: nowrap; }
|
||||||
|
.cm-s-xq span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; }
|
||||||
|
.cm-s-xq span.cm-atom { color: #7A316F; font-weight:bold; }
|
||||||
|
.cm-s-xq span.cm-number { color: #e36209; }
|
||||||
|
.cm-s-xq span.cm-def { text-decoration:underline; }
|
||||||
|
.cm-s-xq span.cm-variable { color: black; }
|
||||||
|
.cm-s-xq span.cm-variable-2 { color:black; }
|
||||||
|
.cm-s-xq span.cm-variable-3, .cm-s-xq span.cm-type { color: black; }
|
||||||
|
.cm-s-xq span.cm-property { color: #008771; }
|
||||||
|
.cm-s-xq span.cm-operator {}
|
||||||
|
.cm-s-xq span.cm-comment { color: #bbbbbb; font-style: italic; }
|
||||||
|
.cm-s-xq span.cm-string {}
|
||||||
|
.cm-s-xq span.cm-meta { color: yellow; }
|
||||||
|
.cm-s-xq span.cm-qualifier { color: grey; }
|
||||||
|
.cm-s-xq span.cm-builtin { color: #7EA656; }
|
||||||
|
.cm-s-xq span.cm-bracket { color: #cc7; }
|
||||||
|
.cm-s-xq span.cm-tag { color: #3F7F7F; }
|
||||||
|
.cm-s-xq span.cm-attribute { color: #7F007F; }
|
||||||
|
.cm-s-xq span.cm-error { color: #e04141; }
|
||||||
|
|
||||||
|
.cm-s-xq .CodeMirror-activeline-background { background: #e8f2ff; }
|
||||||
|
.cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; }
|
||||||
|
|
||||||
|
.dark .cm-s-xq.CodeMirror { background-color: #222D42; border-color: #2c3950; color: rgb(255 255 255 / 65%); }
|
||||||
|
.dark .cm-s-xq.CodeMirror:hover { background-color: rgb(0 50 42 / 30%); border-color: #008771; transition: all .3s; }
|
||||||
|
.dark .cm-s-xq div.CodeMirror-selected { background: rgba(0, 0, 0, 0.5); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-line::selection, .dark .cm-s-xq .CodeMirror-line > span::selection, .dark .cm-s-xq .CodeMirror-line > span > span::selection { background: rgba(39, 0, 122, 0.99); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-line::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span::-moz-selection, .dark .cm-s-xq .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 0, 122, 0.99); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-gutters { background: rgb(0 0 0 / 30%); border-right: 1px solid #2c3950; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-guttermarker { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-guttermarker-subtle { color: rgb(255 255 255 / 70%); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-linenumber { color: rgb(255 255 255 / 50%); }
|
||||||
|
.dark .cm-s-xq .CodeMirror-cursor { border-left: 1px solid white; }
|
||||||
|
|
||||||
|
.dark .cm-s-xq span.cm-keyword { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq span.cm-atom { color: #c099ff; }
|
||||||
|
.dark .cm-s-xq span.cm-number { color: #9ccfd8; }
|
||||||
|
.dark .cm-s-xq span.cm-def { color: #FFF; text-decoration:underline; }
|
||||||
|
.dark .cm-s-xq span.cm-variable { color: #FFF; }
|
||||||
|
.dark .cm-s-xq span.cm-variable-2 { color: #EEE; }
|
||||||
|
.dark .cm-s-xq span.cm-variable-3, .dark .cm-s-xq span.cm-type { color: #DDD; }
|
||||||
|
.dark .cm-s-xq span.cm-property { color: #f6c177; }
|
||||||
|
.dark .cm-s-xq span.cm-operator {}
|
||||||
|
.dark .cm-s-xq span.cm-comment { color: gray; }
|
||||||
|
.dark .cm-s-xq span.cm-string {}
|
||||||
|
.dark .cm-s-xq span.cm-meta { color: yellow; }
|
||||||
|
.dark .cm-s-xq span.cm-qualifier { color: #FFF700; }
|
||||||
|
.dark .cm-s-xq span.cm-builtin { color: #30a; }
|
||||||
|
.dark .cm-s-xq span.cm-bracket { color: #cc7; }
|
||||||
|
.dark .cm-s-xq span.cm-tag { color: #FFBD40; }
|
||||||
|
.dark .cm-s-xq span.cm-attribute { color: #FFF700; }
|
||||||
|
.dark .cm-s-xq span.cm-error { color: #e04141; }
|
||||||
|
|
||||||
|
.dark .cm-s-xq .CodeMirror-activeline-background { background: #27282E; }
|
||||||
|
.dark .cm-s-xq .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; }
|
||||||
|
|
||||||
|
.Line-Hover{transition: all .2s;}
|
||||||
|
.Line-Hover:hover{ background-color: rgba(0, 102, 85, 0.05) !important; }
|
||||||
|
.dark .Line-Hover:hover{ background-color: rgb(0 0 0 / 20%) !important; }
|
||||||
|
|
||||||
|
.CodeMirror-foldmarker { color: #fc8800; text-shadow: #ffd8aa 1px 1px 2px, #ffd8aa -1px -1px 2px, #ffd8aa 1px -1px 2px, #ffd8aa -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
|
.dark .CodeMirror-foldmarker { color: #ffffff; text-shadow: #bbb 1px 1px 2px, #bbb -1px -1px 2px, #bbb 1px -1px 2px, #bbb -1px 1px 2px; font-family: arial; line-height: .3; cursor: pointer; }
|
||||||
|
|
@ -1,31 +1,3 @@
|
||||||
class User {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.username = "";
|
|
||||||
this.password = "";
|
|
||||||
this.LoginSecret = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Msg {
|
|
||||||
|
|
||||||
constructor(success, msg, obj) {
|
|
||||||
this.success = false;
|
|
||||||
this.msg = "";
|
|
||||||
this.obj = null;
|
|
||||||
|
|
||||||
if (success != null) {
|
|
||||||
this.success = success;
|
|
||||||
}
|
|
||||||
if (msg != null) {
|
|
||||||
this.msg = msg;
|
|
||||||
}
|
|
||||||
if (obj != null) {
|
|
||||||
this.obj = obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DBInbound {
|
class DBInbound {
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
|
@ -37,7 +9,6 @@ class DBInbound {
|
||||||
this.remark = "";
|
this.remark = "";
|
||||||
this.enable = true;
|
this.enable = true;
|
||||||
this.expiryTime = 0;
|
this.expiryTime = 0;
|
||||||
this.limitIp = 0;
|
|
||||||
|
|
||||||
this.listen = "";
|
this.listen = "";
|
||||||
this.port = 0;
|
this.port = 0;
|
||||||
|
|
@ -141,6 +112,19 @@ class DBInbound {
|
||||||
return Inbound.fromJson(config);
|
return Inbound.fromJson(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMultiUser() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS:
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.TROJAN:
|
||||||
|
return true;
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return this.toInbound().isSSMultiUser;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasLink() {
|
hasLink() {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
|
|
@ -153,59 +137,8 @@ class DBInbound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address=this.address, remark=this.remark, clientIndex=0) {
|
genInboundLinks(remarkModel) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(address, remark, clientIndex);
|
return inbound.genInboundLinks(this.remark,remarkModel);
|
||||||
}
|
|
||||||
|
|
||||||
get genInboundLinks() {
|
|
||||||
const inbound = this.toInbound();
|
|
||||||
return inbound.genInboundLinks(this.address, this.remark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AllSetting {
|
|
||||||
|
|
||||||
constructor(data) {
|
|
||||||
this.webListen = "";
|
|
||||||
this.webDomain = "";
|
|
||||||
this.webPort = 2053;
|
|
||||||
this.webCertFile = "";
|
|
||||||
this.webKeyFile = "";
|
|
||||||
this.webBasePath = "/";
|
|
||||||
this.sessionMaxAge = "";
|
|
||||||
this.expireDiff = "";
|
|
||||||
this.trafficDiff = "";
|
|
||||||
this.tgBotEnable = false;
|
|
||||||
this.tgBotToken = "";
|
|
||||||
this.tgBotChatId = "";
|
|
||||||
this.tgRunTime = "@daily";
|
|
||||||
this.tgBotBackup = false;
|
|
||||||
this.tgBotLoginNotify = true;
|
|
||||||
this.tgCpu = "";
|
|
||||||
this.tgLang = "en-US";
|
|
||||||
this.xrayTemplateConfig = "";
|
|
||||||
this.secretEnable = false;
|
|
||||||
this.subEnable = false;
|
|
||||||
this.subListen = "";
|
|
||||||
this.subPort = "2096";
|
|
||||||
this.subPath = "/sub/";
|
|
||||||
this.subDomain = "";
|
|
||||||
this.subCertFile = "";
|
|
||||||
this.subKeyFile = "";
|
|
||||||
this.subUpdates = 0;
|
|
||||||
this.subEncrypt = true;
|
|
||||||
this.subShowInfo = true;
|
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
|
||||||
|
|
||||||
if (data == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ObjectUtil.cloneProps(this, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(other) {
|
|
||||||
return ObjectUtil.equals(this, other);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
900
web/assets/js/model/outbound.js
Normal file
|
|
@ -0,0 +1,900 @@
|
||||||
|
const Protocols = {
|
||||||
|
Freedom: "freedom",
|
||||||
|
Blackhole: "blackhole",
|
||||||
|
DNS: "dns",
|
||||||
|
VMess: "vmess",
|
||||||
|
VLESS: "vless",
|
||||||
|
Trojan: "trojan",
|
||||||
|
Shadowsocks: "shadowsocks",
|
||||||
|
Socks: "socks",
|
||||||
|
HTTP: "http",
|
||||||
|
};
|
||||||
|
|
||||||
|
const SSMethods = {
|
||||||
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
|
};
|
||||||
|
|
||||||
|
const TLS_FLOW_CONTROL = {
|
||||||
|
VISION: "xtls-rprx-vision",
|
||||||
|
VISION_UDP443: "xtls-rprx-vision-udp443",
|
||||||
|
};
|
||||||
|
|
||||||
|
const UTLS_FINGERPRINT = {
|
||||||
|
UTLS_CHROME: "chrome",
|
||||||
|
UTLS_FIREFOX: "firefox",
|
||||||
|
UTLS_SAFARI: "safari",
|
||||||
|
UTLS_IOS: "ios",
|
||||||
|
UTLS_android: "android",
|
||||||
|
UTLS_EDGE: "edge",
|
||||||
|
UTLS_360: "360",
|
||||||
|
UTLS_QQ: "qq",
|
||||||
|
UTLS_RANDOM: "random",
|
||||||
|
UTLS_RANDOMIZED: "randomized",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALPN_OPTION = {
|
||||||
|
H3: "h3",
|
||||||
|
H2: "h2",
|
||||||
|
HTTP1: "http/1.1",
|
||||||
|
};
|
||||||
|
|
||||||
|
const outboundDomainStrategies = [
|
||||||
|
"AsIs",
|
||||||
|
"UseIP",
|
||||||
|
"UseIPv4",
|
||||||
|
"UseIPv6"
|
||||||
|
]
|
||||||
|
|
||||||
|
Object.freeze(Protocols);
|
||||||
|
Object.freeze(SSMethods);
|
||||||
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
|
Object.freeze(ALPN_OPTION);
|
||||||
|
Object.freeze(outboundDomainStrategies);
|
||||||
|
|
||||||
|
class CommonClass {
|
||||||
|
|
||||||
|
static toJsonArray(arr) {
|
||||||
|
return arr.map(obj => obj.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson() {
|
||||||
|
return new CommonClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(format=true) {
|
||||||
|
return format ? JSON.stringify(this.toJson(), null, 2) : JSON.stringify(this.toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TcpStreamSettings extends CommonClass {
|
||||||
|
constructor(type='none', host, path) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.host = host;
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let header = json.header;
|
||||||
|
if (!header) return new TcpStreamSettings();
|
||||||
|
if(header.type == 'http' && header.request){
|
||||||
|
return new TcpStreamSettings(
|
||||||
|
header.type,
|
||||||
|
header.request.headers.Host.join(','),
|
||||||
|
header.request.path.join(','),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new TcpStreamSettings(header.type,'','');
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
request: this.type === 'http' ? {
|
||||||
|
headers: {
|
||||||
|
Host: ObjectUtil.isEmpty(this.host) ? [] : this.host.split(',')
|
||||||
|
},
|
||||||
|
path: ObjectUtil.isEmpty(this.path) ? ["/"] : this.path.split(',')
|
||||||
|
} : undefined,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class KcpStreamSettings extends CommonClass {
|
||||||
|
constructor(mtu=1350, tti=20,
|
||||||
|
uplinkCapacity=5,
|
||||||
|
downlinkCapacity=20,
|
||||||
|
congestion=false,
|
||||||
|
readBufferSize=2,
|
||||||
|
writeBufferSize=2,
|
||||||
|
type='none',
|
||||||
|
seed='',
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.mtu = mtu;
|
||||||
|
this.tti = tti;
|
||||||
|
this.upCap = uplinkCapacity;
|
||||||
|
this.downCap = downlinkCapacity;
|
||||||
|
this.congestion = congestion;
|
||||||
|
this.readBuffer = readBufferSize;
|
||||||
|
this.writeBuffer = writeBufferSize;
|
||||||
|
this.type = type;
|
||||||
|
this.seed = seed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new KcpStreamSettings(
|
||||||
|
json.mtu,
|
||||||
|
json.tti,
|
||||||
|
json.uplinkCapacity,
|
||||||
|
json.downlinkCapacity,
|
||||||
|
json.congestion,
|
||||||
|
json.readBufferSize,
|
||||||
|
json.writeBufferSize,
|
||||||
|
ObjectUtil.isEmpty(json.header) ? 'none' : json.header.type,
|
||||||
|
json.seed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
mtu: this.mtu,
|
||||||
|
tti: this.tti,
|
||||||
|
uplinkCapacity: this.upCap,
|
||||||
|
downlinkCapacity: this.downCap,
|
||||||
|
congestion: this.congestion,
|
||||||
|
readBufferSize: this.readBuffer,
|
||||||
|
writeBufferSize: this.writeBuffer,
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
},
|
||||||
|
seed: this.seed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WsStreamSettings extends CommonClass {
|
||||||
|
constructor(path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new WsStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HttpStreamSettings extends CommonClass {
|
||||||
|
constructor(path='/', host='') {
|
||||||
|
super();
|
||||||
|
this.path = path;
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new HttpStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.host ? json.host.join(',') : '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
path: this.path,
|
||||||
|
host: ObjectUtil.isEmpty(this.host) ? [''] : this.host.split(','),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class QuicStreamSettings extends CommonClass {
|
||||||
|
constructor(security='none',
|
||||||
|
key='', type='none') {
|
||||||
|
super();
|
||||||
|
this.security = security;
|
||||||
|
this.key = key;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new QuicStreamSettings(
|
||||||
|
json.security,
|
||||||
|
json.key,
|
||||||
|
json.header ? json.header.type : 'none',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
security: this.security,
|
||||||
|
key: this.key,
|
||||||
|
header: {
|
||||||
|
type: this.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GrpcStreamSettings extends CommonClass {
|
||||||
|
constructor(serviceName="", multiMode=false) {
|
||||||
|
super();
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.multiMode = multiMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serviceName: this.serviceName,
|
||||||
|
multiMode: this.multiMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TlsStreamSettings extends CommonClass {
|
||||||
|
constructor(serverName='',
|
||||||
|
alpn=[],
|
||||||
|
fingerprint = '',
|
||||||
|
allowInsecure = false) {
|
||||||
|
super();
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.alpn = alpn;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.allowInsecure = allowInsecure;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new TlsStreamSettings(
|
||||||
|
json.serverName,
|
||||||
|
json.alpn,
|
||||||
|
json.fingerprint,
|
||||||
|
json.allowInsecure,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
serverName: this.serverName,
|
||||||
|
alpn: this.alpn,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
allowInsecure: this.allowInsecure,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RealityStreamSettings extends CommonClass {
|
||||||
|
constructor(publicKey = '', fingerprint = '', serverName = '', shortId = '', spiderX = '/') {
|
||||||
|
super();
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
this.serverName = serverName;
|
||||||
|
this.shortId = shortId
|
||||||
|
this.spiderX = spiderX;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
return new RealityStreamSettings(
|
||||||
|
json.publicKey,
|
||||||
|
json.fingerprint,
|
||||||
|
json.serverName,
|
||||||
|
json.shortId,
|
||||||
|
json.spiderX,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
publicKey: this.publicKey,
|
||||||
|
fingerprint: this.fingerprint,
|
||||||
|
serverName: this.serverName,
|
||||||
|
shortId: this.shortId,
|
||||||
|
spiderX: this.spiderX,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class StreamSettings extends CommonClass {
|
||||||
|
constructor(network='tcp',
|
||||||
|
security='none',
|
||||||
|
tlsSettings=new TlsStreamSettings(),
|
||||||
|
realitySettings = new RealityStreamSettings(),
|
||||||
|
tcpSettings=new TcpStreamSettings(),
|
||||||
|
kcpSettings=new KcpStreamSettings(),
|
||||||
|
wsSettings=new WsStreamSettings(),
|
||||||
|
httpSettings=new HttpStreamSettings(),
|
||||||
|
quicSettings=new QuicStreamSettings(),
|
||||||
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.network = network;
|
||||||
|
this.security = security;
|
||||||
|
this.tls = tlsSettings;
|
||||||
|
this.reality = realitySettings;
|
||||||
|
this.tcp = tcpSettings;
|
||||||
|
this.kcp = kcpSettings;
|
||||||
|
this.ws = wsSettings;
|
||||||
|
this.http = httpSettings;
|
||||||
|
this.quic = quicSettings;
|
||||||
|
this.grpc = grpcSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isTls() {
|
||||||
|
return this.security === 'tls';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isReality() {
|
||||||
|
return this.security === "reality";
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new StreamSettings(
|
||||||
|
json.network,
|
||||||
|
json.security,
|
||||||
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
|
TcpStreamSettings.fromJson(json.tcpSettings),
|
||||||
|
KcpStreamSettings.fromJson(json.kcpSettings),
|
||||||
|
WsStreamSettings.fromJson(json.wsSettings),
|
||||||
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
const network = this.network;
|
||||||
|
return {
|
||||||
|
network: network,
|
||||||
|
security: this.security,
|
||||||
|
tlsSettings: this.security == 'tls' ? this.tls.toJson() : undefined,
|
||||||
|
realitySettings: this.security == 'reality' ? this.reality.toJson() : undefined,
|
||||||
|
tcpSettings: network === 'tcp' ? this.tcp.toJson() : undefined,
|
||||||
|
kcpSettings: network === 'kcp' ? this.kcp.toJson() : undefined,
|
||||||
|
wsSettings: network === 'ws' ? this.ws.toJson() : undefined,
|
||||||
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Outbound extends CommonClass {
|
||||||
|
constructor(
|
||||||
|
tag='',
|
||||||
|
protocol=Protocols.VMess,
|
||||||
|
settings=null,
|
||||||
|
streamSettings = new StreamSettings(),
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.tag = tag;
|
||||||
|
this._protocol = protocol;
|
||||||
|
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
||||||
|
this.stream = streamSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
get protocol() {
|
||||||
|
return this._protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
set protocol(protocol) {
|
||||||
|
this._protocol = protocol;
|
||||||
|
this.settings = Outbound.Settings.getSettings(protocol);
|
||||||
|
this.stream = new StreamSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableTls() {
|
||||||
|
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this is used for xtls-rprx-vision
|
||||||
|
canEnableTlsFlow() {
|
||||||
|
if ((this.stream.security != 'none') && (this.stream.network === "tcp")) {
|
||||||
|
return this.protocol === Protocols.VLESS;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableReality() {
|
||||||
|
if (![Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||||
|
return ["tcp", "http", "grpc"].includes(this.stream.network);
|
||||||
|
}
|
||||||
|
|
||||||
|
canEnableStream() {
|
||||||
|
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasVnext() {
|
||||||
|
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasServers() {
|
||||||
|
return [Protocols.Trojan, Protocols.Shadowsocks, Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAddressPort() {
|
||||||
|
return [
|
||||||
|
Protocols.DNS,
|
||||||
|
Protocols.VMess,
|
||||||
|
Protocols.VLESS,
|
||||||
|
Protocols.Trojan,
|
||||||
|
Protocols.Shadowsocks,
|
||||||
|
Protocols.Socks,
|
||||||
|
Protocols.HTTP
|
||||||
|
].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUsername() {
|
||||||
|
return [Protocols.Socks, Protocols.HTTP].includes(this.protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound(
|
||||||
|
json.tag,
|
||||||
|
json.protocol,
|
||||||
|
Outbound.Settings.fromJson(json.protocol, json.settings),
|
||||||
|
StreamSettings.fromJson(json.streamSettings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
tag: this.tag == '' ? undefined : this.tag,
|
||||||
|
protocol: this.protocol,
|
||||||
|
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
||||||
|
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromLink(link) {
|
||||||
|
data = link.split('://');
|
||||||
|
if(data.length !=2) return null;
|
||||||
|
switch(data[0].toLowerCase()){
|
||||||
|
case Protocols.VMess:
|
||||||
|
return this.fromVmessLink(JSON.parse(Base64.decode(data[1])));
|
||||||
|
case Protocols.VLESS:
|
||||||
|
case Protocols.Trojan:
|
||||||
|
case 'ss':
|
||||||
|
return this.fromParamLink(link);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromVmessLink(json={}){
|
||||||
|
let stream = new StreamSettings(json.net, json.tls);
|
||||||
|
|
||||||
|
let network = json.net;
|
||||||
|
if (network === 'tcp') {
|
||||||
|
stream.tcp = new TcpStreamSettings(
|
||||||
|
json.type,
|
||||||
|
json.host ?? '',
|
||||||
|
json.path ?? '');
|
||||||
|
} else if (network === 'kcp') {
|
||||||
|
stream.kcp = new KcpStreamSettings();
|
||||||
|
stream.type = json.type;
|
||||||
|
stream.seed = json.path;
|
||||||
|
} else if (network === 'ws') {
|
||||||
|
stream.ws = new WsStreamSettings(json.path,json.host);
|
||||||
|
} else if (network === 'http' || network == 'h2') {
|
||||||
|
stream.network = 'http'
|
||||||
|
stream.http = new HttpStreamSettings(
|
||||||
|
json.path,
|
||||||
|
json.host);
|
||||||
|
} else if (network === 'quic') {
|
||||||
|
stream.quic = new QuicStreamSettings(
|
||||||
|
json.host ? json.host : 'none',
|
||||||
|
json.path,
|
||||||
|
json.type ? json.type : 'none');
|
||||||
|
} else if (network === 'grpc') {
|
||||||
|
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(json.tls && json.tls == 'tls'){
|
||||||
|
stream.tls = new TlsStreamSettings(
|
||||||
|
json.sni,
|
||||||
|
json.alpn ? json.alpn.split(',') : [],
|
||||||
|
json.fp,
|
||||||
|
json.allowInsecure);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromParamLink(link){
|
||||||
|
const url = new URL(link);
|
||||||
|
let type = url.searchParams.get('type');
|
||||||
|
let security = url.searchParams.get('security') ?? 'none';
|
||||||
|
let stream = new StreamSettings(type, security);
|
||||||
|
|
||||||
|
let headerType = url.searchParams.get('headerType');
|
||||||
|
let host = url.searchParams.get('host');
|
||||||
|
let path = url.searchParams.get('path');
|
||||||
|
|
||||||
|
if (type === 'tcp') {
|
||||||
|
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||||
|
} else if (type === 'kcp') {
|
||||||
|
stream.kcp = new KcpStreamSettings();
|
||||||
|
stream.kcp.type = headerType ?? 'none';
|
||||||
|
stream.kcp.seed = path;
|
||||||
|
} else if (type === 'ws') {
|
||||||
|
stream.ws = new WsStreamSettings(path,host);
|
||||||
|
} else if (type === 'http' || type == 'h2') {
|
||||||
|
stream.http = new HttpStreamSettings(path,host);
|
||||||
|
} else if (type === 'quic') {
|
||||||
|
stream.quic = new QuicStreamSettings(
|
||||||
|
url.searchParams.get('quicSecurity') ?? 'none',
|
||||||
|
url.searchParams.get('key') ?? '',
|
||||||
|
headerType ?? 'none');
|
||||||
|
} else if (type === 'grpc') {
|
||||||
|
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(security == 'tls'){
|
||||||
|
let fp=url.searchParams.get('fp') ?? 'none';
|
||||||
|
let alpn=url.searchParams.get('alpn');
|
||||||
|
let allowInsecure=url.searchParams.get('allowInsecure');
|
||||||
|
let sni=url.searchParams.get('sni') ?? '';
|
||||||
|
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(security == 'reality'){
|
||||||
|
let pbk=url.searchParams.get('pbk');
|
||||||
|
let fp=url.searchParams.get('fp');
|
||||||
|
let sni=url.searchParams.get('sni') ?? '';
|
||||||
|
let sid=url.searchParams.get('sid') ?? '';
|
||||||
|
let spx=url.searchParams.get('spx') ?? '';
|
||||||
|
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = link.split('?');
|
||||||
|
if(data.length != 2) return null;
|
||||||
|
|
||||||
|
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
|
||||||
|
const match = link.match(regex);
|
||||||
|
|
||||||
|
if (!match) return null;
|
||||||
|
let [, protocol, userData, address, port, ] = match;
|
||||||
|
port *= 1;
|
||||||
|
if(protocol == 'ss') {
|
||||||
|
protocol = 'shadowsocks';
|
||||||
|
userData = atob(userData).split(':');
|
||||||
|
}
|
||||||
|
var settings;
|
||||||
|
switch(protocol){
|
||||||
|
case Protocols.VLESS:
|
||||||
|
settings = new Outbound.VLESSSettings(address, port, userData, url.searchParams.get('flow') ?? '');
|
||||||
|
break;
|
||||||
|
case Protocols.Trojan:
|
||||||
|
settings = new Outbound.TrojanSettings(address, port, userData);
|
||||||
|
break;
|
||||||
|
case Protocols.Shadowsocks:
|
||||||
|
let method = userData.splice(0,1)[0];
|
||||||
|
settings = new Outbound.ShadowsocksSettings(address, port, userData.join(":"), method, true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let remark = decodeURIComponent(url.hash);
|
||||||
|
// Remove '#' from url.hash
|
||||||
|
remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port;
|
||||||
|
return new Outbound(remark, protocol, settings, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Outbound.Settings = class extends CommonClass {
|
||||||
|
constructor(protocol) {
|
||||||
|
super();
|
||||||
|
this.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSettings(protocol) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.Freedom: return new Outbound.FreedomSettings();
|
||||||
|
case Protocols.Blackhole: return new Outbound.BlackholeSettings();
|
||||||
|
case Protocols.DNS: return new Outbound.DNSSettings();
|
||||||
|
case Protocols.VMess: return new Outbound.VmessSettings();
|
||||||
|
case Protocols.VLESS: return new Outbound.VLESSSettings();
|
||||||
|
case Protocols.Trojan: return new Outbound.TrojanSettings();
|
||||||
|
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||||
|
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||||
|
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(protocol, json) {
|
||||||
|
switch (protocol) {
|
||||||
|
case Protocols.Freedom: return Outbound.FreedomSettings.fromJson(json);
|
||||||
|
case Protocols.Blackhole: return Outbound.BlackholeSettings.fromJson(json);
|
||||||
|
case Protocols.DNS: return Outbound.DNSSettings.fromJson(json);
|
||||||
|
case Protocols.VMess: return Outbound.VmessSettings.fromJson(json);
|
||||||
|
case Protocols.VLESS: return Outbound.VLESSSettings.fromJson(json);
|
||||||
|
case Protocols.Trojan: return Outbound.TrojanSettings.fromJson(json);
|
||||||
|
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||||
|
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||||
|
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.FreedomSettings = class extends CommonClass {
|
||||||
|
constructor(domainStrategy='', fragment={}) {
|
||||||
|
super();
|
||||||
|
this.domainStrategy = domainStrategy;
|
||||||
|
this.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.FreedomSettings(
|
||||||
|
json.domainStrategy,
|
||||||
|
json.fragment ? Outbound.FreedomSettings.Fragment.fromJson(json.fragment) : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
domainStrategy: ObjectUtil.isEmpty(this.domainStrategy) ? undefined : this.domainStrategy,
|
||||||
|
fragment: Object.keys(this.fragment).length === 0 ? undefined : this.fragment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||||
|
constructor(packets='1-3',length='',interval=''){
|
||||||
|
super();
|
||||||
|
this.packets = packets;
|
||||||
|
this.length = length;
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.FreedomSettings.Fragment(
|
||||||
|
json.packets,
|
||||||
|
json.length,
|
||||||
|
json.interval,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.BlackholeSettings = class extends CommonClass {
|
||||||
|
constructor(type) {
|
||||||
|
super();
|
||||||
|
this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
return new Outbound.BlackholeSettings(
|
||||||
|
json.response ? json.response.type : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
response: ObjectUtil.isEmpty(this.type) ? undefined : {type: this.type},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.DNSSettings = class extends CommonClass {
|
||||||
|
constructor(network='udp', address='1.1.1.1', port=53) {
|
||||||
|
super();
|
||||||
|
this.network = network;
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}){
|
||||||
|
return new Outbound.DNSSettings(
|
||||||
|
json.network,
|
||||||
|
json.address,
|
||||||
|
json.port,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.VmessSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, id) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VmessSettings();
|
||||||
|
return new Outbound.VmessSettings(
|
||||||
|
json.vnext[0].address,
|
||||||
|
json.vnext[0].port,
|
||||||
|
json.vnext[0].users[0].id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
vnext: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: [{id: this.id}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.VLESSSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, id, flow, encryption='none') {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.id = id;
|
||||||
|
this.flow = flow;
|
||||||
|
this.encryption = encryption
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.vnext)) return new Outbound.VLESSSettings();
|
||||||
|
return new Outbound.VLESSSettings(
|
||||||
|
json.vnext[0].address,
|
||||||
|
json.vnext[0].port,
|
||||||
|
json.vnext[0].users[0].id,
|
||||||
|
json.vnext[0].users[0].flow,
|
||||||
|
json.vnext[0].users[0].encryption,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
vnext: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: [{id: this.id, flow: this.flow, encryption: 'none',}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.TrojanSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
if(ObjectUtil.isArrEmpty(json.servers)) return new Outbound.TrojanSettings();
|
||||||
|
return new Outbound.TrojanSettings(
|
||||||
|
json.servers[0].address,
|
||||||
|
json.servers[0].port,
|
||||||
|
json.servers[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
password: this.password,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.ShadowsocksSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, password, method, uot) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.password = password;
|
||||||
|
this.method = method;
|
||||||
|
this.uot = uot;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
let servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{}];
|
||||||
|
return new Outbound.ShadowsocksSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
servers[0].password,
|
||||||
|
servers[0].method,
|
||||||
|
servers[0].uot,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
password: this.password,
|
||||||
|
method: this.method,
|
||||||
|
uot: this.uot,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.SocksSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, user, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
|
return new Outbound.SocksSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Outbound.HttpSettings = class extends CommonClass {
|
||||||
|
constructor(address, port, user, password) {
|
||||||
|
super();
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.user = user;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json={}) {
|
||||||
|
servers = json.servers;
|
||||||
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
|
return new Outbound.HttpSettings(
|
||||||
|
servers[0].address,
|
||||||
|
servers[0].port,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||||
|
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
servers: [{
|
||||||
|
address: this.address,
|
||||||
|
port: this.port,
|
||||||
|
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
48
web/assets/js/model/setting.js
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
class AllSetting {
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.webListen = "";
|
||||||
|
this.webDomain = "";
|
||||||
|
this.webPort = 2053;
|
||||||
|
this.webCertFile = "";
|
||||||
|
this.webKeyFile = "";
|
||||||
|
this.webBasePath = "/";
|
||||||
|
this.sessionMaxAge = "";
|
||||||
|
this.pageSize = 0;
|
||||||
|
this.expireDiff = "";
|
||||||
|
this.trafficDiff = "";
|
||||||
|
this.remarkModel = "-ieo";
|
||||||
|
this.tgBotEnable = false;
|
||||||
|
this.tgBotToken = "";
|
||||||
|
this.tgBotChatId = "";
|
||||||
|
this.tgRunTime = "@daily";
|
||||||
|
this.tgBotBackup = false;
|
||||||
|
this.tgBotLoginNotify = false;
|
||||||
|
this.tgCpu = "";
|
||||||
|
this.tgLang = "en-US";
|
||||||
|
this.xrayTemplateConfig = "";
|
||||||
|
this.secretEnable = false;
|
||||||
|
this.subEnable = false;
|
||||||
|
this.subListen = "";
|
||||||
|
this.subPort = "2096";
|
||||||
|
this.subPath = "/sub/";
|
||||||
|
this.subDomain = "";
|
||||||
|
this.subCertFile = "";
|
||||||
|
this.subKeyFile = "";
|
||||||
|
this.subUpdates = 0;
|
||||||
|
this.subEncrypt = true;
|
||||||
|
this.subShowInfo = false;
|
||||||
|
this.subURI = '';
|
||||||
|
|
||||||
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ObjectUtil.cloneProps(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
equals(other) {
|
||||||
|
return ObjectUtil.equals(this, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,13 +8,6 @@ const Protocols = {
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VmessMethods = {
|
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
|
||||||
CHACHA20_POLY1305: 'chacha20-poly1305',
|
|
||||||
AUTO: 'auto',
|
|
||||||
NONE: 'none',
|
|
||||||
};
|
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
AES_256_GCM: 'aes-256-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
AES_128_GCM: 'aes-128-gcm',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
|
|
@ -91,7 +84,6 @@ const SNIFFING_OPTION = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
Object.freeze(VmessMethods);
|
|
||||||
Object.freeze(SSMethods);
|
Object.freeze(SSMethods);
|
||||||
Object.freeze(XTLS_FLOW_CONTROL);
|
Object.freeze(XTLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
|
|
@ -422,7 +414,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuicStreamSettings extends XrayCommonClass {
|
class QuicStreamSettings extends XrayCommonClass {
|
||||||
constructor(security=VmessMethods.NONE,
|
constructor(security='none',
|
||||||
key=RandomUtil.randomSeq(10), type='none') {
|
key=RandomUtil.randomSeq(10), type='none') {
|
||||||
super();
|
super();
|
||||||
this.security = security;
|
this.security = security;
|
||||||
|
|
@ -485,7 +477,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
alpn=[ALPN_OPTION.HTTP1,ALPN_OPTION.H2],
|
alpn=[ALPN_OPTION.HTTP1,ALPN_OPTION.H2],
|
||||||
settings=new TlsStreamSettings.Settings()) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.sni = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
|
|
@ -527,7 +519,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serverName: this.server,
|
serverName: this.sni,
|
||||||
minVersion: this.minVersion,
|
minVersion: this.minVersion,
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
|
|
@ -586,27 +578,21 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
|
constructor(allowInsecure = false, fingerprint = '') {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
|
||||||
this.domains = domains;
|
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.serverName,
|
|
||||||
json.domains,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
|
||||||
domains: this.domains,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -616,7 +602,7 @@ class XtlsStreamSettings extends XrayCommonClass {
|
||||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||||
settings=new XtlsStreamSettings.Settings()) {
|
settings=new XtlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.sni = serverName;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
@ -650,7 +636,7 @@ class XtlsStreamSettings extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serverName: this.server,
|
serverName: this.sni,
|
||||||
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
certificates: XtlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
|
|
@ -700,21 +686,18 @@ XtlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
XtlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, serverName = '') {
|
constructor(allowInsecure = false) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.serverName = serverName;
|
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new XtlsStreamSettings.Settings(
|
return new XtlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.servername,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
serverName: this.serverName,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -781,18 +764,16 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, spiderX= '/') {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
|
||||||
this.spiderX = spiderX;
|
this.spiderX = spiderX;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new RealityStreamSettings.Settings(
|
return new RealityStreamSettings.Settings(
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.serverName,
|
|
||||||
json.spiderX,
|
json.spiderX,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -800,7 +781,6 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
return {
|
return {
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
|
||||||
spiderX: this.spiderX,
|
spiderX: this.spiderX,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -837,6 +817,7 @@ class SockoptStreamSettings extends XrayCommonClass {
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
|
externalProxy = [],
|
||||||
tlsSettings=new TlsStreamSettings(),
|
tlsSettings=new TlsStreamSettings(),
|
||||||
xtlsSettings=new XtlsStreamSettings(),
|
xtlsSettings=new XtlsStreamSettings(),
|
||||||
realitySettings = new RealityStreamSettings(),
|
realitySettings = new RealityStreamSettings(),
|
||||||
|
|
@ -851,6 +832,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.security = security;
|
this.security = security;
|
||||||
|
this.externalProxy = externalProxy;
|
||||||
this.tls = tlsSettings;
|
this.tls = tlsSettings;
|
||||||
this.xtls = xtlsSettings;
|
this.xtls = xtlsSettings;
|
||||||
this.reality = realitySettings;
|
this.reality = realitySettings;
|
||||||
|
|
@ -909,10 +891,10 @@ class StreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
json.network,
|
json.network,
|
||||||
json.security,
|
json.security,
|
||||||
|
json.externalProxy,
|
||||||
TlsStreamSettings.fromJson(json.tlsSettings),
|
TlsStreamSettings.fromJson(json.tlsSettings),
|
||||||
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
XtlsStreamSettings.fromJson(json.xtlsSettings),
|
||||||
RealityStreamSettings.fromJson(json.realitySettings),
|
RealityStreamSettings.fromJson(json.realitySettings),
|
||||||
|
|
@ -931,6 +913,7 @@ class StreamSettings extends XrayCommonClass {
|
||||||
return {
|
return {
|
||||||
network: network,
|
network: network,
|
||||||
security: this.security,
|
security: this.security,
|
||||||
|
externalProxy: this.externalProxy,
|
||||||
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
tlsSettings: this.isTls ? this.tls.toJson() : undefined,
|
||||||
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
xtlsSettings: this.isXtls ? this.xtls.toJson() : undefined,
|
||||||
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
realitySettings: this.isReality ? this.reality.toJson() : undefined,
|
||||||
|
|
@ -990,6 +973,16 @@ class Inbound extends XrayCommonClass {
|
||||||
return this.clientStats;
|
return this.clientStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get clients() {
|
||||||
|
switch (this.protocol) {
|
||||||
|
case Protocols.VMESS: return this.settings.vmesses;
|
||||||
|
case Protocols.VLESS: return this.settings.vlesses;
|
||||||
|
case Protocols.TROJAN: return this.settings.trojans;
|
||||||
|
case Protocols.SHADOWSOCKS: return this.isSSMultiUser ? this.settings.shadowsockses : null;
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get protocol() {
|
get protocol() {
|
||||||
return this._protocol;
|
return this._protocol;
|
||||||
}
|
}
|
||||||
|
|
@ -1087,9 +1080,9 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
if (this.stream.isTls) return this.stream.tls.sni;
|
||||||
return this.stream.tls.server;
|
if (this.stream.isXtls) return this.stream.xtls.sni;
|
||||||
}
|
if (this.stream.isReality) return this.stream.reality.serverNames;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1140,118 +1133,39 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
isExpiry(index) {
|
isExpiry(index) {
|
||||||
switch (this.protocol) {
|
let exp = this.clients[index].expiryTime;
|
||||||
case Protocols.VMESS:
|
return exp > 0 ? exp < new Date().getTime() : false;
|
||||||
if(this.settings.vmesses[index].expiryTime > 0)
|
|
||||||
return this.settings.vmesses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.VLESS:
|
|
||||||
if(this.settings.vlesses[index].expiryTime > 0)
|
|
||||||
return this.settings.vlesses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
if(this.settings.trojans[index].expiryTime > 0)
|
|
||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
if(this.settings.shadowsockses.length > 0 && this.settings.shadowsockses[index].expiryTime > 0)
|
|
||||||
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
canEnableTls() {
|
||||||
switch (this.protocol) {
|
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
||||||
case Protocols.VMESS:
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.network) {
|
|
||||||
case "tcp":
|
|
||||||
case "ws":
|
|
||||||
case "http":
|
|
||||||
case "quic":
|
|
||||||
case "grpc":
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableReality() {
|
canEnableReality() {
|
||||||
switch (this.protocol) {
|
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||||
case Protocols.VLESS:
|
return ["tcp", "http", "grpc"].includes(this.network);
|
||||||
case Protocols.TROJAN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (this.network) {
|
|
||||||
case "tcp":
|
|
||||||
case "http":
|
|
||||||
case "grpc":
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//this is used for xtls-rprx-vision
|
//this is used for xtls-rprx-vision
|
||||||
canEnableTlsFlow() {
|
canEnableTlsFlow() {
|
||||||
if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
|
if (((this.stream.security === 'tls') || (this.stream.security === 'reality')) && (this.network === "tcp")) {
|
||||||
switch (this.protocol) {
|
return this.protocol === Protocols.VLESS;
|
||||||
case Protocols.VLESS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
canSetTls() {
|
|
||||||
return this.canEnableTls();
|
|
||||||
}
|
|
||||||
|
|
||||||
canEnableXtls() {
|
canEnableXtls() {
|
||||||
switch (this.protocol) {
|
if(![Protocols.VLESS, Protocols.TROJAN].includes(this.protocol)) return false;
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.network === "tcp";
|
return this.network === "tcp";
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableStream() {
|
canEnableStream() {
|
||||||
switch (this.protocol) {
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
case Protocols.VMESS:
|
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canSniffing() {
|
canSniffing() {
|
||||||
switch (this.protocol) {
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
case Protocols.VMESS:
|
|
||||||
case Protocols.VLESS:
|
|
||||||
case Protocols.TROJAN:
|
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
|
@ -1264,19 +1178,20 @@ class Inbound extends XrayCommonClass {
|
||||||
this.sniffing = new Sniffing();
|
this.sniffing = new Sniffing();
|
||||||
}
|
}
|
||||||
|
|
||||||
genVmessLink(address='', remark='', clientIndex=0) {
|
genVmessLink(address='', port=this.port, forceTls, remark='', clientId) {
|
||||||
if (this.protocol !== Protocols.VMESS) {
|
if (this.protocol !== Protocols.VMESS) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
let obj = {
|
let obj = {
|
||||||
v: '2',
|
v: '2',
|
||||||
ps: remark,
|
ps: remark,
|
||||||
add: address,
|
add: address,
|
||||||
port: this.port,
|
port: port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: clientId,
|
||||||
net: this.stream.network,
|
net: this.stream.network,
|
||||||
type: 'none',
|
type: 'none',
|
||||||
tls: this.stream.security,
|
tls: security,
|
||||||
};
|
};
|
||||||
let network = this.stream.network;
|
let network = this.stream.network;
|
||||||
if (network === 'tcp') {
|
if (network === 'tcp') {
|
||||||
|
|
@ -1316,12 +1231,9 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream.security === 'tls') {
|
if (security === 'tls') {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
obj.add = this.stream.tls.server;
|
obj.sni = this.stream.tls.sni;
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.serverName)){
|
|
||||||
obj.sni = this.stream.tls.settings.serverName;
|
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
if (!ObjectUtil.isEmpty(this.stream.tls.settings.fingerprint)){
|
||||||
obj.fp = this.stream.tls.settings.fingerprint;
|
obj.fp = this.stream.tls.settings.fingerprint;
|
||||||
|
|
@ -1337,11 +1249,10 @@ class Inbound extends XrayCommonClass {
|
||||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
genVLESSLink(address = '', remark='', clientIndex=0) {
|
genVLESSLink(address = '', port=this.port, forceTls, remark='', clientId, flow) {
|
||||||
const settings = this.settings;
|
const uuid = clientId;
|
||||||
const uuid = settings.vlesses[clientIndex].id;
|
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -1392,58 +1303,51 @@ class Inbound extends XrayCommonClass {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (security === 'tls') {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings.fingerprint);
|
if (this.stream.isTls){
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
if(this.stream.tls.settings.allowInsecure){
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
params.set("allowInsecure", "1");
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
}
|
params.set("allowInsecure", "1");
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
}
|
||||||
address = this.stream.tls.server;
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
}
|
params.set("sni", this.stream.tls.sni);
|
||||||
if (this.stream.tls.settings.serverName !== ''){
|
}
|
||||||
params.set("sni", this.stream.tls.settings.serverName);
|
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
||||||
}
|
params.set("flow", flow);
|
||||||
if (type === "tcp" && this.settings.vlesses[clientIndex].flow.length > 0) {
|
}
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this.xtls) {
|
else if (security === 'xtls') {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
|
||||||
address = this.stream.xtls.server;
|
params.set("sni", this.stream.xtls.sni);
|
||||||
}
|
}
|
||||||
if (this.stream.xtls.settings.serverName !== ''){
|
params.set("flow", flow);
|
||||||
params.set("sni", this.stream.xtls.settings.serverName);
|
|
||||||
}
|
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this.reality) {
|
else if (security === 'reality') {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.network === 'tcp' && !ObjectUtil.isEmpty(this.settings.vlesses[clientIndex].flow)) {
|
|
||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
|
||||||
}
|
|
||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
|
||||||
address = this.stream.reality.settings.serverName;
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
|
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
||||||
|
params.set("flow", flow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|
@ -1459,10 +1363,10 @@ class Inbound extends XrayCommonClass {
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', remark='', clientIndex = 0) {
|
genSSLink(address='', port=this.port, forceTls, remark='', clientPassword) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -1513,11 +1417,26 @@ class Inbound extends XrayCommonClass {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (security === 'tls') {
|
||||||
|
params.set("security", "tls");
|
||||||
|
if (this.stream.isTls){
|
||||||
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
|
params.set("sni", this.stream.tls.sni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let password = new Array();
|
let password = new Array();
|
||||||
if (this.isSS2022) password.push(settings.password);
|
if (this.isSS2022) password.push(settings.password);
|
||||||
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
if (this.isSSMultiUser) password.push(clientPassword);
|
||||||
|
|
||||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
url.searchParams.set(key, value)
|
url.searchParams.set(key, value)
|
||||||
|
|
@ -1526,9 +1445,8 @@ class Inbound extends XrayCommonClass {
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', port=this.port, forceTls, remark = '', clientPassword) {
|
||||||
let settings = this.settings;
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
const port = this.port;
|
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
const params = new Map();
|
const params = new Map();
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
|
|
@ -1580,59 +1498,52 @@ class Inbound extends XrayCommonClass {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.tls) {
|
if (security === 'tls') {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
params.set("fp" , this.stream.tls.settings.fingerprint);
|
if (this.stream.isTls){
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("fp" , this.stream.tls.settings.fingerprint);
|
||||||
if(this.stream.tls.settings.allowInsecure){
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
params.set("allowInsecure", "1");
|
if(this.stream.tls.settings.allowInsecure){
|
||||||
|
params.set("allowInsecure", "1");
|
||||||
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
||||||
|
params.set("sni", this.stream.tls.sni);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.server)) {
|
|
||||||
address = this.stream.tls.server;
|
|
||||||
}
|
|
||||||
if (this.stream.tls.settings.serverName !== ''){
|
|
||||||
params.set("sni", this.stream.tls.settings.serverName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this.reality) {
|
else if (security === 'reality') {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (this.stream.reality.shortIds.length > 0) {
|
if (this.stream.reality.shortIds.length > 0) {
|
||||||
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
params.set("sid", this.stream.reality.shortIds.split(",")[0]);
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
|
||||||
address = this.stream.reality.settings.serverName;
|
|
||||||
}
|
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this.xtls) {
|
else if (security === 'xtls') {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.xtls.server)) {
|
if (!ObjectUtil.isEmpty(this.stream.xtls.sni)){
|
||||||
address = this.stream.xtls.server;
|
params.set("sni", this.stream.xtls.sni);
|
||||||
}
|
}
|
||||||
if (this.stream.xtls.settings.serverName !== ''){
|
params.set("flow", flow);
|
||||||
params.set("sni", this.stream.xtls.settings.serverName);
|
|
||||||
}
|
|
||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
params.set("security", "none");
|
params.set("security", "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
const link = `trojan://${clientPassword}@${address}:${port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
url.searchParams.set(key, value)
|
url.searchParams.set(key, value)
|
||||||
|
|
@ -1641,38 +1552,63 @@ class Inbound extends XrayCommonClass {
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, port, forceTls, remark, client.id);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, port, forceTls, remark, client.id, client.flow);
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
return this.genSSLink(address, remark, clientIndex);
|
return this.genSSLink(address, port, forceTls, remark, this.isSSMultiUser ? client.password : '');
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, port, forceTls, remark, client.password);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(address = '', remark = '') {
|
genAllLinks(remark='', remarkModel = '-ieo', client){
|
||||||
let link = '';
|
let result = [];
|
||||||
switch (this.protocol) {
|
let email = client ? client.email : '';
|
||||||
case Protocols.VMESS:
|
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||||
case Protocols.VLESS:
|
let port = this.port;
|
||||||
case Protocols.TROJAN:
|
const separationChar = remarkModel.charAt(0);
|
||||||
case Protocols.SHADOWSOCKS:
|
const orderChars = remarkModel.slice(1);
|
||||||
JSON.parse(this.settings).clients.forEach((client,index) => {
|
let orders = {
|
||||||
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
'i': remark,
|
||||||
this.stream.tls.settings.domains.forEach((domain) => {
|
'e': client ? client.email : '',
|
||||||
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
'o': '',
|
||||||
});
|
};
|
||||||
} else {
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
|
||||||
}
|
result.push({
|
||||||
|
remark: r,
|
||||||
|
link: this.genLink(addr, port, 'same', r, client)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.stream.externalProxy.forEach((ep) => {
|
||||||
|
orders['o'] = ep.remark;
|
||||||
|
let r = orderChars.split('').map(char => orders[char]).filter(x => x.length > 0).join(separationChar);
|
||||||
|
result.push({
|
||||||
|
remark: r,
|
||||||
|
link: this.genLink(ep.dest, ep.port, ep.forceTls, r, client)
|
||||||
});
|
});
|
||||||
return link;
|
});
|
||||||
default: return '';
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||||
|
if(this.clients){
|
||||||
|
let links = [];
|
||||||
|
this.clients.forEach((client) => {
|
||||||
|
this.genAllLinks(remark,remarkModel,client).forEach(l => {
|
||||||
|
links.push(l.link);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return links.join('\r\n');
|
||||||
|
} else {
|
||||||
|
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, remark);
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1691,7 +1627,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let streamSettings;
|
let streamSettings;
|
||||||
if (this.canEnableStream() || this.protocol === Protocols.TROJAN) {
|
if (this.canEnableStream()) {
|
||||||
streamSettings = this.stream.toJson();
|
streamSettings = this.stream.toJson();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -1783,7 +1719,7 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
|
@ -1793,6 +1729,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
@ -1805,6 +1742,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
get _expiryTime() {
|
get _expiryTime() {
|
||||||
|
|
@ -1838,10 +1776,10 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
vlesses=[new Inbound.VLESSSettings.VLESS()],
|
vlesses=[new Inbound.VLESSSettings.VLESS()],
|
||||||
decryption='none',
|
decryption='none',
|
||||||
fallbacks=[],) {
|
fallbacks=[]) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vlesses = vlesses;
|
this.vlesses = vlesses;
|
||||||
this.decryption = 'none'; // Using decryption is not implemented here
|
this.decryption = decryption;
|
||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1858,22 +1796,21 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
return new Inbound.VLESSSettings(
|
return new Inbound.VLESSSettings(
|
||||||
Protocols.VLESS,
|
Protocols.VLESS,
|
||||||
json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
json.clients.map(client => Inbound.VLESSSettings.VLESS.fromJson(client)),
|
||||||
'none',
|
json.decryption || 'none',
|
||||||
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),
|
Inbound.VLESSSettings.Fallback.fromJson(json.fallbacks),);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
clients: Inbound.VLESSSettings.toJsonArray(this.vlesses),
|
||||||
decryption: 'none',
|
decryption: this.decryption,
|
||||||
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
fallbacks: Inbound.VLESSSettings.toJsonArray(this.fallbacks),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
|
|
@ -1884,6 +1821,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
@ -1897,6 +1835,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1973,11 +1912,11 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
this.fallbacks = fallbacks;
|
this.fallbacks = fallbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTrojanFallback() {
|
addFallback() {
|
||||||
this.fallbacks.push(new Inbound.TrojanSettings.Fallback());
|
this.fallbacks.push(new Inbound.TrojanSettings.Fallback());
|
||||||
}
|
}
|
||||||
|
|
||||||
delTrojanFallback(index) {
|
delFallback(index) {
|
||||||
this.fallbacks.splice(index, 1);
|
this.fallbacks.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1986,17 +1925,17 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
Protocols.TROJAN,
|
Protocols.TROJAN,
|
||||||
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
||||||
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
||||||
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
|
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
|
|
@ -2007,6 +1946,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
|
@ -2020,6 +1960,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2034,6 +1975,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2138,7 +2080,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16), reset=0) {
|
||||||
super();
|
super();
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
|
@ -2149,6 +2091,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
this.enable = enable;
|
this.enable = enable;
|
||||||
this.tgId = tgId;
|
this.tgId = tgId;
|
||||||
this.subId = subId;
|
this.subId = subId;
|
||||||
|
this.reset = reset;
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
|
@ -2162,6 +2105,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
enable: this.enable,
|
enable: this.enable,
|
||||||
tgId: this.tgId,
|
tgId: this.tgId,
|
||||||
subId: this.subId,
|
subId: this.subId,
|
||||||
|
reset: this.reset,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2176,6 +2120,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
json.enable,
|
json.enable,
|
||||||
json.tgId,
|
json.tgId,
|
||||||
json.subId,
|
json.subId,
|
||||||
|
json.reset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,13 +52,15 @@ function safeBase64(str) {
|
||||||
|
|
||||||
function formatSecond(second) {
|
function formatSecond(second) {
|
||||||
if (second < 60) {
|
if (second < 60) {
|
||||||
return second.toFixed(0) + ' s';
|
return second.toFixed(0) + 's';
|
||||||
} else if (second < 3600) {
|
} else if (second < 3600) {
|
||||||
return (second / 60).toFixed(0) + ' m';
|
return (second / 60).toFixed(0) + 'm';
|
||||||
} else if (second < 3600 * 24) {
|
} else if (second < 3600 * 24) {
|
||||||
return (second / 3600).toFixed(0) + ' h';
|
return (second / 3600).toFixed(0) + 'h';
|
||||||
} else {
|
} else {
|
||||||
return (second / 3600 / 24).toFixed(0) + ' d';
|
day = Math.floor(second / 3600 / 24);
|
||||||
|
remain = ((second/3600) - (day*24)).toFixed(0);
|
||||||
|
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +74,7 @@ function addZero(num) {
|
||||||
|
|
||||||
function toFixed(num, n) {
|
function toFixed(num, n) {
|
||||||
n = Math.pow(10, n);
|
n = Math.pow(10, n);
|
||||||
return Math.round(num * n) / n;
|
return Math.floor(num * n) / n;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debounce(fn, delay) {
|
function debounce(fn, delay) {
|
||||||
|
|
@ -115,15 +117,52 @@ function setCookie(cname, cvalue, exdays) {
|
||||||
function usageColor(data, threshold, total) {
|
function usageColor(data, threshold, total) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case data === null:
|
case data === null:
|
||||||
return 'blue';
|
return "purple";
|
||||||
case total <= 0:
|
case total < 0:
|
||||||
return 'blue';
|
return "green";
|
||||||
|
case total == 0:
|
||||||
|
return "purple";
|
||||||
case data < total - threshold:
|
case data < total - threshold:
|
||||||
return 'cyan';
|
return "green";
|
||||||
case data < total:
|
case data < total:
|
||||||
return 'orange';
|
return "orange";
|
||||||
default:
|
default:
|
||||||
return 'red';
|
return "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clientUsageColor(clientStats, trafficDiff) {
|
||||||
|
switch (true) {
|
||||||
|
case !clientStats || clientStats.total == 0:
|
||||||
|
return "#7a316f"; // purple
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
|
||||||
|
return "#008771"; // Green
|
||||||
|
case clientStats.up + clientStats.down < clientStats.total:
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
default:
|
||||||
|
return "#cf3c3c"; // Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function userExpiryColor(threshold, client, isDark = false) {
|
||||||
|
if (!client.enable) {
|
||||||
|
return isDark ? '#2c3950' : '#bcbcbc';
|
||||||
|
}
|
||||||
|
now = new Date().getTime(),
|
||||||
|
expiry = client.expiryTime;
|
||||||
|
switch (true) {
|
||||||
|
case expiry === null:
|
||||||
|
return "#7a316f"; // purple
|
||||||
|
case expiry < 0:
|
||||||
|
return "#008771"; // Green
|
||||||
|
case expiry == 0:
|
||||||
|
return "#7a316f"; // purple
|
||||||
|
case now < expiry - threshold:
|
||||||
|
return "#008771"; // Green
|
||||||
|
case now < expiry:
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
default:
|
||||||
|
return "#cf3c3c"; // Red
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,21 @@
|
||||||
|
class Msg {
|
||||||
|
constructor(success, msg, obj) {
|
||||||
|
this.success = false;
|
||||||
|
this.msg = "";
|
||||||
|
this.obj = null;
|
||||||
|
|
||||||
|
if (success != null) {
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
if (msg != null) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
if (obj != null) {
|
||||||
|
this.obj = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class HttpUtil {
|
class HttpUtil {
|
||||||
static _handleMsg(msg) {
|
static _handleMsg(msg) {
|
||||||
if (!(msg instanceof Msg)) {
|
if (!(msg instanceof Msg)) {
|
||||||
|
|
@ -158,7 +176,7 @@ class ObjectUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
|
return this.isEmpty(obj) ? false : obj.toString().toLowerCase().indexOf(key.toLowerCase()) >= 0;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
g.GET("/createbackup", a.createBackup)
|
g.GET("/createbackup", a.createBackup)
|
||||||
|
g.POST("/onlines", a.onlines)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,3 +106,7 @@ func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
func (a *APIController) createBackup(c *gin.Context) {
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
a.Tgbot.SendBackupToAdmins()
|
a.Tgbot.SendBackupToAdmins()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIController) onlines(c *gin.Context) {
|
||||||
|
a.inboundController.onlines(c)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
|
@ -37,6 +38,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
g.POST("/import", a.importInbound)
|
||||||
|
g.POST("/onlines", a.onlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbounds(c *gin.Context) {
|
func (a *InboundController) getInbounds(c *gin.Context) {
|
||||||
|
|
@ -265,6 +268,31 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) importInbound(c *gin.Context) {
|
||||||
|
inbound := &model.Inbound{}
|
||||||
|
err := json.Unmarshal([]byte(c.PostForm("data")), inbound)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := session.GetLoginUser(c)
|
||||||
|
inbound.Id = 0
|
||||||
|
inbound.UserId = user.Id
|
||||||
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
|
|
||||||
|
for index := range inbound.ClientStats {
|
||||||
|
inbound.ClientStats[index].Id = 0
|
||||||
|
inbound.ClientStats[index].Enable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
needRestart := false
|
||||||
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||||
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -278,3 +306,7 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) onlines(c *gin.Context) {
|
||||||
|
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/update", a.updateSetting)
|
g.POST("/update", a.updateSetting)
|
||||||
g.POST("/updateUser", a.updateUser)
|
g.POST("/updateUser", a.updateUser)
|
||||||
g.POST("/restartPanel", a.restartPanel)
|
g.POST("/restartPanel", a.restartPanel)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultJsonConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/updateUserSecret", a.updateSecret)
|
g.POST("/updateUserSecret", a.updateSecret)
|
||||||
g.POST("/getUserSecret", a.getUserSecret)
|
g.POST("/getUserSecret", a.getUserSecret)
|
||||||
}
|
}
|
||||||
|
|
@ -55,54 +55,12 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
result, err := a.settingService.GetDefaultSettings(c.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
|
||||||
type settingFunc func() (interface{}, error)
|
|
||||||
|
|
||||||
settings := map[string]settingFunc{
|
|
||||||
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
|
||||||
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
|
||||||
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
|
||||||
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
|
||||||
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
|
||||||
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
|
||||||
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
|
||||||
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
|
||||||
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
|
||||||
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
|
||||||
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
|
||||||
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
|
||||||
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
|
|
||||||
for key, fn := range settings {
|
|
||||||
value, err := fn()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
subTLS := false
|
|
||||||
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
|
||||||
subTLS = true
|
|
||||||
}
|
|
||||||
result["subTLS"] = subTLS
|
|
||||||
|
|
||||||
delete(result, "subKeyFile")
|
|
||||||
delete(result, "subCertFile")
|
|
||||||
|
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,3 +127,12 @@ func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
jsonObj(c, user, nil)
|
jsonObj(c, user, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
|
|
|
||||||
63
web/controller/xray_setting.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type XraySettingController struct {
|
||||||
|
XraySettingService service.XraySettingService
|
||||||
|
SettingService service.SettingService
|
||||||
|
InboundService service.InboundService
|
||||||
|
XrayService service.XrayService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
||||||
|
a := &XraySettingController{}
|
||||||
|
a.initRouter(g)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/xray")
|
||||||
|
|
||||||
|
g.POST("/", a.getXraySetting)
|
||||||
|
g.POST("/update", a.updateSetting)
|
||||||
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
|
xraySetting, err := a.SettingService.GetXrayConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inboundTags, err := a.InboundService.GetInboundTags()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
xrayResponse := "{ \"xraySetting\": " + xraySetting + ", \"inboundTags\": " + inboundTags + " }"
|
||||||
|
jsonObj(c, xrayResponse, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) updateSetting(c *gin.Context) {
|
||||||
|
xraySetting := c.PostForm("xraySetting")
|
||||||
|
err := a.XraySettingService.SaveXraySetting(xraySetting)
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||||
|
defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *XraySettingController) getXrayResult(c *gin.Context) {
|
||||||
|
jsonObj(c, a.XrayService.GetXrayResult(), nil)
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,9 @@ import (
|
||||||
type XUIController struct {
|
type XUIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
settingController *SettingController
|
settingController *SettingController
|
||||||
|
xraySettingController *XraySettingController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
func NewXUIController(g *gin.RouterGroup) *XUIController {
|
||||||
|
|
@ -24,9 +25,11 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.GET("/inbounds", a.inbounds)
|
g.GET("/inbounds", a.inbounds)
|
||||||
g.GET("/settings", a.settings)
|
g.GET("/settings", a.settings)
|
||||||
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
a.settingController = NewSettingController(g)
|
a.settingController = NewSettingController(g)
|
||||||
|
a.xraySettingController = NewXraySettingController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XUIController) index(c *gin.Context) {
|
func (a *XUIController) index(c *gin.Context) {
|
||||||
|
|
@ -40,3 +43,7 @@ func (a *XUIController) inbounds(c *gin.Context) {
|
||||||
func (a *XUIController) settings(c *gin.Context) {
|
func (a *XUIController) settings(c *gin.Context) {
|
||||||
html(c, "settings.html", "pages.settings.title", nil)
|
html(c, "settings.html", "pages.settings.title", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XUIController) xraySettings(c *gin.Context) {
|
||||||
|
html(c, "xray.html", "pages.xray.title", nil)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@ package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
"x-ui/xray"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
|
|
@ -16,47 +14,39 @@ type Msg struct {
|
||||||
Obj interface{} `json:"obj"`
|
Obj interface{} `json:"obj"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pager struct {
|
|
||||||
Current int `json:"current"`
|
|
||||||
PageSize int `json:"page_size"`
|
|
||||||
Total int `json:"total"`
|
|
||||||
OrderBy string `json:"order_by"`
|
|
||||||
Desc bool `json:"desc"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
List interface{} `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
WebDomain string `json:"webDomain" form:"webDomain"`
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
PageSize int `json:"pageSize" form:"pageSize"`
|
||||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
RemarkModel string `json:"remarkModel" form:"remarkModel"`
|
||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
SubPort int `json:"subPort" form:"subPort"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
SubPath string `json:"subPath" form:"subPath"`
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
|
|
@ -106,14 +96,14 @@ func (s *AllSetting) CheckValid() error {
|
||||||
if !strings.HasSuffix(s.WebBasePath, "/") {
|
if !strings.HasSuffix(s.WebBasePath, "/") {
|
||||||
s.WebBasePath += "/"
|
s.WebBasePath += "/"
|
||||||
}
|
}
|
||||||
|
if !strings.HasPrefix(s.SubPath, "/") {
|
||||||
xrayConfig := &xray.Config{}
|
s.SubPath = "/" + s.SubPath
|
||||||
err := json.Unmarshal([]byte(s.XrayTemplateConfig), xrayConfig)
|
}
|
||||||
if err != nil {
|
if !strings.HasSuffix(s.SubPath, "/") {
|
||||||
return common.NewError("xray template config invalid:", err)
|
s.SubPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = time.LoadLocation(s.TimeLocation)
|
_, err := time.LoadLocation(s.TimeLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError("time location not exist:", s.TimeLocation)
|
return common.NewError("time location not exist:", s.TimeLocation)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<meta name="renderer" content="webkit">
|
<meta name="renderer" content="webkit">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||||
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||||
|
|
@ -13,6 +13,20 @@
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
/* vazirmatn-regular - arabic_latin_latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('{{ .base_path }}assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2');
|
||||||
|
unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Vazirmatn', 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji',
|
||||||
|
'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
{{define "js"}}
|
{{define "js"}}
|
||||||
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
<script src="{{ .base_path }}assets/vue@2.6.12/vue.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
<script src="{{ .base_path }}assets/moment/moment.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/ant-design-vue@1.7.2/antd.min.js"></script>
|
<script src="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
<script src="{{ .base_path }}assets/axios/axios.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/model/models.js?{{ .cur_ver }}"></script>
|
|
||||||
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const basePath = '{{ .base_path }}';
|
const basePath = '{{ .base_path }}';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
{{define "promptModal"}}
|
{{define "promptModal"}}
|
||||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}'>
|
|
||||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||||
v-model="promptModal.value"
|
v-model="promptModal.value"
|
||||||
:autosize="{minRows: 10, maxRows: 20}"
|
:autosize="{minRows: 10, maxRows: 20}"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "qrcodeModal"}}
|
{{define "qrcodeModal"}}
|
||||||
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="300px">
|
width="300px">
|
||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
@ -22,39 +22,25 @@
|
||||||
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
clientIndex: 0,
|
|
||||||
inbound: new Inbound(),
|
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
client: null,
|
client: null,
|
||||||
qrcodes: [],
|
qrcodes: [],
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
subId: '',
|
subId: '',
|
||||||
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
|
show: function (title = '', dbInbound, client) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.clientIndex = clientIndex;
|
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
settings = JSON.parse(this.inbound.settings);
|
this.client = client;
|
||||||
this.client = settings.clients[clientIndex];
|
|
||||||
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
|
|
||||||
address = this.dbInbound.address;
|
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
|
||||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
|
||||||
this.qrcodes.push({
|
|
||||||
remark: remarkText,
|
|
||||||
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.qrcodes.push({
|
this.qrcodes.push({
|
||||||
remark: remark,
|
remark: l.remark,
|
||||||
link: this.inbound.genLink(address, remark, clientIndex)
|
link: l.link
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
|
|
@ -86,8 +72,7 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
return app.subSettings.subURI+subID;
|
||||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
|
|
@ -31,7 +31,10 @@
|
||||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => app.$message.success('{{ i18n "copied" }}'));
|
this.clipboard.on('success', () => {
|
||||||
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,65 +2,232 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{{template "head" .}}
|
{{template "head" .}}
|
||||||
<style>
|
<style>
|
||||||
|
h1 {
|
||||||
#app {
|
text-align: center;
|
||||||
padding-top: 100px;
|
margin: 20px 0 50px 0;
|
||||||
|
}
|
||||||
|
.ant-btn,
|
||||||
|
.ant-input {
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 30px;
|
||||||
|
}
|
||||||
|
.ant-input-group-addon {
|
||||||
|
border-radius: 0 30px 30px 0;
|
||||||
|
width: 50px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper .ant-input-prefix {
|
||||||
|
left: 23px;
|
||||||
|
}
|
||||||
|
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
#login {
|
||||||
|
animation: charge 0.5s both;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 2rem;
|
||||||
|
padding: 3rem;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
#login:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
|
}
|
||||||
|
@keyframes charge {
|
||||||
|
from {
|
||||||
|
transform: translateY(5rem);
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
to {
|
||||||
h1 {
|
transform: translateY(0);
|
||||||
text-align: center;
|
opacity: 1;
|
||||||
color: #fff;
|
|
||||||
margin: 20px 0 50px 0;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.wave {
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
top: 80%;
|
||||||
|
left: 60%;
|
||||||
|
width: 6000px;
|
||||||
|
height: 6000px;
|
||||||
|
background-color: rgba(0, 135, 113, 0.08);
|
||||||
|
margin-left: -3000px;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
rotate: 115deg;
|
||||||
|
}
|
||||||
|
.wave2 {
|
||||||
|
opacity: 0.4;
|
||||||
|
rotate: 105deg;
|
||||||
|
}
|
||||||
|
.wave3 {
|
||||||
|
opacity: 0.2;
|
||||||
|
rotate: 62deg;
|
||||||
|
}
|
||||||
|
.under {
|
||||||
|
background-color: #dbf5ed;
|
||||||
|
}
|
||||||
|
.dark .wave {
|
||||||
|
background: rgb(10 117 87 / 20%);
|
||||||
|
}
|
||||||
|
.dark .under {
|
||||||
|
background-color: #101828;
|
||||||
|
}
|
||||||
|
.dark #login {
|
||||||
|
background-color: #151f31;
|
||||||
|
}
|
||||||
|
.dark h1 {
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-btn, .ant-input {
|
.ant-btn-primary-login {
|
||||||
height: 50px;
|
width: 100%;
|
||||||
border-radius: 30px;
|
}
|
||||||
|
.ant-btn-primary-login:focus,
|
||||||
|
.ant-btn-primary-login:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #006655;
|
||||||
|
border-color: #006655;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
270deg,
|
||||||
|
rgba(123, 199, 77, 0) 30%,
|
||||||
|
#009980,
|
||||||
|
rgba(123, 199, 77, 0) 100%
|
||||||
|
);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
animation: ma-bg-move ease-in-out 5s infinite;
|
||||||
|
background-position-x: -500px;
|
||||||
|
width: 95%;
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
||||||
|
}
|
||||||
|
.ant-btn-primary-login.active,
|
||||||
|
.ant-btn-primary-login:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #006655;
|
||||||
|
border-color: #006655;
|
||||||
|
}
|
||||||
|
@keyframes ma-bg-move {
|
||||||
|
0% {
|
||||||
|
background-position: -500px 0;
|
||||||
}
|
}
|
||||||
|
50% {
|
||||||
.ant-input-group-addon {
|
background-position: 1000px 0;
|
||||||
border-radius: 0 30px 30px 0;
|
|
||||||
width: 50px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
|
100% {
|
||||||
.ant-input-affix-wrapper .ant-input-prefix {
|
background-position: 1000px 0;
|
||||||
left: 23px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
.wave-btn-bg {
|
||||||
padding-left: 50px;
|
position: relative;
|
||||||
|
border-radius: 25px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg {
|
||||||
|
color: #fff;
|
||||||
|
position: relative;
|
||||||
|
background-color: #0a7557;
|
||||||
|
border: 2px double transparent;
|
||||||
|
background-origin: border-box;
|
||||||
|
background-clip: padding-box, border-box;
|
||||||
|
background-size: 300%;
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg:hover {animation: wave-btn-tara 4s ease infinite;}
|
||||||
|
.dark .wave-btn-bg-cl {
|
||||||
|
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
|
||||||
|
radial-gradient(circle at left top, #006655, #009980, #006655) !important;
|
||||||
|
border-radius: 3em;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:hover {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
top: -5px;
|
||||||
|
left: -5px;
|
||||||
|
bottom: -5px;
|
||||||
|
right: -5px;
|
||||||
|
z-index: -1;
|
||||||
|
background: inherit;
|
||||||
|
background-size: inherit;
|
||||||
|
border-radius: 4em;
|
||||||
|
opacity: 0;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
.dark .wave-btn-bg-cl:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(20px);
|
||||||
|
animation: wave-btn-tara 8s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes wave-btn-tara {
|
||||||
|
to {
|
||||||
|
background-position: 300%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.centered {
|
.dark .ant-btn-primary-login {
|
||||||
display: flex;
|
font-size: 14px;
|
||||||
text-align: center;
|
color: #fff;
|
||||||
align-items: center;
|
text-align: center;
|
||||||
justify-content: center;
|
background-image: linear-gradient(
|
||||||
}
|
rgba(13, 14, 33, 0.45),
|
||||||
|
rgba(13, 14, 33, 0.35)
|
||||||
.title {
|
);
|
||||||
font-size: 32px;
|
border-radius: 2rem;
|
||||||
font-weight: bold;
|
border: none;
|
||||||
}
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
height: 46px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
touch-action: manipulation;
|
||||||
|
padding: 0 15px;
|
||||||
|
width: 100%;
|
||||||
|
animation: none;
|
||||||
|
background-position-x: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak class="login" :class="themeSwitcher.darkCardClass">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-layout-content>
|
<a-layout-content class="under" style="min-height: 0;">
|
||||||
|
<div class='wave'></div>
|
||||||
|
<div class='wave wave2'></div>
|
||||||
|
<div class='wave wave3'></div>
|
||||||
|
<a-row type="flex" justify="center" align="middle" style="height: 100%; overflow: auto;">
|
||||||
|
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col>
|
||||||
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<a-col span="24">
|
||||||
<a-form>
|
<a-form>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}'
|
||||||
@keydown.enter.native="login" autofocus>
|
@keydown.enter.native="login" autofocus>
|
||||||
<a-icon slot="prefix" type="user" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
<a-icon slot="prefix" type="user" style="font-size: 16px;"/>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
|
@ -76,18 +243,20 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
<div class="wave-btn-bg wave-btn-bg-cl">
|
||||||
:style="loading ? { width: '50px' } : { display: 'block', width: '100%' }">
|
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
:style="loading ? { width: '50px' } : { display: 'inline-block' }">
|
||||||
</a-button>
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="12">
|
<a-col :span="24">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|
@ -96,12 +265,19 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<theme-switch />
|
<a-col>
|
||||||
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
|
</a-col>
|
||||||
|
<a-col>
|
||||||
|
<theme-switch />
|
||||||
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</transition>
|
</transition>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
|
|
@ -109,6 +285,12 @@
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
<script>
|
<script>
|
||||||
|
class User {
|
||||||
|
constructor() {
|
||||||
|
this.username = "";
|
||||||
|
this.password = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
|
|
@ -121,7 +303,6 @@
|
||||||
lang: ""
|
lang: ""
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.updateBackground();
|
|
||||||
this.lang = getLang();
|
this.lang = getLang();
|
||||||
this.secretEnable = await this.getSecretStatus();
|
this.secretEnable = await this.getSecretStatus();
|
||||||
},
|
},
|
||||||
|
|
@ -143,21 +324,8 @@
|
||||||
return msg.obj;
|
return msg.obj;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateBackground() {
|
|
||||||
const leftColor = RandomUtil.randomIntRange(0x222222, 0xFFFFFF / 2).toString(16);
|
|
||||||
const rightColor = RandomUtil.randomIntRange(0xFFFFFF / 2, 0xDDDDDD).toString(16);
|
|
||||||
const deg = RandomUtil.randomIntRange(0, 360);
|
|
||||||
const background = `linear-gradient(${deg}deg, #${leftColor} 10%, #${rightColor} 100%)`;
|
|
||||||
document.querySelector('#app').style.background = this.themeSwitcher.isDarkTheme ? colors.dark.bg : background;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'themeSwitcher.isDarkTheme'(newVal, oldVal) {
|
|
||||||
this.updateBackground();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,121 +1,123 @@
|
||||||
{{define "clientsBulkModal"}}
|
{{define "clientsBulkModal"}}
|
||||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title"
|
||||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
@ok="clientsBulkModal.ok" :confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}'>
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 350px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option :value="0">Random</a-select-option>
|
<a-select-option :value="0">Random</a-select-option>
|
||||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||||
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||||
<a-select-option :value="4">Prefix+Num+Postfix [ BE CAREFUL! ]</a-select-option>
|
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item><br />
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||||
<span slot="label">{{ i18n "pages.client.first" }}</span>
|
|
||||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>1">
|
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||||
<span slot="label">{{ i18n "pages.client.last" }}</span>
|
|
||||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>0">
|
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||||
<span slot="label">{{ i18n "pages.client.prefix" }}</span>
|
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
|
||||||
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 120px"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod>2">
|
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
|
||||||
<span slot="label">{{ i18n "pages.client.postfix" }}</span>
|
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
|
||||||
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 120px"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="clientsBulkModal.emailMethod < 2">
|
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="app.subSettings.enable">
|
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||||
<span slot="label">
|
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.xtls" label="Flow">
|
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="clientsBulkModal.inbound.canEnableTlsFlow()" label="Flow" layout="inline">
|
|
||||||
<a-select v-model="clientsBulkModal.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.xtls">
|
||||||
|
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="app.subSettings.enable">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
Subscription
|
||||||
|
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="app.tgBotEnable">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
Telegram ID
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimit" }} </span>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model="clientsBulkModal.limitIp" min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.inbounds.totalFlow" }} (GB)
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
||||||
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme" v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||||
v-model="clientsBulkModal.expiryTime" style="width: 300px;"></a-date-picker>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||||
|
<template slot="label">
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
@ -142,6 +144,7 @@
|
||||||
tgId: "",
|
tgId: "",
|
||||||
flow: "",
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
|
reset: 0,
|
||||||
ok() {
|
ok() {
|
||||||
clients = [];
|
clients = [];
|
||||||
method = clientsBulkModal.emailMethod;
|
method = clientsBulkModal.emailMethod;
|
||||||
|
|
@ -170,6 +173,7 @@
|
||||||
if (clientsBulkModal.inbound.xtls) {
|
if (clientsBulkModal.inbound.xtls) {
|
||||||
newClient.flow = clientsBulkModal.flow;
|
newClient.flow = clientsBulkModal.flow;
|
||||||
}
|
}
|
||||||
|
newClient.reset = clientsBulkModal.reset;
|
||||||
clients.push(newClient);
|
clients.push(newClient);
|
||||||
}
|
}
|
||||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||||
|
|
@ -199,6 +203,7 @@
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
|
this.reset = 0;
|
||||||
},
|
},
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
{{define "clientsModal"}}
|
{{define "clientsModal"}}
|
||||||
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
<a-modal id="client-modal" v-model="clientModal.visible" :title="clientModal.title" @ok="clientModal.ok"
|
||||||
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="clientModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="clientModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
|
<template v-if="isEdit">
|
||||||
|
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">Account is (Expired|Traffic Ended) And Disabled</a-tag>
|
||||||
|
</template>
|
||||||
{{template "form/client"}}
|
{{template "form/client"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -35,7 +38,7 @@
|
||||||
this.isEdit = isEdit;
|
this.isEdit = isEdit;
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.inbound.clients;
|
||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
|
|
@ -49,15 +52,6 @@
|
||||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||||
this.confirm = confirm;
|
this.confirm = confirm;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
|
||||||
switch (protocol) {
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getClientId(protocol, client) {
|
getClientId(protocol, client) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.TROJAN: return client.password;
|
case Protocols.TROJAN: return client.password;
|
||||||
|
|
@ -109,8 +103,11 @@
|
||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
||||||
},
|
},
|
||||||
get statsColor() {
|
get delayedStart() {
|
||||||
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
return this.clientModal.delayedStart;
|
||||||
|
},
|
||||||
|
set delayedStart(value) {
|
||||||
|
this.clientModal.delayedStart = value;
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
|
@ -151,7 +148,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span>{{ i18n "menu.settings"}}</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="{{ .base_path }}panel/xray">
|
||||||
|
<a-icon type="tool"></a-icon>
|
||||||
|
<span>{{ i18n "menu.xray"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
<!-- <span>Client</span>-->
|
<!-- <span>Client</span>-->
|
||||||
|
|
@ -26,7 +30,7 @@
|
||||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|
@ -38,14 +42,14 @@
|
||||||
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
<a-drawer id="sider-drawer" placement="left" :closable="false"
|
||||||
@close="siderDrawer.close()"
|
@close="siderDrawer.close()"
|
||||||
:visible="siderDrawer.visible"
|
:visible="siderDrawer.visible"
|
||||||
:wrap-class-name="themeSwitcher.darkDrawerClass"
|
:wrap-class-name="themeSwitcher.currentTheme"
|
||||||
:wrap-style="{ padding: 0 }">
|
:wrap-style="{ padding: 0 }">
|
||||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||||
</div>
|
</div>
|
||||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||||
<a-menu-item mode="inline">
|
<a-menu-item mode="inline">
|
||||||
<a-icon type="bg-colors"></a-icon>
|
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||||
<theme-switch />
|
<theme-switch />
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="$emit('input', $event.target.value)">
|
@input="$emit('input', $event.target.value)">
|
||||||
<template v-if="icon" #prefix>
|
<template v-if="icon" #prefix>
|
||||||
<a-icon :type="icon" :style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
<a-icon :type="icon" style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||||
@click="toggleShowPassword"
|
@click="toggleShowPassword"
|
||||||
:style="'font-size: 16px;' + themeSwitcher.textStyle" />
|
style="font-size: 16px;" />
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template v-if="type === 'text'">
|
<template v-if="type === 'text'">
|
||||||
<a-input :value="value" @input="$emit('input', $event.target.value)"></a-input>
|
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'number'">
|
<template v-else-if="type === 'number'">
|
||||||
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="type === 'switch'">
|
<template v-else-if="type === 'switch'">
|
||||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
{{define "component/setting"}}
|
{{define "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
Vue.component('setting-list-item', {
|
Vue.component('setting-list-item', {
|
||||||
props: ["type", "title", "desc", "value", "min"],
|
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
|
||||||
template: `{{template "component/settingListItem"}}`,
|
template: `{{template "component/settingListItem"}}`,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
{{define "component/themeSwitchTemplate"}}
|
{{define "component/themeSwitchTemplate"}}
|
||||||
<template>
|
<template>
|
||||||
<a-switch :default-checked="themeSwitcher.isDarkTheme"
|
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||||
checked-children="☀"
|
|
||||||
un-checked-children="🌙"
|
|
||||||
@change="themeSwitcher.toggleTheme()">
|
@change="themeSwitcher.toggleTheme()">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -10,39 +8,17 @@
|
||||||
|
|
||||||
{{define "component/themeSwitcher"}}
|
{{define "component/themeSwitcher"}}
|
||||||
<script>
|
<script>
|
||||||
const colors = {
|
|
||||||
dark: {
|
|
||||||
bg: "#242c3a",
|
|
||||||
text: "hsla(0,0%,100%,.65)"
|
|
||||||
},
|
|
||||||
light: {
|
|
||||||
bg: '#f0f2f5',
|
|
||||||
text: "rgba(0, 0, 0, 0.7)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createThemeSwitcher() {
|
function createThemeSwitcher() {
|
||||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||||
const theme = isDarkTheme ? 'dark' : 'light';
|
const theme = isDarkTheme ? 'dark' : 'light';
|
||||||
return {
|
return {
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
bgStyle: `background: ${colors[theme].bg};`,
|
|
||||||
textStyle: `color: ${colors[theme].text};`,
|
|
||||||
darkClass: isDarkTheme ? 'ant-dark' : '',
|
|
||||||
darkCardClass: isDarkTheme ? 'ant-card-dark' : '',
|
|
||||||
darkDrawerClass: isDarkTheme ? 'ant-drawer-dark' : '',
|
|
||||||
get currentTheme() {
|
get currentTheme() {
|
||||||
return this.isDarkTheme ? 'dark' : 'light';
|
return this.isDarkTheme ? 'dark' : 'light';
|
||||||
},
|
},
|
||||||
toggleTheme() {
|
toggleTheme() {
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
this.theme = this.isDarkTheme ? 'dark' : 'light';
|
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
this.bgStyle = `background: ${colors[this.theme].bg};`;
|
|
||||||
this.textStyle = `color: ${colors[this.theme].text};`;
|
|
||||||
this.darkClass = this.isDarkTheme ? 'ant-dark' : '';
|
|
||||||
this.darkCardClass = this.isDarkTheme ? 'ant-card-dark' : '';
|
|
||||||
this.darkDrawerClass = this.isDarkTheme ? 'ant-drawer-dark' : '';
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,161 +1,168 @@
|
||||||
{{define "form/client"}}
|
{{define "form/client"}}
|
||||||
<a-form layout="inline" v-if="client">
|
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<template v-if="isEdit">
|
|
||||||
<a-tag v-if="isExpiry || isTrafficExhausted" color="red" style="margin-bottom: 10px;display: block;text-align: center;">
|
|
||||||
Account is (Expired|Traffic Ended) And Disabled
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||||
<a-switch v-model="client.enable"></a-switch>
|
<a-switch v-model="client.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
{{ i18n "pages.inbounds.email" }}
|
||||||
|
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
<a-input v-model.trim="client.email"></a-input>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
<template slot="label">
|
||||||
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
</template>
|
||||||
|
{{ i18n "password" }}
|
||||||
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"@click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
|
||||||
|
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="client.password"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
<template slot="label">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-tooltip>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
</template>
|
||||||
|
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="client.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
Subscription
|
||||||
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
<a-input v-model.trim="client.subId"></a-input>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable" >
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
Telegram ID
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<a-input v-model.trim="client.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<a-tooltip>
|
||||||
<a-tooltip>
|
<template slot="title">
|
||||||
<template slot="title">
|
<span>{{ i18n "pages.inbounds.IPLimitDesc"}}</span>
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
</template>
|
||||||
</template>
|
<span>{{ i18n "pages.inbounds.IPLimit"}} </span>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
<a-tooltip>
|
||||||
<a-tooltip>
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
||||||
|
</template>
|
||||||
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
|
||||||
<a-form layout="block">
|
<a-form layout="block">
|
||||||
<a-textarea id="clientIPs" readonly
|
<a-textarea id="clientIPs" readonly @click="getDBClientIps(client.email)" placeholder="Click To Get IPs"
|
||||||
@click="getDBClientIps(client.email)"
|
:auto-size="{ minRows: 5, maxRows: 10 }">
|
||||||
placeholder="Click To Get IPs"
|
</a-textarea>
|
||||||
:auto-size="{ minRows: 5, maxRows: 10 }"
|
|
||||||
>
|
|
||||||
</a-textarea>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item v-if="inbound.xtls" label='Flow'>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.inbounds.totalFlow" }}(GB)
|
||||||
</a-tooltip>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
<template v-if="isEdit && clientStats">
|
|
||||||
<br>
|
|
||||||
<span> {{ i18n "usage" }}:</span>
|
|
||||||
<a-tag :color="statsColor">
|
|
||||||
[[ sizeFormat(clientStats.up) ]] /
|
|
||||||
[[ sizeFormat(clientStats.down) ]]
|
|
||||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
|
||||||
</a-tag>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
|
||||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
|
||||||
v-if="client.email.length > 0"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||||
|
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||||
|
[[ sizeFormat(clientStats.up) ]] /
|
||||||
|
[[ sizeFormat(clientStats.down) ]]
|
||||||
|
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||||
|
</a-tag>
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
|
<a-icon type="retweet"
|
||||||
|
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||||
|
v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientModal.delayedStart" @click="client._expiryTime=0"></a-switch>
|
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientModal.delayedStart">
|
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||||
<a-input-number v-model="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else>
|
<a-form-item v-else>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template>
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
</template>
|
<a-icon type="question-circle"></a-icon>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||||
<a-tag color="red" v-if="isExpiry">Expired</a-tag>
|
</a-form-item>
|
||||||
|
<a-form-item v-if="client.expiryTime != 0">
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">{{ i18n "pages.client.renewDesc" }}</template>
|
||||||
|
{{ i18n "pages.client.renew" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,61 +1,63 @@
|
||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "remark" }}'>
|
<a-form-item label='{{ i18n "remark" }}'>
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
{{ i18n "monitor" }}
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "monitor" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-input v-model.trim="inbound.listen"></a-input>
|
<a-input v-model.trim="inbound.listen"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model="inbound.port"></a-input-number>
|
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.inbounds.totalFlow" }}(GB)
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<template slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</template>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vmess settings -->
|
<!-- vmess settings -->
|
||||||
|
|
@ -96,6 +98,7 @@
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
<template v-if="inbound.canEnableStream()">
|
<template v-if="inbound.canEnableStream()">
|
||||||
{{template "form/streamSettings"}}
|
{{template "form/streamSettings"}}
|
||||||
|
{{template "form/externalProxy" }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
|
|
|
||||||
307
web/html/xui/form/outbound.html
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|
{{define "form/outbound"}}
|
||||||
|
<!-- base -->
|
||||||
|
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
||||||
|
<a-tab-pane key="1" tab="Form">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
|
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
||||||
|
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- freedom settings-->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||||
|
<a-form-item label='Strategy'>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.domainStrategy"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Fragment'>
|
||||||
|
<a-switch
|
||||||
|
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||||
|
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||||
|
<a-form-item label='Packets'>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.fragment.packets"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Length'>
|
||||||
|
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Interval'>
|
||||||
|
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- blackhole settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||||
|
<a-form-item label='Response Type'>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.type"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- dns settings -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.DNS">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
|
<a-select
|
||||||
|
v-model="outbound.settings.network"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Address + Port -->
|
||||||
|
<template v-if="outbound.hasAddressPort()">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.address" }}'>
|
||||||
|
<a-input v-model.trim="outbound.settings.address"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
|
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Vnext (vless/vmess) settings -->
|
||||||
|
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||||
|
<a-form-item label='ID'>
|
||||||
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<!-- vless settings -->
|
||||||
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
|
<a-form-item label='Flow'>
|
||||||
|
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||||
|
<template v-if="outbound.hasServers()">
|
||||||
|
<template v-if="outbound.hasUsername()">
|
||||||
|
<a-form-item label='{{ i18n "username" }}'>
|
||||||
|
<a-input v-model.trim="outbound.settings.user"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
<a-input v-model.trim="outbound.settings.password"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<!-- shadowsocks -->
|
||||||
|
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
||||||
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
|
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='UDP over TCP'>
|
||||||
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- stream settings -->
|
||||||
|
<template v-if="outbound.canEnableStream()">
|
||||||
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
|
<a-select-option value="ws">WS</a-select-option>
|
||||||
|
<a-select-option value="http">HTTP2</a-select-option>
|
||||||
|
<a-select-option value="quic">QUIC</a-select-option>
|
||||||
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
|
<a-switch
|
||||||
|
:checked="outbound.stream.tcp.type === 'http'"
|
||||||
|
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
|
</a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="outbound.stream.tcp.type == 'http'">
|
||||||
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- kcp -->
|
||||||
|
<template v-if="outbound.stream.network === 'kcp'">
|
||||||
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
|
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
<a-input v-model="outbound.stream.kcp.seed"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='mtu'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='tti (ms)'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='uplink capacity (MB/S)'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='downlink capacity (MB/S)'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='congestion'>
|
||||||
|
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='read buffer size (MB)'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='write buffer size (MB)'>
|
||||||
|
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- ws -->
|
||||||
|
<template v-if="outbound.stream.network === 'ws'">
|
||||||
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
|
<a-input v-model="outbound.stream.ws.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
|
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- http -->
|
||||||
|
<template v-if="outbound.stream.network === 'http'">
|
||||||
|
<a-form-item label='{{ i18n "host" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.http.host"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- quic -->
|
||||||
|
<template v-if="outbound.stream.network === 'quic'">
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
|
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none</a-select-option>
|
||||||
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
|
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
|
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||||
|
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||||
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
|
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||||
|
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- grpc -->
|
||||||
|
<template v-if="outbound.stream.network === 'grpc'">
|
||||||
|
<a-form-item label='serviceName'>
|
||||||
|
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='MultiMode'>
|
||||||
|
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- tls settings -->
|
||||||
|
<template v-if="outbound.canEnableTls()">
|
||||||
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
|
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||||
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
|
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="outbound.stream.isTls">
|
||||||
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS">
|
||||||
|
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value=''>None</a-select-option>
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="ALPN">
|
||||||
|
<a-select
|
||||||
|
mode="multiple"
|
||||||
|
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
v-model="outbound.stream.tls.alpn">
|
||||||
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Allow insecure">
|
||||||
|
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- reality settings -->
|
||||||
|
<template v-if="outbound.stream.isReality">
|
||||||
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS">
|
||||||
|
<a-select v-model="outbound.stream.reality.fingerprint"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Short Id">
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="SpiderX">
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Public Key">
|
||||||
|
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
|
<a-form-item style="margin: 10px 0">
|
||||||
|
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
||||||
|
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
{{define "form/dokodemo"}}
|
{{define "form/dokodemo"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||||
<a-input-number v-model="inbound.settings.port"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='FollowRedirect'>
|
||||||
<a-form-item label="FollowRedirect">
|
|
||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form layout="inline">
|
<a-form>
|
||||||
<a-form-item>
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<a-row>
|
<tr>
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
</a-row>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
</tr>
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
</table>
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
addon-before='{{ i18n "password" }}'>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,96 +1,8 @@
|
||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<template v-if="inbound.isSSMultiUser">
|
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
{{template "form/client"}}
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Password">
|
|
||||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.password" style="width: 250px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
<a-collapse v-else>
|
<a-collapse v-else>
|
||||||
|
|
@ -108,23 +20,38 @@
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
<table width="100%" class="ant-table-tbody">
|
||||||
<a-form layout="inline">
|
<tr>
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
|
<td>
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-form-item>
|
||||||
</a-select>
|
<a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
</a-form-item>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
<a-form-item v-if="inbound.isSS2022" label='{{ i18n "password" }}'>
|
</a-select>
|
||||||
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
</a-form-item>
|
||||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
</td>
|
||||||
</a-form-item>
|
</tr>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<tr v-if="inbound.isSS2022">
|
||||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<td>{{ i18n "password" }}
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
</td>
|
||||||
<a-select-option value="udp">UDP</a-select-option>
|
<td>
|
||||||
</a-select>
|
<a-form-item>
|
||||||
</a-form-item>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
|
||||||
</a-form>
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||||
|
<a-select-option value="tcp">tcp</a-select-option>
|
||||||
|
<a-select-option value="udp">udp</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
{{define "form/socks"}}
|
{{define "form/socks"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
|
||||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
|
||||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
|
||||||
<a-form-item>
|
|
||||||
<a-row>
|
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
|
||||||
</a-row>
|
|
||||||
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
|
||||||
<a-input style="width: 45%" v-model.trim="account.user"
|
|
||||||
addon-before='{{ i18n "username" }}'></a-input>
|
|
||||||
<a-input style="width: 55%" v-model.trim="account.pass"
|
|
||||||
addon-before='{{ i18n "password" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
|
||||||
</a-input-group>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
<a-form-item label='{{ i18n "pages.inbounds.enable" }} udp'>
|
||||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="inbound.settings.udp" label="IP">
|
<a-form-item label="IP" v-if="inbound.settings.udp">
|
||||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
|
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||||
|
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
|
<tr>
|
||||||
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
|
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,123 +1,29 @@
|
||||||
{{define "form/trojan"}}
|
{{define "form/trojan"}}
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
{{template "form/client"}}
|
||||||
<a-form-item>
|
</a-collapse-panel>
|
||||||
<span slot="label">
|
</a-collapse>
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
<a-collapse v-else>
|
||||||
<a-tooltip>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
<template slot="title">
|
<table width="100%">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<tr class="client-table-header">
|
||||||
</template>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
</a-tooltip>
|
<th>Password</th>
|
||||||
</span>
|
</tr>
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<td>[[ client.email ]]</td>
|
||||||
</a-form-item>
|
<td>[[ client.password ]]</td>
|
||||||
<a-form-item label="Password">
|
</tr>
|
||||||
<a-icon @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
</table>
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
</a-collapse-panel>
|
||||||
</a-form-item>
|
</a-collapse>
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
|
||||||
<a-select v-model="client.flow" style="width: 150px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="">{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
<a-collapse v-else>
|
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
|
||||||
<table width="100%">
|
|
||||||
<tr class="client-table-header">
|
|
||||||
<th>{{ i18n "pages.inbounds.email" }}</th>
|
|
||||||
<th>Password</th>
|
|
||||||
</tr>
|
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
|
||||||
<td>[[ client.email ]]</td>
|
|
||||||
<td>[[ client.password ]]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-form>
|
|
||||||
<template v-if="inbound.isTcp">
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addTrojanFallback()">
|
<a-button type="primary" size="small"
|
||||||
|
@click="inbound.settings.addFallback()">
|
||||||
+
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
@ -125,28 +31,28 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- trojan fallbacks -->
|
<!-- trojan fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
Fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delTrojanFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label="Name">
|
<a-form-item label='Name'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label='Alpn'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Path">
|
<a-form-item label='Path'>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
<a-input v-model="fallback.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Dest">
|
<a-form-item label='Dest'>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xVer">
|
<a-form-item label='xVer'>
|
||||||
<a-input-number v-model="fallback.xver"></a-input-number>
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,131 +1,31 @@
|
||||||
{{define "form/vless"}}
|
{{define "form/vless"}}
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
{{template "form/client"}}
|
||||||
<a-form-item>
|
</a-collapse-panel>
|
||||||
<span slot="label">
|
</a-collapse>
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
<a-collapse v-else>
|
||||||
<a-tooltip>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
<template slot="title">
|
<table width="100%">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<tr class="client-table-header">
|
||||||
</template>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
</a-tooltip>
|
<th>Flow</th>
|
||||||
</span>
|
<th>ID</th>
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
</tr>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
</a-form-item>
|
<td>[[ client.email ]]</td>
|
||||||
<a-form-item label="ID">
|
<td>[[ client.flow ]]</td>
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<td>[[ client.id ]]</td>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
</tr>
|
||||||
</a-form-item>
|
</table>
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
</a-collapse-panel>
|
||||||
<span slot="label">
|
</a-collapse>
|
||||||
Subscription
|
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in XTLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else-if="inbound.canEnableTlsFlow()" label="Flow">
|
|
||||||
<a-select v-model="inbound.settings.vlesses[index].flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
<a-collapse v-else>
|
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
|
||||||
<table width="100%">
|
|
||||||
<tr class="client-table-header">
|
|
||||||
<th>{{ i18n "pages.inbounds.email" }}</th>
|
|
||||||
<th>Flow</th>
|
|
||||||
<th>ID</th>
|
|
||||||
</tr>
|
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
|
||||||
<td>[[ client.email ]]</td>
|
|
||||||
<td>[[ client.flow ]]</td>
|
|
||||||
<td>[[ client.id ]]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-form>
|
|
||||||
<template v-if="inbound.isTcp">
|
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">
|
<a-button type="primary" size="small"
|
||||||
|
@click="inbound.settings.addFallback()">
|
||||||
+
|
+
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
@ -133,28 +33,28 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- vless fallbacks -->
|
<!-- vless fallbacks -->
|
||||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-divider>
|
<a-divider style="margin:0;">
|
||||||
fallback[[ index + 1 ]]
|
Fallback[[ index + 1 ]]
|
||||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<a-form-item label="Name">
|
<a-form-item label='Name'>
|
||||||
<a-input v-model="fallback.name"></a-input>
|
<a-input v-model="fallback.name"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label='Alpn'>
|
||||||
<a-input v-model="fallback.alpn"></a-input>
|
<a-input v-model="fallback.alpn"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Path">
|
<a-form-item label='Path'>
|
||||||
<a-input v-model="fallback.path"></a-input>
|
<a-input v-model="fallback.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Dest">
|
<a-form-item label='Dest'>
|
||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="xVer">
|
<a-form-item label='xVer'>
|
||||||
<a-input-number v-model="fallback.xver"></a-input-number>
|
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-divider v-if="inbound.settings.fallbacks.length - 1 === index"/>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -1,111 +1,21 @@
|
||||||
{{define "form/vmess"}}
|
{{define "form/vmess"}}
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
{{template "form/client"}}
|
||||||
<a-form-item>
|
</a-collapse-panel>
|
||||||
<span slot="label">
|
</a-collapse>
|
||||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
<a-collapse v-else>
|
||||||
<a-tooltip>
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vmesses.length">
|
||||||
<template slot="title">
|
<table width="100%">
|
||||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
<tr class="client-table-header">
|
||||||
</template>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
</a-tooltip>
|
<th>ID</th>
|
||||||
</span>
|
</tr>
|
||||||
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<td>[[ client.email ]]</td>
|
||||||
</a-form-item>
|
<td>[[ client.id ]]</td>
|
||||||
<br>
|
</tr>
|
||||||
<a-form-item label="ID">
|
</table>
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
</a-collapse-panel>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
</a-collapse>
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
|
||||||
<span slot="label">
|
|
||||||
Subscription
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
|
||||||
<span slot="label">
|
|
||||||
Telegram ID
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span> (GB)
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
|
||||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
|
||||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-else>
|
|
||||||
<span slot="label">
|
|
||||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="client._expiryTime" style="width: 170px;"></a-date-picker>
|
|
||||||
</a-form-item>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
<a-collapse v-else>
|
|
||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
|
||||||
<table width="100%">
|
|
||||||
<tr class="client-table-header">
|
|
||||||
<th>{{ i18n "pages.inbounds.email" }}</th>
|
|
||||||
<th>ID</th>
|
|
||||||
</tr>
|
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
|
||||||
<td>[[ client.email ]]</td>
|
|
||||||
<td>[[ client.id ]]</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-collapse-panel>
|
|
||||||
</a-collapse>
|
|
||||||
</a-form>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-form layout="inline">
|
<a-divider style="margin:0;"></a-divider>
|
||||||
<a-form-item>
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<span slot="label">
|
<a-form-item>
|
||||||
Sniffing
|
<span slot="label">
|
||||||
<a-tooltip>
|
Sniffing
|
||||||
<template slot="title">
|
<a-tooltip>
|
||||||
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
<template slot="title">
|
||||||
</template>
|
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
</template>
|
||||||
</a-tooltip>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</span>
|
</a-tooltip>
|
||||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
</span>
|
||||||
</a-form-item>
|
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||||
<a-form-item>
|
</a-form-item>
|
||||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||||
</a-checkbox-group>
|
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||||
</a-form-item>
|
</a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
26
web/html/xui/form/stream/external_proxy.html
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{{define "form/externalProxy"}}
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-divider style="margin:0;"></a-divider>
|
||||||
|
<a-form-item label="External Proxy">
|
||||||
|
<a-switch v-model="externalProxy"></a-switch>
|
||||||
|
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||||
|
<template>
|
||||||
|
<a-tooltip title="Force TLS">
|
||||||
|
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||||
|
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||||
|
<a-select-option value="tls">TLS</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||||
|
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||||
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||||
|
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{{define "form/streamGRPC"}}
|
{{define "form/streamGRPC"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="ServiceName">
|
<a-form-item label="Service Name">
|
||||||
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
|
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Multi Mode">
|
<a-form-item label="MultiMode">
|
||||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
{{define "form/streamHTTP"}}
|
{{define "form/streamHTTP"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Host">
|
<a-form-item>
|
||||||
<a-row v-for="(host, index) in inbound.stream.http.host">
|
<template slot="label">{{ i18n "host" }}
|
||||||
<a-input v-model.trim="inbound.stream.http.host[index]"></a-input>
|
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
|
||||||
</a-row>
|
</template>
|
||||||
|
<template v-for="(host, index) in inbound.stream.http.host">
|
||||||
|
<a-input v-model.trim="inbound.stream.http.host[index]">
|
||||||
|
<a-button size="small" slot="addonAfter"
|
||||||
|
@click="inbound.stream.http.removeHost(index)"
|
||||||
|
v-if="inbound.stream.http.host.length>1">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,47 +1,38 @@
|
||||||
{{define "form/streamKCP"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.kcp.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">None (Not Camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">SRTP (Camouflage Video Call)</a-select-option>
|
<a-select-option value="srtp">SRTP (video call)</a-select-option>
|
||||||
<a-select-option value="utp">UTP (Camouflage BT Download)</a-select-option>
|
<a-select-option value="utp">UTP (BT DownloaD)</a-select-option>
|
||||||
<a-select-option value="wechat-video">Wechat-Video (Camouflage WeChat Video)</a-select-option>
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
<a-select-option value="dtls">DTLS (Camouflage DTLS 1.2 Packages)</a-select-option>
|
<a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option>
|
||||||
<a-select-option value="wireguard">Wireguard (Camouflage Wireguard Packages)</a-select-option>
|
<a-select-option value="wireguard">WireGuard (WireGuard packages)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
<a-input v-model="inbound.stream.kcp.seed"></a-input>
|
||||||
<a-input v-model="inbound.stream.kcp.seed" style="width: 150px;" ></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='MTU'>
|
||||||
<a-form-item label="MTU">
|
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.mtu"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='TTI(ms)'>
|
||||||
<a-form-item label="TTI (ms)">
|
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.tti"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='Uplink(Mb/s)'>
|
||||||
<a-form-item label="Uplink Capacity (MB/S)">
|
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.upCap"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='Downlink(Mb/s)'>
|
||||||
<a-form-item label="Downlink Capacity (MB/S)">
|
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.downCap"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='Congestion'>
|
||||||
<a-form-item label="Congestion">
|
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='Read buffer(MB)'>
|
||||||
<a-form-item label="Read Buffer Size (MB)">
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.readBuffer"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='Write buffer(MB)'>
|
||||||
<a-form-item label="Write Buffer Size (MB)">
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||||
<a-input-number v-model="inbound.stream.kcp.writeBuffer"></a-input-number>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
{{define "form/streamQUIC"}}
|
{{define "form/streamQUIC"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.security" style="width: 165px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none</a-select-option>
|
<a-select-option value="none">none</a-select-option>
|
||||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||||
<a-select-option value="srtp">srtp (camouflage video call)</a-select-option>
|
<a-select-option value="srtp">SRTP (video call)</a-select-option>
|
||||||
<a-select-option value="utp">utp (camouflage BT download)</a-select-option>
|
<a-select-option value="utp">UTP (BT Download)</a-select-option>
|
||||||
<a-select-option value="wechat-video">wechat-video (camouflage WeChat video)</a-select-option>
|
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||||
<a-select-option value="dtls">dtls (camouflage DTLS 1.2 packages)</a-select-option>
|
<a-select-option value="dtls">DTLS (DTLS 1.2 packages)</a-select-option>
|
||||||
<a-select-option value="wireguard">wireguard (camouflage wireguard packages)</a-select-option>
|
<a-select-option value="wireguard">WireGuard (WireGuard Packages)</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
{{define "form/streamSettings"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label="{{ i18n "transmission" }}">
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="kcp">KCP</a-select-option>
|
<a-select-option value="kcp">KCP</a-select-option>
|
||||||
<a-select-option value="ws">WS</a-select-option>
|
<a-select-option value="ws">WS</a-select-option>
|
||||||
|
|
@ -42,7 +43,6 @@
|
||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
<template>
|
<template>
|
||||||
{{template "form/streamSockopt"}}
|
{{template "form/streamSockopt"}}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,26 @@
|
||||||
{{define "form/streamSockopt"}}
|
{{define "form/streamSockopt"}}
|
||||||
<a-form layout="inline">
|
<a-divider style="margin:0;"></a-divider>
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Transparent Proxy">
|
<a-form-item label="Transparent Proxy">
|
||||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
|
<template v-if="inbound.stream.sockoptSwitch">
|
||||||
<tr>
|
<a-form-item label="Accept Proxy Protocol">
|
||||||
<td>Accept Proxy Protocol</td>
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
<td>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label="TCP FastOpen">
|
||||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</td>
|
<a-form-item label="Route Mark">
|
||||||
</tr>
|
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||||
<tr>
|
</a-form-item>
|
||||||
<td>TCP FastOpen</td>
|
<a-form-item label="T-Proxy">
|
||||||
<td>
|
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item>
|
<a-select-option value="off">OFF</a-select-option>
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
</a-form-item>
|
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||||
</td>
|
</a-select>
|
||||||
</tr>
|
</a-form-item>
|
||||||
<tr>
|
</template>
|
||||||
<td>Route Mark</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>T-Proxy</td>
|
|
||||||
<td>
|
|
||||||
<a-form-item>
|
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="off">OFF</a-select-option>
|
|
||||||
<a-select-option value="redirect">Redirect</a-select-option>
|
|
||||||
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -1,52 +1,51 @@
|
||||||
{{define "form/streamTCP"}}
|
{{define "form/streamTCP"}}
|
||||||
<!-- tcp type -->
|
<!-- tcp type -->
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()">
|
||||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||||
<a-switch
|
<a-switch
|
||||||
:checked="inbound.stream.tcp.type === 'http'"
|
:checked="inbound.stream.tcp.type === 'http'"
|
||||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp request -->
|
<!-- tcp request -->
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestPath" }}'>
|
|
||||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
|
||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
|
||||||
</a-row>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row>
|
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
|
||||||
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">+</a-button>
|
</template>
|
||||||
</a-row>
|
<template v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-button size="small" slot="addonAfter"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
@click="inbound.stream.tcp.request.removePath(index)"
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
</a-input>
|
||||||
<template slot="addonAfter">
|
</template>
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||||
|
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<!-- tcp response -->
|
||||||
</a-form>
|
|
||||||
|
|
||||||
<!-- tcp response -->
|
|
||||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
@ -56,18 +55,19 @@
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||||
<a-row>
|
<a-button size="small"
|
||||||
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
</a-form-item>
|
||||||
</a-row>
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name" }}'></a-input>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
{{define "form/streamWS"}}
|
{{define "form/streamWS"}}
|
||||||
<a-form layout="inline">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
||||||
<a-form-item>
|
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||||
<a-row>
|
</a-form-item>
|
||||||
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
</a-row>
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
</a-input>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.name"}}'></a-input>
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
|
||||||
</template>
|
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
||||||
|
|
@ -1,219 +1,194 @@
|
||||||
{{define "form/tlsSettings"}}
|
{{define "form/tlsSettings"}}
|
||||||
<!-- tls enable -->
|
<!-- tls enable -->
|
||||||
<a-form layout="inline" v-if="inbound.canSetTls()">
|
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item v-if="inbound.canEnableTls()" label="TLS">
|
<a-divider style="margin:0;"></a-divider>
|
||||||
<a-switch v-model="inbound.tls">
|
<a-form-item label='{{ i18n "security" }}'>
|
||||||
</a-switch>
|
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||||
</a-form-item>
|
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||||
<a-form-item v-if="inbound.canEnableReality()">
|
|
||||||
<span slot="label">
|
|
||||||
Reality
|
|
||||||
<a-tooltip>
|
|
||||||
<template slot="title">
|
|
||||||
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</span>
|
|
||||||
<a-switch v-model="inbound.reality"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="inbound.canEnableXtls()">
|
|
||||||
<span slot="label">
|
|
||||||
XTLS
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-radio-button v-if="inbound.canEnableXtls()" value="xtls">XTLS</a-radio-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
<a-tooltip>
|
||||||
<a-switch v-model="inbound.xtls"></a-switch>
|
<template slot="title">
|
||||||
</a-form-item>
|
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
||||||
</a-form>
|
|
||||||
|
|
||||||
<!-- tls settings -->
|
|
||||||
<a-form v-if="inbound.tls" layout="inline">
|
|
||||||
<a-form-item label='Multi Domain'>
|
|
||||||
<a-switch v-model="multiDomain"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="multiDomain">
|
|
||||||
<a-row>
|
|
||||||
<span>Domains:</span>
|
|
||||||
<a-button v-if="multiDomain" type="primary" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})" style="margin-left: 10px">+</a-button>
|
|
||||||
</a-row>
|
|
||||||
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
|
||||||
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
|
||||||
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
|
||||||
<template slot="addonAfter">
|
|
||||||
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||||
</a-input-group>
|
</a-tooltip>
|
||||||
|
<a-radio-button value="tls">TLS</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-else label='{{ i18n "domainName" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="CipherSuites">
|
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 300px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value="">auto</a-select-option>
|
|
||||||
<a-select-option v-for="key in TLS_CIPHER_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="MinVersion">
|
|
||||||
<a-select v-model="inbound.stream.tls.minVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="MaxVersion">
|
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width: 60px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
|
||||||
<a-input v-model.trim="inbound.stream.tls.settings.serverName" style="width: 250px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="uTLS">
|
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
|
||||||
style="width: 170px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
|
||||||
<a-select-option value=''>None</a-select-option>
|
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Alpn">
|
|
||||||
<a-select
|
|
||||||
mode="multiple"
|
|
||||||
style="width: 250px"
|
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
|
||||||
v-model="inbound.stream.tls.alpn">
|
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="Allow insecure">
|
|
||||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="Reject Unknown SNI">
|
|
||||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
|
||||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-if="cert.useFile">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
|
||||||
<a-input v-model.trim="cert.certFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
|
||||||
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.key"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="ocspStapling">
|
|
||||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
|
||||||
</a-form>
|
|
||||||
|
|
||||||
<!-- xtls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-else-if="inbound.xtls" layout="inline">
|
<template v-if="inbound.stream.isTls">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
|
||||||
<a-input v-model.trim="inbound.stream.xtls.settings.serverName" style="width: 250px"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Alpn">
|
|
||||||
<a-checkbox-group v-model="inbound.stream.xtls.alpn" style="width:200px">
|
|
||||||
<a-checkbox v-for="key in ALPN_OPTION" :value="key">[[ key ]]</a-checkbox>
|
|
||||||
</a-checkbox-group>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="Allow insecure">
|
|
||||||
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
<template v-for="cert,index in inbound.stream.xtls.certs">
|
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
|
||||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" style="margin-left: 10px">+</a-button>
|
|
||||||
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small" @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<a-form-item label="CipherSuites">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-input v-model.trim="cert.certFile" style="width:300px;"></a-input>
|
<a-select-option value="">auto</a-select-option>
|
||||||
|
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Min/Max Version">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-select v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
<a-select v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="uTLS">
|
||||||
|
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value=''>None</a-select-option>
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="ALPN">
|
||||||
|
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
v-model="inbound.stream.tls.alpn">
|
||||||
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Allow insecure">
|
||||||
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Reject Unknown SNI">
|
||||||
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
|
||||||
|
style="margin-left: 10px">+</a-button>
|
||||||
|
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
|
||||||
|
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<template v-if="cert.useFile">
|
||||||
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
</a-form-item>
|
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<template v-else>
|
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
</a-form-item>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.cert"></a-input>
|
<a-form-item label=" ">
|
||||||
</a-form-item>
|
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
"pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="cert.key"></a-input>
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<a-form-item label='ocspStapling'>
|
||||||
|
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- xtls settings -->
|
||||||
<a-form v-else-if="inbound.reality" layout="inline">
|
<template v-else-if="inbound.xtls">
|
||||||
<a-form-item label="Show">
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-switch v-model="inbound.stream.reality.show">
|
<a-input v-model.trim="inbound.stream.xtls.sni"></a-input>
|
||||||
</a-switch>
|
</a-form-item>
|
||||||
</a-form-item>
|
<a-form-item label="ALPN">
|
||||||
<a-form-item label="xVer">
|
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
<a-input-number v-model="inbound.stream.reality.xver" :min="0" style="width: 60px"></a-input-number>
|
v-model="inbound.stream.xtls.alpn">
|
||||||
</a-form-item>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
<a-form-item label="uTLS">
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
</a-form-item>
|
||||||
style="width: 135px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-form-item label="Allow insecure">
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
||||||
</a-select>
|
</a-form-item>
|
||||||
</a-form-item>
|
<template v-for="cert,index in inbound.stream.xtls.certs">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
</a-form-item>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
<a-form-item label="Dest">
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
</a-radio-group>
|
||||||
</a-form-item>
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()"
|
||||||
<a-form-item label="Server Names">
|
style="margin-left: 10px">+</a-button>
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small"
|
||||||
</a-form-item>
|
@click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
<a-form-item label="ShortIds">
|
</a-form-item>
|
||||||
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
|
<template v-if="cert.useFile">
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width: 150px;"></a-input>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
</a-form-item>
|
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||||
<br>
|
</a-form-item>
|
||||||
<a-form-item label="SpiderX">
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width: 150px;"></a-input>
|
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Private Key">
|
<a-form-item label=" ">
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n
|
||||||
</a-form-item>
|
"pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
<a-form-item label="Public Key">
|
</a-form-item>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 300px"></a-input>
|
</template>
|
||||||
</a-form-item>
|
<template v-else>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Key</a-button>
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<!-- reality settings -->
|
||||||
|
<template v-if="inbound.stream.isReality">
|
||||||
|
<a-form-item label='Show'>
|
||||||
|
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Xver'>
|
||||||
|
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='uTLS'>
|
||||||
|
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Dest'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Server Names'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.client.renew" }}</span>
|
||||||
|
</template>
|
||||||
|
Short Ids
|
||||||
|
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
|
||||||
|
</a-icon>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='SpiderX'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Private Key'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Public Key'>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label=" ">
|
||||||
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -2,34 +2,65 @@
|
||||||
<template slot="actions" slot-scope="text, client, index">
|
<template slot="actions" slot-scope="text, client, index">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "qrCode" }}</template>
|
<template slot="title">{{ i18n "qrCode" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record,index);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="qrcode" v-if="record.hasLink()" @click="showQrcode(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
<template slot="title">{{ i18n "pages.client.edit" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="edit" @click="openEditClient(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "info" }}</template>
|
<template slot="title">{{ i18n "info" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="info-circle" @click="showInfo(record,index);"></a-icon>
|
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||||
<a-icon style="font-size: 24px;" type="retweet" @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0"></a-icon>
|
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.resetTrafficContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "reset"}}'
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: #008771' : 'color: #008771'"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px; cursor: pointer;" class="normal-icon" type="retweet" v-if="client.email.length > 0"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
<template slot="title"><span style="color: #FF4D4F"> {{ i18n "delete"}}</span></template>
|
||||||
<a-icon style="font-size: 24px;" type="delete" v-if="isRemovable(record.id)" @click="delClient(record.id,client)"></a-icon>
|
<a-popconfirm @confirm="delClient(record.id,client,false)"
|
||||||
|
title='{{ i18n "pages.inbounds.deleteClientContent"}}'
|
||||||
|
:overlay-class-name="themeSwitcher.currentTheme"
|
||||||
|
ok-text='{{ i18n "delete"}}'
|
||||||
|
ok-type="danger"
|
||||||
|
cancel-text='{{ i18n "cancel"}}'>
|
||||||
|
<a-icon slot="icon" type="question-circle-o" style="color: #e04141"></a-icon>
|
||||||
|
<a-icon style="font-size: 24px; cursor: pointer;" class="delete-icon" type="delete" v-if="isRemovable(record.id)"></a-icon>
|
||||||
|
</a-popconfirm>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, client, index">
|
<template slot="enable" slot-scope="text, client, index">
|
||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="online" slot-scope="text, client, index">
|
||||||
|
<template v-if="isClientOnline(client.email)">
|
||||||
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-tag>{{ i18n "offline" }}</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template slot="client" slot-scope="text, client">
|
<template slot="client" slot-scope="text, client">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
|
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
|
</template>
|
||||||
|
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
|
</a-badge>
|
||||||
|
</a-tooltip>
|
||||||
[[ client.email ]]
|
[[ client.email ]]
|
||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content" v-if="client.email">
|
<template slot="content" v-if="client.email">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -38,28 +69,200 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="client.totalGB > 0">
|
<tr v-if="client.totalGB > 0">
|
||||||
<td>{{ i18n "remained" }}</td>
|
<td>{{ i18n "remained" }}</td>
|
||||||
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="statsColor(record, client.email)">
|
<table>
|
||||||
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
<tr>
|
||||||
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
<template v-else>
|
[[ sizeFormat(getSumStats(record, client.email)) ]]
|
||||||
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
</td>
|
||||||
</template>
|
<td width="120px" v-if="!client.enable">
|
||||||
</a-tag>
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="60px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
<template slot="content">
|
||||||
</a-tag>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-else-if="client.expiryTime < 0" color="cyan">
|
<template v-else>
|
||||||
[[ client._expiryTime ]] {{ i18n "pages.client.days" }}
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
</a-tag>
|
<template slot="content">
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template slot="actionMenu" slot-scope="text, client, index">
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="ellipsis" style="font-size: 20px;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item v-if="record.hasLink()" @click="showQrcode(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="openEditClient(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="edit"></a-icon>
|
||||||
|
{{ i18n "pages.client.edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="showInfo(record.id,client);">
|
||||||
|
<a-icon style="font-size: 14px;" type="info-circle"></a-icon>
|
||||||
|
{{ i18n "info" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="resetClientTraffic(client,record.id)" v-if="client.email.length > 0">
|
||||||
|
<a-icon style="font-size: 14px;" type="retweet"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isRemovable(record.id)" @click="delClient(record.id,client)">
|
||||||
|
<a-icon style="font-size: 14px;" type="delete"></a-icon>
|
||||||
|
<span style="color: #FF4D4F"> {{ i18n "delete"}}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item>
|
||||||
|
<a-switch v-model="client.enable" size="small" @change="switchEnableClient(record.id,client)">
|
||||||
|
</a-switch>
|
||||||
|
{{ i18n "enable"}}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="info" slot-scope="text, client, index">
|
||||||
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-if="!client.enable">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'"
|
||||||
|
:show-info="false"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else-if="client.totalGB > 0">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content" v-if="client.email">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="statsProgress(record, client.email)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="120px" v-else class="infinite-bar">
|
||||||
|
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||||
|
:show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : ''"
|
||||||
|
:percent="100"></a-progress>
|
||||||
|
</td>
|
||||||
|
<td width="80px">
|
||||||
|
<template v-if="client.totalGB > 0">[[ client._totalGB + "GB" ]]</template>
|
||||||
|
<span v-else style="font-weight: 100;font-size: 14pt;">∞</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-divider style="margin: 0; border-collapse: separate;"></a-divider>
|
||||||
|
{{ i18n "pages.inbounds.expireDate" }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-if="client.expiryTime !=0 && client.reset >0">
|
||||||
|
<td width="80px" style="margin:0; text-align: right;font-size: 1em;">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</td>
|
||||||
|
<td width="120px" class="infinite-bar">
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-progress :show-info="false"
|
||||||
|
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||||
|
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td colspan="3" style="text-align: center;">
|
||||||
|
<a-popover v-if="client.expiryTime != 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}</span>
|
||||||
|
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px; border: none;"
|
||||||
|
:color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)">
|
||||||
|
[[ remainedDays(client.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">∞</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!client.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;"><a-icon type="solution"></a-icon></a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
@ -3,71 +3,89 @@
|
||||||
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
v-model="infoModal.visible" title='{{ i18n "pages.inbounds.details"}}'
|
||||||
:closable="true"
|
:closable="true"
|
||||||
:mask-closable="true"
|
:mask-closable="true"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
width="600px"
|
width="600px"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<a-row>
|
||||||
<tr><td>
|
<a-col :xs="24" :md="12">
|
||||||
<table>
|
<table>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td>
|
||||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag color="green">[[ dbInbound.port ]]</a-tag></td></tr>
|
<a-tooltip :title="[[ dbInbound.address ]]">
|
||||||
</table>
|
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
|
||||||
</td>
|
</a-tooltip>
|
||||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
</td></tr>
|
||||||
<table>
|
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
|
||||||
<tr>
|
</table>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
</a-col>
|
||||||
</tr>
|
<a-col :xs="24" :md="12">
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
<table>
|
||||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<tr>
|
||||||
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "host" }}</td>
|
||||||
|
<td v-if="inbound.host">
|
||||||
|
<a-tooltip :title="[[ inbound.host ]]">
|
||||||
|
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
|
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "path" }}</td>
|
||||||
|
<td v-if="inbound.path">
|
||||||
|
<a-tooltip :title="[[ inbound.path ]]">
|
||||||
|
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
|
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
<tr v-if="inbound.path"><td>{{ i18n "path" }}</td><td><a-tag color="green">[[ inbound.path ]]</a-tag></td></tr>
|
<template v-if="inbound.isQuic">
|
||||||
<tr v-else><td>{{ i18n "path" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||||
</template>
|
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||||
|
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="inbound.isQuic">
|
<template v-if="inbound.isKcp">
|
||||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.quicKey ]]</a-tag></td></tr>
|
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag color="green">[[ inbound.quicType ]]</a-tag></td></tr>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="inbound.isKcp">
|
<template v-if="inbound.isGrpc">
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag color="green">[[ inbound.kcpType ]]</a-tag></td></tr>
|
<tr><td>grpc serviceName</td><td>
|
||||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag color="green">[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
<a-tooltip :title="[[ inbound.serviceName ]]">
|
||||||
</template>
|
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
<template v-if="inbound.isGrpc">
|
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
<tr><td>grpc serviceName</td><td><a-tag color="green">[[ inbound.serviceName ]]</a-tag></td></tr>
|
</template>
|
||||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</a-col>
|
||||||
</td></tr>
|
<template v-if="dbInbound.hasLink()">
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
{{ i18n "security" }}
|
||||||
<td v-if="inbound.tls">
|
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
<br />
|
||||||
tls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
<template v-if="inbound.stream.security != 'none'">
|
||||||
</td>
|
{{ i18n "domainName" }}
|
||||||
<td v-else-if="inbound.xtls">
|
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||||
xtls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
</template>
|
||||||
xtls {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
</template>
|
||||||
</td>
|
|
||||||
<td v-else-if="inbound.reality">
|
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
|
||||||
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
|
||||||
</td>
|
|
||||||
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "encryption" }}</td>
|
<td>{{ i18n "encryption" }}</td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
</tr><tr v-if="inbound.isSS2022">
|
</tr><tr v-if="inbound.isSS2022">
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
<td>
|
||||||
|
<a-tooltip :title="[[ inbound.settings.password ]]">
|
||||||
|
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
|
|
@ -82,21 +100,29 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.id">
|
<tr v-if="infoModal.clientSettings.id">
|
||||||
<td>ID</td>
|
<td>ID</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.id ]]</a-tag></td>
|
<td><a-tag>[[ infoModal.clientSettings.id ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
||||||
<td>Flow</td>
|
<td>Flow</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.inbound.xtls">
|
||||||
|
<td>Flow</td>
|
||||||
|
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="infoModal.clientSettings.password">
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
<td>Password</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings.password ]]</a-tag></td>
|
<td>
|
||||||
|
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
|
||||||
|
<a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag>
|
||||||
|
</a-tooltip>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "status" }}</td>
|
<td>{{ i18n "status" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
<a-tag v-if="isEnable" color="green">{{ i18n "enabled" }}</a-tag>
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
<a-tag v-else>{{ i18n "disabled" }}</a-tag>
|
||||||
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -104,84 +130,84 @@
|
||||||
<td>{{ i18n "usage" }}</td>
|
<td>{{ i18n "usage" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||||
<a-tag color="blue">↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%; text-align: center;">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "remained" }}</th>
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
||||||
</a-tag>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
|
||||||
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
|
||||||
</a-tag>
|
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
|
||||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
|
||||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</td>
|
||||||
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="cyan">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
|
<td>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
</td>
|
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
||||||
</tr>
|
</a-tag>
|
||||||
</table>
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
</td>
|
||||||
<a-divider>Subscription link</a-divider>
|
<td>
|
||||||
<a-row>
|
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||||
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||||
<a-col :span="2">
|
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
</a-tag>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
</template>
|
||||||
<a-icon type="snippets"></a-icon>
|
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}</a-tag>
|
||||||
</button>
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</a-tooltip>
|
</td>
|
||||||
</a-col>
|
</tr>
|
||||||
</a-row>
|
</table>
|
||||||
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
|
<a-divider>Subscription link</a-divider>
|
||||||
|
<a-row>
|
||||||
|
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
|
<a-divider>Telegram ID</a-divider>
|
||||||
|
<a-row>
|
||||||
|
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="dbInbound.hasLink()">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
|
<a-col :sx="24" :md="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
|
||||||
<a-divider>Telegram Username</a-divider>
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
|
||||||
<a-col :span="2">
|
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
|
||||||
<a-icon type="snippets"></a-icon>
|
|
||||||
</button>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</template>
|
|
||||||
<template v-if="dbInbound.hasLink()">
|
|
||||||
<a-divider>URL</a-divider>
|
|
||||||
<a-row v-for="(link,index) in infoModal.links">
|
|
||||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
|
||||||
<a-col :span="2" style="text-align: right;">
|
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
|
||||||
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
|
||||||
<a-icon type="snippets"></a-icon>
|
|
||||||
</button>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
||||||
<a-divider>URL</a-divider>
|
<a-divider>URL</a-divider>
|
||||||
<a-row v-for="(link,index) in infoModal.links">
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
<a-col :span="22"><a-tag color="green">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
<a-col :span="2" style="text-align: right;">
|
<a-col :span="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
|
@ -199,9 +225,9 @@
|
||||||
<th>FollowRedirect</th>
|
<th>FollowRedirect</th>
|
||||||
</tr><tr>
|
</tr><tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
|
@ -212,29 +238,29 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.auth ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-if="inbound.settings.auth == 'password'">
|
<template v-if="inbound.settings.auth == 'password'">
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td>{{ i18n "username" }}</td>
|
<td>{{ i18n "username" }}</td>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td>[[ index ]]</td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th>{{ i18n "username" }}</th>
|
<th>{{ i18n "username" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
</tr><tr v-for="account,index in inbound.settings.accounts">
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td>[[ index ]]</td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
@ -245,7 +271,6 @@
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
settings: null,
|
|
||||||
clientSettings: null,
|
clientSettings: null,
|
||||||
clientStats: [],
|
clientStats: [],
|
||||||
upStats: 0,
|
upStats: 0,
|
||||||
|
|
@ -260,27 +285,10 @@
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.settings = JSON.parse(this.inbound.settings);
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||||
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
|
|
||||||
address = this.dbInbound.address;
|
|
||||||
this.links = [];
|
|
||||||
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
|
||||||
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
|
||||||
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
|
||||||
this.links.push({
|
|
||||||
remark: remarkText,
|
|
||||||
link: this.inbound.genLink(domain.domain, remarkText, index)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.links.push({
|
|
||||||
remark: remark,
|
|
||||||
link: this.inbound.genLink(address, remark, index)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (this.clientSettings) {
|
if (this.clientSettings) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(this.clientSettings.subId);
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
|
|
@ -295,8 +303,7 @@
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
return app.subSettings.subURI+subID;
|
||||||
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -312,23 +319,23 @@
|
||||||
return this.infoModal.inbound;
|
return this.infoModal.inbound;
|
||||||
},
|
},
|
||||||
get isActive() {
|
get isActive() {
|
||||||
if (infoModal.clientStats) {
|
if(infoModal.clientStats){
|
||||||
return infoModal.clientStats.enable;
|
return infoModal.clientStats.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return true;
|
||||||
},
|
},
|
||||||
get isEnable() {
|
get isEnable() {
|
||||||
if (infoModal.clientSettings) {
|
if(infoModal.clientSettings){
|
||||||
return infoModal.clientSettings.enable;
|
return infoModal.clientSettings.enable;
|
||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId, content) {
|
copyToClipboard(elmentId,content) {
|
||||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.infoModal.clipboard.on('success', () => {
|
this.infoModal.clipboard.on('success', () => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
this.infoModal.clipboard.destroy();
|
this.infoModal.clipboard.destroy();
|
||||||
|
|
@ -338,6 +345,7 @@
|
||||||
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{{define "inboundModal"}}
|
{{define "inboundModal"}}
|
||||||
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok"
|
||||||
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
|
||||||
{{template "form/inbound"}}
|
{{template "form/inbound"}}
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
@ -43,15 +43,6 @@
|
||||||
loading(loading) {
|
loading(loading) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
|
||||||
switch (protocol) {
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|
@ -70,7 +61,7 @@
|
||||||
return inModal.isEdit;
|
return inModal.isEdit;
|
||||||
},
|
},
|
||||||
get client() {
|
get client() {
|
||||||
return inModal.getClients(this.inbound.protocol, this.inbound.settings)[0];
|
return inModal.inbound.clients[0];
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
|
|
@ -78,22 +69,25 @@
|
||||||
set delayedExpireDays(days) {
|
set delayedExpireDays(days) {
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
get multiDomain() {
|
get externalProxy() {
|
||||||
return this.inbound.stream.tls.settings.domains.length > 0;
|
return this.inbound.stream.externalProxy.length > 0;
|
||||||
},
|
},
|
||||||
set multiDomain(value) {
|
set externalProxy(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.externalProxy = [{
|
||||||
inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }];
|
forceTls: "same",
|
||||||
|
dest: window.location.hostname,
|
||||||
|
port: inModal.inbound.port,
|
||||||
|
remark: ""
|
||||||
|
}];
|
||||||
} else {
|
} else {
|
||||||
inModal.inbound.stream.tls.server = "";
|
inModal.inbound.stream.externalProxy = [];
|
||||||
inModal.inbound.stream.tls.settings.domains = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
if (!inModal.inbound.canSetTls()) {
|
if (!inModal.inbound.canEnableTls()) {
|
||||||
this.inModal.inbound.stream.security = 'none';
|
this.inModal.inbound.stream.security = 'none';
|
||||||
}
|
}
|
||||||
if (!inModal.inbound.canEnableReality()) {
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
|
|
@ -104,6 +98,11 @@
|
||||||
client.flow = "";
|
client.flow = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if ((this.inModal.inbound.protocol == Protocols.VLESS || this.inModal.inbound.protocol == Protocols.TROJAN) && !inModal.inbound.xtls) {
|
||||||
|
this.inModal.inbound.settings.vlesses.forEach(client => {
|
||||||
|
client.flow = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
SSMethodChange() {
|
SSMethodChange() {
|
||||||
if (this.inModal.inbound.isSSMultiUser) {
|
if (this.inModal.inbound.isSSMultiUser) {
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,60 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||||
}
|
}
|
||||||
tr.hideExpandIcon .ant-table-row-expand-icon {
|
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.infinite-tag {
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 2rem;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #F2EAF1;
|
||||||
|
border: #D5BED2 solid 1px;
|
||||||
|
}
|
||||||
|
.dark .infinite-bar .ant-progress-inner .ant-progress-bg {
|
||||||
|
background-color: #3c1536;
|
||||||
|
border: #7a316f solid 1px;
|
||||||
|
}
|
||||||
|
.ant-collapse {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.online-animation .ant-badge-status-dot {
|
||||||
|
animation: 1.2s ease infinite normal none running onlineAnimation;
|
||||||
|
}
|
||||||
|
@keyframes onlineAnimation {
|
||||||
|
0%, 50%, 100% { transform: scale(1); opacity: 1; }
|
||||||
|
10% { transform: scale(1.5); opacity: .2; }
|
||||||
|
}
|
||||||
|
.info-large-tag {
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip="loading">
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
||||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable style="margin-bottom: 20px;" :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||||
|
|
@ -43,39 +76,59 @@
|
||||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="24" :sm="24" :lg="12">
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
|
</a-back-top>
|
||||||
{{ i18n "clients" }}:
|
{{ i18n "clients" }}:
|
||||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="24" :sm="24" :lg="12">
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
<a-button type="primary" icon="plus" @click="openAddInbound">{{ i18n "pages.inbounds.addInbound" }}</a-button>
|
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-button type="primary" icon="menu">{{ i18n "pages.inbounds.generalActions" }}</a-button>
|
<a-button type="primary" icon="menu">
|
||||||
|
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||||
|
</a-button>
|
||||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item key="import">
|
||||||
|
<a-icon type="import"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.importInbound" }}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="export">
|
<a-menu-item key="export">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
|
|
@ -95,12 +148,12 @@
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="24" :lg="12" style="text-align: right;">
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
<a-select v-model="refreshInterval"
|
<a-select v-model="refreshInterval"
|
||||||
style="width: 65px;"
|
style="width: 65px;"
|
||||||
v-if="isRefreshEnabled"
|
v-if="isRefreshEnabled"
|
||||||
@change="changeRefreshInterval"
|
@change="changeRefreshInterval"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||||
|
|
@ -108,31 +161,36 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-switch v-model="enableFilter"
|
<div style="display: flex; align-items: center; justify-content: flex-start;">
|
||||||
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
<a-switch v-model="enableFilter"
|
||||||
@change="toggleFilter" style="margin-right: 10px;">
|
style="margin-right: .5rem;"
|
||||||
</a-switch>
|
@change="toggleFilter">
|
||||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
</a-switch>
|
||||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-back-top></a-back-top>
|
||||||
|
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1200 }"
|
:scroll="isMobile ? {} : { x: 1000 }"
|
||||||
:pagination="false"
|
:pagination=pagination(searchedInbounds)
|
||||||
:expand-icon-as-cell="false"
|
:expand-icon-as-cell="false"
|
||||||
:expand-row-by-click="false"
|
:expand-row-by-click="false"
|
||||||
:expand-icon-column-index="0"
|
:expand-icon-column-index="0"
|
||||||
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
:indent-size="0"
|
||||||
style="margin-top: 20px"
|
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||||
@change="() => getDBInbounds()">
|
style="margin-top: 10px">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
<a-icon type="edit" style="font-size: 22px" @click="openEditInbound(dbInbound.id);"></a-icon>
|
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a>
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
|
||||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item key="edit">
|
<a-menu-item key="edit">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
|
|
@ -142,7 +200,7 @@
|
||||||
<a-icon type="qrcode"></a-icon>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
<template v-if="dbInbound.isMultiUser()">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
|
|
@ -170,6 +228,10 @@
|
||||||
{{ i18n "info"}}
|
{{ i18n "info"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
</template>
|
</template>
|
||||||
|
<a-menu-item key="clipboard">
|
||||||
|
<a-icon type="copy"></a-icon>
|
||||||
|
{{ i18n "pages.inbounds.copyToClipboard" }}
|
||||||
|
</a-menu-item>
|
||||||
<a-menu-item key="resetTraffic">
|
<a-menu-item key="resetTraffic">
|
||||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
|
@ -181,43 +243,53 @@
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
</span>
|
</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item v-if="isMobile">
|
||||||
|
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
|
{{ i18n "pages.inbounds.enable" }}
|
||||||
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="blue">XTLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="cyan">Reality</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="clients" slot-scope="text, dbInbound">
|
<template slot="clients" slot-scope="text, dbInbound">
|
||||||
<template v-if="clientCount[dbInbound.id]">
|
<template v-if="clientCount[dbInbound.id]">
|
||||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template slot="content">
|
<template slot="content">
|
||||||
<table cellpadding="2" width="100%">
|
<table cellpadding="2" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -230,7 +302,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
<template v-if="dbInbound.total > 0">
|
<template v-if="dbInbound.total > 0">
|
||||||
[[ sizeFormat(dbInbound.total) ]]
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
|
@ -245,35 +317,117 @@
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||||
<template v-if="dbInbound.expiryTime > 0">
|
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag v-if="dbInbound.isExpiry" color="red">
|
<template slot="content">
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</template>
|
||||||
|
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||||
|
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-else color="blue">
|
</a-popover>
|
||||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
<a-tag v-else color="purple" class="infinite-tag">∞</a-tag>
|
||||||
</a-tag>
|
</template>
|
||||||
</template>
|
<template slot="info" slot-scope="text, dbInbound">
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||||
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||||
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||||
|
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="clientCount[dbInbound.id]">
|
||||||
|
<td>{{ i18n "clients" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||||
|
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||||
|
</template>
|
||||||
|
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
|
<template slot="content">
|
||||||
|
<table cellpadding="2" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>∞</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
|
||||||
|
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||||
|
</a-tag>
|
||||||
|
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">∞</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-badge>
|
||||||
|
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||||
|
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||||
|
<a-icon type="info"></a-icon>
|
||||||
|
</a-button>
|
||||||
|
</a-badge>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expandedRowRender" slot-scope="record">
|
<template slot="expandedRowRender" slot-scope="record">
|
||||||
<a-table
|
<a-table
|
||||||
v-if="(record.protocol === Protocols.VLESS) || (record.protocol === Protocols.VMESS)"
|
:row-key="client => client.id"
|
||||||
:row-key="client => client.id"
|
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||||
:columns="innerColumns"
|
:data-source="getInboundClients(record)"
|
||||||
:data-source="getInboundClients(record)"
|
:pagination=pagination(getInboundClients(record))
|
||||||
:pagination="false"
|
style="margin: -12px 2px -13px;">
|
||||||
style="margin-left: 20px;"
|
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
|
||||||
</a-table>
|
|
||||||
<a-table
|
|
||||||
v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
|
|
||||||
:row-key="client => client.id"
|
|
||||||
:columns="innerTrojanColumns"
|
|
||||||
:data-source="getInboundClients(record)"
|
|
||||||
:pagination="false"
|
|
||||||
style="margin-left: 20px;"
|
|
||||||
>
|
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -285,13 +439,20 @@
|
||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/uri/URI.min.js"></script>
|
||||||
|
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||||
|
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
const columns = [{
|
const columns = [{
|
||||||
title: "ID",
|
title: "ID",
|
||||||
align: 'right',
|
align: 'right',
|
||||||
dataIndex: "id",
|
dataIndex: "id",
|
||||||
width: 30,
|
width: 40,
|
||||||
|
responsive: ["xs"],
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
|
@ -320,7 +481,7 @@
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 40,
|
width: 50,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
|
|
@ -330,26 +491,46 @@
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 80,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'expiryTime' },
|
scopedSlots: { customRender: 'expiryTime' },
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const mobileColums = [{
|
||||||
|
title: "ID",
|
||||||
|
align: 'right',
|
||||||
|
dataIndex: "id",
|
||||||
|
width: 10,
|
||||||
|
responsive: ["s"],
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 25,
|
||||||
|
scopedSlots: { customRender: 'action' },
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
|
align: 'left',
|
||||||
|
width: 70,
|
||||||
|
dataIndex: "remark",
|
||||||
|
}, {
|
||||||
|
title: '{{ i18n "pages.inbounds.info" }}',
|
||||||
|
align: 'center',
|
||||||
|
width: 10,
|
||||||
|
scopedSlots: { customRender: 'info' },
|
||||||
|
}];
|
||||||
|
|
||||||
const innerColumns = [
|
const innerColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 65, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 30, scopedSlots: { customRender: 'enable' } },
|
||||||
|
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UUID', width: 120, dataIndex: "id" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerMobileColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
|
||||||
{ title: '{{ i18n "password" }}', width: 170, dataIndex: "password" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
|
|
@ -370,17 +551,18 @@
|
||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: [],
|
clientCount: [],
|
||||||
|
onlineClients: [],
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
port: 0,
|
subURI : ''
|
||||||
path: '',
|
|
||||||
domain: '',
|
|
||||||
tls: false
|
|
||||||
},
|
},
|
||||||
tgBotEnable: false
|
remarkModel: '-ieo',
|
||||||
|
tgBotEnable: false,
|
||||||
|
pageSize: 0,
|
||||||
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
|
|
@ -393,11 +575,19 @@
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
|
async getOnlineUsers() {
|
||||||
|
const msg = await HttpUtil.post('/panel/inbound/onlines');
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onlineClients = msg.obj != null ? msg.obj : [];
|
||||||
|
},
|
||||||
async getDefaultSettings() {
|
async getDefaultSettings() {
|
||||||
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
|
@ -411,11 +601,10 @@
|
||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
port: subPort,
|
subURI: subURI
|
||||||
path: subPath,
|
|
||||||
domain: subDomain,
|
|
||||||
tls: subTLS
|
|
||||||
};
|
};
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
this.remarkModel = remarkModel;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
|
|
@ -441,8 +630,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = inbound.clients;
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
if (clients) {
|
if (clients) {
|
||||||
|
|
@ -450,6 +639,7 @@
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||||
|
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
|
|
@ -471,6 +661,7 @@
|
||||||
deactive: deactive,
|
deactive: deactive,
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
|
|
@ -530,6 +721,9 @@
|
||||||
},
|
},
|
||||||
generalActions(action) {
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
|
case "import":
|
||||||
|
this.importInbound();
|
||||||
|
break;
|
||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
break;
|
break;
|
||||||
|
|
@ -547,10 +741,10 @@
|
||||||
clickAction(action, dbInbound) {
|
clickAction(action, dbInbound) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "qrcode":
|
case "qrcode":
|
||||||
this.showQrcode(dbInbound);
|
this.showQrcode(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "showInfo":
|
case "showInfo":
|
||||||
this.showInfo(dbInbound);
|
this.showInfo(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
case "edit":
|
case "edit":
|
||||||
this.openEditInbound(dbInbound.id);
|
this.openEditInbound(dbInbound.id);
|
||||||
|
|
@ -564,6 +758,9 @@
|
||||||
case "export":
|
case "export":
|
||||||
this.inboundLinks(dbInbound.id);
|
this.inboundLinks(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "clipboard":
|
||||||
|
this.copyToClipboard(dbInbound.id);
|
||||||
|
break;
|
||||||
case "resetTraffic":
|
case "resetTraffic":
|
||||||
this.resetTraffic(dbInbound.id);
|
this.resetTraffic(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
|
@ -586,6 +783,7 @@
|
||||||
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
title: '{{ i18n "pages.inbounds.cloneInbound"}} \"' + dbInbound.remark + '\"',
|
||||||
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.cloneInboundContent"}}',
|
||||||
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
okText: '{{ i18n "pages.inbounds.cloneInboundOk"}}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
const baseInbound = dbInbound.toInbound();
|
const baseInbound = dbInbound.toInbound();
|
||||||
|
|
@ -752,7 +950,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
|
|
@ -767,31 +965,26 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
delClient(dbInboundId, client) {
|
delClient(dbInboundId, client,confirmation = true) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
okText: '{{ i18n "delete"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
});
|
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||||
},
|
});
|
||||||
getClients(protocol, clientSettings) {
|
} else {
|
||||||
switch (protocol) {
|
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses;
|
|
||||||
default: return null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientId(protocol, client) {
|
getClientId(protocol, client) {
|
||||||
|
|
@ -805,7 +998,7 @@
|
||||||
newDbInbound = new DBInbound(dbInbound);
|
newDbInbound = new DBInbound(dbInbound);
|
||||||
if (dbInbound.listen.startsWith("@")){
|
if (dbInbound.listen.startsWith("@")){
|
||||||
rootInbound = this.inbounds.find((i) =>
|
rootInbound = this.inbounds.find((i) =>
|
||||||
i.tls &&
|
i.isTcp &&
|
||||||
['trojan','vless'].includes(i.protocol) &&
|
['trojan','vless'].includes(i.protocol) &&
|
||||||
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
||||||
);
|
);
|
||||||
|
|
@ -813,18 +1006,27 @@
|
||||||
newDbInbound.listen = rootInbound.listen;
|
newDbInbound.listen = rootInbound.listen;
|
||||||
newDbInbound.port = rootInbound.port;
|
newDbInbound.port = rootInbound.port;
|
||||||
newInbound = newDbInbound.toInbound();
|
newInbound = newDbInbound.toInbound();
|
||||||
newInbound.stream.security = 'tls';
|
newInbound.stream.security = rootInbound.stream.security;
|
||||||
newInbound.stream.tls = rootInbound.stream.tls;
|
newInbound.stream.tls = rootInbound.stream.tls;
|
||||||
|
newInbound.stream.externalProxy = rootInbound.stream.externalProxy;
|
||||||
newDbInbound.streamSettings = newInbound.stream.toString();
|
newDbInbound.streamSettings = newInbound.stream.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newDbInbound;
|
return newDbInbound;
|
||||||
},
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInboundId, client) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, clientIndex);
|
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInboundId, client) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
index=0;
|
||||||
|
if (dbInbound.isMultiUser()){
|
||||||
|
inbound = dbInbound.toInbound();
|
||||||
|
clients = inbound.clients;
|
||||||
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
|
}
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
infoModal.show(newDbInbound, index);
|
infoModal.show(newDbInbound, index);
|
||||||
},
|
},
|
||||||
|
|
@ -836,7 +1038,7 @@
|
||||||
this.loading()
|
this.loading()
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = inbound.clients;
|
||||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
|
|
@ -850,31 +1052,27 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getInboundClients(dbInbound) {
|
getInboundClients(dbInbound) {
|
||||||
if (dbInbound.protocol == Protocols.VLESS) {
|
return dbInbound.toInbound().clients;
|
||||||
return dbInbound.toInbound().settings.vlesses;
|
|
||||||
} else if (dbInbound.protocol == Protocols.VMESS) {
|
|
||||||
return dbInbound.toInbound().settings.vmesses;
|
|
||||||
} else if (dbInbound.protocol == Protocols.TROJAN) {
|
|
||||||
return dbInbound.toInbound().settings.trojans;
|
|
||||||
} else if (dbInbound.protocol == Protocols.SHADOWSOCKS) {
|
|
||||||
return dbInbound.toInbound().settings.shadowsockses;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
resetClientTraffic(client, dbInboundId) {
|
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||||
this.$confirm({
|
if (confirmation){
|
||||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
this.$confirm({
|
||||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||||
okText: '{{ i18n "reset"}}',
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
})
|
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
resetAllTraffic() {
|
resetAllTraffic() {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
title: '{{ i18n "pages.inbounds.resetAllTrafficTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
content: '{{ i18n "pages.inbounds.resetAllTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
onOk: () => this.submit('/panel/inbound/resetAllTraffics'),
|
||||||
|
|
@ -884,7 +1082,7 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
title: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficTitle"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficTitle"}}',
|
||||||
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
content: dbInboundId > 0 ? '{{ i18n "pages.inbounds.resetInboundClientTrafficContent"}}' : '{{ i18n "pages.inbounds.resetAllClientTrafficContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "reset"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/resetAllClientTraffics/' + dbInboundId),
|
||||||
|
|
@ -894,48 +1092,117 @@
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "reset"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
onOk: () => this.submit('/panel/inbound/delDepletedClients/' + dbInboundId),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
isExpiry(dbInbound, index) {
|
isExpiry(dbInbound, index) {
|
||||||
return dbInbound.toInbound().isExpiry(index)
|
return dbInbound.toInbound().isExpiry(index);
|
||||||
},
|
},
|
||||||
getUpStats(dbInbound, email) {
|
getUpStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.up : 0
|
return clientStats ? clientStats.up : 0;
|
||||||
},
|
},
|
||||||
getDownStats(dbInbound, email) {
|
getDownStats(dbInbound, email) {
|
||||||
if (email.length == 0) return 0
|
if (email.length == 0) return 0;
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
|
||||||
return clientStats ? clientStats.down : 0
|
|
||||||
},
|
|
||||||
statsColor(dbInbound, email) {
|
|
||||||
if(email.length == 0) return 'blue';
|
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
return clientStats ? clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getSumStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientStats ? clientStats.up + clientStats.down : 0;
|
||||||
|
},
|
||||||
|
getRemStats(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 0;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
remained = clientStats.total - (clientStats.up + clientStats.down);
|
||||||
|
return remained>0 ? remained : 0;
|
||||||
|
},
|
||||||
|
clientStatsColor(dbInbound, email) {
|
||||||
|
if (email.length == 0) return clientUsageColor();
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
return clientUsageColor(clientStats, app.trafficDiff)
|
||||||
|
},
|
||||||
|
statsProgress(dbInbound, email) {
|
||||||
|
if (email.length == 0) return 100;
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return 0;
|
||||||
|
if (clientStats.total == 0) return 100;
|
||||||
|
return 100*(clientStats.down + clientStats.up)/clientStats.total;
|
||||||
|
},
|
||||||
|
expireProgress(expTime, reset) {
|
||||||
|
now = new Date().getTime();
|
||||||
|
remainedSeconds = expTime < 0 ? -expTime/1000 : (expTime-now)/1000;
|
||||||
|
resetSeconds = reset * 86400;
|
||||||
|
if (remainedSeconds >= resetSeconds) return 0;
|
||||||
|
return 100*(1-(remainedSeconds/resetSeconds));
|
||||||
|
},
|
||||||
|
remainedDays(expTime){
|
||||||
|
if (expTime == 0) return null;
|
||||||
|
if (expTime < 0) return formatSecond(expTime/-1000);
|
||||||
|
now = new Date().getTime();
|
||||||
|
if (expTime < now) return '{{ i18n "depleted" }}';
|
||||||
|
return formatSecond((expTime-now)/1000);
|
||||||
|
},
|
||||||
|
statsExpColor(dbInbound, email){
|
||||||
|
if (email.length == 0) return '#7a316f';
|
||||||
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
|
if (!clientStats) return '#7a316f';
|
||||||
|
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
|
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||||
|
switch (true) {
|
||||||
|
case statsColor == "red" || expColor == "red":
|
||||||
|
return "#cf3c3c"; // Red
|
||||||
|
case statsColor == "orange" || expColor == "orange":
|
||||||
|
return "#f37b24"; // Orange
|
||||||
|
case statsColor == "green" || expColor == "green":
|
||||||
|
return "#008771"; // Green
|
||||||
|
default:
|
||||||
|
return "#7a316f"; // purple
|
||||||
|
}
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null;
|
||||||
return clientStats ? clientStats['enable'] : true
|
return clientStats ? clientStats['enable'] : true;
|
||||||
},
|
},
|
||||||
isRemovable(dbInbound_id) {
|
isClientOnline(email) {
|
||||||
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1
|
return this.onlineClients.includes(email);
|
||||||
|
},
|
||||||
|
isRemovable(dbInboundId) {
|
||||||
|
return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInboundId)).length > 1
|
||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
|
||||||
|
},
|
||||||
|
importInbound() {
|
||||||
|
promptModal.open({
|
||||||
|
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
||||||
|
type: 'textarea',
|
||||||
|
value: '',
|
||||||
|
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||||
|
confirm: async (dbInboundText) => {
|
||||||
|
await this.submit('/panel/inbound/import', {data: dbInboundText}, promptModal);
|
||||||
|
promptModal.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
copyText += dbInbound.genInboundLinks
|
copyText.push(dbInbound.genInboundLinks(this.remarkModel));
|
||||||
}
|
}
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText, 'All-Inbounds');
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||||
|
},
|
||||||
|
copyToClipboard(dbInboundId) {
|
||||||
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
|
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
|
||||||
},
|
},
|
||||||
async startDataRefreshLoop() {
|
async startDataRefreshLoop() {
|
||||||
while (this.isRefreshEnabled) {
|
while (this.isRefreshEnabled) {
|
||||||
|
|
@ -963,6 +1230,30 @@
|
||||||
this.spinning = false;
|
this.spinning = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
pagination(obj){
|
||||||
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
|
// Set page options based on object size
|
||||||
|
sizeOptions = []
|
||||||
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
|
sizeOptions.push(i.toString());
|
||||||
|
}
|
||||||
|
// Add option to see all in one page
|
||||||
|
sizeOptions.push(i.toString());
|
||||||
|
|
||||||
|
p = {
|
||||||
|
showSizeChanger: true,
|
||||||
|
size: 'small',
|
||||||
|
position: 'bottom',
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
pageSizeOptions: sizeOptions
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
onResize() {
|
||||||
|
this.isMobile = window.innerWidth <= 768;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchKey: debounce(function (newVal) {
|
searchKey: debounce(function (newVal) {
|
||||||
|
|
@ -970,6 +1261,8 @@
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
this.getDefaultSettings();
|
this.getDefaultSettings();
|
||||||
if (this.isRefreshEnabled) {
|
if (this.isRefreshEnabled) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
.ant-layout-content {
|
.ant-layout-content {
|
||||||
margin: 24px 16px;
|
margin: 24px 16px;
|
||||||
}
|
}
|
||||||
|
.ant-card-hoverable {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
|
|
@ -18,21 +21,20 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<a-layout id="app" v-cloak>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
|
||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout" :style="themeSwitcher.bgStyle">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
|
|
@ -40,7 +42,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
|
|
@ -53,7 +54,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
|
|
@ -62,7 +62,6 @@
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
|
|
@ -77,37 +76,41 @@
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
{{ i18n "pages.index.xrayStatus" }}:
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||||
<a-tooltip v-if="status.xray.state === State.Error">
|
<a-popover v-if="status.xray.state === State.Error"
|
||||||
<template slot="title">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<p v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
<span slot="title" style="font-size: 12pt">Error in running xray-core
|
||||||
|
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
|
</span>
|
||||||
|
<template slot="content">
|
||||||
|
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-popover>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
Xray:
|
Xray:
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
|
|
@ -116,18 +119,18 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
{{ i18n "usage"}}:
|
||||||
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
Threads [[ status.appStats.threads ]]
|
Threads [[ status.appStats.threads ]]
|
||||||
|
|
@ -135,7 +138,7 @@
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
IPv4:
|
IPv4:
|
||||||
|
|
@ -143,7 +146,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv4 ]]
|
[[ status.publicIP.ipv4 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
|
@ -152,14 +155,14 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv6 ]]
|
[[ status.publicIP.ipv6 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
TCP: [[ status.tcpCount ]]
|
TCP: [[ status.tcpCount ]]
|
||||||
|
|
@ -167,7 +170,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
|
@ -176,14 +179,14 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
|
|
@ -192,7 +195,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
|
@ -202,14 +205,14 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
|
|
@ -218,7 +221,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalSent" }}
|
{{ i18n "pages.index.totalSent" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
|
|
@ -228,7 +231,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
{{ i18n "pages.index.totalReceive" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
@ -241,21 +244,21 @@
|
||||||
|
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
|
||||||
:closable="true" @ok="() => versionModal.visible = false"
|
:closable="true" @ok="() => versionModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
footer="">
|
footer="">
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'blue' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
|
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px"
|
||||||
footer="">
|
footer="">
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
|
|
@ -263,7 +266,7 @@
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs()"
|
@change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
|
|
@ -274,7 +277,7 @@
|
||||||
<a-select v-model="logModal.level"
|
<a-select v-model="logModal.level"
|
||||||
style="width: 120px"
|
style="width: 120px"
|
||||||
@change="openLogs()"
|
@change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
<a-select-option value="info">Info</a-select-option>
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
|
@ -286,7 +289,7 @@
|
||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
<a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
|
|
@ -295,17 +298,16 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-input type="textarea" v-model="logModal.logs" disabled="true"
|
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div>
|
||||||
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" :class="themeSwitcher.darkCardClass"
|
:closable="true" :class="themeSwitcher.currentTheme"
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||||
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
|
:message="backupModal.description"
|
||||||
[[ backupModal.description ]]
|
show-icon
|
||||||
</p>
|
></a-alert>
|
||||||
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
<a-space direction="horizontal" style="text-align: center" style="margin-bottom: 10px;">
|
||||||
<a-button type="primary" @click="exportDatabase()">
|
<a-button type="primary" @click="exportDatabase()">
|
||||||
[[ backupModal.exportText ]]
|
[[ backupModal.exportText ]]
|
||||||
|
|
@ -318,6 +320,7 @@
|
||||||
|
|
||||||
</a-layout>
|
</a-layout>
|
||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
|
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
{{template "textModal"}}
|
{{template "textModal"}}
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -346,11 +349,11 @@
|
||||||
get color() {
|
get color() {
|
||||||
const percent = this.percent;
|
const percent = this.percent;
|
||||||
if (percent < 80) {
|
if (percent < 80) {
|
||||||
return '#67C23A';
|
return '#008771'; // Green
|
||||||
} else if (percent < 90) {
|
} else if (percent < 90) {
|
||||||
return '#E6A23C';
|
return "#f37b24"; // Orange
|
||||||
} else {
|
} else {
|
||||||
return '#F56C6C';
|
return "#cf3c3c"; // Red
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -427,9 +430,46 @@
|
||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs? logs.join("\n"): "No Record...";
|
this.logs = logs? this.formatLogs(logs) : "No Record...";
|
||||||
|
},
|
||||||
|
formatLogs(logs) {
|
||||||
|
let formattedLogs = '';
|
||||||
|
const levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
|
||||||
|
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
|
||||||
|
|
||||||
|
logs.forEach((log, index) => {
|
||||||
|
let [data, message] = log.split(" - ",2);
|
||||||
|
const parts = data.split(" ")
|
||||||
|
if(index>0) formattedLogs += '<br>';
|
||||||
|
|
||||||
|
if (parts.length === 3) {
|
||||||
|
const d = parts[0];
|
||||||
|
const t = parts[1];
|
||||||
|
const level = parts[2];
|
||||||
|
const levelIndex = levels.indexOf(level,levels) || 5;
|
||||||
|
|
||||||
|
//formattedLogs += `<span style="color: gray;">${index + 1}.</span>`;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[0]};">${d} ${t}</span> `;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${level}</span>`;
|
||||||
|
} else {
|
||||||
|
const levelIndex = levels.indexOf(data,levels) || 5;
|
||||||
|
formattedLogs += `<span style="color: ${levelColors[levelIndex]}">${data}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(message){
|
||||||
|
if(message.startsWith("XRAY:"))
|
||||||
|
message = "<b>XRAY: </b>" + message.substring(5);
|
||||||
|
else
|
||||||
|
message = "<b>X-UI: </b>" + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedLogs += message ? ' - ' + message : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedLogs;
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
|
@ -504,7 +544,7 @@
|
||||||
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
title: '{{ i18n "pages.index.xraySwitchVersionDialog"}}',
|
||||||
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
content: '{{ i18n "pages.index.xraySwitchVersionDialogDesc"}}' + ` ${version}?`,
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.darkCardClass,
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
|
|
@ -531,13 +571,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(){
|
async openLogs(){
|
||||||
this.loading(true);
|
logModal.loading = true;
|
||||||
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
this.loading(false);
|
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj);
|
logModal.show(msg.obj);
|
||||||
|
await PromiseUtil.sleep(500);
|
||||||
|
logModal.loading = false;
|
||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
|
|
|
||||||
1405
web/html/xui/xray.html
Normal file
127
web/html/xui/xray_outbound_modal.html
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
{{define "outModal"}}
|
||||||
|
<a-modal id="out-modal" v-model="outModal.visible" :title="outModal.title" @ok="outModal.ok"
|
||||||
|
:confirm-loading="outModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-button-props="{ props: { disabled: !outModal.isValid } }" style="overflow: hidden;"
|
||||||
|
:ok-text="outModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
{{template "form/outbound"}}
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const outModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
outbound: new Outbound(),
|
||||||
|
jsonMode: false,
|
||||||
|
link: '',
|
||||||
|
cm: null,
|
||||||
|
duplicateTag: false,
|
||||||
|
isValid: true,
|
||||||
|
activeKey: '1',
|
||||||
|
tags: [],
|
||||||
|
ok() {
|
||||||
|
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.jsonMode = false;
|
||||||
|
this.link = '';
|
||||||
|
this.activeKey = '1';
|
||||||
|
this.visible = true;
|
||||||
|
this.outbound = isEdit ? Outbound.fromJson(outbound) : new Outbound();
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.tags = tags;
|
||||||
|
this.check()
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
outModal.visible = false;
|
||||||
|
outModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
outModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
check(){
|
||||||
|
if(outModal.outbound.tag == '' || outModal.tags.includes(outModal.outbound.tag)){
|
||||||
|
this.duplicateTag = true;
|
||||||
|
this.isValid = false;
|
||||||
|
} else {
|
||||||
|
this.duplicateTag = false;
|
||||||
|
this.isValid = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleJson(jsonTab) {
|
||||||
|
textAreaObj = document.getElementById('outboundJson');
|
||||||
|
if(jsonTab){
|
||||||
|
if(this.cm != null) {
|
||||||
|
this.cm.toTextArea();
|
||||||
|
this.cm=null;
|
||||||
|
}
|
||||||
|
textAreaObj.value = JSON.stringify(this.outbound.toJson(), null, 2);
|
||||||
|
this.cm = CodeMirror.fromTextArea(textAreaObj, app.cmOptions);
|
||||||
|
this.cm.on('change',editor => {
|
||||||
|
value = editor.getValue();
|
||||||
|
if(this.isJsonString(value)){
|
||||||
|
this.outbound = Outbound.fromJson(JSON.parse(value));
|
||||||
|
this.check();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.activeKey = '2';
|
||||||
|
} else {
|
||||||
|
if(this.cm != null) {
|
||||||
|
this.cm.toTextArea();
|
||||||
|
this.cm=null;
|
||||||
|
}
|
||||||
|
this.activeKey = '1';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isJsonString(str) {
|
||||||
|
try {
|
||||||
|
JSON.parse(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#out-modal',
|
||||||
|
data: {
|
||||||
|
outModal: outModal,
|
||||||
|
get outbound() {
|
||||||
|
return outModal.outbound;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
streamNetworkChange() {
|
||||||
|
if (this.outModal.outbound.protocol == Protocols.VLESS && !outModal.outbound.canEnableTlsFlow()) {
|
||||||
|
delete this.outModal.outbound.settings.flow;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canEnableTls() {
|
||||||
|
return this.outModal.outbound.canEnableTls();
|
||||||
|
},
|
||||||
|
convertLink(){
|
||||||
|
newOutbound = Outbound.fromLink(outModal.link);
|
||||||
|
if(newOutbound){
|
||||||
|
this.outModal.outbound = newOutbound;
|
||||||
|
this.outModal.toggleJson(true);
|
||||||
|
this.outModal.check();
|
||||||
|
this.$message.success('Link imported successfully...');
|
||||||
|
outModal.link = '';
|
||||||
|
} else {
|
||||||
|
this.$message.error('Wrong Link!');
|
||||||
|
outModal.link = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
140
web/html/xui/xray_reverse_modal.html
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
{{define "reverseModal"}}
|
||||||
|
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||||
|
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||||
|
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
|
||||||
|
<a-input v-model.trim="reverseModal.reverse.tag"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'>
|
||||||
|
<a-input v-model.trim="reverseModal.reverse.domain"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="reverseModal.reverse.type=='bridge'">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||||
|
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
|
||||||
|
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model="reverseModal.rules[0].inboundTag"
|
||||||
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
|
||||||
|
<a-checkbox-group
|
||||||
|
v-model="reverseModal.rules[1].inboundTag"
|
||||||
|
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
const reverseModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
reverse: {
|
||||||
|
tag: "",
|
||||||
|
type: "",
|
||||||
|
domain: ""
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{ outboundTag: '', inboundTag: []},
|
||||||
|
{ outboundTag: '', inboundTag: []}
|
||||||
|
],
|
||||||
|
inboundTags: [],
|
||||||
|
outboundTags: [],
|
||||||
|
ok() {
|
||||||
|
reverseModal.rules[0].domain = ["full:" + reverseModal.reverse.domain];
|
||||||
|
reverseModal.rules[0].type = 'field';
|
||||||
|
reverseModal.rules[1].type = 'field';
|
||||||
|
|
||||||
|
if(reverseModal.reverse.type == 'bridge'){
|
||||||
|
reverseModal.rules[0].inboundTag = [reverseModal.reverse.tag];
|
||||||
|
reverseModal.rules[1].inboundTag = [reverseModal.reverse.tag];
|
||||||
|
} else {
|
||||||
|
reverseModal.rules[0].outboundTag = reverseModal.reverse.tag;
|
||||||
|
reverseModal.rules[1].outboundTag = reverseModal.reverse.tag;
|
||||||
|
}
|
||||||
|
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
this.reverse = {
|
||||||
|
tag: reverse.tag,
|
||||||
|
type: reverse.type,
|
||||||
|
domain: reverse.domain,
|
||||||
|
};
|
||||||
|
reverse;
|
||||||
|
rules0 = rules.filter(r => r.domain != null);
|
||||||
|
if(rules0.length == 0) rules0 = [{ outboundTag: '', domain: ["full:" + this.reverse.domain], inboundTag: []}];
|
||||||
|
rules1 = rules.filter(r => r.domain == null);
|
||||||
|
if(rules1.length == 0) rules1 = [{ outboundTag: '', inboundTag: []}];
|
||||||
|
this.rules = [];
|
||||||
|
this.rules.push({
|
||||||
|
domain: rules0[0].domain,
|
||||||
|
outboundTag: rules0[0].outboundTag,
|
||||||
|
inboundTag: rules0.map(r => r.inboundTag).flat()
|
||||||
|
});
|
||||||
|
this.rules.push({
|
||||||
|
outboundTag: rules1[0].outboundTag,
|
||||||
|
inboundTag: rules1.map(r => r.inboundTag).flat()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.reverse = {
|
||||||
|
tag: "reverse-" + app.reverseData.length,
|
||||||
|
type: "bridge",
|
||||||
|
domain: "reverse.xui"
|
||||||
|
}
|
||||||
|
this.rules = [
|
||||||
|
{ outboundTag: '', inboundTag: []},
|
||||||
|
{ outboundTag: '', inboundTag: []}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
reverseModal.visible = false;
|
||||||
|
reverseModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
reverseModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#reverse-modal',
|
||||||
|
data: {
|
||||||
|
reverseModal: reverseModal,
|
||||||
|
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
228
web/html/xui/xray_rule_modal.html
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
{{define "ruleModal"}}
|
||||||
|
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||||
|
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||||
|
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='Domain Matcher'>
|
||||||
|
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
Source IPs <a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">Source Port
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
Source Port <a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Network'>
|
||||||
|
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Protocol'>
|
||||||
|
<a-select v-model="ruleModal.rule.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Attributes'>
|
||||||
|
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span: 24}">
|
||||||
|
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||||
|
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
IP <a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.ip"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
Domain <a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.domain"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<template slot="label">
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||||
|
</template>
|
||||||
|
Port <a-icon type="question-circle"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-input v-model.trim="ruleModal.rule.port"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Inbound Tags'>
|
||||||
|
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='Outbound Tag'>
|
||||||
|
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const ruleModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
confirmLoading: false,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
rule: {
|
||||||
|
type: "field",
|
||||||
|
domainMatcher: "",
|
||||||
|
domain: "",
|
||||||
|
ip: "",
|
||||||
|
port: "",
|
||||||
|
sourcePort: "",
|
||||||
|
network: "",
|
||||||
|
source: "",
|
||||||
|
user: "",
|
||||||
|
inboundTag: [],
|
||||||
|
protocol: [],
|
||||||
|
attrs: [],
|
||||||
|
outboundTag: "",
|
||||||
|
},
|
||||||
|
inboundTags: [],
|
||||||
|
outboundTags: [],
|
||||||
|
users: [],
|
||||||
|
balancerTag: [],
|
||||||
|
ok() {
|
||||||
|
newRule = ruleModal.getResult();
|
||||||
|
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
this.rule.domainMatcher = rule.domainMatcher;
|
||||||
|
this.rule.domain = rule.domain ? rule.domain.join(',') : [];
|
||||||
|
this.rule.ip = rule.ip ? rule.ip.join(',') : [];
|
||||||
|
this.rule.port = rule.port;
|
||||||
|
this.rule.sourcePort = rule.sourcePort;
|
||||||
|
this.rule.network = rule.network;
|
||||||
|
this.rule.source = rule.source ? rule.source.join(',') : [];
|
||||||
|
this.rule.user = rule.user ? rule.user.join(',') : [];
|
||||||
|
this.rule.inboundTag = rule.inboundTag;
|
||||||
|
this.rule.protocol = rule.protocol;
|
||||||
|
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||||
|
this.rule.outboundTag = rule.outboundTag;
|
||||||
|
} else {
|
||||||
|
this.rule = {
|
||||||
|
domainMatcher: "",
|
||||||
|
domain: "",
|
||||||
|
ip: "",
|
||||||
|
port: "",
|
||||||
|
sourcePort: "",
|
||||||
|
network: "",
|
||||||
|
source: "",
|
||||||
|
user: "",
|
||||||
|
inboundTag: [],
|
||||||
|
protocol: [],
|
||||||
|
attrs: [],
|
||||||
|
outboundTag: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||||
|
this.inboundTags.push(...app.inboundTags);
|
||||||
|
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||||
|
if(app.templateSettings.reverse){
|
||||||
|
if(app.templateSettings.reverse.bridges) {
|
||||||
|
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||||
|
}
|
||||||
|
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
ruleModal.visible = false;
|
||||||
|
ruleModal.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading) {
|
||||||
|
ruleModal.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
getResult() {
|
||||||
|
value = ruleModal.rule;
|
||||||
|
rule = {};
|
||||||
|
newRule = {};
|
||||||
|
rule.type = "field";
|
||||||
|
rule.domainMatcher = value.domainMatcher;
|
||||||
|
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
|
||||||
|
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
|
||||||
|
rule.port = value.port;
|
||||||
|
rule.sourcePort = value.sourcePort;
|
||||||
|
rule.network = value.network;
|
||||||
|
rule.source = value.source.length>0 ? value.source.split(',') : [];
|
||||||
|
rule.user = value.user.length>0 ? value.user.split(',') : [];
|
||||||
|
rule.inboundTag = value.inboundTag;
|
||||||
|
rule.protocol = value.protocol;
|
||||||
|
rule.attrs = Object.fromEntries(value.attrs);
|
||||||
|
rule.outboundTag = value.outboundTag;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
|
if (
|
||||||
|
value !== null &&
|
||||||
|
value !== undefined &&
|
||||||
|
!(Array.isArray(value) && value.length === 0) &&
|
||||||
|
!(typeof value === 'object' && Object.keys(value).length === 0) &&
|
||||||
|
value !== ''
|
||||||
|
) {
|
||||||
|
newRule[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newRule;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#rule-modal',
|
||||||
|
data: {
|
||||||
|
ruleModal: ruleModal,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
|
@ -168,9 +168,13 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||||
|
|
||||||
err = tx.Save(inbound).Error
|
err = tx.Save(inbound).Error
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, client := range clients {
|
if len(inbound.ClientStats) == 0 {
|
||||||
s.AddClientStat(tx, inbound.Id, &client)
|
for _, client := range clients {
|
||||||
|
s.AddClientStat(tx, inbound.Id, &client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return inbound, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := false
|
needRestart := false
|
||||||
|
|
@ -263,7 +267,18 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||||
|
|
||||||
tag := oldInbound.Tag
|
tag := oldInbound.Tag
|
||||||
|
|
||||||
err = s.updateClientTraffics(oldInbound, inbound)
|
db := database.GetDB()
|
||||||
|
tx := db.Begin()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
} else {
|
||||||
|
tx.Commit()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = s.updateClientTraffics(tx, oldInbound, inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return inbound, false, err
|
return inbound, false, err
|
||||||
}
|
}
|
||||||
|
|
@ -304,11 +319,10 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||||
}
|
}
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
|
|
||||||
db := database.GetDB()
|
return inbound, needRestart, tx.Save(oldInbound).Error
|
||||||
return inbound, needRestart, db.Save(oldInbound).Error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inbound, newInbound *model.Inbound) error {
|
||||||
oldClients, err := s.GetClients(oldInbound)
|
oldClients, err := s.GetClients(oldInbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -318,17 +332,6 @@ func (s *InboundService) updateClientTraffics(oldInbound *model.Inbound, newInbo
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db := database.GetDB()
|
|
||||||
tx := db.Begin()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
tx.Rollback()
|
|
||||||
} else {
|
|
||||||
tx.Commit()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var emailExists bool
|
var emailExists bool
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
|
|
@ -601,7 +604,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
|
|
||||||
if len(clients[0].Email) > 0 {
|
if len(clients[0].Email) > 0 {
|
||||||
if len(oldEmail) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
err = s.UpdateClientStat(oldEmail, &clients[0])
|
err = s.UpdateClientStat(tx, oldEmail, &clients[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -676,6 +679,13 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needRestart0, count, err := s.autoRenewClients(tx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Error in renew clients:", err)
|
||||||
|
} else if count > 0 {
|
||||||
|
logger.Debugf("%v clients renewed", count)
|
||||||
|
}
|
||||||
|
|
||||||
needRestart1, count, err := s.disableInvalidClients(tx)
|
needRestart1, count, err := s.disableInvalidClients(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error in disabling invalid clients:", err)
|
logger.Warning("Error in disabling invalid clients:", err)
|
||||||
|
|
@ -689,7 +699,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
logger.Debugf("%v inbounds disabled", count)
|
logger.Debugf("%v inbounds disabled", count)
|
||||||
}
|
}
|
||||||
return nil, (needRestart1 || needRestart2)
|
return nil, (needRestart0 || needRestart1 || needRestart2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
|
||||||
|
|
@ -716,9 +726,15 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
||||||
|
|
||||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
|
// Empty onlineUsers
|
||||||
|
if p != nil {
|
||||||
|
p.SetOnlineClients(nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onlineClients []string
|
||||||
|
|
||||||
emails := make([]string, 0, len(traffics))
|
emails := make([]string, 0, len(traffics))
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
emails = append(emails, traffic.Email)
|
emails = append(emails, traffic.Email)
|
||||||
|
|
@ -744,11 +760,19 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
||||||
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
if dbClientTraffics[dbTraffic_index].Email == traffics[traffic_index].Email {
|
||||||
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
dbClientTraffics[dbTraffic_index].Up += traffics[traffic_index].Up
|
||||||
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
dbClientTraffics[dbTraffic_index].Down += traffics[traffic_index].Down
|
||||||
|
|
||||||
|
// Add user in onlineUsers array on traffic
|
||||||
|
if traffics[traffic_index].Up+traffics[traffic_index].Down > 0 {
|
||||||
|
onlineClients = append(onlineClients, traffics[traffic_index].Email)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set onlineUsers
|
||||||
|
p.SetOnlineClients(onlineClients)
|
||||||
|
|
||||||
err = tx.Save(dbClientTraffics).Error
|
err = tx.Save(dbClientTraffics).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("AddClientTraffic update data ", err)
|
logger.Warning("AddClientTraffic update data ", err)
|
||||||
|
|
@ -809,6 +833,102 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||||
return dbClientTraffics, nil
|
return dbClientTraffics, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||||
|
// check for time expired
|
||||||
|
var traffics []*xray.ClientTraffic
|
||||||
|
now := time.Now().Unix() * 1000
|
||||||
|
var err, err1 error
|
||||||
|
|
||||||
|
err = tx.Model(xray.ClientTraffic{}).Where("reset > 0 and expiry_time > 0 and expiry_time <= ?", now).Find(&traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
// return if there is no client to renew
|
||||||
|
if len(traffics) == 0 {
|
||||||
|
return false, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound_ids []int
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
needRestart := false
|
||||||
|
var clientsToAdd []struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, traffic := range traffics {
|
||||||
|
inbound_ids = append(inbound_ids, traffic.InboundId)
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Where("id IN ?", inbound_ids).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
for inbound_index := range inbounds {
|
||||||
|
settings := map[string]interface{}{}
|
||||||
|
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||||
|
clients := settings["clients"].([]interface{})
|
||||||
|
for client_index := range clients {
|
||||||
|
c := clients[client_index].(map[string]interface{})
|
||||||
|
for traffic_index, traffic := range traffics {
|
||||||
|
if traffic.Email == c["email"].(string) {
|
||||||
|
newExpiryTime := traffic.ExpiryTime
|
||||||
|
for newExpiryTime < now {
|
||||||
|
newExpiryTime += (int64(traffic.Reset) * 86400000)
|
||||||
|
}
|
||||||
|
c["expiryTime"] = newExpiryTime
|
||||||
|
traffics[traffic_index].ExpiryTime = newExpiryTime
|
||||||
|
traffics[traffic_index].Down = 0
|
||||||
|
traffics[traffic_index].Up = 0
|
||||||
|
if !traffic.Enable {
|
||||||
|
traffics[traffic_index].Enable = true
|
||||||
|
clientsToAdd = append(clientsToAdd,
|
||||||
|
struct {
|
||||||
|
protocol string
|
||||||
|
tag string
|
||||||
|
client map[string]interface{}
|
||||||
|
}{
|
||||||
|
protocol: string(inbounds[inbound_index].Protocol),
|
||||||
|
tag: inbounds[inbound_index].Tag,
|
||||||
|
client: c,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
clients[client_index] = interface{}(c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings["clients"] = clients
|
||||||
|
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
inbounds[inbound_index].Settings = string(newSettings)
|
||||||
|
}
|
||||||
|
err = tx.Save(inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
err = tx.Save(traffics).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, err
|
||||||
|
}
|
||||||
|
if p != nil {
|
||||||
|
err1 = s.xrayApi.Init(p.GetAPIPort())
|
||||||
|
if err1 != nil {
|
||||||
|
return true, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
for _, clientToAdd := range clientsToAdd {
|
||||||
|
err1 = s.xrayApi.AddUser(clientToAdd.protocol, clientToAdd.tag, clientToAdd.client)
|
||||||
|
if err1 != nil {
|
||||||
|
needRestart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.xrayApi.Close()
|
||||||
|
}
|
||||||
|
return needRestart, int64(len(traffics)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error) {
|
||||||
now := time.Now().Unix() * 1000
|
now := time.Now().Unix() * 1000
|
||||||
needRestart := false
|
needRestart := false
|
||||||
|
|
@ -881,6 +1001,17 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
|
||||||
return needRestart, count, err
|
return needRestart, count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetInboundTags() (string, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inboundTags []string
|
||||||
|
err := db.Model(model.Inbound{}).Select("tag").Find(&inboundTags).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tags, _ := json.Marshal(inboundTags)
|
||||||
|
return string(tags), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
func (s *InboundService) MigrationRemoveOrphanedTraffics() {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
db.Exec(`
|
db.Exec(`
|
||||||
|
|
@ -902,6 +1033,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = true
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
|
clientTraffic.Reset = client.Reset
|
||||||
result := tx.Create(&clientTraffic)
|
result := tx.Create(&clientTraffic)
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -910,16 +1042,15 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
|
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||||
db := database.GetDB()
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
|
|
||||||
result := db.Model(xray.ClientTraffic{}).
|
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime})
|
"expiry_time": client.ExpiryTime,
|
||||||
|
"reset": client.Reset})
|
||||||
err := result.Error
|
err := result.Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -1415,7 +1546,7 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
whereText := "inbound_id "
|
whereText := "reset = 0 and inbound_id "
|
||||||
if id < 0 {
|
if id < 0 {
|
||||||
whereText += "> ?"
|
whereText += "> ?"
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1669,9 +1800,53 @@ func (s *InboundService) MigrationRequirements() {
|
||||||
|
|
||||||
// Remove orphaned traffics
|
// Remove orphaned traffics
|
||||||
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
|
||||||
|
|
||||||
|
// Migrate old MultiDomain to External Proxy
|
||||||
|
var externalProxy []struct {
|
||||||
|
Id int
|
||||||
|
Port int
|
||||||
|
StreamSettings []byte
|
||||||
|
}
|
||||||
|
err = tx.Raw(`select id, port, stream_settings
|
||||||
|
from inbounds
|
||||||
|
WHERE protocol in ('vmess','vless','trojan')
|
||||||
|
AND json_extract(stream_settings, '$.security') = 'tls'
|
||||||
|
AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`).Scan(&externalProxy).Error
|
||||||
|
if err != nil || len(externalProxy) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range externalProxy {
|
||||||
|
var reverses interface{}
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal(ep.StreamSettings, &stream)
|
||||||
|
if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok {
|
||||||
|
if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok {
|
||||||
|
if domains, ok := settings["domains"].([]interface{}); ok {
|
||||||
|
for _, domain := range domains {
|
||||||
|
if domainMap, ok := domain.(map[string]interface{}); ok {
|
||||||
|
domainMap["forceTls"] = "same"
|
||||||
|
domainMap["port"] = ep.Port
|
||||||
|
domainMap["dest"] = domainMap["domain"].(string)
|
||||||
|
delete(domainMap, "domain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reverses = settings["domains"]
|
||||||
|
delete(settings, "domains")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream["externalProxy"] = reverses
|
||||||
|
newStream, _ := json.MarshalIndent(stream, " ", " ")
|
||||||
|
tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) MigrateDB() {
|
func (s *InboundService) MigrateDB() {
|
||||||
s.MigrationRequirements()
|
s.MigrationRequirements()
|
||||||
s.MigrationRemoveOrphanedTraffics()
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetOnlineClinets() []string {
|
||||||
|
return p.GetOnlineClients()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
|
|
||||||
status.AppStats.Mem = rtm.Sys
|
status.AppStats.Mem = rtm.Sys
|
||||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||||
if p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
status.AppStats.Uptime = p.GetUptime()
|
status.AppStats.Uptime = p.GetUptime()
|
||||||
} else {
|
} else {
|
||||||
status.AppStats.Uptime = 0
|
status.AppStats.Uptime = 0
|
||||||
|
|
@ -380,14 +380,6 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = copyZipFile("geosite.dat", xray.GetGeositePath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = copyZipFile("geoip.dat", xray.GetGeoipPath())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
|
@ -435,6 +427,11 @@ func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetDb() ([]byte, error) {
|
func (s *ServerService) GetDb() ([]byte, error) {
|
||||||
|
// Update by manually trigger a checkpoint operation
|
||||||
|
err := database.Checkpoint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Open the file for reading
|
// Open the file for reading
|
||||||
file, err := os.Open(config.GetDBPath())
|
file, err := os.Open(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ var defaultValueMap = map[string]string{
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
"sessionMaxAge": "0",
|
"sessionMaxAge": "0",
|
||||||
|
"pageSize": "0",
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
|
"remarkModel": "-ieo",
|
||||||
"timeLocation": "Asia/Tehran",
|
"timeLocation": "Asia/Tehran",
|
||||||
"tgBotEnable": "false",
|
"tgBotEnable": "false",
|
||||||
"tgBotToken": "",
|
"tgBotToken": "",
|
||||||
|
|
@ -53,6 +55,7 @@ var defaultValueMap = map[string]string{
|
||||||
"subUpdates": "12",
|
"subUpdates": "12",
|
||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
"subShowInfo": "true",
|
"subShowInfo": "true",
|
||||||
|
"subURI": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
|
|
@ -70,7 +73,7 @@ func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
|
||||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
settings := make([]*model.Setting, 0)
|
settings := make([]*model.Setting, 0)
|
||||||
err := db.Model(model.Setting{}).Find(&settings).Error
|
err := db.Model(model.Setting{}).Not("key = ?", "xrayTemplateConfig").Find(&settings).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -309,6 +312,10 @@ func (s *SettingService) GetSessionMaxAge() (int, error) {
|
||||||
return s.getInt("sessionMaxAge")
|
return s.getInt("sessionMaxAge")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetRemarkModel() (string, error) {
|
||||||
|
return s.getString("remarkModel")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSecretStatus() (bool, error) {
|
func (s *SettingService) GetSecretStatus() (bool, error) {
|
||||||
return s.getBool("secretEnable")
|
return s.getBool("secretEnable")
|
||||||
}
|
}
|
||||||
|
|
@ -406,6 +413,14 @@ func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||||
return s.getBool("subShowInfo")
|
return s.getBool("subShowInfo")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubURI() (string, error) {
|
||||||
|
return s.getString("subURI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetPageSize() (int, error) {
|
||||||
|
return s.getInt("pageSize")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
if err := allSetting.CheckValid(); err != nil {
|
if err := allSetting.CheckValid(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -426,3 +441,71 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
}
|
}
|
||||||
return common.Combine(errs...)
|
return common.Combine(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||||
|
var jsonData interface{}
|
||||||
|
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return jsonData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
|
type settingFunc func() (interface{}, error)
|
||||||
|
settings := map[string]settingFunc{
|
||||||
|
"expireDiff": func() (interface{}, error) { return s.GetExpireDiff() },
|
||||||
|
"trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() },
|
||||||
|
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
|
||||||
|
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
|
||||||
|
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||||
|
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||||
|
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||||
|
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||||
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
|
||||||
|
for key, fn := range settings {
|
||||||
|
value, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
||||||
|
subURI := ""
|
||||||
|
subPort, _ := s.GetSubPort()
|
||||||
|
subPath, _ := s.GetSubPath()
|
||||||
|
subDomain, _ := s.GetSubDomain()
|
||||||
|
subKeyFile, _ := s.GetSubKeyFile()
|
||||||
|
subCertFile, _ := s.GetSubCertFile()
|
||||||
|
subTLS := false
|
||||||
|
if subKeyFile != "" && subCertFile != "" {
|
||||||
|
subTLS = true
|
||||||
|
}
|
||||||
|
if subDomain == "" {
|
||||||
|
subDomain = strings.Split(host, ":")[0]
|
||||||
|
}
|
||||||
|
if subTLS {
|
||||||
|
subURI = "https://"
|
||||||
|
} else {
|
||||||
|
subURI = "http://"
|
||||||
|
}
|
||||||
|
if (subPort == 443 && subTLS) || (subPort == 80 && !subTLS) {
|
||||||
|
subURI += subDomain
|
||||||
|
} else {
|
||||||
|
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||||
|
}
|
||||||
|
if subPath[0] == byte('/') {
|
||||||
|
subURI += subPath
|
||||||
|
} else {
|
||||||
|
subURI += "/" + subPath
|
||||||
|
}
|
||||||
|
result["subURI"] = subURI
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
||||||