@@ -1,57 +0,0 @@
|
|||||||
name: Sing-box Docker Image CI
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get latest release
|
|
||||||
id: get_release
|
|
||||||
run: |
|
|
||||||
latest_release=$(curl -Ls "https://api.github.com/repos/sagernet/sing-box/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
|
||||||
echo "latest_release: $latest_release"
|
|
||||||
echo "latest_release=$latest_release" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
alireza7/s-ui-singbox
|
|
||||||
ghcr.io/alireza0/s-ui-singbox
|
|
||||||
tags: |
|
|
||||||
type=sha
|
|
||||||
type=pep440,pattern=${{ steps.get_release.outputs.latest_release }}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: core/
|
|
||||||
push: true
|
|
||||||
build-args: SINGBOX_VER=${{ steps.get_release.outputs.latest_release }}
|
|
||||||
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/386
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
mv frontend/dist backend/web/html
|
mv frontend/dist backend/web/html
|
||||||
|
|
||||||
- name: Build s-ui & singbox
|
- name: Build s-ui
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
@@ -88,27 +88,15 @@ jobs:
|
|||||||
export CC=s390x-linux-gnu-gcc
|
export CC=s390x-linux-gnu-gcc
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#### Build Sing-Box
|
|
||||||
export VERSION=v1.10.1
|
|
||||||
git clone -b $VERSION https://github.com/SagerNet/sing-box
|
|
||||||
cd sing-box
|
|
||||||
go build -tags with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor \
|
|
||||||
-v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${VERSION}' -s -w -buildid=" \
|
|
||||||
-o sing-box ./cmd/sing-box
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
### Build s-ui
|
### Build s-ui
|
||||||
cd backend
|
cd backend
|
||||||
go build -o ../sui main.go
|
go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o ../sui main.go
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
mkdir s-ui
|
mkdir s-ui
|
||||||
cp sui s-ui/
|
cp sui s-ui/
|
||||||
cp s-ui.service s-ui/
|
cp s-ui.service s-ui/
|
||||||
cp sing-box.service s-ui/
|
cp s-ui.sh s-ui/
|
||||||
mkdir s-ui/bin
|
|
||||||
cp sing-box/sing-box s-ui/bin/
|
|
||||||
cp core/runSingbox.sh s-ui/bin/
|
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ ENV GOARCH=$TARGETARCH
|
|||||||
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
|
RUN apk update && apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY backend/ ./
|
COPY backend/ ./
|
||||||
COPY --from=front-builder /app/dist/ /app/web/html/
|
COPY --from=front-builder /app/dist/ /app/web/html/
|
||||||
RUN go build -ldflags="-w -s" -o sui main.go
|
RUN go build -ldflags="-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go
|
||||||
|
|
||||||
FROM --platform=$TARGETPLATFORM alpine
|
FROM --platform=$TARGETPLATFORM alpine
|
||||||
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
LABEL org.opencontainers.image.authors="alireza7@gmail.com"
|
||||||
|
|||||||
+102
-19
@@ -1,10 +1,11 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
"s-ui/singbox"
|
|
||||||
"s-ui/util"
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,11 +18,12 @@ type APIHandler struct {
|
|||||||
service.ConfigService
|
service.ConfigService
|
||||||
service.ClientService
|
service.ClientService
|
||||||
service.TlsService
|
service.TlsService
|
||||||
service.InDataService
|
service.InboundService
|
||||||
|
service.OutboundService
|
||||||
|
service.EndpointService
|
||||||
service.PanelService
|
service.PanelService
|
||||||
service.StatsService
|
service.StatsService
|
||||||
service.ServerService
|
service.ServerService
|
||||||
singbox.Controller
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIHandler(g *gin.RouterGroup) {
|
func NewAPIHandler(g *gin.RouterGroup) {
|
||||||
@@ -45,6 +47,8 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
action := c.Param("postAction")
|
action := c.Param("postAction")
|
||||||
remoteIP := getRemoteIp(c)
|
remoteIP := getRemoteIp(c)
|
||||||
|
loginUser := GetLoginUser(c)
|
||||||
|
hostname := getHostname(c)
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case "login":
|
case "login":
|
||||||
@@ -81,25 +85,31 @@ func (a *APIHandler) postHandler(c *gin.Context) {
|
|||||||
jsonMsg(c, "", err)
|
jsonMsg(c, "", err)
|
||||||
}
|
}
|
||||||
case "save":
|
case "save":
|
||||||
loginUser := GetLoginUser(c)
|
obj := c.Request.FormValue("object")
|
||||||
data := map[string]string{}
|
act := c.Request.FormValue("action")
|
||||||
err = c.ShouldBind(&data)
|
data := c.Request.FormValue("data")
|
||||||
if err == nil {
|
objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), loginUser, hostname)
|
||||||
err = a.ConfigService.SaveChanges(data, loginUser)
|
if err != nil {
|
||||||
}
|
|
||||||
jsonMsg(c, "save", err)
|
jsonMsg(c, "save", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = a.loadPartialData(c, objs)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, obj, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
case "restartApp":
|
case "restartApp":
|
||||||
err = a.PanelService.RestartPanel(3)
|
err = a.PanelService.RestartPanel(3)
|
||||||
jsonMsg(c, "restartApp", err)
|
jsonMsg(c, "restartApp", err)
|
||||||
case "restartSb":
|
case "restartSb":
|
||||||
err = a.Controller.Restart()
|
err = a.ConfigService.RestartCore()
|
||||||
jsonMsg(c, "restartSb", err)
|
jsonMsg(c, "restartSb", err)
|
||||||
case "linkConvert":
|
case "linkConvert":
|
||||||
link := c.Request.FormValue("link")
|
link := c.Request.FormValue("link")
|
||||||
result, _, err := util.GetOutbound(link, 0)
|
result, _, err := util.GetOutbound(link, 0)
|
||||||
jsonObj(c, result, err)
|
jsonObj(c, result, err)
|
||||||
default:
|
default:
|
||||||
jsonMsg(c, "API call", nil)
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +131,12 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, data, nil)
|
jsonObj(c, data, nil)
|
||||||
|
case "inbounds", "outbounds", "endpoints", "tls", "clients", "config":
|
||||||
|
err := a.loadPartialData(c, []string{action})
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, action, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
case "users":
|
case "users":
|
||||||
users, err := a.UserService.GetUsers()
|
users, err := a.UserService.GetUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,10 +172,9 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
onlines, err := a.StatsService.GetOnlines()
|
onlines, err := a.StatsService.GetOnlines()
|
||||||
jsonObj(c, onlines, err)
|
jsonObj(c, onlines, err)
|
||||||
case "logs":
|
case "logs":
|
||||||
service := c.Query("s")
|
|
||||||
count := c.Query("c")
|
count := c.Query("c")
|
||||||
level := c.Query("l")
|
level := c.Query("l")
|
||||||
logs := a.ServerService.GetLogs(service, count, level)
|
logs := a.ServerService.GetLogs(count, level)
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
case "changes":
|
case "changes":
|
||||||
actor := c.Query("a")
|
actor := c.Query("a")
|
||||||
@@ -173,7 +188,7 @@ func (a *APIHandler) getHandler(c *gin.Context) {
|
|||||||
keypair := a.ServerService.GenKeypair(kType, options)
|
keypair := a.ServerService.GenKeypair(kType, options)
|
||||||
jsonObj(c, keypair, nil)
|
jsonObj(c, keypair, nil)
|
||||||
default:
|
default:
|
||||||
jsonMsg(c, "API call", nil)
|
jsonMsg(c, "failed", common.NewError("unknown action: ", action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +203,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
|
|
||||||
sysInfo := a.ServerService.GetSingboxInfo()
|
sysInfo := a.ServerService.GetSingboxInfo()
|
||||||
if sysInfo["running"] == false {
|
if sysInfo["running"] == false {
|
||||||
logs := a.ServerService.GetLogs("sing-box", "1", "debug")
|
logs := a.ServerService.GetLogs("1", "debug")
|
||||||
if len(logs) > 0 {
|
if len(logs) > 0 {
|
||||||
data["lastLog"] = logs[0]
|
data["lastLog"] = logs[0]
|
||||||
}
|
}
|
||||||
@@ -198,7 +213,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if isUpdated {
|
if isUpdated {
|
||||||
config, err := a.ConfigService.GetConfig()
|
config, err := a.SettingService.GetConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -210,7 +225,15 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
inData, err := a.InDataService.GetAll()
|
inbounds, err := a.InboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
outbounds, err := a.OutboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
endpoints, err := a.EndpointService.GetAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -218,10 +241,12 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
data["config"] = *config
|
data["config"] = json.RawMessage(config)
|
||||||
data["clients"] = clients
|
data["clients"] = clients
|
||||||
data["tls"] = tlsConfigs
|
data["tls"] = tlsConfigs
|
||||||
data["inData"] = inData
|
data["inbounds"] = inbounds
|
||||||
|
data["outbounds"] = outbounds
|
||||||
|
data["endpoints"] = endpoints
|
||||||
data["subURI"] = subURI
|
data["subURI"] = subURI
|
||||||
data["onlines"] = onlines
|
data["onlines"] = onlines
|
||||||
} else {
|
} else {
|
||||||
@@ -230,3 +255,61 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) {
|
|||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) loadPartialData(c *gin.Context, objs []string) error {
|
||||||
|
data := make(map[string]interface{}, 0)
|
||||||
|
|
||||||
|
for _, obj := range objs {
|
||||||
|
switch obj {
|
||||||
|
case "inbounds":
|
||||||
|
id := c.Query("id")
|
||||||
|
inbounds, err := a.InboundService.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = inbounds
|
||||||
|
case "outbounds":
|
||||||
|
outbounds, err := a.OutboundService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = outbounds
|
||||||
|
case "endpoints":
|
||||||
|
endpoints, err := a.EndpointService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = endpoints
|
||||||
|
case "tls":
|
||||||
|
tlsConfigs, err := a.TlsService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = tlsConfigs
|
||||||
|
case "clients":
|
||||||
|
clients, err := a.ClientService.GetAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = clients
|
||||||
|
case "config":
|
||||||
|
config, err := a.SettingService.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data[obj] = json.RawMessage(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObj(c, data, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APIHandler) postActions(c *gin.Context) (string, json.RawMessage, error) {
|
||||||
|
var data map[string]json.RawMessage
|
||||||
|
err := c.ShouldBind(&data)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return string(data["action"]), data["data"], nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ func getRemoteIp(c *gin.Context) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHostname(c *gin.Context) string {
|
||||||
|
host := c.Request.Host
|
||||||
|
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||||
|
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
func jsonMsg(c *gin.Context, msg string, err error) {
|
func jsonMsg(c *gin.Context, msg string, err error) {
|
||||||
jsonMsgObj(c, msg, nil, err)
|
jsonMsgObj(c, msg, nil, err)
|
||||||
}
|
}
|
||||||
@@ -46,7 +54,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.Success = false
|
m.Success = false
|
||||||
m.Msg = msg + err.Error()
|
m.Msg = msg + ": " + err.Error()
|
||||||
logger.Warning("failed :", err)
|
logger.Warning("failed :", err)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
|
|||||||
+24
-5
@@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
|
"s-ui/core"
|
||||||
"s-ui/cronjob"
|
"s-ui/cronjob"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
@@ -15,9 +16,12 @@ import (
|
|||||||
|
|
||||||
type APP struct {
|
type APP struct {
|
||||||
service.SettingService
|
service.SettingService
|
||||||
|
configService *service.ConfigService
|
||||||
webServer *web.Server
|
webServer *web.Server
|
||||||
subServer *sub.Server
|
subServer *sub.Server
|
||||||
cronJob *cronjob.CronJob
|
cronJob *cronjob.CronJob
|
||||||
|
logger *logging.Logger
|
||||||
|
core *core.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApp() *APP {
|
func NewApp() *APP {
|
||||||
@@ -34,15 +38,17 @@ func (a *APP) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init Setting
|
||||||
|
a.SettingService.GetAllSetting()
|
||||||
|
|
||||||
|
a.core = core.NewCore()
|
||||||
|
|
||||||
a.cronJob = cronjob.NewCronJob()
|
a.cronJob = cronjob.NewCronJob()
|
||||||
a.webServer = web.NewServer()
|
a.webServer = web.NewServer()
|
||||||
a.subServer = sub.NewServer()
|
a.subServer = sub.NewServer()
|
||||||
|
|
||||||
configService := service.NewConfigService()
|
a.configService = service.NewConfigService(a.core)
|
||||||
err = configService.InitConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +78,11 @@ func (a *APP) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.configService.StartCore("")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +96,10 @@ func (a *APP) Stop() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop Web Server err:", err)
|
logger.Warning("stop Web Server err:", err)
|
||||||
}
|
}
|
||||||
|
err = a.configService.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop Core err:", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APP) initLog() {
|
func (a *APP) initLog() {
|
||||||
@@ -106,3 +121,7 @@ func (a *APP) RestartApp() {
|
|||||||
a.Stop()
|
a.Stop()
|
||||||
a.Start()
|
a.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APP) GetCore() *core.Core {
|
||||||
|
return a.core
|
||||||
|
}
|
||||||
|
|||||||
+6
-1
@@ -4,6 +4,7 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"s-ui/cmd/migration"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ func ParseCmd() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" admin set/reset/show first admin credentials")
|
fmt.Println(" admin set/reset/show first admin credentials")
|
||||||
|
fmt.Println(" uri Show panel URI")
|
||||||
fmt.Println(" migrate migrate form older version")
|
fmt.Println(" migrate migrate form older version")
|
||||||
fmt.Println(" setting set/reset/show settings")
|
fmt.Println(" setting set/reset/show settings")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -71,8 +73,11 @@ func ParseCmd() {
|
|||||||
showAdmin()
|
showAdmin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "uri":
|
||||||
|
getPanelURI()
|
||||||
|
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
migration.MigrateDb()
|
||||||
|
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
|
|||||||
@@ -1,50 +1,14 @@
|
|||||||
package cmd
|
package migration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func migrateDb() {
|
|
||||||
// void running on first install
|
|
||||||
path := config.GetDBPath()
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(path))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
tx := db.Begin()
|
|
||||||
defer func() {
|
|
||||||
if err == nil {
|
|
||||||
tx.Commit()
|
|
||||||
} else {
|
|
||||||
tx.Rollback()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fmt.Println("Start migrating database...")
|
|
||||||
err = migrateClientSchema(tx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = changesObj(tx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println("Migration done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateClientSchema(db *gorm.DB) error {
|
func migrateClientSchema(db *gorm.DB) error {
|
||||||
rows, err := db.Raw("PRAGMA table_info(clients)").Rows()
|
rows, err := db.Raw("PRAGMA table_info(clients)").Rows()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -95,10 +59,21 @@ func migrateClientSchema(db *gorm.DB) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.AutoMigrate(model.Client{})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func changesObj(db *gorm.DB) error {
|
func changesObj(db *gorm.DB) error {
|
||||||
return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error
|
return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func to1_1(db *gorm.DB) error {
|
||||||
|
err := migrateClientSchema(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = changesObj(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"s-ui/database/model"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InboundData struct {
|
||||||
|
Id uint
|
||||||
|
Tag string
|
||||||
|
Addrs json.RawMessage
|
||||||
|
OutJson json.RawMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveJsonToDb(db *gorm.DB) error {
|
||||||
|
binFolderPath := os.Getenv("SUI_BIN_FOLDER")
|
||||||
|
if binFolderPath == "" {
|
||||||
|
binFolderPath = "bin"
|
||||||
|
}
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configPath := dir + "/" + binFolderPath + "/config.json"
|
||||||
|
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var oldConfig map[string]interface{}
|
||||||
|
err = json.Unmarshal(data, &oldConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldInbounds := oldConfig["inbounds"].([]interface{})
|
||||||
|
db.Migrator().DropTable(&model.Inbound{})
|
||||||
|
db.AutoMigrate(&model.Inbound{})
|
||||||
|
for _, inbound := range oldInbounds {
|
||||||
|
inbObj, _ := inbound.(map[string]interface{})
|
||||||
|
tag, _ := inbObj["tag"].(string)
|
||||||
|
if tlsObj, ok := inbObj["tls"]; ok {
|
||||||
|
var tls_id uint
|
||||||
|
err = db.Raw("SELECT id FROM tls WHERE inbounds like ?", `%"`+tag+`"%`).Find(&tls_id).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind or Create tls_id
|
||||||
|
if tls_id > 0 {
|
||||||
|
inbObj["tls_id"] = tls_id
|
||||||
|
} else {
|
||||||
|
tls_server, _ := json.MarshalIndent(tlsObj, "", " ")
|
||||||
|
if len(tls_server) > 5 {
|
||||||
|
tlsObject := tlsObj.(map[string]interface{})
|
||||||
|
tlsClientObj := map[string]interface{}{}
|
||||||
|
if enabled, ok := tlsObject["enabled"]; ok {
|
||||||
|
tlsClientObj["enabled"] = enabled
|
||||||
|
}
|
||||||
|
if alpn, ok := tlsObject["alpn"]; ok {
|
||||||
|
tlsClientObj["alpn"] = alpn
|
||||||
|
}
|
||||||
|
if sni, ok := tlsObject["server_name"]; ok {
|
||||||
|
tlsClientObj["server_name"] = sni
|
||||||
|
}
|
||||||
|
tls_client, _ := json.MarshalIndent(tlsClientObj, "", " ")
|
||||||
|
newTls := &model.Tls{
|
||||||
|
Name: tag,
|
||||||
|
Server: tls_server,
|
||||||
|
Client: tls_client,
|
||||||
|
}
|
||||||
|
err = db.Create(newTls).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inbObj["tls_id"] = newTls.Id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbData InboundData
|
||||||
|
db.Raw("select id,addrs,out_json from inbound_data where tag = ?", tag).Find(&inbData)
|
||||||
|
if inbData.Id > 0 {
|
||||||
|
inbObj["out_json"] = inbData.OutJson
|
||||||
|
var addrs []map[string]interface{}
|
||||||
|
json.Unmarshal(inbData.Addrs, &addrs)
|
||||||
|
for index, addr := range addrs {
|
||||||
|
if tlsEnable, ok := addr["tls"].(bool); ok {
|
||||||
|
newTls := map[string]interface{}{
|
||||||
|
"enabled": tlsEnable,
|
||||||
|
}
|
||||||
|
if insecure, ok := addr["insecure"].(bool); ok {
|
||||||
|
newTls["insecure"] = insecure
|
||||||
|
delete(addrs[index], "insecure")
|
||||||
|
}
|
||||||
|
if sni, ok := addr["server_name"].(string); ok {
|
||||||
|
newTls["server_name"] = sni
|
||||||
|
delete(addrs[index], "server_name")
|
||||||
|
}
|
||||||
|
addrs[index]["tls"] = newTls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inbObj["addrs"] = addrs
|
||||||
|
} else {
|
||||||
|
inbObj["out_json"] = json.RawMessage("{}")
|
||||||
|
inbObj["addrs"] = json.RawMessage("[]")
|
||||||
|
}
|
||||||
|
// Delete deprecated fields
|
||||||
|
delete(inbObj, "sniff")
|
||||||
|
delete(inbObj, "sniff_override_destination")
|
||||||
|
delete(inbObj, "sniff_timeout")
|
||||||
|
delete(inbObj, "domain_strategy")
|
||||||
|
inbJson, _ := json.Marshal(inbObj)
|
||||||
|
|
||||||
|
var newInbound model.Inbound
|
||||||
|
err = newInbound.UnmarshalJSON(inbJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.Create(&newInbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(oldConfig, "inbounds")
|
||||||
|
|
||||||
|
blockOutboundTags := []string{}
|
||||||
|
dnsOutboundTags := []string{}
|
||||||
|
|
||||||
|
oldOutbounds := oldConfig["outbounds"].([]interface{})
|
||||||
|
db.Migrator().DropTable(&model.Outbound{}, &model.Endpoint{})
|
||||||
|
db.AutoMigrate(&model.Outbound{}, &model.Endpoint{})
|
||||||
|
for _, outbound := range oldOutbounds {
|
||||||
|
outType, _ := outbound.(map[string]interface{})["type"].(string)
|
||||||
|
outboundRaw, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
|
if outType == "wireguard" { // Check if it is Entrypoint
|
||||||
|
var newEntrypoint model.Endpoint
|
||||||
|
err = newEntrypoint.UnmarshalJSON(outboundRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = db.Create(&newEntrypoint).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else { // It is Outbound
|
||||||
|
var newOutbound model.Outbound
|
||||||
|
err = newOutbound.UnmarshalJSON(outboundRaw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Delete deprecated fields
|
||||||
|
if newOutbound.Type == "direct" {
|
||||||
|
var options map[string]interface{}
|
||||||
|
json.Unmarshal(newOutbound.Options, &options)
|
||||||
|
delete(options, "override_address")
|
||||||
|
delete(options, "override_port")
|
||||||
|
newOutbound.Options, _ = json.Marshal(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch newOutbound.Type {
|
||||||
|
case "dns":
|
||||||
|
dnsOutboundTags = append(dnsOutboundTags, newOutbound.Tag)
|
||||||
|
case "block":
|
||||||
|
blockOutboundTags = append(blockOutboundTags, newOutbound.Tag)
|
||||||
|
default:
|
||||||
|
err = db.Create(&newOutbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(oldConfig, "outbounds")
|
||||||
|
|
||||||
|
// Check routing rules
|
||||||
|
if routingRules, ok := oldConfig["route"].(map[string]interface{}); ok {
|
||||||
|
if rules, hasRules := routingRules["rules"].([]interface{}); hasRules {
|
||||||
|
hasDns := false
|
||||||
|
for index, rule := range rules {
|
||||||
|
ruleObj, _ := rule.(map[string]interface{})
|
||||||
|
isBlock := false
|
||||||
|
isDns := false
|
||||||
|
outboundTag, _ := ruleObj["outbound"].(string)
|
||||||
|
for _, tag := range blockOutboundTags {
|
||||||
|
if tag == outboundTag {
|
||||||
|
isBlock = true
|
||||||
|
delete(ruleObj, "outbound")
|
||||||
|
ruleObj["action"] = "reject"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, tag := range dnsOutboundTags {
|
||||||
|
if tag == outboundTag {
|
||||||
|
isDns = true
|
||||||
|
hasDns = true
|
||||||
|
delete(ruleObj, "outbound")
|
||||||
|
ruleObj["action"] = "hijack-dns"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isBlock && !isDns {
|
||||||
|
ruleObj["action"] = "route"
|
||||||
|
}
|
||||||
|
rules[index] = ruleObj
|
||||||
|
}
|
||||||
|
if hasDns {
|
||||||
|
rules = append(rules, map[string]interface{}{"action": "sniff"})
|
||||||
|
}
|
||||||
|
routingRules["rules"] = rules
|
||||||
|
}
|
||||||
|
oldConfig["route"] = routingRules
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove v2rayapi and clashapi from experimental config
|
||||||
|
experimental := oldConfig["experimental"].(map[string]interface{})
|
||||||
|
delete(experimental, "v2ray_api")
|
||||||
|
delete(experimental, "clash_api")
|
||||||
|
oldConfig["experimental"] = experimental
|
||||||
|
|
||||||
|
// Save the other configs
|
||||||
|
var otherConfigs json.RawMessage
|
||||||
|
otherConfigs, err = json.MarshalIndent(oldConfig, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Save(&model.Setting{
|
||||||
|
Key: "config",
|
||||||
|
Value: string(otherConfigs),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateTls(db *gorm.DB) error {
|
||||||
|
if !db.Migrator().HasColumn(&model.Tls{}, "inbounds") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Migrator().DropColumn(&model.Tls{}, "inbounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropInboundData(db *gorm.DB) error {
|
||||||
|
if !db.Migrator().HasTable(&InboundData{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Migrator().DropTable(&InboundData{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateClients(db *gorm.DB) error {
|
||||||
|
var oldClients []model.Client
|
||||||
|
err := db.Model(model.Client{}).Scan(&oldClients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, oldClient := range oldClients {
|
||||||
|
var old_inbounds []string
|
||||||
|
err = json.Unmarshal(oldClient.Inbounds, &old_inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var inbound_ids []uint
|
||||||
|
err = db.Raw("SELECT id FROM inbounds WHERE tag in ?", old_inbounds).Find(&inbound_ids).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldClients[index].Inbounds, _ = json.Marshal(inbound_ids)
|
||||||
|
}
|
||||||
|
return db.Save(oldClients).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func to1_2(db *gorm.DB) error {
|
||||||
|
err := moveJsonToDb(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = migrateTls(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = dropInboundData(db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return migrateClients(db)
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MigrateDb() {
|
||||||
|
// void running on first install
|
||||||
|
path := config.GetDBPath()
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
println("Database not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := gorm.Open(sqlite.Open(path))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
currentVersion := config.GetVersion()
|
||||||
|
dbVersion := ""
|
||||||
|
tx.Raw("SELECT value FROM settings WHERE key = ?", "version").Find(&dbVersion)
|
||||||
|
fmt.Println("Current version:", currentVersion, "\nDatabase version:", dbVersion)
|
||||||
|
|
||||||
|
if currentVersion == dbVersion {
|
||||||
|
fmt.Println("Database is up to date, no need to migrate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Start migrating database...")
|
||||||
|
|
||||||
|
// Before 1.2
|
||||||
|
if dbVersion == "" {
|
||||||
|
err = to1_1(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Migration to 1.1 failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = to1_2(tx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Migration to 1.2 failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set version
|
||||||
|
err = tx.Raw("UPDATE settings SET value = ? WHERE key = ?", currentVersion, "version").Error
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Update version failed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Migration done!")
|
||||||
|
}
|
||||||
@@ -2,9 +2,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/service"
|
"s-ui/service"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resetSetting() {
|
func resetSetting() {
|
||||||
@@ -103,3 +108,64 @@ func showSetting() {
|
|||||||
fmt.Println("\tSub URI:\t", (*allSetting)["subURI"])
|
fmt.Println("\tSub URI:\t", (*allSetting)["subURI"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPanelURI() {
|
||||||
|
err := database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
Port, _ := settingService.GetPort()
|
||||||
|
BasePath, _ := settingService.GetWebPath()
|
||||||
|
Listen, _ := settingService.GetListen()
|
||||||
|
Domain, _ := settingService.GetWebDomain()
|
||||||
|
KeyFile, _ := settingService.GetKeyFile()
|
||||||
|
CertFile, _ := settingService.GetCertFile()
|
||||||
|
TLS := false
|
||||||
|
if KeyFile != "" && CertFile != "" {
|
||||||
|
TLS = true
|
||||||
|
}
|
||||||
|
Proto := ""
|
||||||
|
if TLS {
|
||||||
|
Proto = "https://"
|
||||||
|
} else {
|
||||||
|
Proto = "http://"
|
||||||
|
}
|
||||||
|
PortText := fmt.Sprintf(":%d", Port)
|
||||||
|
if (Port == 443 && TLS) || (Port == 80 && !TLS) {
|
||||||
|
PortText = ""
|
||||||
|
}
|
||||||
|
if len(Domain) > 0 {
|
||||||
|
fmt.Println(Proto + Domain + PortText + BasePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(Listen) > 0 {
|
||||||
|
fmt.Println(Proto + Listen + PortText + BasePath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Local address:")
|
||||||
|
// get ip address
|
||||||
|
netInterfaces, _ := net.Interfaces()
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
|
||||||
|
addrs := netInterfaces[i].Addrs
|
||||||
|
for _, address := range addrs {
|
||||||
|
IP := strings.Split(address.Addr, "/")[0]
|
||||||
|
if strings.Contains(address.Addr, ".") {
|
||||||
|
fmt.Println(Proto + IP + PortText + BasePath)
|
||||||
|
} else if address.Addr[0:6] != "fe80::" {
|
||||||
|
fmt.Println(Proto + "[" + IP + "]" + PortText + BasePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := http.Get("https://api.ipify.org?format=text")
|
||||||
|
if err == nil {
|
||||||
|
defer resp.Body.Close()
|
||||||
|
ip, err := io.ReadAll(resp.Body)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("\nGlobal address:\n%s%s%s%s\n", Proto, ip, PortText, BasePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,9 +14,6 @@ var version string
|
|||||||
//go:embed name
|
//go:embed name
|
||||||
var name string
|
var name string
|
||||||
|
|
||||||
//go:embed config.json
|
|
||||||
var defaultConfig string
|
|
||||||
|
|
||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -48,43 +46,18 @@ func IsDebug() bool {
|
|||||||
return os.Getenv("SUI_DEBUG") == "true"
|
return os.Getenv("SUI_DEBUG") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBinFolderPath() string {
|
|
||||||
binFolderPath := os.Getenv("SUI_BIN_FOLDER")
|
|
||||||
if binFolderPath == "" {
|
|
||||||
binFolderPath = "bin"
|
|
||||||
}
|
|
||||||
return binFolderPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("SUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath == "" {
|
||||||
|
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
dbFolderPath = "/usr/local/s-ui/db"
|
dbFolderPath = "/usr/local/s-ui/db"
|
||||||
}
|
}
|
||||||
|
dbFolderPath = dir + "/db"
|
||||||
|
}
|
||||||
return dbFolderPath
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDefaultConfig() string {
|
|
||||||
apiEnv := GetEnvApi()
|
|
||||||
if len(apiEnv) > 0 {
|
|
||||||
return strings.Replace(defaultConfig, "127.0.0.1:1080", apiEnv, 1)
|
|
||||||
}
|
|
||||||
return defaultConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEnvApi() string {
|
|
||||||
return os.Getenv("SINGBOX_API")
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsSystemd() bool {
|
|
||||||
pid := os.Getppid()
|
|
||||||
cmdline, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return string(cmdline) == "systemd\n"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"log": {
|
|
||||||
"level": "info"
|
|
||||||
},
|
|
||||||
"dns": {},
|
|
||||||
"inbounds": [],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "direct",
|
|
||||||
"type": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns-out"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns-out"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"experimental": {
|
|
||||||
"v2ray_api": {
|
|
||||||
"listen": "127.0.0.1:1080",
|
|
||||||
"stats": {
|
|
||||||
"enabled": true,
|
|
||||||
"inbounds": [],
|
|
||||||
"outbounds": [
|
|
||||||
"direct"
|
|
||||||
],
|
|
||||||
"users": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
|
"github.com/sagernet/sing-box/route"
|
||||||
|
sbCommon "github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
|
type Box struct {
|
||||||
|
createdAt time.Time
|
||||||
|
logFactory log.Factory
|
||||||
|
logger log.ContextLogger
|
||||||
|
network *route.NetworkManager
|
||||||
|
endpoint *endpoint.Manager
|
||||||
|
inbound *inbound.Manager
|
||||||
|
outbound *outbound.Manager
|
||||||
|
connection *route.ConnectionManager
|
||||||
|
router *route.Router
|
||||||
|
services []adapter.LifecycleService
|
||||||
|
connTracker *ConnTracker
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
option.Options
|
||||||
|
Context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func Context(
|
||||||
|
ctx context.Context,
|
||||||
|
inboundRegistry adapter.InboundRegistry,
|
||||||
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
) context.Context {
|
||||||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBox(options Options) (*Box, error) {
|
||||||
|
var err error
|
||||||
|
createdAt := time.Now()
|
||||||
|
ctx := options.Context
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
|
||||||
|
if endpointRegistry == nil {
|
||||||
|
return nil, common.NewError("missing endpoint registry in context")
|
||||||
|
}
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, common.NewError("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, common.NewError("missing outbound registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
|
experimentalOptions := sbCommon.PtrValueOrDefault(options.Experimental)
|
||||||
|
var needCacheFile bool
|
||||||
|
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled {
|
||||||
|
needCacheFile = true
|
||||||
|
}
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
var defaultLogWriter io.Writer
|
||||||
|
if platformInterface != nil {
|
||||||
|
defaultLogWriter = io.Discard
|
||||||
|
}
|
||||||
|
var logFactory log.Factory
|
||||||
|
logFactory, err = NewFactory(log.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: sbCommon.PtrValueOrDefault(options.Log),
|
||||||
|
DefaultWriter: defaultLogWriter,
|
||||||
|
BaseTime: createdAt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("create log factory", err)
|
||||||
|
}
|
||||||
|
factory = logFactory
|
||||||
|
|
||||||
|
routeOptions := sbCommon.PtrValueOrDefault(options.Route)
|
||||||
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
|
||||||
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize network manager", err)
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
|
router, err := route.NewRouter(ctx, logFactory, routeOptions, sbCommon.PtrValueOrDefault(options.DNS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize router", err)
|
||||||
|
}
|
||||||
|
for i, endpointOptions := range options.Endpoints {
|
||||||
|
var tag string
|
||||||
|
if endpointOptions.Tag != "" {
|
||||||
|
tag = endpointOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = endpointManager.Create(ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
endpointOptions.Type,
|
||||||
|
endpointOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize endpoint["+F.ToString(i)+"] "+tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, inboundOptions := range options.Inbounds {
|
||||||
|
var tag string
|
||||||
|
if inboundOptions.Tag != "" {
|
||||||
|
tag = inboundOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = inboundManager.Create(ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
inboundOptions.Type,
|
||||||
|
inboundOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize inbound[", i, "] ", tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, outboundOptions := range options.Outbounds {
|
||||||
|
var tag string
|
||||||
|
if outboundOptions.Tag != "" {
|
||||||
|
tag = outboundOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
outboundCtx := ctx
|
||||||
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = outboundManager.Create(
|
||||||
|
outboundCtx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize outbound["+F.ToString(i)+"] "+tag, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outboundManager.Initialize(sbCommon.Must1(
|
||||||
|
direct.NewOutbound(
|
||||||
|
ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger("outbound/direct"),
|
||||||
|
"direct",
|
||||||
|
option.DirectOutboundOptions{},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
if platformInterface != nil {
|
||||||
|
err = platformInterface.Initialize(networkManager)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("initialize platform interface", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if connTracker == nil {
|
||||||
|
connTracker = NewConnTracker()
|
||||||
|
}
|
||||||
|
router.SetTracker(connTracker)
|
||||||
|
|
||||||
|
var services []adapter.LifecycleService
|
||||||
|
|
||||||
|
if needCacheFile {
|
||||||
|
cacheFile := cachefile.New(ctx, sbCommon.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
services = append(services, cacheFile)
|
||||||
|
}
|
||||||
|
ntpOptions := sbCommon.PtrValueOrDefault(options.NTP)
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError(err, "create NTP service")
|
||||||
|
}
|
||||||
|
timeService := ntp.NewService(ntp.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Dialer: ntpDialer,
|
||||||
|
Logger: logFactory.NewLogger("ntp"),
|
||||||
|
Server: ntpOptions.ServerOptions.Build(),
|
||||||
|
Interval: time.Duration(ntpOptions.Interval),
|
||||||
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
|
})
|
||||||
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
|
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
||||||
|
}
|
||||||
|
return &Box{
|
||||||
|
network: networkManager,
|
||||||
|
endpoint: endpointManager,
|
||||||
|
inbound: inboundManager,
|
||||||
|
outbound: outboundManager,
|
||||||
|
connection: connectionManager,
|
||||||
|
router: router,
|
||||||
|
createdAt: createdAt,
|
||||||
|
logFactory: logFactory,
|
||||||
|
logger: logFactory.Logger(),
|
||||||
|
services: services,
|
||||||
|
connTracker: connTracker,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) PreStart() error {
|
||||||
|
err := s.preStart()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: remove catch error
|
||||||
|
defer func() {
|
||||||
|
v := recover()
|
||||||
|
if v != nil {
|
||||||
|
s.logger.Error(err.Error())
|
||||||
|
s.logger.Error("panic on early close: " + fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Start() error {
|
||||||
|
err := s.start()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: remove catch error
|
||||||
|
defer func() {
|
||||||
|
v := recover()
|
||||||
|
if v != nil {
|
||||||
|
s.logger.Debug(err.Error())
|
||||||
|
s.logger.Error("panic on early start: " + fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) preStart() error {
|
||||||
|
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||||
|
monitor.Start("start logger")
|
||||||
|
err := s.logFactory.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError(err, "start logger")
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) start() error {
|
||||||
|
err := s.preStart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Close() error {
|
||||||
|
select {
|
||||||
|
case <-s.done:
|
||||||
|
return os.ErrClosed
|
||||||
|
default:
|
||||||
|
close(s.done)
|
||||||
|
}
|
||||||
|
err := sbCommon.Close(
|
||||||
|
s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.network,
|
||||||
|
)
|
||||||
|
for _, lifecycleService := range s.services {
|
||||||
|
err1 := lifecycleService.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
s.logger.Debug(lifecycleService.Name(), " close error: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err1 := s.logFactory.Close()
|
||||||
|
if err1 != nil {
|
||||||
|
s.logger.Debug("logger close error: ", err1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Uptime() uint32 {
|
||||||
|
return uint32(time.Now().Sub(s.createdAt).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Network() adapter.NetworkManager {
|
||||||
|
return s.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Router() adapter.Router {
|
||||||
|
return s.router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Inbound() adapter.InboundManager {
|
||||||
|
return s.inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
|
return s.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Endpoint() adapter.EndpointManager {
|
||||||
|
return s.endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) ConnTracker() *ConnTracker {
|
||||||
|
return s.connTracker
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Counter struct {
|
||||||
|
read *atomic.Int64
|
||||||
|
write *atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnTracker struct {
|
||||||
|
access sync.Mutex
|
||||||
|
createdAt time.Time
|
||||||
|
inbounds map[string]Counter
|
||||||
|
outbounds map[string]Counter
|
||||||
|
users map[string]Counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnTracker() *ConnTracker {
|
||||||
|
return &ConnTracker{
|
||||||
|
createdAt: time.Now(),
|
||||||
|
inbounds: make(map[string]Counter),
|
||||||
|
outbounds: make(map[string]Counter),
|
||||||
|
users: make(map[string]Counter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) getReadCounters(inbound string, outbound string, user string) ([]*atomic.Int64, []*atomic.Int64) {
|
||||||
|
var readCounter []*atomic.Int64
|
||||||
|
var writeCounter []*atomic.Int64
|
||||||
|
c.access.Lock()
|
||||||
|
if inbound != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.inbounds, inbound).read)
|
||||||
|
writeCounter = append(writeCounter, c.inbounds[inbound].write)
|
||||||
|
}
|
||||||
|
if outbound != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.outbounds, outbound).read)
|
||||||
|
writeCounter = append(writeCounter, c.outbounds[outbound].write)
|
||||||
|
}
|
||||||
|
if user != "" {
|
||||||
|
readCounter = append(readCounter, c.loadOrCreateCounter(&c.users, user).read)
|
||||||
|
writeCounter = append(writeCounter, c.users[user].write)
|
||||||
|
}
|
||||||
|
c.access.Unlock()
|
||||||
|
return readCounter, writeCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) loadOrCreateCounter(obj *map[string]Counter, name string) Counter {
|
||||||
|
counter, loaded := (*obj)[name]
|
||||||
|
if loaded {
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
counter = Counter{read: &atomic.Int64{}, write: &atomic.Int64{}}
|
||||||
|
(*obj)[name] = counter
|
||||||
|
return counter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
||||||
|
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||||
|
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) RoutedPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) network.PacketConn {
|
||||||
|
readCounter, writeCounter := c.getReadCounters(metadata.Inbound, matchOutbound.Tag(), metadata.User)
|
||||||
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConnTracker) GetStats() *[]model.Stats {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
dt := time.Now().Unix()
|
||||||
|
|
||||||
|
s := []model.Stats{}
|
||||||
|
for inbound, counter := range c.inbounds {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "inbound",
|
||||||
|
Tag: inbound,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "inbound",
|
||||||
|
Tag: inbound,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for outbound, counter := range c.outbounds {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "outbound",
|
||||||
|
Tag: outbound,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "outbound",
|
||||||
|
Tag: outbound,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for user, counter := range c.users {
|
||||||
|
down := counter.write.Swap(0)
|
||||||
|
up := counter.read.Swap(0)
|
||||||
|
if down > 0 || up > 0 {
|
||||||
|
s = append(s, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "user",
|
||||||
|
Tag: user,
|
||||||
|
Direction: false,
|
||||||
|
Traffic: down,
|
||||||
|
}, model.Stats{
|
||||||
|
DateTime: dt,
|
||||||
|
Resource: "user",
|
||||||
|
Tag: user,
|
||||||
|
Direction: true,
|
||||||
|
Traffic: up,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"s-ui/logger"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Core) AddInbound(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var inbound_config option.Inbound
|
||||||
|
err = inbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = inbound_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("inbound/"+inbound_config.Type+"["+inbound_config.Tag+"]"),
|
||||||
|
inbound_config.Tag,
|
||||||
|
inbound_config.Type,
|
||||||
|
inbound_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveInbound(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove inbound: ", tag)
|
||||||
|
return inbound_manager.Remove(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) AddOutbound(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var outbound_config option.Outbound
|
||||||
|
|
||||||
|
err = outbound_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = outbound_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("outbound/"+outbound_config.Type+"["+outbound_config.Tag+"]"),
|
||||||
|
outbound_config.Tag,
|
||||||
|
outbound_config.Type,
|
||||||
|
outbound_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveOutbound(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove outbound: ", tag)
|
||||||
|
return outbound_manager.Remove(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) AddEndpoint(config []byte) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var endpoint_config option.Endpoint
|
||||||
|
|
||||||
|
err = endpoint_config.UnmarshalJSONContext(globalCtx, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = endpoint_manager.Create(
|
||||||
|
globalCtx,
|
||||||
|
router,
|
||||||
|
factory.NewLogger("endpoint/"+endpoint_config.Type+"["+endpoint_config.Tag+"]"),
|
||||||
|
endpoint_config.Tag,
|
||||||
|
endpoint_config.Type,
|
||||||
|
endpoint_config.Options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) RemoveEndpoint(tag string) error {
|
||||||
|
if !c.isRunning {
|
||||||
|
return common.NewError("sing-box is not running")
|
||||||
|
}
|
||||||
|
logger.Info("remove endpoint: ", tag)
|
||||||
|
return endpoint_manager.Remove(tag)
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
suiLog "s-ui/logger"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/observable"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PlatformWriter struct{}
|
||||||
|
|
||||||
|
func (p PlatformWriter) DisableColors() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (p PlatformWriter) WriteMessage(level log.Level, message string) {
|
||||||
|
switch level {
|
||||||
|
case log.LevelInfo:
|
||||||
|
suiLog.Info(message)
|
||||||
|
case log.LevelWarn:
|
||||||
|
suiLog.Warning(message)
|
||||||
|
case log.LevelPanic:
|
||||||
|
case log.LevelFatal:
|
||||||
|
case log.LevelError:
|
||||||
|
suiLog.Error(message)
|
||||||
|
default:
|
||||||
|
suiLog.Debug(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory(options log.Options) (log.Factory, error) {
|
||||||
|
logOptions := options.Options
|
||||||
|
|
||||||
|
if logOptions.Disabled {
|
||||||
|
return log.NewNOPFactory(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var logWriter io.Writer
|
||||||
|
var logFilePath string
|
||||||
|
|
||||||
|
switch logOptions.Output {
|
||||||
|
case "":
|
||||||
|
logWriter = options.DefaultWriter
|
||||||
|
if logWriter == nil {
|
||||||
|
logWriter = os.Stderr
|
||||||
|
}
|
||||||
|
case "stderr":
|
||||||
|
logWriter = os.Stderr
|
||||||
|
case "stdout":
|
||||||
|
logWriter = os.Stdout
|
||||||
|
default:
|
||||||
|
logFilePath = logOptions.Output
|
||||||
|
}
|
||||||
|
logFormatter := log.Formatter{
|
||||||
|
BaseTime: options.BaseTime,
|
||||||
|
DisableColors: logOptions.DisableColor || logFilePath != "",
|
||||||
|
DisableTimestamp: !logOptions.Timestamp && logFilePath != "",
|
||||||
|
FullTimestamp: logOptions.Timestamp,
|
||||||
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
|
}
|
||||||
|
factory := NewDefaultFactory(
|
||||||
|
options.Context,
|
||||||
|
logFormatter,
|
||||||
|
logWriter,
|
||||||
|
logFilePath,
|
||||||
|
)
|
||||||
|
if logOptions.Level != "" {
|
||||||
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.Error("parse log level", err)
|
||||||
|
}
|
||||||
|
factory.SetLevel(logLevel)
|
||||||
|
} else {
|
||||||
|
factory.SetLevel(log.LevelTrace)
|
||||||
|
}
|
||||||
|
return factory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ log.Factory = (*defaultFactory)(nil)
|
||||||
|
|
||||||
|
type defaultFactory struct {
|
||||||
|
ctx context.Context
|
||||||
|
formatter log.Formatter
|
||||||
|
writer io.Writer
|
||||||
|
file *os.File
|
||||||
|
filePath string
|
||||||
|
level log.Level
|
||||||
|
subscriber *observable.Subscriber[log.Entry]
|
||||||
|
observer *observable.Observer[log.Entry]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultFactory(
|
||||||
|
ctx context.Context,
|
||||||
|
formatter log.Formatter,
|
||||||
|
writer io.Writer,
|
||||||
|
filePath string,
|
||||||
|
) log.ObservableFactory {
|
||||||
|
factory := &defaultFactory{
|
||||||
|
ctx: ctx,
|
||||||
|
formatter: formatter,
|
||||||
|
writer: writer,
|
||||||
|
filePath: filePath,
|
||||||
|
level: log.LevelTrace,
|
||||||
|
subscriber: observable.NewSubscriber[log.Entry](128),
|
||||||
|
}
|
||||||
|
return factory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Start() error {
|
||||||
|
if f.filePath != "" {
|
||||||
|
logFile, err := filemanager.OpenFile(f.ctx, f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writer = logFile
|
||||||
|
f.file = logFile
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(f.file),
|
||||||
|
f.subscriber,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Level() log.Level {
|
||||||
|
return f.level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) SetLevel(level log.Level) {
|
||||||
|
f.level = level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Logger() log.ContextLogger {
|
||||||
|
return f.NewLogger("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) NewLogger(tag string) log.ContextLogger {
|
||||||
|
return &observableLogger{f, tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) Subscribe() (subscription observable.Subscription[log.Entry], done <-chan struct{}, err error) {
|
||||||
|
return f.observer.Subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *defaultFactory) UnSubscribe(sub observable.Subscription[log.Entry]) {
|
||||||
|
f.observer.UnSubscribe(sub)
|
||||||
|
}
|
||||||
|
|
||||||
|
type observableLogger struct {
|
||||||
|
*defaultFactory
|
||||||
|
tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Log(ctx context.Context, level log.Level, args []any) {
|
||||||
|
level = log.OverrideLevelFromContext(level, ctx)
|
||||||
|
if level > l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := F.ToString(args...)
|
||||||
|
switch level {
|
||||||
|
case log.LevelInfo:
|
||||||
|
suiLog.Info(l.tag, msg)
|
||||||
|
case log.LevelWarn:
|
||||||
|
suiLog.Warning(l.tag, msg)
|
||||||
|
case log.LevelPanic:
|
||||||
|
case log.LevelFatal:
|
||||||
|
case log.LevelError:
|
||||||
|
suiLog.Error(l.tag, msg)
|
||||||
|
default:
|
||||||
|
suiLog.Debug(l.tag, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Trace(args ...any) {
|
||||||
|
l.TraceContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Debug(args ...any) {
|
||||||
|
l.DebugContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Info(args ...any) {
|
||||||
|
l.InfoContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Warn(args ...any) {
|
||||||
|
l.WarnContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Error(args ...any) {
|
||||||
|
l.ErrorContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Fatal(args ...any) {
|
||||||
|
l.FatalContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Panic(args ...any) {
|
||||||
|
l.PanicContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) TraceContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelTrace, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) DebugContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelDebug, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) InfoContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelInfo, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelWarn, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) ErrorContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelError, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) FatalContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelFatal, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) PanicContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, log.LevelPanic, args)
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"s-ui/logger"
|
||||||
|
|
||||||
|
sb "github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
_ "github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
|
_ "github.com/sagernet/sing-box/experimental/v2rayapi"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||||
|
_ "github.com/sagernet/sing-dns/quic"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalCtx context.Context
|
||||||
|
inbound_manager adapter.InboundManager
|
||||||
|
outbound_manager adapter.OutboundManager
|
||||||
|
endpoint_manager adapter.EndpointManager
|
||||||
|
router adapter.Router
|
||||||
|
connTracker *ConnTracker
|
||||||
|
factory log.Factory
|
||||||
|
)
|
||||||
|
|
||||||
|
type Core struct {
|
||||||
|
isRunning bool
|
||||||
|
instance *Box
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCore() *Core {
|
||||||
|
globalCtx = context.Background()
|
||||||
|
globalCtx = sb.Context(globalCtx, inboundRegistry(), outboundRegistry(), EndpointRegistry())
|
||||||
|
return &Core{
|
||||||
|
isRunning: false,
|
||||||
|
instance: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) GetCtx() context.Context {
|
||||||
|
return globalCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) GetInstance() *Box {
|
||||||
|
return c.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) Start(sbConfig []byte) error {
|
||||||
|
var opt option.Options
|
||||||
|
err := opt.UnmarshalJSONContext(globalCtx, sbConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Unmarshal config err:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.instance, err = NewBox(Options{
|
||||||
|
Context: globalCtx,
|
||||||
|
Options: opt,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.instance.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalCtx = service.ContextWith(globalCtx, c)
|
||||||
|
inbound_manager = service.FromContext[adapter.InboundManager](globalCtx)
|
||||||
|
outbound_manager = service.FromContext[adapter.OutboundManager](globalCtx)
|
||||||
|
endpoint_manager = service.FromContext[adapter.EndpointManager](globalCtx)
|
||||||
|
router = service.FromContext[adapter.Router](globalCtx)
|
||||||
|
|
||||||
|
c.isRunning = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) Stop() error {
|
||||||
|
if c.isRunning {
|
||||||
|
c.isRunning = false
|
||||||
|
return c.instance.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Core) IsRunning() bool {
|
||||||
|
return c.isRunning
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/block"
|
||||||
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
|
"github.com/sagernet/sing-box/protocol/dns"
|
||||||
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria2"
|
||||||
|
"github.com/sagernet/sing-box/protocol/mixed"
|
||||||
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
|
_ "github.com/sagernet/sing-box/protocol/naive/quic"
|
||||||
|
"github.com/sagernet/sing-box/protocol/redirect"
|
||||||
|
"github.com/sagernet/sing-box/protocol/shadowsocks"
|
||||||
|
"github.com/sagernet/sing-box/protocol/shadowtls"
|
||||||
|
"github.com/sagernet/sing-box/protocol/socks"
|
||||||
|
"github.com/sagernet/sing-box/protocol/ssh"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tor"
|
||||||
|
"github.com/sagernet/sing-box/protocol/trojan"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tuic"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
|
"github.com/sagernet/sing-box/protocol/wireguard"
|
||||||
|
_ "github.com/sagernet/sing-box/transport/v2rayquic"
|
||||||
|
_ "github.com/sagernet/sing-dns/quic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func inboundRegistry() *inbound.Registry {
|
||||||
|
registry := inbound.NewRegistry()
|
||||||
|
|
||||||
|
tun.RegisterInbound(registry)
|
||||||
|
redirect.RegisterRedirect(registry)
|
||||||
|
redirect.RegisterTProxy(registry)
|
||||||
|
direct.RegisterInbound(registry)
|
||||||
|
|
||||||
|
socks.RegisterInbound(registry)
|
||||||
|
http.RegisterInbound(registry)
|
||||||
|
mixed.RegisterInbound(registry)
|
||||||
|
|
||||||
|
shadowsocks.RegisterInbound(registry)
|
||||||
|
vmess.RegisterInbound(registry)
|
||||||
|
trojan.RegisterInbound(registry)
|
||||||
|
naive.RegisterInbound(registry)
|
||||||
|
shadowtls.RegisterInbound(registry)
|
||||||
|
vless.RegisterInbound(registry)
|
||||||
|
|
||||||
|
hysteria.RegisterInbound(registry)
|
||||||
|
tuic.RegisterInbound(registry)
|
||||||
|
hysteria2.RegisterInbound(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func outboundRegistry() *outbound.Registry {
|
||||||
|
registry := outbound.NewRegistry()
|
||||||
|
|
||||||
|
direct.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
block.RegisterOutbound(registry)
|
||||||
|
dns.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
group.RegisterSelector(registry)
|
||||||
|
group.RegisterURLTest(registry)
|
||||||
|
|
||||||
|
socks.RegisterOutbound(registry)
|
||||||
|
http.RegisterOutbound(registry)
|
||||||
|
shadowsocks.RegisterOutbound(registry)
|
||||||
|
vmess.RegisterOutbound(registry)
|
||||||
|
trojan.RegisterOutbound(registry)
|
||||||
|
tor.RegisterOutbound(registry)
|
||||||
|
ssh.RegisterOutbound(registry)
|
||||||
|
shadowtls.RegisterOutbound(registry)
|
||||||
|
vless.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
hysteria.RegisterOutbound(registry)
|
||||||
|
tuic.RegisterOutbound(registry)
|
||||||
|
hysteria2.RegisterOutbound(registry)
|
||||||
|
wireguard.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
func EndpointRegistry() *endpoint.Registry {
|
||||||
|
registry := endpoint.NewRegistry()
|
||||||
|
|
||||||
|
wireguard.RegisterEndpoint(registry)
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package cronjob
|
||||||
|
|
||||||
|
import (
|
||||||
|
"s-ui/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckCoreJob struct {
|
||||||
|
service.ConfigService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckCoreJob() *CheckCoreJob {
|
||||||
|
return &CheckCoreJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CheckCoreJob) Run() {
|
||||||
|
s.ConfigService.StartCore("")
|
||||||
|
}
|
||||||
@@ -25,6 +25,8 @@ func (c *CronJob) Start(loc *time.Location, trafficAge int) error {
|
|||||||
c.cron.AddJob("@every 1m", NewDepleteJob())
|
c.cron.AddJob("@every 1m", NewDepleteJob())
|
||||||
// Start deleting old stats
|
// Start deleting old stats
|
||||||
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
c.cron.AddJob("@daily", NewDelStatsJob(trafficAge))
|
||||||
|
// Start core if it is not running
|
||||||
|
c.cron.AddJob("@every 5s", NewCheckCoreJob())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DepleteJob struct {
|
type DepleteJob struct {
|
||||||
service.ConfigService
|
service.ClientService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDepleteJob() *DepleteJob {
|
func NewDepleteJob() *DepleteJob {
|
||||||
@@ -14,7 +14,7 @@ func NewDepleteJob() *DepleteJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DepleteJob) Run() {
|
func (s *DepleteJob) Run() {
|
||||||
err := s.ConfigService.DepleteClients()
|
err := s.ClientService.DepleteClients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Disable depleted users failed: ", err)
|
logger.Warning("Disable depleted users failed: ", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StatsJob struct {
|
type StatsJob struct {
|
||||||
service.SingBoxService
|
service.StatsService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatsJob() *StatsJob {
|
func NewStatsJob() *StatsJob {
|
||||||
return new(StatsJob)
|
return &StatsJob{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsJob) Run() {
|
func (s *StatsJob) Run() {
|
||||||
err := s.SingBoxService.GetStats()
|
err := s.StatsService.SaveStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Get stats failed: ", err)
|
logger.Warning("Get stats failed: ", err)
|
||||||
return
|
return
|
||||||
|
|||||||
+18
-1
@@ -1,6 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
@@ -48,6 +49,10 @@ func OpenDB(dbPath string) error {
|
|||||||
Logger: gormLogger,
|
Logger: gormLogger,
|
||||||
}
|
}
|
||||||
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||||
|
|
||||||
|
if config.IsDebug() {
|
||||||
|
db = db.Debug()
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +62,22 @@ func InitDB(dbPath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default Outbounds
|
||||||
|
if !db.Migrator().HasTable(&model.Outbound{}) {
|
||||||
|
db.Migrator().CreateTable(&model.Outbound{})
|
||||||
|
defaultOutbound := []model.Outbound{
|
||||||
|
{Type: "direct", Tag: "direct", Options: json.RawMessage(`{}`)},
|
||||||
|
{Type: "dns", Tag: "dns-out", Options: json.RawMessage(`{}`)},
|
||||||
|
}
|
||||||
|
db.Create(&defaultOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
err = db.AutoMigrate(
|
err = db.AutoMigrate(
|
||||||
&model.Setting{},
|
&model.Setting{},
|
||||||
&model.Tls{},
|
&model.Tls{},
|
||||||
&model.InboundData{},
|
&model.Inbound{},
|
||||||
|
&model.Outbound{},
|
||||||
|
&model.Endpoint{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Stats{},
|
&model.Stats{},
|
||||||
&model.Client{},
|
&model.Client{},
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Endpoint) UnmarshalJSON(data []byte) error {
|
||||||
|
var err error
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err = json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract fixed fields and store the rest in Options
|
||||||
|
if val, exists := raw["id"].(float64); exists {
|
||||||
|
o.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
o.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
o.Tag = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
o.Options, err = json.Marshal(raw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (o Endpoint) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = o.Type
|
||||||
|
combined["tag"] = o.Tag
|
||||||
|
|
||||||
|
if o.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(o.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Inbound struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
|
||||||
|
// Foreign key to tls table
|
||||||
|
TlsId uint `json:"tls_id" form:"tls_id"`
|
||||||
|
Tls *Tls `json:"tls" form:"tls" gorm:"foreignKey:TlsId;references:Id"`
|
||||||
|
|
||||||
|
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
||||||
|
OutJson json.RawMessage `json:"out_json" form:"out_json"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Inbound) UnmarshalJSON(data []byte) error {
|
||||||
|
var err error
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err = json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract fixed fields and store the rest in Options
|
||||||
|
if val, exists := raw["id"].(float64); exists {
|
||||||
|
i.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
i.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
i.Tag, _ = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// TlsId
|
||||||
|
if val, exists := raw["tls_id"].(float64); exists {
|
||||||
|
i.TlsId = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "tls_id")
|
||||||
|
delete(raw, "tls")
|
||||||
|
delete(raw, "users")
|
||||||
|
|
||||||
|
// Addrs
|
||||||
|
i.Addrs, _ = json.MarshalIndent(raw["addrs"], "", " ")
|
||||||
|
delete(raw, "addrs")
|
||||||
|
|
||||||
|
// OutJson
|
||||||
|
i.OutJson, _ = json.MarshalIndent(raw["out_json"], "", " ")
|
||||||
|
delete(raw, "out_json")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
i.Options, err = json.MarshalIndent(raw, "", " ")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (i Inbound) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = i.Type
|
||||||
|
combined["tag"] = i.Tag
|
||||||
|
if i.Tls != nil {
|
||||||
|
combined["tls"] = i.Tls.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(i.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Inbound) MarshalFull() (*map[string]interface{}, error) {
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["id"] = i.Id
|
||||||
|
combined["type"] = i.Type
|
||||||
|
combined["tag"] = i.Tag
|
||||||
|
combined["tls_id"] = i.TlsId
|
||||||
|
combined["addrs"] = i.Addrs
|
||||||
|
combined["out_json"] = i.OutJson
|
||||||
|
|
||||||
|
if i.Options != nil {
|
||||||
|
var restFields map[string]interface{}
|
||||||
|
if err := json.Unmarshal(i.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &combined, nil
|
||||||
|
}
|
||||||
@@ -11,18 +11,10 @@ type Setting struct {
|
|||||||
type Tls struct {
|
type Tls struct {
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Name string `json:"name" form:"name"`
|
Name string `json:"name" form:"name"`
|
||||||
Inbounds json.RawMessage `json:"inbounds" form:"inbounds"`
|
|
||||||
Server json.RawMessage `json:"server" form:"server"`
|
Server json.RawMessage `json:"server" form:"server"`
|
||||||
Client json.RawMessage `json:"client" form:"client"`
|
Client json.RawMessage `json:"client" form:"client"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundData struct {
|
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
Tag string `json:"tag" form:"tag"`
|
|
||||||
Addrs json.RawMessage `json:"addrs" form:"addrs"`
|
|
||||||
OutJson json.RawMessage `json:"outJson" form:"outJson"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
Username string `json:"username" form:"username"`
|
Username string `json:"username" form:"username"`
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Type string `json:"type" form:"type"`
|
||||||
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
|
Options json.RawMessage `json:"-" form:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) UnmarshalJSON(data []byte) error {
|
||||||
|
var err error
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err = json.Unmarshal(data, &raw); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract fixed fields and store the rest in Options
|
||||||
|
if val, exists := raw["id"].(float64); exists {
|
||||||
|
o.Id = uint(val)
|
||||||
|
}
|
||||||
|
delete(raw, "id")
|
||||||
|
o.Type, _ = raw["type"].(string)
|
||||||
|
delete(raw, "type")
|
||||||
|
o.Tag = raw["tag"].(string)
|
||||||
|
delete(raw, "tag")
|
||||||
|
|
||||||
|
// Remaining fields
|
||||||
|
o.Options, err = json.Marshal(raw)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON customizes marshalling
|
||||||
|
func (o Outbound) MarshalJSON() ([]byte, error) {
|
||||||
|
// Combine fixed fields and dynamic fields into one map
|
||||||
|
combined := make(map[string]interface{})
|
||||||
|
combined["type"] = o.Type
|
||||||
|
combined["tag"] = o.Tag
|
||||||
|
|
||||||
|
if o.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(o.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range restFields {
|
||||||
|
combined[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(combined)
|
||||||
|
}
|
||||||
+76
-13
@@ -7,62 +7,125 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.9
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
github.com/v2fly/v2ray-core/v5 v5.17.1
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.67.1
|
gorm.io/driver/sqlite v1.5.7
|
||||||
gorm.io/driver/sqlite v1.5.6
|
|
||||||
gorm.io/gorm v1.25.12
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/adrg/xdg v0.5.0 // indirect
|
github.com/ebitengine/purego v0.8.1 // indirect
|
||||||
|
google.golang.org/grpc v1.67.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||||
github.com/bytedance/sonic v1.12.3 // indirect
|
github.com/bytedance/sonic v1.12.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||||
|
github.com/caddyserver/certmagic v0.20.0 // indirect
|
||||||
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/cretz/bine v0.2.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||||
github.com/gin-contrib/sessions v1.0.1
|
github.com/gin-contrib/sessions v1.0.1
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0 // indirect
|
||||||
|
github.com/go-chi/render v1.0.3 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
|
github.com/gofrs/uuid/v5 v5.3.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/btree v1.1.3 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/libdns/alidns v1.0.3 // indirect
|
||||||
|
github.com/libdns/cloudflare v0.1.1 // indirect
|
||||||
|
github.com/libdns/libdns v0.2.2 // indirect; indiresct
|
||||||
|
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa // indirect
|
||||||
|
github.com/mholt/acmez v1.2.0 // indirect
|
||||||
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/onsi/ginkgo/v2 v2.10.0 // indirect
|
||||||
|
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||||
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
|
||||||
|
github.com/sagernet/cors v1.2.1 // indirect
|
||||||
|
github.com/sagernet/fswatch v0.1.1 // indirect
|
||||||
|
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect
|
||||||
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
|
github.com/sagernet/quic-go v0.48.2-beta.1 // indirect
|
||||||
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
||||||
|
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-beta.2 // indirect
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect
|
||||||
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f // indirect
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1 // indirect
|
||||||
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||||
|
github.com/sagernet/utls v1.6.7 // indirect
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.5 // indirect
|
||||||
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.11.0 // indirect
|
golang.org/x/arch v0.11.0 // indirect
|
||||||
golang.org/x/crypto v0.28.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/mod v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/net v0.31.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
golang.org/x/time v0.7.0 // indirect
|
||||||
|
golang.org/x/tools v0.24.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+179
-97
@@ -1,34 +1,29 @@
|
|||||||
github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY=
|
|
||||||
github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4=
|
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
|
|
||||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
|
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.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/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
|
|
||||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
|
|
||||||
github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM=
|
|
||||||
github.com/bufbuild/protocompile v0.10.0/go.mod h1:G9qQIQo0xZ6Uyj6CMNz0saGmx2so+KONo8/KrELABiY=
|
|
||||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||||
|
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||||
@@ -43,6 +38,8 @@ github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
|||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-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=
|
||||||
@@ -56,23 +53,23 @@ github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27
|
|||||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI=
|
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||||
github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE=
|
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
|
||||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
@@ -81,89 +78,152 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
|
|
||||||
github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
|
|
||||||
github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
|
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||||
|
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||||
|
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||||
|
github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU=
|
||||||
|
github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||||
|
github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
||||||
|
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
||||||
|
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
||||||
|
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||||
|
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||||
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1Ws=
|
|
||||||
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
|
|
||||||
github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ=
|
|
||||||
github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
|
||||||
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
|
||||||
|
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||||
|
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
|
||||||
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
|
||||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
|
||||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
|
||||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
|
||||||
github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
|
|
||||||
github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
|
|
||||||
github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q=
|
|
||||||
github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E=
|
|
||||||
github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKpXEe0=
|
|
||||||
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
|
||||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
|
||||||
|
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||||
|
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||||
|
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||||
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
|
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs=
|
||||||
|
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
|
||||||
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
|
github.com/sagernet/quic-go v0.48.2-beta.1 h1:W0plrLWa1XtOWDTdX3CJwxmQuxkya12nN5BRGZ87kEg=
|
||||||
|
github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
|
||||||
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
|
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.5 h1:RD2j8WmJsvAbbBkAlJWaiYmnd+v/JohBiweoew7kMwo=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.8 h1:PoxDdN7y8D4oImT3cQ05Sq1ZYnYsJberkUkIEHIGwWE=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
|
||||||
|
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.6 h1:MPdL2Yem/xM0RhejCO7krYvl1Zbd1zkSjKluKpHnHPQ=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.6/go.mod h1:6dO5V0A37cLlhvKnxCmZinSpZXz7ZSk11x3rgI+xH1I=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.11 h1:bVR0n3oQ3hGcuc/CSS7axsOeRNCRlCGkYVOKl0wxbsw=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.11/go.mod h1:GZnZUzUHZ6Bgm7D/i8unNORv3537u1s03tLXFdxCRpg=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.15 h1:oWcs/PHgKaeWKbTfgz/020KEVvDqQv/tQWe7zpyktkc=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.15/go.mod h1:+QZDsF4HkdiGcMfz+JNOfONLh9CnZjIwJJQNWEzhiaQ=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19 h1:uL2xlXpz4t7BduLbXiLe5QqpyiMhvNNRThBzhTJ4p00=
|
||||||
|
github.com/sagernet/sing-box v1.11.0-beta.19/go.mod h1:UXUN/lwRT9mAM8PK7upPOwgqooOV2vU+CcjBfwT1rYg=
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
||||||
|
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
||||||
|
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
|
||||||
|
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-alpha.4 h1:P9xAx3nIfcqb9M8jfgs0uLm+VxCcaY++FCqaBfHY3dQ=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-alpha.4/go.mod h1:h5RkKTmUhudJKzK7c87FPXD5w1bJjVyxMN9+opZcctA=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-beta.2 h1:ikoQ7zTR0o/2rlI5H5FeNC0j5bQJJHb1uoyXFRu3yGk=
|
||||||
|
github.com/sagernet/sing-quic v0.4.0-beta.2/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
|
||||||
|
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.2 h1:GK7r2jWKm7RhlJGTq4QadgFcebQia1c3BO3OlYMcQJ0=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.2/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.6 h1:xaIHoH78MqTSvZqQ4SQto8pC1A+X4qXReDRNaC8DQeI=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.6/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f h1:dTnXP0e3LbSa4EpUmuOGhllanKPei4vPKfzlLvk76Pc=
|
||||||
|
github.com/sagernet/sing-tun v0.6.0-beta.7.0.20241229131914-aa9d9c62966f/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1 h1:5sXQ23uwNlZuDvygzi0dFtnG0Csm/SNqTjAHXJkpuj4=
|
||||||
|
github.com/sagernet/sing-vmess v0.2.0-beta.1/go.mod h1:fLyE1emIcvQ5DV8reFWnufquZ7MkCSYM5ThodsR9NrQ=
|
||||||
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||||
|
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
||||||
|
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.4 h1:8uyM5fxfEXdu4RH05uOK+v25i3lTNdCYMPSAUJ14FnI=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.4/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||||
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -172,6 +232,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@@ -186,53 +247,74 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
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/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w=
|
|
||||||
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M=
|
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
|
||||||
github.com/v2fly/v2ray-core/v5 v5.17.1 h1:IIMMtmRdaG5HTYNn6VX1xKULknJl7nhkSFnmoTb5TDQ=
|
|
||||||
github.com/v2fly/v2ray-core/v5 v5.17.1/go.mod h1:IhDN0rhXJnNcs9jUuC5sILTGCT2L+4yr0+tfD8ZVuL8=
|
|
||||||
github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI=
|
|
||||||
github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
|
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
|
|
||||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
|
|
||||||
github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
|
|
||||||
github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||||
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
|
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/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||||
@@ -247,10 +329,10 @@ 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.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
|
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||||
|
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||||
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
|
|
||||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package logger
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"s-ui/config"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
@@ -26,10 +25,10 @@ func InitLogger(level logging.Level) {
|
|||||||
|
|
||||||
backend, err = logging.NewSyslogBackend("")
|
backend, err = logging.NewSyslogBackend("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Unable to use syslog: " + err.Error())
|
fmt.Println("Unable to use syslog: " + err.Error())
|
||||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
}
|
}
|
||||||
if config.IsSystemd() && err != nil {
|
if err != nil {
|
||||||
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
||||||
} else {
|
} else {
|
||||||
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
||||||
@@ -43,6 +42,10 @@ func InitLogger(level logging.Level) {
|
|||||||
logger = newLogger
|
logger = newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogger() *logging.Logger {
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
|
|||||||
+232
-25
@@ -5,12 +5,15 @@ import (
|
|||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientService struct {
|
type ClientService struct {
|
||||||
|
InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) GetAll() ([]model.Client, error) {
|
func (s *ClientService) GetAll() ([]model.Client, error) {
|
||||||
@@ -23,48 +26,236 @@ func (s *ClientService) GetAll() ([]model.Client, error) {
|
|||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error {
|
func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) {
|
||||||
var err error
|
var err error
|
||||||
for _, change := range changes {
|
var inboundIds []uint
|
||||||
client := model.Client{}
|
|
||||||
err = json.Unmarshal(change.Obj, &client)
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var client model.Client
|
||||||
|
err = json.Unmarshal(data, &client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case "addbulk":
|
||||||
|
var clients []*model.Client
|
||||||
|
err = json.Unmarshal(data, &clients)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(clients[0].Inbounds, &inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Save(clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
switch change.Action {
|
|
||||||
case "new":
|
|
||||||
err = tx.Create(&client).Error
|
|
||||||
case "del":
|
case "del":
|
||||||
err = tx.Where("id = ?", change.Index).Delete(model.Client{}).Error
|
var id uint
|
||||||
default:
|
err = json.Unmarshal(data, &id)
|
||||||
err = tx.Save(client).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var client model.Client
|
||||||
|
err = tx.Where("id = ?", id).First(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return err
|
err = json.Unmarshal(client.Inbounds, &inboundIds)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Where("id = ?", id).Delete(model.Client{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, common.NewErrorf("unknown action: %s", act)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
return inboundIds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) updateLinksWithFixedInbounds(tx *gorm.DB, clients []*model.Client, inbounIds []uint, hostname string) error {
|
||||||
|
var err error
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
|
||||||
|
// Zero inbounds means removing local links only
|
||||||
|
if len(inbounIds) > 0 {
|
||||||
|
err = tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for index, client := range clients {
|
||||||
|
var clientLinks, newClientLinks []map[string]string
|
||||||
|
json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
|
||||||
|
for _, newLink := range newLinks {
|
||||||
|
newClientLinks = append(newClientLinks, map[string]string{
|
||||||
|
"remark": inbound.Tag,
|
||||||
|
"type": "local",
|
||||||
|
"uri": newLink,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add no local links
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["type"] != "local" {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[index].Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) UpdateClientsOnInboundDelete(tx *gorm.DB, id uint, tag string) error {
|
||||||
|
var clients []model.Client
|
||||||
|
err := tx.Table("clients").
|
||||||
|
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", id).
|
||||||
|
Find(&clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
// Delete inbounds
|
||||||
|
var clientInbounds, newClientInbounds []uint
|
||||||
|
json.Unmarshal(client.Inbounds, &clientInbounds)
|
||||||
|
for _, clientInbound := range clientInbounds {
|
||||||
|
if clientInbound != id {
|
||||||
|
newClientInbounds = append(newClientInbounds, clientInbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.Inbounds, err = json.MarshalIndent(newClientInbounds, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Delete links
|
||||||
|
var clientLinks, newClientLinks []map[string]string
|
||||||
|
json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["remark"] != tag {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) UpdateLinksByInboundChange(tx *gorm.DB, inbounIds []uint, hostname string) error {
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ? and type in ?", inbounIds, util.InboundTypeWithLink).Find(&inbounds).Error
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
var clients []model.Client
|
||||||
|
err = tx.Table("clients").
|
||||||
|
Where("EXISTS (SELECT 1 FROM json_each(clients.inbounds) WHERE json_each.value = ?)", inbound.Id).
|
||||||
|
Find(&clients).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, client := range clients {
|
||||||
|
var clientLinks, newClientLinks []map[string]string
|
||||||
|
json.Unmarshal(client.Links, &clientLinks)
|
||||||
|
newLinks := util.LinkGenerator(client.Config, &inbound, hostname)
|
||||||
|
for _, newLink := range newLinks {
|
||||||
|
newClientLinks = append(newClientLinks, map[string]string{
|
||||||
|
"remark": inbound.Tag,
|
||||||
|
"type": "local",
|
||||||
|
"uri": newLink,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, clientLink := range clientLinks {
|
||||||
|
if clientLink["remark"] != inbound.Tag {
|
||||||
|
newClientLinks = append(newClientLinks, clientLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Links, err = json.MarshalIndent(newClientLinks, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Save(&client).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ClientService) DepleteClients() error {
|
||||||
var err error
|
var err error
|
||||||
var clients []model.Client
|
var clients []model.Client
|
||||||
var changes []model.Changes
|
var changes []model.Changes
|
||||||
|
var users []string
|
||||||
|
var inboundIds []uint
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
|
||||||
|
tx := db.Begin()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
tx.Commit()
|
||||||
|
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||||
|
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||||
|
if err1 != nil {
|
||||||
|
logger.Error("unable to restart inbounds: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tx.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Scan(&clients).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dt := time.Now().Unix()
|
dt := time.Now().Unix()
|
||||||
var users, inbounds []string
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
logger.Debug("Client ", client.Name, " is going to be disabled")
|
logger.Debug("Client ", client.Name, " is going to be disabled")
|
||||||
users = append(users, client.Name)
|
users = append(users, client.Name)
|
||||||
var userInbounds []string
|
var userInbounds []uint
|
||||||
json.Unmarshal(client.Inbounds, &userInbounds)
|
json.Unmarshal(client.Inbounds, &userInbounds)
|
||||||
inbounds = append(inbounds, userInbounds...)
|
inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds)
|
||||||
changes = append(changes, model.Changes{
|
changes = append(changes, model.Changes{
|
||||||
DateTime: dt,
|
DateTime: dt,
|
||||||
Actor: "DepleteJob",
|
Actor: "DepleteJob",
|
||||||
@@ -76,16 +267,32 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) {
|
|||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
if len(changes) > 0 {
|
if len(changes) > 0 {
|
||||||
err = db.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
err = tx.Model(model.Client{}).Where("enable = true AND ((volume >0 AND up+down > volume) OR (expiry > 0 AND expiry < ?))", now).Update("enable", false).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
err = db.Model(model.Changes{}).Create(&changes).Error
|
err = tx.Model(model.Changes{}).Create(&changes).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
LastUpdate = dt
|
LastUpdate = dt
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, inbounds, nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid duplicate inboundIds
|
||||||
|
func (s *ClientService) uniqueAppendInboundIds(a []uint, b []uint) []uint {
|
||||||
|
m := make(map[uint]bool)
|
||||||
|
for _, v := range a {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
for _, v := range b {
|
||||||
|
m[v] = true
|
||||||
|
}
|
||||||
|
var res []uint
|
||||||
|
for k := range m {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
+158
-281
@@ -2,26 +2,27 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"s-ui/core"
|
||||||
"s-ui/config"
|
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"s-ui/singbox"
|
"s-ui/util/common"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ApiAddr string
|
var (
|
||||||
var LastUpdate int64
|
LastUpdate int64
|
||||||
var IsSystemd bool
|
corePtr *core.Core
|
||||||
|
)
|
||||||
|
|
||||||
type ConfigService struct {
|
type ConfigService struct {
|
||||||
ClientService
|
ClientService
|
||||||
TlsService
|
TlsService
|
||||||
InDataService
|
|
||||||
singbox.Controller
|
|
||||||
SettingService
|
SettingService
|
||||||
|
InboundService
|
||||||
|
OutboundService
|
||||||
|
EndpointService
|
||||||
}
|
}
|
||||||
|
|
||||||
type SingBoxConfig struct {
|
type SingBoxConfig struct {
|
||||||
@@ -30,90 +31,95 @@ type SingBoxConfig struct {
|
|||||||
Ntp json.RawMessage `json:"ntp"`
|
Ntp json.RawMessage `json:"ntp"`
|
||||||
Inbounds []json.RawMessage `json:"inbounds"`
|
Inbounds []json.RawMessage `json:"inbounds"`
|
||||||
Outbounds []json.RawMessage `json:"outbounds"`
|
Outbounds []json.RawMessage `json:"outbounds"`
|
||||||
|
Endpoints []json.RawMessage `json:"endpoints"`
|
||||||
Route json.RawMessage `json:"route"`
|
Route json.RawMessage `json:"route"`
|
||||||
Experimental json.RawMessage `json:"experimental"`
|
Experimental json.RawMessage `json:"experimental"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigService() *ConfigService {
|
func NewConfigService(core *core.Core) *ConfigService {
|
||||||
|
corePtr = core
|
||||||
return &ConfigService{}
|
return &ConfigService{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) InitConfig() error {
|
func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) {
|
||||||
IsSystemd = config.IsSystemd()
|
var err error
|
||||||
configPath := config.GetBinFolderPath()
|
if len(data) == 0 {
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
data, err = s.SettingService.GetConfig()
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
defaultConfig := []byte(config.GetDefaultConfig())
|
|
||||||
err = os.MkdirAll(configPath, 01764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.WriteFile(configPath+"/config.json", defaultConfig, 0764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data = defaultConfig
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var singboxConfig SingBoxConfig
|
|
||||||
err = json.Unmarshal(data, &singboxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.RefreshApiAddr(&singboxConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) GetConfig() (*SingBoxConfig, error) {
|
|
||||||
configPath := config.GetBinFolderPath()
|
|
||||||
data, err := os.ReadFile(configPath + "/config.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
singboxConfig := SingBoxConfig{}
|
singboxConfig := SingBoxConfig{}
|
||||||
err = json.Unmarshal(data, &singboxConfig)
|
err = json.Unmarshal([]byte(data), &singboxConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
singboxConfig.Inbounds, err = s.InboundService.GetAllConfig(database.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
singboxConfig.Outbounds, err = s.OutboundService.GetAllConfig(database.GetDB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &singboxConfig, nil
|
return &singboxConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error {
|
func (s *ConfigService) StartCore(defaultConfig string) error {
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
singboxConfig, err := s.GetConfig(defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawConfig, err := json.MarshalIndent(singboxConfig, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.Start(rawConfig)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("start sing-box err:", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("sing-box started")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) RestartCore() error {
|
||||||
|
err := s.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.StartCore("")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) restartCoreWithConfig(config json.RawMessage) error {
|
||||||
|
err := s.StopCore()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.StartCore(string(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) StopCore() error {
|
||||||
|
err := corePtr.Stop()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Info("sing-box stopped")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConfigService) Save(obj string, act string, data json.RawMessage, loginUser string, hostname string) ([]string, error) {
|
||||||
var err error
|
var err error
|
||||||
var clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes
|
var inboundIds []uint
|
||||||
if _, ok := changes["clients"]; ok {
|
var inboundId uint
|
||||||
err = json.Unmarshal([]byte(changes["clients"]), &clientChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["tls"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["tls"]), &tlsChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["inData"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["inData"]), &inChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["settings"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["settings"]), &settingChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, ok := changes["config"]; ok {
|
|
||||||
err = json.Unmarshal([]byte(changes["config"]), &configChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
@@ -125,99 +131,96 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string)
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if len(clientChanges) > 0 {
|
switch obj {
|
||||||
err = s.ClientService.Save(tx, clientChanges)
|
case "clients":
|
||||||
if err != nil {
|
inboundIds, err = s.ClientService.Save(tx, act, data, hostname)
|
||||||
return err
|
case "tls":
|
||||||
}
|
inboundIds, err = s.TlsService.Save(tx, act, data)
|
||||||
}
|
|
||||||
if len(tlsChanges) > 0 {
|
|
||||||
err = s.TlsService.Save(tx, tlsChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(inChanges) > 0 {
|
|
||||||
err = s.InDataService.Save(tx, inChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(settingChanges) > 0 {
|
|
||||||
err = s.SettingService.Save(tx, settingChanges)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(configChanges) > 0 {
|
|
||||||
singboxConfig, err := s.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newConfig := *singboxConfig
|
|
||||||
for _, change := range configChanges {
|
|
||||||
rawObject := change.Obj
|
|
||||||
switch change.Key {
|
|
||||||
case "all":
|
|
||||||
err = json.Unmarshal(rawObject, &newConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "log":
|
|
||||||
newConfig.Log = rawObject
|
|
||||||
case "dns":
|
|
||||||
newConfig.Dns = rawObject
|
|
||||||
case "ntp":
|
|
||||||
newConfig.Ntp = rawObject
|
|
||||||
case "route":
|
|
||||||
newConfig.Route = rawObject
|
|
||||||
case "experimental":
|
|
||||||
newConfig.Experimental = rawObject
|
|
||||||
case "inbounds":
|
case "inbounds":
|
||||||
if change.Action == "edit" {
|
inboundId, err = s.InboundService.Save(tx, act, data, hostname)
|
||||||
newConfig.Inbounds[change.Index] = rawObject
|
|
||||||
} else if change.Action == "del" {
|
|
||||||
newConfig.Inbounds = append(newConfig.Inbounds[:change.Index], newConfig.Inbounds[change.Index+1:]...)
|
|
||||||
} else {
|
|
||||||
newConfig.Inbounds = append(newConfig.Inbounds, rawObject)
|
|
||||||
}
|
|
||||||
case "outbounds":
|
case "outbounds":
|
||||||
if change.Action == "edit" {
|
err = s.OutboundService.Save(tx, act, data)
|
||||||
newConfig.Outbounds[change.Index] = rawObject
|
case "endpoints":
|
||||||
} else if change.Action == "del" {
|
err = s.EndpointService.Save(tx, act, data)
|
||||||
newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...)
|
case "config":
|
||||||
} else {
|
err = s.SettingService.SaveConfig(tx, data)
|
||||||
newConfig.Outbounds = append(newConfig.Outbounds, rawObject)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Save(&newConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
err = s.restartCoreWithConfig(data)
|
||||||
|
default:
|
||||||
|
return nil, common.NewError("unknown object: ", obj)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log changes
|
|
||||||
dt := time.Now().Unix()
|
dt := time.Now().Unix()
|
||||||
allChanges := append(clientChanges, settingChanges...)
|
err = tx.Create(&model.Changes{
|
||||||
allChanges = append(allChanges, configChanges...)
|
DateTime: dt,
|
||||||
allChanges = append(allChanges, tlsChanges...)
|
Actor: loginUser,
|
||||||
allChanges = append(allChanges, inChanges...)
|
Key: obj,
|
||||||
if len(allChanges) > 0 {
|
Action: act,
|
||||||
for index := range allChanges {
|
Obj: data,
|
||||||
allChanges[index].DateTime = dt
|
}).Error
|
||||||
allChanges[index].Actor = loginUser
|
|
||||||
}
|
|
||||||
err = tx.Model(model.Changes{}).Create(&allChanges).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// Commit changes so far
|
||||||
|
tx.Commit()
|
||||||
|
LastUpdate = time.Now().Unix()
|
||||||
|
var objs []string = []string{obj}
|
||||||
|
tx = db.Begin()
|
||||||
|
|
||||||
|
// Update side changes
|
||||||
|
|
||||||
|
// Update client links
|
||||||
|
if obj == "tls" && len(inboundIds) > 0 {
|
||||||
|
err = s.ClientService.UpdateLinksByInboundChange(tx, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs = append(objs, "clients")
|
||||||
|
}
|
||||||
|
if obj == "inbounds" && act != "add" {
|
||||||
|
switch act {
|
||||||
|
case "edit":
|
||||||
|
err = s.ClientService.UpdateLinksByInboundChange(tx, []uint{inboundId}, hostname)
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = s.ClientService.UpdateClientsOnInboundDelete(tx, inboundId, tag)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objs = append(objs, "clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
LastUpdate = dt
|
// Update out_json of inbounds when tls is changed
|
||||||
|
if obj == "tls" && len(inboundIds) > 0 {
|
||||||
|
err = s.InboundService.UpdateOutJsons(tx, inboundIds, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, common.NewError("unable to update out_json of inbounds: ", err.Error())
|
||||||
|
}
|
||||||
|
objs = append(objs, "inbounds")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
if len(inboundIds) > 0 && corePtr.IsRunning() {
|
||||||
|
err1 := s.InboundService.RestartInbounds(tx, inboundIds)
|
||||||
|
if err1 != nil {
|
||||||
|
logger.Error("unable to restart inbounds: ", err1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to start core if it is not running
|
||||||
|
if !corePtr.IsRunning() {
|
||||||
|
s.StartCore("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) CheckChanges(lu string) (bool, error) {
|
func (s *ConfigService) CheckChanges(lu string) (bool, error) {
|
||||||
@@ -238,132 +241,6 @@ func (s *ConfigService) CheckChanges(lu string) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConfigService) Save(singboxConfig *SingBoxConfig) error {
|
|
||||||
configPath := config.GetBinFolderPath()
|
|
||||||
_, err := os.Stat(configPath + "/config.json")
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = os.MkdirAll(configPath, 01764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.MarshalIndent(singboxConfig, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile(configPath+"/config.json", data, 0764)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.RefreshApiAddr(singboxConfig)
|
|
||||||
s.Controller.Restart()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) RefreshApiAddr(singboxConfig *SingBoxConfig) error {
|
|
||||||
Env_API := config.GetEnvApi()
|
|
||||||
if len(Env_API) > 0 {
|
|
||||||
ApiAddr = Env_API
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
if singboxConfig == nil {
|
|
||||||
singboxConfig, err = s.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var experimental struct {
|
|
||||||
V2rayApi struct {
|
|
||||||
Listen string `json:"listen"`
|
|
||||||
Stats interface{} `jaon:"stats"`
|
|
||||||
} `json:"v2ray_api"`
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(singboxConfig.Experimental, &experimental)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiAddr = experimental.V2rayApi.Listen
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) DepleteClients() error {
|
|
||||||
users, inbounds, err := s.ClientService.DepleteClients()
|
|
||||||
if err != nil || len(users) == 0 || len(inbounds) == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
singboxConfig, err := s.GetConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for inbound_index, inbound := range singboxConfig.Inbounds {
|
|
||||||
var inboundJson map[string]interface{}
|
|
||||||
json.Unmarshal(inbound, &inboundJson)
|
|
||||||
if s.contains(inbounds, inboundJson["tag"].(string)) {
|
|
||||||
inbound_users, ok := inboundJson["users"].([]interface{})
|
|
||||||
if ok {
|
|
||||||
var updatedUsers []interface{}
|
|
||||||
for _, user := range inbound_users {
|
|
||||||
userMap, ok := user.(map[string]interface{})
|
|
||||||
if ok {
|
|
||||||
name, exists := userMap["name"].(string)
|
|
||||||
if exists && s.contains(users, name) {
|
|
||||||
// Skip the user exists
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
username, exists := userMap["username"].(string)
|
|
||||||
if exists && s.contains(users, username) {
|
|
||||||
// Skip the username exists
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updatedUsers = append(updatedUsers, user)
|
|
||||||
}
|
|
||||||
// Exception for Naive and ShadowTLSv3
|
|
||||||
if len(updatedUsers) == 0 {
|
|
||||||
if inboundJson["type"].(string) == "naive" ||
|
|
||||||
(inboundJson["type"].(string) == "shadowtls" &&
|
|
||||||
inboundJson["version"].(float64) == 3) {
|
|
||||||
updatedUsers = append(updatedUsers, make(map[string]interface{}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundJson["users"] = updatedUsers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modifiedInbound, err := json.MarshalIndent(inboundJson, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
singboxConfig.Inbounds[inbound_index] = modifiedInbound
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.Save(singboxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) contains(slice []string, item string) bool {
|
|
||||||
for _, str := range slice {
|
|
||||||
if str == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
|
func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes {
|
||||||
c, _ := strconv.Atoi(count)
|
c, _ := strconv.Atoi(count)
|
||||||
whereString := "`id`>0"
|
whereString := "`id`>0"
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EndpointService struct{}
|
||||||
|
|
||||||
|
func (o *EndpointService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
endpoints := []*model.Endpoint{}
|
||||||
|
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
epData := map[string]interface{}{
|
||||||
|
"id": endpoint.Id,
|
||||||
|
"type": endpoint.Type,
|
||||||
|
"tag": endpoint.Tag,
|
||||||
|
}
|
||||||
|
if endpoint.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(endpoint.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range restFields {
|
||||||
|
epData[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, epData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *EndpointService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var endpointsJson []json.RawMessage
|
||||||
|
var endpoints []*model.Endpoint
|
||||||
|
err := db.Model(model.Endpoint{}).Scan(&endpoints).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
endpointJson, err := endpoint.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
endpointsJson = append(endpointsJson, endpointJson)
|
||||||
|
}
|
||||||
|
return endpointsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EndpointService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var endpoint model.Endpoint
|
||||||
|
err = endpoint.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
configData, err := endpoint.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Endpoint{}).Select("tag").Where("id = ?", endpoint.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveEndpoint(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = corePtr.AddEndpoint(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&endpoint).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveEndpoint(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Endpoint{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"s-ui/database"
|
|
||||||
"s-ui/database/model"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InDataService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InDataService) GetAll() ([]model.InboundData, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
inData := []model.InboundData{}
|
|
||||||
err := db.Model(model.InboundData{}).Scan(&inData).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return inData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InDataService) Save(tx *gorm.DB, changes []model.Changes) error {
|
|
||||||
var err error
|
|
||||||
for _, change := range changes {
|
|
||||||
inData := model.InboundData{}
|
|
||||||
err = json.Unmarshal(change.Obj, &inData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch change.Action {
|
|
||||||
case "new":
|
|
||||||
err = tx.Create(&inData).Error
|
|
||||||
case "del":
|
|
||||||
err = tx.Where("id = ?", change.Index).Delete(model.InboundData{}).Error
|
|
||||||
default:
|
|
||||||
err = tx.Save(inData).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util"
|
||||||
|
"s-ui/util/common"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InboundService struct{}
|
||||||
|
|
||||||
|
func (s *InboundService) Get(ids string) (*[]map[string]interface{}, error) {
|
||||||
|
if ids == "" {
|
||||||
|
return s.GetAll()
|
||||||
|
}
|
||||||
|
return s.getById(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) getById(ids string) (*[]map[string]interface{}, error) {
|
||||||
|
var inbound []model.Inbound
|
||||||
|
var result []map[string]interface{}
|
||||||
|
db := database.GetDB()
|
||||||
|
err := db.Model(model.Inbound{}).Where("id in ?", strings.Split(ids, ",")).Scan(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, inb := range inbound {
|
||||||
|
inbData, err := inb.MarshalFull()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, *inbData)
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inbounds := []model.Inbound{}
|
||||||
|
err := db.Model(model.Inbound{}).Scan(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
inbData := map[string]interface{}{
|
||||||
|
"id": inbound.Id,
|
||||||
|
"type": inbound.Type,
|
||||||
|
"tag": inbound.Tag,
|
||||||
|
"tls_id": inbound.TlsId,
|
||||||
|
}
|
||||||
|
if inbound.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(inbound.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbData["listen"] = restFields["listen"]
|
||||||
|
inbData["listen_port"] = restFields["listen_port"]
|
||||||
|
}
|
||||||
|
data = append(data, inbData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) FromIds(ids []uint) ([]*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
inbounds := []*model.Inbound{}
|
||||||
|
err := db.Model(model.Inbound{}).Where("id in ?", ids).Scan(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbounds, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) (uint, error) {
|
||||||
|
var err error
|
||||||
|
var id uint
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var inbound model.Inbound
|
||||||
|
err = inbound.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
id = inbound.Id
|
||||||
|
if inbound.TlsId > 0 {
|
||||||
|
err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Inbound{}).Select("tag").Where("id = ?", inbound.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveInbound(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundConfig, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = corePtr.AddInbound(inboundConfig)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = util.FillOutJson(&inbound, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveInbound(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Select("id").Where("tag = ?", tag).Scan(&id).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Inbound{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) UpdateOutJsons(tx *gorm.DB, inboundIds []uint, hostname string) error {
|
||||||
|
var inbounds []model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", inboundIds).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err = util.FillOutJson(&inbound, hostname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Where("tag = ?", inbound.Tag).Update("out_json", inbound.OutJson).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var inboundsJson []json.RawMessage
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).Preload("Tls").Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
inboundJson, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inboundJson, err = s.addUsers(db, inboundJson, inbound.Id, inbound.Type)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inboundsJson = append(inboundsJson, inboundJson)
|
||||||
|
}
|
||||||
|
return inboundsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) addUsers(db *gorm.DB, inboundJson []byte, inboundId uint, inboundType string) ([]byte, error) {
|
||||||
|
switch inboundType {
|
||||||
|
case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return inboundJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var inbound map[string]interface{}
|
||||||
|
err := json.Unmarshal(inboundJson, &inbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if inboundType == "shadowsocks" {
|
||||||
|
method, _ := inbound["method"].(string)
|
||||||
|
if method == "2022-blake3-aes-128-gcm" {
|
||||||
|
inboundType = "shadowsocks16"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var users []string
|
||||||
|
err = db.Raw(`SELECT json_extract(clients.config, ?)
|
||||||
|
FROM clients, json_each(clients.inbounds) as je
|
||||||
|
WHERE clients.enable = true AND je.value = ?;`,
|
||||||
|
"$."+inboundType, inboundId).Scan(&users).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var usersJson []json.RawMessage
|
||||||
|
for _, user := range users {
|
||||||
|
usersJson = append(usersJson, json.RawMessage(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound["users"] = usersJson
|
||||||
|
return json.Marshal(inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) RestartInbounds(tx *gorm.DB, ids []uint) error {
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
err := tx.Model(model.Inbound{}).Preload("Tls").Where("id in ?", ids).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
err = corePtr.RemoveInbound(inbound.Tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inboundConfig, err := inbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type)
|
||||||
|
err = corePtr.AddInbound(inboundConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"s-ui/database"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OutboundService struct{}
|
||||||
|
|
||||||
|
func (o *OutboundService) GetAll() (*[]map[string]interface{}, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
outbounds := []*model.Outbound{}
|
||||||
|
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var data []map[string]interface{}
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
outData := map[string]interface{}{
|
||||||
|
"id": outbound.Id,
|
||||||
|
"type": outbound.Type,
|
||||||
|
"tag": outbound.Tag,
|
||||||
|
}
|
||||||
|
if outbound.Options != nil {
|
||||||
|
var restFields map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(outbound.Options, &restFields); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, v := range restFields {
|
||||||
|
outData[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data = append(data, outData)
|
||||||
|
}
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OutboundService) GetAllConfig(db *gorm.DB) ([]json.RawMessage, error) {
|
||||||
|
var outboundsJson []json.RawMessage
|
||||||
|
var outbounds []*model.Outbound
|
||||||
|
err := db.Model(model.Outbound{}).Scan(&outbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
outboundJson, err := outbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outboundsJson = append(outboundsJson, outboundJson)
|
||||||
|
}
|
||||||
|
return outboundsJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OutboundService) Save(tx *gorm.DB, act string, data json.RawMessage) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch act {
|
||||||
|
case "new", "edit":
|
||||||
|
var outbound model.Outbound
|
||||||
|
err = outbound.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
configData, err := outbound.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if act == "edit" {
|
||||||
|
var oldTag string
|
||||||
|
err = tx.Model(model.Outbound{}).Select("tag").Where("id = ?", outbound.Id).Find(&oldTag).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = corePtr.RemoveOutbound(oldTag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = corePtr.AddOutbound(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Save(&outbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "del":
|
||||||
|
var tag string
|
||||||
|
err = json.Unmarshal(data, &tag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if corePtr.IsRunning() {
|
||||||
|
err = corePtr.RemoveOutbound(tag)
|
||||||
|
if err != nil && err != os.ErrInvalid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = tx.Where("tag = ?", tag).Delete(model.Outbound{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return common.NewErrorf("unknown action: %s", act)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+68
-50
@@ -1,24 +1,24 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"encoding/base64"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"s-ui/config"
|
"s-ui/config"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/cpu"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/shirou/gopsutil/v3/host"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v4/host"
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v4/mem"
|
||||||
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerService struct {
|
type ServerService struct{}
|
||||||
SingBoxService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
|
func (s *ServerService) GetStatus(request string) *map[string]interface{} {
|
||||||
status := make(map[string]interface{}, 0)
|
status := make(map[string]interface{}, 0)
|
||||||
@@ -91,15 +91,21 @@ func (s *ServerService) GetNetInfo() map[string]interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
func (s *ServerService) GetSingboxInfo() map[string]interface{} {
|
||||||
info := make(map[string]interface{}, 0)
|
var rtm runtime.MemStats
|
||||||
sysStats, err := s.SingBoxService.GetSysStats()
|
runtime.ReadMemStats(&rtm)
|
||||||
if err == nil {
|
isRunning := corePtr.IsRunning()
|
||||||
info["running"] = true
|
uptime := uint32(0)
|
||||||
info["stats"] = sysStats
|
if isRunning {
|
||||||
} else {
|
uptime = corePtr.GetInstance().Uptime()
|
||||||
info["running"] = s.SingBoxService.IsRunning()
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"running": isRunning,
|
||||||
|
"stats": map[string]interface{}{
|
||||||
|
"NumGoroutine": uint32(runtime.NumGoroutine()),
|
||||||
|
"Alloc": rtm.Alloc,
|
||||||
|
"Uptime": uptime,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return info
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
||||||
@@ -139,48 +145,60 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetLogs(service string, count string, level string) []string {
|
func (s *ServerService) GetLogs(count string, level string) []string {
|
||||||
c, _ := strconv.Atoi(count)
|
c, _ := strconv.Atoi(count)
|
||||||
|
|
||||||
if service == "s-ui" {
|
|
||||||
return logger.GetLogs(c, level)
|
return logger.GetLogs(c, level)
|
||||||
}
|
}
|
||||||
var lines []string
|
|
||||||
var cmdArgs []string
|
|
||||||
if IsSystemd {
|
|
||||||
cmdArgs = []string{"journalctl", "-u", service, "--no-pager", "-n", count, "-p", level}
|
|
||||||
} else {
|
|
||||||
cmdArgs = []string{"tail", "/logs/" + service + ".log", "-n", count}
|
|
||||||
}
|
|
||||||
// Run the command
|
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return []string{"Failed to get logs!", err.Error()}
|
|
||||||
}
|
|
||||||
lines = strings.Split(out.String(), "\n")
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerService) GenKeypair(keyType string, options string) []string {
|
func (s *ServerService) GenKeypair(keyType string, options string) []string {
|
||||||
if len(keyType) == 0 {
|
if len(keyType) == 0 {
|
||||||
return []string{"No keypair to generate"}
|
return []string{"No keypair to generate"}
|
||||||
}
|
}
|
||||||
sbExec := s.GetBinaryPath()
|
|
||||||
cmdArgs := []string{"generate", keyType + "-keypair"}
|
switch keyType {
|
||||||
if keyType == "tls" || keyType == "ech" {
|
case "ech":
|
||||||
cmdArgs = append(cmdArgs, options)
|
return s.generateECHKeyPair(options)
|
||||||
|
case "tls":
|
||||||
|
return s.generateTLSKeyPair(options)
|
||||||
|
case "reality":
|
||||||
|
return s.generateRealityKeyPair()
|
||||||
|
case "wireguard":
|
||||||
|
return generateWireGuardKey()
|
||||||
}
|
}
|
||||||
// Run the command
|
|
||||||
cmd := exec.Command(sbExec, cmdArgs...)
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return []string{"Failed to generate keypair"}
|
return []string{"Failed to generate keypair"}
|
||||||
}
|
}
|
||||||
return strings.Split(out.String(), "\n")
|
|
||||||
|
func (s *ServerService) generateECHKeyPair(options string) []string {
|
||||||
|
parts := strings.Split(options, ",")
|
||||||
|
configPem, keyPem, err := tls.ECHKeygenDefault(parts[0], parts[1] == "true")
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate ECH keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return append(strings.Split(configPem, "\n"), strings.Split(keyPem, "\n")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) generateTLSKeyPair(serverName string) []string {
|
||||||
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, 12, 0))
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate TLS keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return append(strings.Split(string(privateKeyPem), "\n"), strings.Split(string(publicKeyPem), "\n")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) generateRealityKeyPair() []string {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate Reality keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
return []string{"PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]), "PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:])}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWireGuardKey() []string {
|
||||||
|
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to generate wireguard keypair: ", err.Error()}
|
||||||
|
}
|
||||||
|
return []string{"PrivateKey: " + privateKey.String(), "PublicKey: " + privateKey.PublicKey().String()}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"s-ui/config"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
"s-ui/logger"
|
"s-ui/logger"
|
||||||
@@ -14,6 +15,25 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultConfig = `{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"dns": {},
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": [
|
||||||
|
"dns"
|
||||||
|
],
|
||||||
|
"outbound": "dns-out",
|
||||||
|
"action": "route"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"experimental": {}
|
||||||
|
}`
|
||||||
|
|
||||||
var defaultValueMap = map[string]string{
|
var defaultValueMap = map[string]string{
|
||||||
"webListen": "",
|
"webListen": "",
|
||||||
"webDomain": "",
|
"webDomain": "",
|
||||||
@@ -37,6 +57,8 @@ var defaultValueMap = map[string]string{
|
|||||||
"subShowInfo": "false",
|
"subShowInfo": "false",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
"subJsonExt": "",
|
"subJsonExt": "",
|
||||||
|
"config": defaultConfig,
|
||||||
|
"version": config.GetVersion(),
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -67,6 +89,8 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) {
|
|||||||
|
|
||||||
// Due to security principles
|
// Due to security principles
|
||||||
delete(allSetting, "secret")
|
delete(allSetting, "secret")
|
||||||
|
delete(allSetting, "config")
|
||||||
|
delete(allSetting, "version")
|
||||||
|
|
||||||
return &allSetting, nil
|
return &allSetting, nil
|
||||||
}
|
}
|
||||||
@@ -311,6 +335,22 @@ func (s *SettingService) GetFinalSubURI(host string) (string, error) {
|
|||||||
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
|
return protocol + "://" + host + port + (*allSetting)["subPath"], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetConfig() (string, error) {
|
||||||
|
return s.getString("config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetConfig(config string) error {
|
||||||
|
return s.setString("config", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SaveConfig(tx *gorm.DB, config json.RawMessage) error {
|
||||||
|
configs, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Model(model.Setting{}).Where("key = ?", "config").Update("value", string(configs)).Error
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error {
|
||||||
var err error
|
var err error
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"s-ui/singbox"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SingBoxService struct {
|
|
||||||
singbox.V2rayAPI
|
|
||||||
singbox.Controller
|
|
||||||
StatsService
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SingBoxService) GetStats() error {
|
|
||||||
s.V2rayAPI.Init(ApiAddr)
|
|
||||||
defer s.V2rayAPI.Close()
|
|
||||||
stats, err := s.V2rayAPI.GetStats(true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = s.StatsService.SaveStats(stats)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SingBoxService) GetSysStats() (*map[string]interface{}, error) {
|
|
||||||
err := s.V2rayAPI.Init(ApiAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer s.V2rayAPI.Close()
|
|
||||||
resp, err := s.V2rayAPI.GetSysStats()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]interface{})
|
|
||||||
result["NumGoroutine"] = resp.NumGoroutine
|
|
||||||
result["Alloc"] = resp.Alloc
|
|
||||||
result["Uptime"] = resp.Uptime
|
|
||||||
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
@@ -19,18 +19,22 @@ var onlineResources = &onlines{}
|
|||||||
type StatsService struct {
|
type StatsService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
func (s *StatsService) SaveStats() error {
|
||||||
var err error
|
if !corePtr.IsRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
stats := corePtr.GetInstance().ConnTracker().GetStats()
|
||||||
|
|
||||||
// Reset onlines
|
// Reset onlines
|
||||||
onlineResources.Inbound = nil
|
onlineResources.Inbound = nil
|
||||||
onlineResources.Outbound = nil
|
onlineResources.Outbound = nil
|
||||||
onlineResources.User = nil
|
onlineResources.User = nil
|
||||||
|
|
||||||
if len(stats) == 0 {
|
if len(*stats) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -41,7 +45,7 @@ func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, stat := range stats {
|
for _, stat := range *stats {
|
||||||
if stat.Resource == "user" {
|
if stat.Resource == "user" {
|
||||||
if stat.Direction {
|
if stat.Direction {
|
||||||
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
err = tx.Model(model.Client{}).Where("name = ?", stat.Tag).
|
||||||
@@ -70,7 +74,7 @@ func (s *StatsService) SaveStats(stats []*model.Stats) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) {
|
func (s *StatsService) GetStats(resource string, tag string, limit int) ([]model.Stats, error) {
|
||||||
var err error
|
var err error
|
||||||
var result []model.Stats
|
var result []model.Stats
|
||||||
|
|
||||||
@@ -78,7 +82,11 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.
|
|||||||
timeDiff := currentTime - (int64(limit) * 3600)
|
timeDiff := currentTime - (int64(limit) * 3600)
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Model(model.Stats{}).Where("resource = ? AND tag = ? AND date_time > ?", resorce, tag, timeDiff).Scan(&result).Error
|
resources := []string{resource}
|
||||||
|
if resource == "endpoint" {
|
||||||
|
resources = []string{"inbound", "outbound"}
|
||||||
|
}
|
||||||
|
err = db.Model(model.Stats{}).Where("resource in ? AND tag = ? AND date_time > ?", resources, tag, timeDiff).Scan(&result).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-14
@@ -4,11 +4,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"s-ui/database"
|
"s-ui/database"
|
||||||
"s-ui/database/model"
|
"s-ui/database/model"
|
||||||
|
"s-ui/util/common"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TlsService struct {
|
type TlsService struct {
|
||||||
|
InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TlsService) GetAll() ([]model.Tls, error) {
|
func (s *TlsService) GetAll() ([]model.Tls, error) {
|
||||||
@@ -22,25 +24,45 @@ func (s *TlsService) GetAll() ([]model.Tls, error) {
|
|||||||
return tlsConfig, nil
|
return tlsConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error {
|
func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) {
|
||||||
var err error
|
var err error
|
||||||
for _, change := range changes {
|
var inboundIds []uint
|
||||||
tlsConfig := model.Tls{}
|
|
||||||
err = json.Unmarshal(change.Obj, &tlsConfig)
|
switch action {
|
||||||
|
case "new", "edit":
|
||||||
|
var tls model.Tls
|
||||||
|
err = json.Unmarshal(data, &tls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch change.Action {
|
err = tx.Save(&tls).Error
|
||||||
case "new":
|
if err != nil {
|
||||||
err = tx.Create(&tlsConfig).Error
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.Model(model.Inbound{}).Select("id").Where("tls_id = ?", tls.Id).Scan(&inboundIds).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inboundIds, nil
|
||||||
case "del":
|
case "del":
|
||||||
err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error
|
var id uint
|
||||||
default:
|
err = json.Unmarshal(data, &id)
|
||||||
err = tx.Save(tlsConfig).Error
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
|
}
|
||||||
|
var inboundCount int64
|
||||||
|
err = tx.Model(model.Inbound{}).Where("tls_id = ?", id).Count(&inboundCount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if inboundCount > 0 {
|
||||||
|
return nil, common.NewError("tls in use")
|
||||||
|
}
|
||||||
|
err = tx.Where("id = ?", id).Delete(model.Tls{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package singbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"s-ui/config"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var serviceName = "sing-box"
|
|
||||||
|
|
||||||
type Controller struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetBinaryName() string {
|
|
||||||
return "sing-box"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetBinaryPath() string {
|
|
||||||
return config.GetBinFolderPath() + "/" + s.GetBinaryName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) GetConfigPath() string {
|
|
||||||
return config.GetBinFolderPath() + "/config.json"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) IsRunning() bool {
|
|
||||||
cmd := exec.Command("pgrep", "sing-box")
|
|
||||||
output, err := cmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If pgrep found the Controller, its output will not be empty
|
|
||||||
return strings.TrimSpace(string(output)) != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) signalSingbox(signal string) error {
|
|
||||||
return os.WriteFile(config.GetBinFolderPath()+"/signal", []byte(signal), fs.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) Restart() error {
|
|
||||||
return s.signalSingbox("restart")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Controller) Stop() error {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
return errors.New("Sing-Box is not running")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.signalSingbox("stop")
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package singbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"regexp"
|
|
||||||
"s-ui/database/model"
|
|
||||||
"s-ui/util/common"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
statsService "github.com/v2fly/v2ray-core/v5/app/stats/command"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type V2rayAPI struct {
|
|
||||||
StatsServiceClient *statsService.StatsServiceClient
|
|
||||||
grpcClient *grpc.ClientConn
|
|
||||||
isConnected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) Init(ApiAddr string) (err error) {
|
|
||||||
if len(ApiAddr) == 0 {
|
|
||||||
return common.NewError("The api address is wrong: ", ApiAddr)
|
|
||||||
}
|
|
||||||
v.grpcClient, err = grpc.NewClient(ApiAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.isConnected = true
|
|
||||||
|
|
||||||
ssClient := statsService.NewStatsServiceClient(v.grpcClient)
|
|
||||||
|
|
||||||
v.StatsServiceClient = &ssClient
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) Close() {
|
|
||||||
v.grpcClient.Close()
|
|
||||||
v.StatsServiceClient = nil
|
|
||||||
v.isConnected = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) GetStats(reset bool) ([]*model.Stats, error) {
|
|
||||||
if v.grpcClient == nil {
|
|
||||||
return nil, common.NewError("v2ray api is not initialized")
|
|
||||||
}
|
|
||||||
var trafficRegex = regexp.MustCompile("(inbound|outbound|user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
|
||||||
|
|
||||||
client := *v.StatsServiceClient
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
request := &statsService.QueryStatsRequest{
|
|
||||||
Reset_: reset,
|
|
||||||
}
|
|
||||||
resp, err := client.QueryStats(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dt := time.Now().Unix()
|
|
||||||
stats := make([]*model.Stats, 0)
|
|
||||||
for _, stat := range resp.GetStat() {
|
|
||||||
if stat.Value > 0 {
|
|
||||||
matchs := trafficRegex.FindStringSubmatch(stat.Name)
|
|
||||||
if len(matchs) > 3 {
|
|
||||||
stat := model.Stats{
|
|
||||||
DateTime: dt,
|
|
||||||
Resource: matchs[1],
|
|
||||||
Tag: matchs[2],
|
|
||||||
Direction: matchs[3] == "uplink",
|
|
||||||
Traffic: stat.Value,
|
|
||||||
}
|
|
||||||
stats = append(stats, &stat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2rayAPI) GetSysStats() (*statsService.SysStatsResponse, error) {
|
|
||||||
if v.grpcClient == nil {
|
|
||||||
return nil, common.NewError("v2ray api is not initialized")
|
|
||||||
}
|
|
||||||
client := *v.StatsServiceClient
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
|
||||||
defer cancel()
|
|
||||||
request := &statsService.SysStatsRequest{}
|
|
||||||
resp, err := client.GetSysStats(ctx, request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
+14
-22
@@ -87,27 +87,27 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) {
|
|||||||
return &resultStr, nil
|
return &resultStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JsonService) getData(subId string) (*model.Client, *[]model.InboundData, error) {
|
func (j *JsonService) getData(subId string) (*model.Client, []*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
client := &model.Client{}
|
client := &model.Client{}
|
||||||
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
var inbounds []string
|
var clientInbounds []uint
|
||||||
err = json.Unmarshal(client.Inbounds, &inbounds)
|
err = json.Unmarshal(client.Inbounds, &clientInbounds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
inDatas := &[]model.InboundData{}
|
var inbounds []*model.Inbound
|
||||||
err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error
|
err = db.Model(model.Inbound{}).Where("id in ?", clientInbounds).Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return client, inDatas, nil
|
return client, inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]model.InboundData) (*[]map[string]interface{}, *[]string, error) {
|
func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inbounds []*model.Inbound) (*[]map[string]interface{}, *[]string, error) {
|
||||||
var outbounds []map[string]interface{}
|
var outbounds []map[string]interface{}
|
||||||
var configs map[string]interface{}
|
var configs map[string]interface{}
|
||||||
var outTags []string
|
var outTags []string
|
||||||
@@ -116,7 +116,7 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, inData := range *inDatas {
|
for _, inData := range inbounds {
|
||||||
if len(inData.OutJson) < 5 {
|
if len(inData.OutJson) < 5 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -161,22 +161,14 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode
|
|||||||
newOut["server_port"] = int(port)
|
newOut["server_port"] = int(port)
|
||||||
|
|
||||||
// Override TLS
|
// Override TLS
|
||||||
newTls, overrideTls := addr["tls"].(bool)
|
outTls, _ := newOut["tls"].(map[string]interface{})
|
||||||
if overrideTls {
|
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
tlsIf := map[string]interface{}{}
|
for key, value := range addrTls {
|
||||||
if newTls {
|
outTls[key] = value
|
||||||
tlsIf["enabled"] = true
|
|
||||||
newSNI, overrideSNI := addr["server_name"].(string)
|
|
||||||
if overrideSNI {
|
|
||||||
tlsIf["server_name"] = newSNI
|
|
||||||
}
|
|
||||||
newInsecure, overrideInsecure := addr["insecure"].(bool)
|
|
||||||
if overrideInsecure {
|
|
||||||
tlsIf["insecure"] = newInsecure
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newOut["tls"] = tlsIf
|
newOut["tls"] = outTls
|
||||||
}
|
|
||||||
remark, _ := addr["remark"].(string)
|
remark, _ := addr["remark"].(string)
|
||||||
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
|
newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark)
|
||||||
newOut["tag"] = newTag
|
newOut["tag"] = newTag
|
||||||
|
|||||||
@@ -0,0 +1,509 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"s-ui/database/model"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InboundTypeWithLink = []string{"shadowsocks", "naive", "hysteria", "hysteria2", "tuic", "vless", "trojan", "vmess"}
|
||||||
|
|
||||||
|
func LinkGenerator(clientConfig json.RawMessage, i *model.Inbound, hostname string) []string {
|
||||||
|
inbound, err := i.MarshalFull()
|
||||||
|
if err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tls map[string]interface{}
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
json.Unmarshal(i.Tls.Client, &tls)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userConfig map[string]map[string]interface{}
|
||||||
|
if err := json.Unmarshal(clientConfig, &userConfig); err != nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Addrs []map[string]interface{}
|
||||||
|
json.Unmarshal(i.Addrs, &Addrs)
|
||||||
|
if len(Addrs) == 0 {
|
||||||
|
Addrs = append(Addrs, map[string]interface{}{
|
||||||
|
"server": hostname,
|
||||||
|
"server_port": (*inbound)["listen_port"],
|
||||||
|
"remark": i.Tag,
|
||||||
|
})
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
Addrs[0]["tls"] = tls
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for index, addr := range Addrs {
|
||||||
|
addrRemark, _ := addr["remark"].(string)
|
||||||
|
Addrs[index]["remark"] = i.Tag + addrRemark
|
||||||
|
if addrTls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
newTls := map[string]interface{}{}
|
||||||
|
if oldTls, hasOldTls := tls["tls"].(map[string]interface{}); hasOldTls {
|
||||||
|
for k, v := range oldTls {
|
||||||
|
newTls[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Override tls
|
||||||
|
for k, v := range addrTls {
|
||||||
|
newTls[k] = v
|
||||||
|
}
|
||||||
|
Addrs[index]["tls"] = newTls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case "shadowsocks":
|
||||||
|
return shadowsocksLink(userConfig, *inbound, Addrs)
|
||||||
|
case "naive":
|
||||||
|
return naiveLink(userConfig["naive"], *inbound, Addrs)
|
||||||
|
case "hysteria":
|
||||||
|
return hysteriaLink(userConfig["hysteria"], *inbound, Addrs)
|
||||||
|
case "hysteria2":
|
||||||
|
return hysteria2Link(userConfig["hysteria2"], *inbound, Addrs)
|
||||||
|
case "tuic":
|
||||||
|
return tuicLink(userConfig["tuic"], *inbound, Addrs)
|
||||||
|
case "vless":
|
||||||
|
return vlessLink(userConfig["vless"], *inbound, Addrs)
|
||||||
|
case "trojan":
|
||||||
|
return trojanLink(userConfig["trojan"], *inbound, Addrs)
|
||||||
|
case "vmess":
|
||||||
|
return vmessLink(userConfig["vmess"], *inbound, Addrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shadowsocksLink(
|
||||||
|
userConfig map[string]map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
var userPass []string
|
||||||
|
method, _ := inbound["method"].(string)
|
||||||
|
var pass string
|
||||||
|
if method == "2022-blake3-aes-128-gcm" {
|
||||||
|
pass, _ = userConfig["shadowsocks16"]["password"].(string)
|
||||||
|
} else {
|
||||||
|
pass, _ = userConfig["shadowsocks"]["password"].(string)
|
||||||
|
}
|
||||||
|
userPass = append(userPass, pass)
|
||||||
|
|
||||||
|
if strings.HasPrefix(method, "2022") {
|
||||||
|
inbPass, _ := inbound["password"].(string)
|
||||||
|
userPass = append(userPass, inbPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
uriBase := fmt.Sprintf("ss://%s", toBase64([]byte(fmt.Sprintf("%s:%s", method, strings.Join(userPass, ":")))))
|
||||||
|
|
||||||
|
var links []string
|
||||||
|
for _, addr := range addrs {
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
links = append(links, fmt.Sprintf("%s@%s:%d", uriBase, addr["server"].(string), uint(port)))
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func naiveLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
username, _ := userConfig["username"].(string)
|
||||||
|
|
||||||
|
baseUri := "http2://"
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
params["padding"] = "1"
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["peer"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["tfo"] = "1"
|
||||||
|
} else {
|
||||||
|
params["tfo"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := baseUri + toBase64([]byte(fmt.Sprintf("%s:%s@%s:%d", username, password, addr["server"].(string), uint(port))))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteriaLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
baseUri := "hysteria://"
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if upmbps, ok := inbound["up_mbps"].(string); ok {
|
||||||
|
params["up_mbps"] = upmbps
|
||||||
|
}
|
||||||
|
if downmbps, ok := inbound["down_mbps"].(string); ok {
|
||||||
|
params["down_mbps"] = downmbps
|
||||||
|
}
|
||||||
|
if auth, ok := userConfig["auth_str"].(string); ok {
|
||||||
|
params["auth"] = auth
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["peer"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"].(string); ok {
|
||||||
|
params["obfs"] = obfs
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["fastopen"] = "1"
|
||||||
|
} else {
|
||||||
|
params["fastopen"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteria2Link(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
baseUri := fmt.Sprintf("%s%s@", "hysteria2://", password)
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if upmbps, ok := inbound["up_mbps"].(string); ok {
|
||||||
|
params["up_mbps"] = upmbps
|
||||||
|
}
|
||||||
|
if downmbps, ok := inbound["down_mbps"].(string); ok {
|
||||||
|
params["down_mbps"] = downmbps
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"].(map[string]interface{}); ok {
|
||||||
|
if obfsType, ok := obfs["type"].(string); ok {
|
||||||
|
params["obfs"] = obfsType
|
||||||
|
}
|
||||||
|
if obfsPassword, ok := obfs["password"].(string); ok {
|
||||||
|
params["obfs-password"] = obfsPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tfo, ok := inbound["tcp_fast_open"].(bool); ok && tfo {
|
||||||
|
params["fastopen"] = "1"
|
||||||
|
} else {
|
||||||
|
params["fastopen"] = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuicLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
baseUri := fmt.Sprintf("%s%s:%s@", "tuic://", uuid, password)
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := map[string]string{}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok {
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
if disableSni, ok := tls["disable_sni"].(bool); ok && disableSni {
|
||||||
|
params["disableSni"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if congestionControl, ok := inbound["congestion_control"].(string); ok {
|
||||||
|
params["congestion_control"] = congestionControl
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("%s%s:%d", baseUri, addr["server"].(string), uint(port))
|
||||||
|
links = append(links, addParams(uri, params, addr["remark"].(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func vlessLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
baseParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := baseParams
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
params["security"] = "reality"
|
||||||
|
if pbk, ok := reality["public_key"].(string); ok {
|
||||||
|
params["pbk"] = pbk
|
||||||
|
}
|
||||||
|
if sid, ok := reality["short_id"].(string); ok {
|
||||||
|
params["sid"] = sid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params["security"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
if flow, ok := userConfig["flow"].(string); ok {
|
||||||
|
params["flow"] = flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("vless://%s@%s:%d", uuid, addr["server"].(string), uint(port))
|
||||||
|
uri = addParams(uri, params, addr["remark"].(string))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojanLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
password, _ := userConfig["password"].(string)
|
||||||
|
baseParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
params := baseParams
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
if reality, ok := tls["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
params["security"] = "reality"
|
||||||
|
if pbk, ok := reality["public_key"].(string); ok {
|
||||||
|
params["pbk"] = pbk
|
||||||
|
}
|
||||||
|
if sid, ok := reality["short_id"].(string); ok {
|
||||||
|
params["sid"] = sid
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
params["security"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
params["allowInsecure"] = "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
params["sni"] = sni
|
||||||
|
}
|
||||||
|
if alpn, ok := tls["alpn"].([]interface{}); ok {
|
||||||
|
alpnList := make([]string, len(alpn))
|
||||||
|
for i, v := range alpn {
|
||||||
|
alpnList[i] = v.(string)
|
||||||
|
}
|
||||||
|
params["alpn"] = strings.Join(alpnList, ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
uri := fmt.Sprintf("trojan://%s@%s:%d", password, addr["server"].(string), uint(port))
|
||||||
|
uri = addParams(uri, params, addr["remark"].(string))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmessLink(
|
||||||
|
userConfig map[string]interface{},
|
||||||
|
inbound map[string]interface{},
|
||||||
|
addrs []map[string]interface{}) []string {
|
||||||
|
|
||||||
|
uuid, _ := userConfig["uuid"].(string)
|
||||||
|
trasportParams := getTransportParams(inbound["transport"])
|
||||||
|
var links []string
|
||||||
|
|
||||||
|
baseParams := map[string]interface{}{
|
||||||
|
"v": 2,
|
||||||
|
"id": uuid,
|
||||||
|
"aid": 0,
|
||||||
|
}
|
||||||
|
if trasportParams["type"] == "http" || trasportParams["type"] == "tcp" {
|
||||||
|
baseParams["net"] = "tcp"
|
||||||
|
if trasportParams["type"] == "http" {
|
||||||
|
baseParams["type"] = "http"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseParams["net"] = trasportParams["type"]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
obj := baseParams
|
||||||
|
obj["addr"], _ = addr["server"].(string)
|
||||||
|
port, _ := addr["server_port"].(float64)
|
||||||
|
obj["port"] = uint(port)
|
||||||
|
obj["ps"], _ = addr["remark"].(string)
|
||||||
|
if trasportParams["host"] != "" {
|
||||||
|
obj["host"] = trasportParams["host"]
|
||||||
|
}
|
||||||
|
if trasportParams["path"] != "" {
|
||||||
|
obj["path"] = trasportParams["path"]
|
||||||
|
}
|
||||||
|
if tls, ok := addr["tls"].(map[string]interface{}); ok && tls["enabled"].(bool) {
|
||||||
|
obj["tls"] = "tls"
|
||||||
|
if insecure, ok := tls["insecure"].(bool); ok && insecure {
|
||||||
|
obj["allowInsecure"] = 1
|
||||||
|
}
|
||||||
|
if sni, ok := tls["server_name"].(string); ok {
|
||||||
|
obj["sni"] = sni
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj["tls"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("vmess://%s", toBase64(jsonStr))
|
||||||
|
links = append(links, uri)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBase64(d []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addParams(uri string, params map[string]string, remark string) string {
|
||||||
|
URL, _ := url.Parse(uri)
|
||||||
|
q := URL.Query()
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
URL.RawQuery = q.Encode()
|
||||||
|
URL.Fragment = remark
|
||||||
|
return URL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransportParams(t interface{}) map[string]string {
|
||||||
|
params := map[string]string{}
|
||||||
|
trasport, _ := t.(map[string]interface{})
|
||||||
|
if transportType, ok := trasport["type"].(string); ok {
|
||||||
|
params["type"] = transportType
|
||||||
|
} else {
|
||||||
|
params["type"] = "tcp"
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
switch params["type"] {
|
||||||
|
case "http":
|
||||||
|
if host, ok := trasport["host"].([]interface{}); ok {
|
||||||
|
var hosts []string
|
||||||
|
for _, v := range host {
|
||||||
|
hosts = append(hosts, v.(string))
|
||||||
|
}
|
||||||
|
params["host"] = strings.Join(hosts, ",")
|
||||||
|
}
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
case "ws":
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
if headers, ok := trasport["headers"].(map[string]interface{}); ok {
|
||||||
|
if host, ok := headers["Host"].(string); ok {
|
||||||
|
params["peer"] = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "grpc":
|
||||||
|
if serviceName, ok := trasport["service_name"].(string); ok {
|
||||||
|
params["serviceName"] = serviceName
|
||||||
|
}
|
||||||
|
case "httpupgrade":
|
||||||
|
if host, ok := trasport["host"].(string); ok {
|
||||||
|
params["peer"] = host
|
||||||
|
}
|
||||||
|
if path, ok := trasport["path"].(string); ok {
|
||||||
|
params["path"] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"s-ui/database/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fill Inbound's out_json
|
||||||
|
func FillOutJson(i *model.Inbound, hostname string) error {
|
||||||
|
var outJson map[string]interface{}
|
||||||
|
err := json.Unmarshal(i.OutJson, &outJson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.TlsId > 0 {
|
||||||
|
addTls(&outJson, i.Tls)
|
||||||
|
} else {
|
||||||
|
delete(outJson, "tls")
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound, err := i.MarshalFull()
|
||||||
|
|
||||||
|
outJson["type"] = i.Type
|
||||||
|
outJson["tag"] = i.Tag
|
||||||
|
outJson["server"] = hostname
|
||||||
|
outJson["server_port"] = (*inbound)["listen_port"]
|
||||||
|
|
||||||
|
switch i.Type {
|
||||||
|
case "http", "socks", "mixed":
|
||||||
|
case "shadowsocks":
|
||||||
|
shadowsocksOut(&outJson, *inbound)
|
||||||
|
return nil
|
||||||
|
case "shadowtls":
|
||||||
|
shadowTlsOut(&outJson, *inbound)
|
||||||
|
case "hysteria":
|
||||||
|
hysteriaOut(&outJson, *inbound)
|
||||||
|
case "hysteria2":
|
||||||
|
hysteria2Out(&outJson, *inbound)
|
||||||
|
case "tuic":
|
||||||
|
tuicOut(&outJson, *inbound)
|
||||||
|
case "vless":
|
||||||
|
vlessOut(&outJson, *inbound)
|
||||||
|
case "trojan":
|
||||||
|
trojanOut(&outJson, *inbound)
|
||||||
|
case "vmess":
|
||||||
|
vmessOut(&outJson, *inbound)
|
||||||
|
default:
|
||||||
|
for key := range outJson {
|
||||||
|
delete(outJson, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i.OutJson, err = json.MarshalIndent(outJson, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTls function
|
||||||
|
func addTls(out *map[string]interface{}, tls *model.Tls) {
|
||||||
|
var tlsServer, tlsConfig map[string]interface{}
|
||||||
|
err := json.Unmarshal(tls.Server, &tlsServer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(tls.Client, &tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if enabled, ok := tlsServer["enabled"]; ok {
|
||||||
|
tlsConfig["enabled"] = enabled
|
||||||
|
}
|
||||||
|
if serverName, ok := tlsServer["server_name"]; ok {
|
||||||
|
tlsConfig["server_name"] = serverName
|
||||||
|
}
|
||||||
|
if alpn, ok := tlsServer["alpn"]; ok {
|
||||||
|
tlsConfig["alpn"] = alpn
|
||||||
|
}
|
||||||
|
if minVersion, ok := tlsServer["min_version"]; ok {
|
||||||
|
tlsConfig["min_version"] = minVersion
|
||||||
|
}
|
||||||
|
if maxVersion, ok := tlsServer["max_version"]; ok {
|
||||||
|
tlsConfig["max_version"] = maxVersion
|
||||||
|
}
|
||||||
|
if cipherSuites, ok := tlsServer["cipher_suites"]; ok {
|
||||||
|
tlsConfig["cipher_suites"] = cipherSuites
|
||||||
|
}
|
||||||
|
if reality, ok := tlsServer["reality"].(map[string]interface{}); ok && reality["enabled"].(bool) {
|
||||||
|
realityConfig := tlsConfig["reality"].(map[string]interface{})
|
||||||
|
realityConfig["enabled"] = true
|
||||||
|
if shortIDs, ok := reality["short_id"].([]interface{}); ok && len(shortIDs) > 0 {
|
||||||
|
realityConfig["short_id"] = shortIDs[rand.Intn(len(shortIDs))]
|
||||||
|
}
|
||||||
|
tlsConfig["reality"] = realityConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
(*out)["tls"] = tlsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protocol-specific functions
|
||||||
|
func shadowsocksOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if method, ok := inbound["method"].(string); ok {
|
||||||
|
(*out)["method"] = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func shadowTlsOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if version, ok := inbound["version"].(float64); ok && int(version) == 3 {
|
||||||
|
(*out)["version"] = 3
|
||||||
|
} else {
|
||||||
|
for key := range *out {
|
||||||
|
delete(*out, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*out)["tls"] = map[string]interface{}{"enabled": true}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteriaOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if upMbps, ok := inbound["down_mbps"]; ok {
|
||||||
|
(*out)["up_mbps"] = upMbps
|
||||||
|
}
|
||||||
|
if downMbps, ok := inbound["up_mbps"]; ok {
|
||||||
|
(*out)["down_mbps"] = downMbps
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"]; ok {
|
||||||
|
(*out)["obfs"] = obfs
|
||||||
|
}
|
||||||
|
if recvWindow, ok := inbound["recv_window_conn"]; ok {
|
||||||
|
(*out)["recv_window_conn"] = recvWindow
|
||||||
|
}
|
||||||
|
if disableMTU, ok := inbound["disable_mtu_discovery"]; ok {
|
||||||
|
(*out)["disable_mtu_discovery"] = disableMTU
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hysteria2Out(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if upMbps, ok := inbound["down_mbps"]; ok {
|
||||||
|
(*out)["up_mbps"] = upMbps
|
||||||
|
}
|
||||||
|
if downMbps, ok := inbound["up_mbps"]; ok {
|
||||||
|
(*out)["down_mbps"] = downMbps
|
||||||
|
}
|
||||||
|
if obfs, ok := inbound["obfs"]; ok {
|
||||||
|
(*out)["obfs"] = obfs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuicOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if congestionControl, ok := inbound["congestion_control"].(string); ok {
|
||||||
|
(*out)["congestion_control"] = congestionControl
|
||||||
|
} else {
|
||||||
|
(*out)["congestion_control"] = "cubic"
|
||||||
|
}
|
||||||
|
if zeroRTT, ok := inbound["zero_rtt_handshake"].(bool); ok {
|
||||||
|
(*out)["zero_rtt_handshake"] = zeroRTT
|
||||||
|
}
|
||||||
|
if heartbeat, ok := inbound["heartbeat"]; ok {
|
||||||
|
(*out)["heartbeat"] = heartbeat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vlessOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trojanOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func vmessOut(out *map[string]interface{}, inbound map[string]interface{}) {
|
||||||
|
if transport, ok := inbound["transport"]; ok {
|
||||||
|
(*out)["transport"] = transport
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,4 @@ mkdir -p web/html
|
|||||||
rm -fr web/html/*
|
rm -fr web/html/*
|
||||||
cp -R ../frontend/dist/* web/html/
|
cp -R ../frontend/dist/* web/html/
|
||||||
|
|
||||||
go build -o ../sui main.go
|
go build -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o ../sui main.go
|
||||||
|
|||||||
+1
-61
@@ -3,15 +3,10 @@ services:
|
|||||||
s-ui:
|
s-ui:
|
||||||
image: alireza7/s-ui
|
image: alireza7/s-ui
|
||||||
container_name: s-ui
|
container_name: s-ui
|
||||||
hostname: "S-UI docker"
|
hostname: "s-ui"
|
||||||
volumes:
|
volumes:
|
||||||
- "singbox:/app/bin"
|
|
||||||
- "./db:/app/db"
|
- "./db:/app/db"
|
||||||
- "./cert:/app/cert"
|
- "./cert:/app/cert"
|
||||||
- "logs:/logs"
|
|
||||||
environment:
|
|
||||||
SINGBOX_API: "sing-box:1080"
|
|
||||||
SUI_DB_FOLDER: "db"
|
|
||||||
tty: true
|
tty: true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
@@ -19,64 +14,9 @@ services:
|
|||||||
- "2096:2096"
|
- "2096:2096"
|
||||||
networks:
|
networks:
|
||||||
- s-ui
|
- s-ui
|
||||||
links:
|
|
||||||
- syslog
|
|
||||||
logging:
|
|
||||||
driver: syslog
|
|
||||||
options:
|
|
||||||
tag: "s-ui"
|
|
||||||
syslog-address: "udp://127.0.0.1:1514"
|
|
||||||
entrypoint: "./entrypoint.sh"
|
entrypoint: "./entrypoint.sh"
|
||||||
depends_on:
|
|
||||||
- syslog
|
|
||||||
|
|
||||||
sing-box:
|
|
||||||
image: alireza7/s-ui-singbox
|
|
||||||
container_name: sing-box
|
|
||||||
volumes:
|
|
||||||
- "singbox:/app/"
|
|
||||||
- "./cert:/cert"
|
|
||||||
networks:
|
|
||||||
- s-ui
|
|
||||||
ports:
|
|
||||||
- "443:443"
|
|
||||||
- "1443:1443"
|
|
||||||
- "2443:2443"
|
|
||||||
- "3443:3443"
|
|
||||||
restart: unless-stopped
|
|
||||||
links:
|
|
||||||
- syslog
|
|
||||||
logging:
|
|
||||||
driver: syslog
|
|
||||||
options:
|
|
||||||
tag: "sing-box"
|
|
||||||
syslog-address: "udp://127.0.0.1:1514"
|
|
||||||
depends_on:
|
|
||||||
- s-ui
|
|
||||||
- syslog
|
|
||||||
|
|
||||||
syslog:
|
|
||||||
image: rsyslog/syslog_appliance_alpine
|
|
||||||
container_name: syslog
|
|
||||||
volumes:
|
|
||||||
- "logs:/logs"
|
|
||||||
networks:
|
|
||||||
- s-ui
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:1514:1514/udp"
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- RSYSLOG_CONF_GLOBAL_CONF=template(name="RemoteLogs" type="string" string="/logs/%programname%.log")
|
|
||||||
- RSYSLOG_CONF_INPUT_UDP="input(type=\"imudp\" port=\"1514\" ruleset=\"remote\")"
|
|
||||||
- RSYSLOG_CONF_RULESET_REMOTE="ruleset(name=\"remote\") { action(type=\"omfile\" dynaFile=\"RemoteLogs\") }"
|
|
||||||
command: >
|
|
||||||
sh -c 'touch /config/container_config'
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
s-ui:
|
s-ui:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
|
||||||
logs:
|
|
||||||
singbox:
|
|
||||||
|
|
||||||
Generated
+364
-354
File diff suppressed because it is too large
Load Diff
@@ -24,30 +24,7 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<OutTLS :outbound="addr" v-if="optionTLS" />
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionTLS">
|
|
||||||
<v-switch
|
|
||||||
:label="$t('tls.enable')"
|
|
||||||
color="primary"
|
|
||||||
hide-details
|
|
||||||
@update:model-value="updateTls($event)"
|
|
||||||
v-model="addr.tls" />
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionSNI">
|
|
||||||
<v-text-field
|
|
||||||
label="SNI"
|
|
||||||
hide-details
|
|
||||||
v-model="addr.server_name">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="optionInsecure">
|
|
||||||
<v-switch
|
|
||||||
:label="$t('tls.insecure')"
|
|
||||||
hide-details
|
|
||||||
color="primary"
|
|
||||||
v-model="addr.insecure" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-col cols="auto" align="end" justify="center">
|
<v-col cols="auto" align="end" justify="center">
|
||||||
@@ -63,12 +40,6 @@
|
|||||||
<v-list-item v-if="hasTls">
|
<v-list-item v-if="hasTls">
|
||||||
<v-switch v-model="optionTLS" color="primary" :label="$t('objects.tls')" hide-details></v-switch>
|
<v-switch v-model="optionTLS" color="primary" :label="$t('objects.tls')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item v-if="addr.tls">
|
|
||||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item v-if="addr.tls">
|
|
||||||
<v-switch v-model="optionInsecure" color="primary" :label="$t('tls.insecure')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -77,6 +48,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import OutTLS from '@/components/tls/OutTLS.vue'
|
||||||
export default {
|
export default {
|
||||||
props: ['addr', 'hasTls'],
|
props: ['addr', 'hasTls'],
|
||||||
data() {
|
data() {
|
||||||
@@ -87,28 +59,15 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
optionTLS: {
|
optionTLS: {
|
||||||
get(): boolean { return this.$props.addr.tls != undefined },
|
get(): boolean { return this.$props.addr.tls != undefined },
|
||||||
set(v:boolean) { this.$props.addr.tls = v ? true : undefined; this.updateTls(v) }
|
set(v:boolean) { this.$props.addr.tls = v ? { enabled: true } : undefined; }
|
||||||
},
|
|
||||||
optionSNI: {
|
|
||||||
get(): boolean { return this.$props.addr.server_name != undefined },
|
|
||||||
set(v:boolean) { this.$props.addr.server_name = v ? '' : undefined }
|
|
||||||
},
|
},
|
||||||
optionRemark: {
|
optionRemark: {
|
||||||
get(): boolean { return this.$props.addr.remark != undefined },
|
get(): boolean { return this.$props.addr.remark != undefined },
|
||||||
set(v:boolean) { this.$props.addr.remark = v ? '' : undefined }
|
set(v:boolean) { this.$props.addr.remark = v ? '' : undefined }
|
||||||
},
|
|
||||||
optionInsecure: {
|
|
||||||
get(): boolean { return this.$props.addr.insecure != undefined },
|
|
||||||
set(v:boolean) { this.$props.addr.insecure = v ? false : undefined }
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
components: {
|
||||||
updateTls(v:boolean) {
|
OutTLS
|
||||||
if (!v) {
|
|
||||||
delete this.$props.addr.insecure
|
|
||||||
delete this.$props.addr.server_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
:label="$t('in.port')"
|
:label="$t('in.port')"
|
||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
required
|
required
|
||||||
v-model.number="inbound.listen_port"></v-text-field>
|
v-model.number="inbound.listen_port"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -27,24 +29,6 @@
|
|||||||
v-model="inbound.detour">
|
v-model="inbound.detour">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.sniff" color="primary" :label="$t('listen.sniffing')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="inbound.sniff">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="inbound.sniff_override_destination" color="primary" :label="$t('listen.sniffingOverride')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('listen.sniffingTimeout')"
|
|
||||||
hide-details
|
|
||||||
type="number"
|
|
||||||
min="50"
|
|
||||||
step="50"
|
|
||||||
:suffix="$t('date.ms')"
|
|
||||||
v-model.number="sniffTimeout"></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionTCP">
|
<v-row v-if="optionTCP">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
@@ -68,16 +52,6 @@
|
|||||||
v-model.number="udpTimeout"></v-text-field>
|
v-model.number="udpTimeout"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="optionDS">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('listen.domainStrategy')"
|
|
||||||
:items="['prefer_ipv4','prefer_ipv6','ipv4_only','ipv6_only']"
|
|
||||||
v-model="inbound.domain_strategy">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions class="pt-0" v-if="inbound.type != 'tun'">
|
<v-card-actions class="pt-0" v-if="inbound.type != 'tun'">
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
@@ -95,9 +69,6 @@
|
|||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
<v-switch v-model="optionUDP" color="primary" :label="$t('listen.udpOptions')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionDS" color="primary" :label="$t('listen.domainStrategy')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -118,10 +89,6 @@ export default {
|
|||||||
get() { return this.$props.inbound.udp_timeout ? parseInt(this.$props.inbound.udp_timeout.replace('m','')) : 5 },
|
get() { return this.$props.inbound.udp_timeout ? parseInt(this.$props.inbound.udp_timeout.replace('m','')) : 5 },
|
||||||
set(newValue:number) { this.$props.inbound.udp_timeout = newValue > 0 ? newValue + 'm' : '5m' }
|
set(newValue:number) { this.$props.inbound.udp_timeout = newValue > 0 ? newValue + 'm' : '5m' }
|
||||||
},
|
},
|
||||||
sniffTimeout: {
|
|
||||||
get() { return this.$props.inbound.sniff_timeout ? parseInt(this.$props.inbound.sniff_timeout.replace('ms','')) : 300 },
|
|
||||||
set(newValue:number) { this.$props.inbound.sniff_timeout = newValue > 0 ? newValue + 'ms' : '300ms' }
|
|
||||||
},
|
|
||||||
optionTCP: {
|
optionTCP: {
|
||||||
get(): boolean {
|
get(): boolean {
|
||||||
return this.$props.inbound.tcp_fast_open != undefined &&
|
return this.$props.inbound.tcp_fast_open != undefined &&
|
||||||
@@ -145,10 +112,6 @@ export default {
|
|||||||
optionDetour: {
|
optionDetour: {
|
||||||
get(): boolean { return this.$props.inbound.detour != undefined },
|
get(): boolean { return this.$props.inbound.detour != undefined },
|
||||||
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
set(v:boolean) { this.$props.inbound.detour = v ? this.inTags[0]?? '' : undefined }
|
||||||
},
|
|
||||||
optionDS: {
|
|
||||||
get(): boolean { return this.$props.inbound.domain_strategy != undefined },
|
|
||||||
set(v:boolean) { this.$props.inbound.domain_strategy = v ? 'prefer_ipv4' : undefined }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<LogVue
|
<LogVue
|
||||||
v-model="logModal.visible"
|
v-model="logModal.visible"
|
||||||
:visible="logModal.visible"
|
:visible="logModal.visible"
|
||||||
:logType="logModal.logType"
|
|
||||||
@close="closeLogs"
|
@close="closeLogs"
|
||||||
/>
|
/>
|
||||||
<v-container class="fill-height" :loading="loading">
|
<v-container class="fill-height" :loading="loading">
|
||||||
@@ -93,7 +92,7 @@
|
|||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
v{{ tilesData.sys?.appVersion }}
|
v{{ tilesData.sys?.appVersion }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs('s-ui')">
|
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs()">
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ $t('basic.log.title') + " - S-UI" }}
|
{{ $t('basic.log.title') + " - S-UI" }}
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
@@ -110,12 +109,6 @@
|
|||||||
<v-col cols="8">
|
<v-col cols="8">
|
||||||
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
<v-chip density="compact" color="success" variant="flat" v-if="tilesData.sbd?.running">{{ $t('yes') }}</v-chip>
|
||||||
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
<v-chip density="compact" color="error" variant="flat" v-else>{{ $t('no') }}</v-chip>
|
||||||
<v-chip density="compact" color="transparent" style="cursor: pointer;" @click="openLogs('sing-box')">
|
|
||||||
<v-tooltip activator="parent" location="top">
|
|
||||||
{{ $t('basic.log.title') + " - Sing-Box" }}
|
|
||||||
</v-tooltip>
|
|
||||||
<v-icon icon="mdi-list-box-outline" :color="tilesData.sbd?.running ? 'success': 'error'" />
|
|
||||||
</v-chip>
|
|
||||||
<v-chip density="compact" color="transparent" v-if="tilesData.sbd?.running && !loading" style="cursor: pointer;" @click="restartSingbox()">
|
<v-chip density="compact" color="transparent" v-if="tilesData.sbd?.running && !loading" style="cursor: pointer;" @click="restartSingbox()">
|
||||||
<v-tooltip activator="parent" location="top">
|
<v-tooltip activator="parent" location="top">
|
||||||
{{ $t('actions.restartSb') }}
|
{{ $t('actions.restartSb') }}
|
||||||
@@ -244,16 +237,13 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
const logModal = ref({
|
const logModal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
logType: "s-ui"
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const openLogs = (logType: string) => {
|
const openLogs = () => {
|
||||||
logModal.value.logType = logType
|
|
||||||
logModal.value.visible = true
|
logModal.value.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeLogs = () => {
|
const closeLogs = () => {
|
||||||
logModal.value.logType = "s-ui"
|
|
||||||
logModal.value.visible = false
|
logModal.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,20 @@
|
|||||||
hide-details
|
hide-details
|
||||||
:items="['4','4a','5']"
|
:items="['4','4a','5']"
|
||||||
:label="$t('version')"
|
:label="$t('version')"
|
||||||
v-model="inData.outJson.version">
|
v-model="inData.out_json.version">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
<v-col cols="12" sm="6" md="4" v-if="needNetwork">
|
||||||
<Network :data="inData.outJson" />
|
<Network :data="inData.out_json" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
<v-col cols="12" sm="6" md="4" v-if="needUot">
|
||||||
<UoT :data="inData.outJson" />
|
<UoT :data="inData.out_json" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.HTTP">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('transport.path')"
|
:label="$t('transport.path')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="inData.outJson.path">
|
v-model="inData.out_json.path">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.VMess || type == inTypes.VLESS">
|
||||||
@@ -36,14 +36,14 @@
|
|||||||
hide-details
|
hide-details
|
||||||
:label="$t('types.vmess.security')"
|
:label="$t('types.vmess.security')"
|
||||||
:items="vmessSecurities"
|
:items="vmessSecurities"
|
||||||
v-model="inData.outJson.security">
|
v-model="inData.out_json.security">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch v-model="inData.outJson.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
<v-switch v-model="inData.out_json.global_padding" color="primary" :label="$t('types.vmess.globalPadding')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch v-model="inData.outJson.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
<v-switch v-model="inData.out_json.authenticated_length" color="primary" :label="$t('types.vmess.authLen')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</template>
|
</template>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
<v-col cols="12" sm="6" md="4" v-if="type == inTypes.Hysteria">
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
hide-details
|
hide-details
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
v-model.number="inData.outJson.recv_window">
|
v-model.number="inData.out_json.recv_window">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<template v-if="type == inTypes.TUIC">
|
<template v-if="type == inTypes.TUIC">
|
||||||
@@ -62,16 +62,16 @@
|
|||||||
label="UDP Relay Mode"
|
label="UDP Relay Mode"
|
||||||
:items="['native', 'quic']"
|
:items="['native', 'quic']"
|
||||||
clearable
|
clearable
|
||||||
@click:clear="delete inData.outJson.udp_relay_mode"
|
@click:clear="delete inData.out_json.udp_relay_mode"
|
||||||
v-model="inData.outJson.udp_relay_mode">
|
v-model="inData.out_json.udp_relay_mode">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" label="UDP Over Stream" v-model="inData.outJson.udp_over_stream" hide-details></v-switch>
|
<v-switch color="primary" label="UDP Over Stream" v-model="inData.out_json.udp_over_stream" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</template>
|
</template>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Headers :data="inData.outJson" v-if="type == inTypes.HTTP" />
|
<Headers :data="inData.out_json" v-if="type == inTypes.HTTP" />
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -114,8 +114,8 @@ export default {
|
|||||||
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) },
|
||||||
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
needUot():boolean { return this.havUoT.includes(this.$props.type) },
|
||||||
packet_encoding: {
|
packet_encoding: {
|
||||||
get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; },
|
get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; },
|
||||||
set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined }
|
set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: { Network, UoT, Headers }
|
components: { Network, UoT, Headers }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('out.addr')"
|
:label="$t('out.addr')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="data.server">
|
v-model="address">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
@@ -13,7 +13,16 @@
|
|||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
hide-details
|
hide-details
|
||||||
v-model="data.server_port">
|
v-model="port">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="KeepAlive"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
hide-details
|
||||||
|
v-model="data.persistent_keepalive_interval">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -36,6 +45,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { KeepAlive } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['data'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
@@ -54,6 +65,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
address: {
|
||||||
|
get() { return this.$props.data.address },
|
||||||
|
set(v:string) { this.$props.data.address = v.length > 0 ? v : undefined }
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
get() { return this.$props.data.port },
|
||||||
|
set(v:number) { this.$props.data.port = v > 0 ? v : undefined }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Direct">
|
<v-card subtitle="Direct">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="direction == 'in'">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :data="data" />
|
<Network :data="data" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['direction','data'],
|
props: ['data'],
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,29 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card subtitle="Hysteria2">
|
<v-card subtitle="Hysteria2">
|
||||||
<v-row v-if="direction == 'in'">
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.masquerade != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="HTTP3 server on auth fail"
|
|
||||||
hide-details
|
|
||||||
v-model="data.masquerade">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('types.pw')"
|
|
||||||
hide-details
|
|
||||||
v-model="data.password">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<Network :data="data" />
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="!data.ignore_client_bandwidth">
|
<v-row v-if="!data.ignore_client_bandwidth">
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
@@ -46,8 +22,12 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="data.obfs != undefined">
|
<template v-if="direction == 'in'">
|
||||||
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="data.ignore_client_bandwidth" color="primary" :label="$t('types.hy.ignoreBw')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.obfs != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('types.hy.obfs')"
|
:label="$t('types.hy.obfs')"
|
||||||
hide-details
|
hide-details
|
||||||
@@ -55,6 +35,90 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-card subtitle="Hysteria2 Masquerade" v-if="data.masquerade != undefined">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select v-model="masqueradeType" hide-details :label="$t('type')" :items="masqTypes"></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8" v-if="masqueradeType == ''">
|
||||||
|
<v-text-field
|
||||||
|
label="HTTP3 server on auth fails"
|
||||||
|
placeholder="file:///var/www | http://127.0.0.1:8080"
|
||||||
|
v-model="data.masquerade"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8" v-if="masqueradeType == 'file'">
|
||||||
|
<v-text-field
|
||||||
|
label="File server root directory"
|
||||||
|
placeholder="/var/www"
|
||||||
|
v-model="data.masquerade.directory"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="masqueradeType == 'string'">
|
||||||
|
<v-text-field
|
||||||
|
label="HTTP Code"
|
||||||
|
type="number"
|
||||||
|
min="100"
|
||||||
|
max="599"
|
||||||
|
v-model.number="data.masquerade.status_code"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row v-if="masqueradeType == 'proxy'">
|
||||||
|
<v-col cols="12" sm="6">
|
||||||
|
<v-text-field
|
||||||
|
label="Target URL"
|
||||||
|
placeholder="http://example.com:8080"
|
||||||
|
v-model="data.masquerade.url"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
label="Rewrite Host"
|
||||||
|
placeholder="example.com"
|
||||||
|
v-model="data.masquerade.rewrite_host"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<template v-if="masqueradeType == 'string'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="8">
|
||||||
|
<v-text-field
|
||||||
|
label="Content"
|
||||||
|
v-model="data.masquerade.content"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<Headers :data="data.masquerade" />
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('types.pw')"
|
||||||
|
hide-details
|
||||||
|
v-model="data.password">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<Network :data="data" />
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="8" v-if="optionMPort">
|
||||||
|
<v-text-field
|
||||||
|
:label="$t('rule.portRange') + ' ' + $t('commaSeparated')"
|
||||||
|
v-model="server_ports">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
@@ -63,12 +127,19 @@
|
|||||||
</template>
|
</template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
|
<template v-if="direction == 'in'">
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
<v-switch v-model="optionObfs" color="primary" :label="$t('types.hy.obfs')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
<v-switch v-model="optionMasq" color="primary" label="Masquerade" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-item>
|
||||||
|
<v-switch v-model="optionMPort" color="primary" :label="$t('rule.portRange')" hide-details></v-switch>
|
||||||
|
</v-list-item>
|
||||||
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -78,22 +149,44 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Network from '@/components/Network.vue'
|
import Network from '@/components/Network.vue'
|
||||||
|
import Headers from '@/components/Headers.vue'
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['direction', 'data'],
|
props: ['direction', 'data'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
|
masqTypes: [
|
||||||
|
{ title: i18n.global.t('rule.simple'), value: '' },
|
||||||
|
{ title: "File server", value: "file" },
|
||||||
|
{ title: "Reverse Proxy", value: "proxy" },
|
||||||
|
{ title: "Fixed response", value: "string" },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
down_mbps: {
|
down_mbps: {
|
||||||
get() { return this.$props.data.down_mbps?? 0 },
|
get() { return this.$props.data.down_mbps?? 0 },
|
||||||
set(newValue:number) { this.$props.data.down_mbps = newValue>0 ? newValue : undefined }
|
set(v:number) { this.$props.data.down_mbps = v>0 ? v : undefined }
|
||||||
},
|
},
|
||||||
up_mbps: {
|
up_mbps: {
|
||||||
get() { return this.$props.data.up_mbps?? 0 },
|
get() { return this.$props.data.up_mbps?? 0 },
|
||||||
set(newValue:number) { this.$props.data.up_mbps = newValue>0 ? newValue : undefined }
|
set(v:number) { this.$props.data.up_mbps = v>0 ? v : undefined }
|
||||||
|
},
|
||||||
|
server_ports: {
|
||||||
|
get() { return this.$props.data.server_ports?.join(',')?? [] },
|
||||||
|
set(v:string) { this.$props.data.server_ports = v.length > 0 ? v.split(',') : undefined }
|
||||||
|
},
|
||||||
|
masqueradeType: {
|
||||||
|
get() { return typeof this.$props.data.masquerade === 'object' ? this.$props.data.masquerade.type?? '' : '' },
|
||||||
|
set(v:string) {
|
||||||
|
if (v == '') {
|
||||||
|
this.$props.data.masquerade = ''
|
||||||
|
} else {
|
||||||
|
this.$props.data.masquerade = { type: v }
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
optionObfs: {
|
optionObfs: {
|
||||||
get(): boolean { return this.$props.data.obfs != undefined },
|
get(): boolean { return this.$props.data.obfs != undefined },
|
||||||
@@ -102,8 +195,12 @@ export default {
|
|||||||
optionMasq: {
|
optionMasq: {
|
||||||
get(): boolean { return this.$props.data.masquerade != undefined },
|
get(): boolean { return this.$props.data.masquerade != undefined },
|
||||||
set(v:boolean) { this.$props.data.masquerade = v ? "" : undefined }
|
set(v:boolean) { this.$props.data.masquerade = v ? "" : undefined }
|
||||||
|
},
|
||||||
|
optionMPort: {
|
||||||
|
get(): boolean { return this.$props.data.server_ports != undefined },
|
||||||
|
set(v:boolean) { this.$props.data.server_ports = v ? [] : undefined }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Network }
|
components: { Network, Headers }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -2,22 +2,40 @@
|
|||||||
<v-card subtitle="Wireguard">
|
<v-card subtitle="Wireguard">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="8">
|
<v-col cols="12" sm="8">
|
||||||
<v-text-field v-model="data.private_key" :label="$t('types.wg.privKey')" hide-details></v-text-field>
|
<v-text-field
|
||||||
|
v-model="data.private_key"
|
||||||
|
:label="$t('types.wg.privKey')"
|
||||||
|
append-icon="mdi-key-star"
|
||||||
|
@click:append="newKey()"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="8">
|
<v-col cols="12" sm="8">
|
||||||
<v-text-field v-model="data.peer_public_key" :label="$t('types.wg.pubKey')" hide-details></v-text-field>
|
<v-text-field v-model="address" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8" v-if="data.pre_shared_key != undefined">
|
|
||||||
<v-text-field v-model="data.pre_shared_key" :label="$t('types.wg.psk')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="8">
|
|
||||||
<v-text-field v-model="local_ips" :label="$t('types.wg.localIp') + ' ' + $t('commaSeparated')" hide-details></v-text-field>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.reserved != undefined">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-text-field v-model="reserved" :label="'Reserved ' + $t('commaSeparated')" hide-details></v-text-field>
|
<v-text-field
|
||||||
|
:label="$t('in.port')"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=1
|
||||||
|
v-model.number="data.listen_port">
|
||||||
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4" v-if="data.udp_timeout != undefined">
|
||||||
|
<v-text-field
|
||||||
|
label="UDP Timeout"
|
||||||
|
hide-details
|
||||||
|
type="number"
|
||||||
|
min=0
|
||||||
|
:suffix="$t('date.m')"
|
||||||
|
v-model.number="udp_timeout">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
<v-col cols="12" sm="6" md="4" v-if="data.workers != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('types.wg.worker')"
|
:label="$t('types.wg.worker')"
|
||||||
@@ -39,24 +57,16 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<Network :data="data" />
|
<v-switch v-model="data.system" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="data.interface_name != undefined">
|
<v-col cols="12" sm="6" md="4" v-if="data.name != undefined">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
:label="$t('types.wg.ifName')"
|
:label="$t('types.wg.ifName')"
|
||||||
hide-details
|
hide-details
|
||||||
v-model.number="data.interface_name">
|
v-model="data.name">
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.system_interface" color="primary" :label="$t('types.wg.sysIf')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-switch v-model="data.gso" color="primary" :label="$t('types.wg.gso')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
<v-menu v-model="menu" :close-on-content-click="false" location="start">
|
||||||
@@ -66,10 +76,7 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionPsk" color="primary" :label="$t('types.wg.psk')" hide-details></v-switch>
|
<v-switch v-model="optionUdp" color="primary" label="UDP Timeout" hide-details></v-switch>
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionRsrv" color="primary" label="Reserved" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionWorker" color="primary" :label="$t('types.wg.worker')" hide-details></v-switch>
|
<v-switch v-model="optionWorker" color="primary" :label="$t('types.wg.worker')" hide-details></v-switch>
|
||||||
@@ -80,9 +87,6 @@
|
|||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-switch v-model="optionInterface" color="primary" :label="$t('types.wg.ifName')" hide-details></v-switch>
|
<v-switch v-model="optionInterface" color="primary" :label="$t('types.wg.ifName')" hide-details></v-switch>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionPeers" color="primary" :label="$t('types.wg.multiPeer')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -95,7 +99,7 @@
|
|||||||
<template v-for="(p, index) in data.peers">
|
<template v-for="(p, index) in data.peers">
|
||||||
<v-card style="margin-top: 1rem;">
|
<v-card style="margin-top: 1rem;">
|
||||||
<v-card-subtitle>
|
<v-card-subtitle>
|
||||||
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" />
|
{{ $t('types.wg.peer') + ' ' + (index+1) }} <v-icon icon="mdi-delete" @click="data.peers.splice(index,1)" v-if="data.peers.length > 1" />
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<Peer :data="p" />
|
<Peer :data="p" />
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -104,12 +108,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Network from '@/components/Network.vue'
|
|
||||||
import Peer from '@/components/WgPeer.vue'
|
import Peer from '@/components/WgPeer.vue'
|
||||||
import { WgPeer } from '@/types/outbounds'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['data'],
|
props: ['data'],
|
||||||
|
emits: ["newWgKey"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
menu: false,
|
menu: false,
|
||||||
@@ -117,13 +120,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addPeer() {
|
addPeer() {
|
||||||
this.$props.data.peers.push({server: '', port: ''})
|
this.$props.data.peers.push(this.$props.data.peers[0])
|
||||||
}
|
},
|
||||||
|
newKey() {
|
||||||
|
this.$emit('newWgKey')
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
optionPsk: {
|
optionUdp: {
|
||||||
get(): boolean { return this.$props.data.pre_shared_key != undefined },
|
get(): boolean { return this.$props.data.udp_timeout != undefined },
|
||||||
set(v:boolean) { this.$props.data.pre_shared_key = v ? "" : undefined }
|
set(v:boolean) { this.$props.data.udp_timeout = v ? "5m" : undefined }
|
||||||
},
|
},
|
||||||
optionRsrv: {
|
optionRsrv: {
|
||||||
get(): boolean { return this.$props.data.reserved != undefined },
|
get(): boolean { return this.$props.data.reserved != undefined },
|
||||||
@@ -138,16 +144,12 @@ export default {
|
|||||||
set(v:boolean) { this.$props.data.mtu = v ? 1408 : undefined }
|
set(v:boolean) { this.$props.data.mtu = v ? 1408 : undefined }
|
||||||
},
|
},
|
||||||
optionInterface: {
|
optionInterface: {
|
||||||
get(): boolean { return this.$props.data.interface_name != undefined },
|
get(): boolean { return this.$props.data.name != undefined },
|
||||||
set(v:boolean) { this.$props.data.interface_name = v ? "" : undefined }
|
set(v:boolean) { this.$props.data.name = v ? "" : undefined }
|
||||||
},
|
},
|
||||||
optionPeers: {
|
address: {
|
||||||
get(): boolean { return this.$props.data.peers != undefined },
|
get() { return this.$props.data.address?.join(',') },
|
||||||
set(v:boolean) { this.$props.data.peers = v ? <WgPeer[]>[] : undefined }
|
set(v:string) { this.$props.data.address = v.length > 0 ? v.split(',') : undefined }
|
||||||
},
|
|
||||||
local_ips: {
|
|
||||||
get() { return this.$props.data.local_address?.join(',') },
|
|
||||||
set(v:string) { this.$props.data.local_address = v.length > 0 ? v.split(',') : undefined }
|
|
||||||
},
|
},
|
||||||
reserved: {
|
reserved: {
|
||||||
get() { return this.$props.data.reserved?.join(',') },
|
get() { return this.$props.data.reserved?.join(',') },
|
||||||
@@ -157,7 +159,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
udp_timeout: {
|
||||||
|
get() { return this.$props.data.udp_timeout ? parseInt(this.$props.data.udp_timeout.replace('m','')) : 5 },
|
||||||
|
set(v:number) { this.$props.data.udp_timeout = v > 0 ? v + 'm' : '5m' }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
components: { Network, Peer }
|
components: { Peer }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { acme } from '@/types/inTls'
|
import { acme } from '@/types/tls'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['tls'],
|
props: ['tls'],
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
import { ech } from '@/types/inTls'
|
import { ech } from '@/types/tls'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -93,7 +93,10 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async genECH(){
|
async genECH(){
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const msg = await HttpUtils.get('api/keypairs', { k: "ech", o: this.iTls.server_name?? "''" })
|
const msg = await HttpUtils.get('api/keypairs', {
|
||||||
|
k: "ech",
|
||||||
|
o: this.iTls.server_name?? "''" + "," + this.iTls.ech.pq_signature_schemes_enabled?? false
|
||||||
|
})
|
||||||
this.loading = false
|
this.loading = false
|
||||||
if (msg.success && this.iTls.ech && this.oTls.ech) {
|
if (msg.success && this.iTls.ech && this.oTls.ech) {
|
||||||
this.iTls.ech.key_path=undefined
|
this.iTls.ech.key_path=undefined
|
||||||
|
|||||||
@@ -1,242 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card :subtitle="$t('objects.tls')">
|
<v-card :subtitle="$t('objects.tls')">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tlsOptional">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-switch color="primary" :label="$t('tls.enable')" v-model="tlsEnable" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.enabled">
|
|
||||||
<v-select
|
<v-select
|
||||||
hide-details
|
hide-details
|
||||||
:label="$t('template')"
|
:label="$t('template')"
|
||||||
:items="tlsItems"
|
:items="tlsItems"
|
||||||
@update:model-value="changeTlsItem($event)"
|
v-model="inbound.tls_id">
|
||||||
v-model="tlsId">
|
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<template v-if="tls.enabled && tlsId == 0">
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-btn-toggle v-model="usePath"
|
|
||||||
class="rounded-xl"
|
|
||||||
density="compact"
|
|
||||||
variant="outlined"
|
|
||||||
shaped
|
|
||||||
mandatory>
|
|
||||||
<v-btn
|
|
||||||
@click="tls.key=undefined; tls.certificate=undefined"
|
|
||||||
>{{ $t('tls.usePath') }}</v-btn>
|
|
||||||
<v-btn
|
|
||||||
@click="tls.key_path=undefined; tls.certificate_path=undefined"
|
|
||||||
>{{ $t('tls.useText') }}</v-btn>
|
|
||||||
</v-btn-toggle>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="usePath == 0">
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('tls.certPath')"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.certificate_path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4">
|
|
||||||
<v-text-field
|
|
||||||
:label="$t('tls.keyPath')"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.key_path">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-else>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('tls.cert')"
|
|
||||||
hide-details
|
|
||||||
v-model="certText">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-textarea
|
|
||||||
:label="$t('tls.key')"
|
|
||||||
hide-details
|
|
||||||
v-model="keyText">
|
|
||||||
</v-textarea>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.server_name != undefined">
|
|
||||||
<v-text-field
|
|
||||||
label="SNI"
|
|
||||||
hide-details
|
|
||||||
v-model="tls.server_name">
|
|
||||||
</v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.alpn">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
label="ALPN"
|
|
||||||
multiple
|
|
||||||
:items="alpn"
|
|
||||||
v-model="tls.alpn">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.min_version">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('tls.minVer')"
|
|
||||||
:items="tlsVersions"
|
|
||||||
v-model="tls.min_version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="4" v-if="tls.max_version">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('tls.maxVer')"
|
|
||||||
:items="tlsVersions"
|
|
||||||
v-model="tls.max_version">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="8" v-if="tls.cipher_suites != undefined">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('tls.cs')"
|
|
||||||
multiple
|
|
||||||
:items="cipher_suites"
|
|
||||||
v-model="tls.cipher_suites">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</template>
|
|
||||||
<v-card-actions v-if="tls.enabled && tlsId == 0">
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-menu v-model="menu" :close-on-content-click="false" location="start" v-if="tls.enabled">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-btn v-bind="props" hide-details variant="tonal">{{ $t('tls.options') }}</v-btn>
|
|
||||||
</template>
|
|
||||||
<v-card>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionSNI" color="primary" label="SNI" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionALPN" color="primary" label="ALPN" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMinV" color="primary" :label="$t('tls.minVer')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionMaxV" color="primary" :label="$t('tls.maxVer')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-switch v-model="optionCS" color="primary" :label="$t('tls.cs')" hide-details></v-switch>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</v-menu>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import { iTls, defaultInTls } from '@/types/inTls'
|
|
||||||
export default {
|
export default {
|
||||||
props: ['inbound', 'tlsConfigs', 'tls_id'],
|
props: ['inbound', 'tlsConfigs'],
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
menu: false,
|
|
||||||
usePath: this.$props.inbound.tls.key == undefined ? 0 : 1,
|
|
||||||
defaults: defaultInTls,
|
|
||||||
alpn: [
|
|
||||||
{ title: "H3", value: 'h3' },
|
|
||||||
{ title: "H2", value: 'h2' },
|
|
||||||
{ title: "Http/1.1", value: 'http/1.1' },
|
|
||||||
],
|
|
||||||
tlsVersions: [ '1.0', '1.1', '1.2', '1.3' ],
|
|
||||||
cipher_suites: [
|
|
||||||
{ title: "RSA-AES128-CBC-SHA", value: "TLS_RSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "RSA-AES256-CBC-SHA", value: "TLS_RSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "RSA-AES128-GCM-SHA256", value: "TLS_RSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "RSA-AES256-GCM-SHA384", value: "TLS_RSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "AES128-GCM-SHA256", value: "TLS_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "AES256-GCM-SHA384", value: "TLS_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "CHACHA20-POLY1305-SHA256", value: "TLS_CHACHA20_POLY1305_SHA256" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES128-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES256-CBC-SHA", value: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-RSA-AES128-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-RSA-AES256-CBC-SHA", value: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES128-GCM-SHA256", value: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "ECDHE-ECDSA-AES256-GCM-SHA384", value: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "ECDHE-RSA-AES128-GCM-SHA256", value: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
|
|
||||||
{ title: "ECDHE-RSA-AES256-GCM-SHA384", value: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
|
|
||||||
{ title: "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
|
|
||||||
{ title: "ECDHE-RSA-CHACHA20-POLY1305-SHA256", value: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
tls(): iTls {
|
|
||||||
return <iTls> this.$props.inbound.tls
|
|
||||||
},
|
|
||||||
tlsItems(): any[] {
|
tlsItems(): any[] {
|
||||||
return [ { title: i18n.global.t('none'), value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
return [ { title: i18n.global.t('none'), value: 0 }, ...this.$props.tlsConfigs?.map((t:any) => { return { title: t.name, value: t.id } } )]
|
||||||
},
|
|
||||||
tlsId: {
|
|
||||||
get() { return this.tls_id.value?? 0 },
|
|
||||||
set(newValue: boolean) { this.$props.tls_id.value = newValue }
|
|
||||||
},
|
|
||||||
tlsEnable: {
|
|
||||||
get() { return this.tls.enabled?? false },
|
|
||||||
set(newValue: boolean) {
|
|
||||||
this.$props.inbound.tls = newValue ? { enabled: true } : {}
|
|
||||||
this.$props.tls_id.value = 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tlsOptional(): boolean {
|
|
||||||
return !['hysteria','hysteria2','tuic','naive'].includes(this.$props.inbound.type)
|
|
||||||
},
|
|
||||||
certText: {
|
|
||||||
get(): string { return this.tls.certificate ? this.tls.certificate.join('\n') : '' },
|
|
||||||
set(newValue:string) { this.tls.certificate = newValue.split('\n') }
|
|
||||||
},
|
|
||||||
keyText: {
|
|
||||||
get(): string { return this.tls.key ? this.tls.key.join('\n') : '' },
|
|
||||||
set(newValue:string) { this.tls.key = newValue.split('\n') }
|
|
||||||
},
|
|
||||||
optionSNI: {
|
|
||||||
get(): boolean { return this.tls.server_name != undefined },
|
|
||||||
set(v:boolean) { this.tls.server_name = v ? '' : undefined }
|
|
||||||
},
|
|
||||||
optionALPN: {
|
|
||||||
get(): boolean { return this.tls.alpn != undefined },
|
|
||||||
set(v:boolean) { this.tls.alpn = v ? defaultInTls.alpn : undefined }
|
|
||||||
},
|
|
||||||
optionMinV: {
|
|
||||||
get(): boolean { return this.tls.min_version != undefined },
|
|
||||||
set(v:boolean) { this.tls.min_version = v ? defaultInTls.min_version : undefined }
|
|
||||||
},
|
|
||||||
optionMaxV: {
|
|
||||||
get(): boolean { return this.tls.max_version != undefined },
|
|
||||||
set(v:boolean) { this.tls.max_version = v ? defaultInTls.max_version : undefined }
|
|
||||||
},
|
|
||||||
optionCS: {
|
|
||||||
get(): boolean { return this.tls.cipher_suites != undefined },
|
|
||||||
set(v:boolean) { this.tls.cipher_suites = v ? defaultInTls.cipher_suites : undefined }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
changeTlsItem(id: number){
|
|
||||||
if (id>0) {
|
|
||||||
const tlsConfig = this.$props.tlsConfigs?.findLast((t:any) => t.id == id)
|
|
||||||
if (tlsConfig) this.$props.inbound.tls = tlsConfig.server
|
|
||||||
} else {
|
|
||||||
this.$props.inbound.tls = { enabled: this.tls.enabled }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,7 +216,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { oTls, defaultOutTls } from '@/types/outTls'
|
import { oTls, defaultOutTls } from '@/types/tls'
|
||||||
export default {
|
export default {
|
||||||
props: ['outbound'],
|
props: ['outbound'],
|
||||||
data() {
|
data() {
|
||||||
@@ -252,11 +252,6 @@ export default {
|
|||||||
],
|
],
|
||||||
fingerprints: [
|
fingerprints: [
|
||||||
{ title: "Chrome", value: "chrome" },
|
{ title: "Chrome", value: "chrome" },
|
||||||
{ title: "Chrome PSK", value: "chrome_psk" },
|
|
||||||
{ title: "Chrome PSK Shuffle", value: "chrome_psk_shuffle" },
|
|
||||||
{ title: "Chrome Padding PSK Shuffle", value: "chrome_padding_psk_shuffle" },
|
|
||||||
{ title: "Chrome Post-Quantum", value: "chrome_pq" },
|
|
||||||
{ title: "Chrome Post-Quantum PSK", value: "chrome_pq_psk" },
|
|
||||||
{ title: "Firefox", value: "firefox" },
|
{ title: "Firefox", value: "firefox" },
|
||||||
{ title: "Microsoft Edge", value: "edge" },
|
{ title: "Microsoft Edge", value: "edge" },
|
||||||
{ title: "Apple Safari", value: "safari" },
|
{ title: "Apple Safari", value: "safari" },
|
||||||
@@ -275,7 +270,7 @@ export default {
|
|||||||
},
|
},
|
||||||
tlsEnable: {
|
tlsEnable: {
|
||||||
get() { return Object.hasOwn(this.tls, 'enabled') ? this.tls.enabled : false },
|
get() { return Object.hasOwn(this.tls, 'enabled') ? this.tls.enabled : false },
|
||||||
set(newValue: boolean) { this.$props.outbound.tls = newValue ? { enabled: true } : {} }
|
set(newValue: boolean) { this.$props.outbound.tls = newValue ? { enabled: true } : { enabled: false } }
|
||||||
},
|
},
|
||||||
disable_sni: {
|
disable_sni: {
|
||||||
get() { return this.tls.disable_sni ?? false },
|
get() { return this.tls.disable_sni ?? false },
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
<v-icon v-if="isMobile" icon="mdi-menu" @click="$emit('toggleDrawer')" />
|
||||||
<span v-else style="width: 24px"></span>
|
<span v-else style="width: 24px"></span>
|
||||||
<v-app-bar-title :text="$t(<string>route.name)" class="align-center text-center " />
|
<v-app-bar-title :text="$t(<string>route.name)" class="align-center text-center " />
|
||||||
<v-btn prepend-icon="mdi-content-save" v-if="stateChange" :text="$t('actions.save')" @click="saveChanges"></v-btn>
|
|
||||||
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
<v-icon icon="mdi-theme-light-dark" @click="toggleTheme()" style="margin: 0 10px;"></v-icon>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { useTheme } from "vuetify"
|
import { useTheme } from "vuetify"
|
||||||
import { FindDiff } from "@/plugins/utils"
|
|
||||||
import Data from "@/store/modules/data"
|
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
defineProps(['isMobile'])
|
defineProps(['isMobile'])
|
||||||
@@ -21,27 +18,9 @@ const route = useRoute();
|
|||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
const darkMode = ref(localStorage.getItem('theme') == "dark")
|
||||||
|
|
||||||
const store = Data()
|
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
darkMode.value = !darkMode.value
|
darkMode.value = !darkMode.value
|
||||||
theme.global.name.value = darkMode.value ? "dark" : "light"
|
theme.global.name.value = darkMode.value ? "dark" : "light"
|
||||||
localStorage.setItem('theme', theme.global.name.value)
|
localStorage.setItem('theme', theme.global.name.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveChanges = () => {
|
|
||||||
store.pushData()
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldData = computed((): any => {
|
|
||||||
return {config: store.oldData.config, clients: store.oldData.clients, tls: store.oldData.tlsConfigs, inData: store.oldData.inData}
|
|
||||||
})
|
|
||||||
|
|
||||||
const newData = computed((): any => {
|
|
||||||
return {config: store.config, clients: store.clients, tls: store.tlsConfigs, inData: store.inData}
|
|
||||||
})
|
|
||||||
|
|
||||||
const stateChange = computed((): any => {
|
|
||||||
return !FindDiff.deepCompare(newData.value,oldData.value)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ const menu = [
|
|||||||
{ title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' },
|
{ title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' },
|
||||||
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
{ title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' },
|
||||||
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
{ title: 'pages.outbounds', icon: 'mdi-cloud-upload', path: '/outbounds' },
|
||||||
|
{ title: 'pages.endpoints', icon: 'mdi-cloud-tags', path: '/endpoints' },
|
||||||
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
{ title: 'pages.rules', icon: 'mdi-routes', path: '/rules' },
|
||||||
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
{ title: 'pages.tls', icon: 'mdi-certificate', path: '/tls' },
|
||||||
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
{ title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' },
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<DatePick :expiry="expDate" @submit="setDate" />
|
<DatePick :expiry="expDate" @submit="setDate" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row v-if="index != -1">
|
<v-row v-if="id > 0">
|
||||||
<v-col cols="12" sm="6" md="4" class="d-flex flex-column">
|
<v-col cols="12" sm="6" md="4" class="d-flex flex-column">
|
||||||
<div class="d-flex justify-space-between align-center">
|
<div class="d-flex justify-space-between align-center">
|
||||||
<div>
|
<div>
|
||||||
@@ -70,19 +70,14 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-combobox
|
<v-select
|
||||||
v-model="clientInbounds"
|
v-model="clientInbounds"
|
||||||
:items="inboundTags"
|
:items="inboundTags"
|
||||||
:label="$t('client.inboundTags')"
|
:label="$t('client.inboundTags')"
|
||||||
multiple
|
multiple
|
||||||
chips
|
chips
|
||||||
hide-details
|
hide-details
|
||||||
></v-combobox>
|
></v-select>
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-switch v-model="clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
@@ -183,13 +178,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Link } from '@/plugins/link'
|
import { createClient, randomConfigs, updateConfigs, Link } from '@/types/clients'
|
||||||
import { createClient, randomConfigs, updateConfigs } from '@/types/clients'
|
|
||||||
import DatePick from '@/components/DateTime.vue'
|
import DatePick from '@/components/DateTime.vue'
|
||||||
import { HumanReadable } from '@/plugins/utils'
|
import { HumanReadable } from '@/plugins/utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'index', 'inboundTags', 'groups', 'stats'],
|
props: ['visible', 'data', 'id', 'inboundTags', 'groups'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -206,7 +200,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.index != -1) {
|
if (this.$props.id > 0) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
const newData = JSON.parse(this.$props.data)
|
||||||
this.client = createClient(newData)
|
this.client = createClient(newData)
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
@@ -217,7 +211,6 @@ export default {
|
|||||||
this.title = "add"
|
this.title = "add"
|
||||||
this.clientConfig = randomConfigs('client')
|
this.clientConfig = randomConfigs('client')
|
||||||
}
|
}
|
||||||
this.clientStats = this.$props.stats
|
|
||||||
this.links = this.client.links.filter(l => l.type == 'local')
|
this.links = this.client.links.filter(l => l.type == 'local')
|
||||||
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
this.extLinks = this.client.links.filter(l => l.type == 'external')
|
||||||
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
this.subLinks = this.client.links.filter(l => l.type == 'sub')
|
||||||
@@ -231,7 +224,6 @@ export default {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
this.client.config = updateConfigs(this.clientConfig, this.client.name)
|
this.client.config = updateConfigs(this.clientConfig, this.client.name)
|
||||||
this.client.links = [
|
this.client.links = [
|
||||||
...this.links,
|
|
||||||
...this.extLinks.filter(l => l.uri != ''),
|
...this.extLinks.filter(l => l.uri != ''),
|
||||||
...this.subLinks.filter(l => l.uri != '')]
|
...this.subLinks.filter(l => l.uri != '')]
|
||||||
this.$emit('save', this.client, this.clientStats)
|
this.$emit('save', this.client, this.clientStats)
|
||||||
@@ -243,8 +235,8 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
clientInbounds: {
|
clientInbounds: {
|
||||||
get() { return this.client.inbounds.length>0 ? this.client.inbounds.filter(i => this.inboundTags.includes(i)) : [] },
|
get() { return this.client.inbounds.length>0 ? this.client.inbounds : [] },
|
||||||
set(newValue:string[]) { this.client.inbounds = newValue.length == 0 ? [] : newValue }
|
set(v:number[]) { this.client.inbounds = v.length == 0 ? [] : v }
|
||||||
},
|
},
|
||||||
expDate: {
|
expDate: {
|
||||||
get() { return this.client.expiry},
|
get() { return this.client.expiry},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
<v-card class="rounded-lg">
|
<v-card class="rounded-lg">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
{{ $t('bulk.add') }}
|
{{ $t('actions.addbulk') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
@@ -53,18 +53,15 @@
|
|||||||
v-model="bulkData.clientInbounds"
|
v-model="bulkData.clientInbounds"
|
||||||
:items="inboundTags"
|
:items="inboundTags"
|
||||||
:label="$t('client.inboundTags')"
|
:label="$t('client.inboundTags')"
|
||||||
|
:return-object="false"
|
||||||
multiple
|
multiple
|
||||||
chips
|
chips
|
||||||
hide-details
|
hide-details
|
||||||
></v-combobox>
|
></v-combobox>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
|
||||||
<v-col cols="auto">
|
|
||||||
<v-switch v-model="bulkData.clientStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<pre dir="ltr">{{ bulkData }}</pre>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -109,7 +106,6 @@ export default {
|
|||||||
clientInbounds: [],
|
clientInbounds: [],
|
||||||
expiry: 0,
|
expiry: 0,
|
||||||
Volume: 0,
|
Volume: 0,
|
||||||
clientStats: false,
|
|
||||||
},
|
},
|
||||||
patterns: [
|
patterns: [
|
||||||
{ title: i18n.global.t("bulk.random"), value: "random" },
|
{ title: i18n.global.t("bulk.random"), value: "random" },
|
||||||
@@ -129,7 +125,6 @@ export default {
|
|||||||
clientInbounds: [],
|
clientInbounds: [],
|
||||||
expiry: 0,
|
expiry: 0,
|
||||||
Volume: 0,
|
Volume: 0,
|
||||||
clientStats: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
@@ -140,6 +135,7 @@ export default {
|
|||||||
push.error(i18n.global.t('error.dplData'))
|
push.error(i18n.global.t('error.dplData'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
this.clients = []
|
||||||
this.loading = true
|
this.loading = true
|
||||||
for(let i=0;i<this.count;i++){
|
for(let i=0;i<this.count;i++){
|
||||||
const name = this.genByPattern(this.bulkData.name, i)
|
const name = this.genByPattern(this.bulkData.name, i)
|
||||||
@@ -157,8 +153,7 @@ export default {
|
|||||||
group: this.bulkData.group
|
group: this.bulkData.group
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
this.$emit('save', this.clients, this.bulkData.clientInbounds, this.bulkData.clientStats)
|
this.$emit('save', this.clients)
|
||||||
this.resetData() // reset to default
|
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
genByPattern(pattern: any[], order :number){
|
genByPattern(pattern: any[], order :number){
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<v-dialog transition="dialog-bottom-transition" width="800">
|
||||||
|
<v-card class="rounded-lg">
|
||||||
|
<v-card-title>
|
||||||
|
{{ $t('actions.' + title) + " " + $t('objects.endpoint') }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
hide-details
|
||||||
|
:label="$t('type')"
|
||||||
|
:items="Object.keys(epTypes).map((key,index) => ({title: key, value: Object.values(epTypes)[index]}))"
|
||||||
|
v-model="endpoint.type"
|
||||||
|
@update:modelValue="changeType">
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="endpoint.tag" :label="$t('objects.tag')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<Wireguard v-if="endpoint.type == epTypes.Wireguard" :data="endpoint" @newWgKey="newWgKey" />
|
||||||
|
<Dial :dial="endpoint" :outTags="tags" />
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
@click="closeModal"
|
||||||
|
>
|
||||||
|
{{ $t('actions.close') }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="loading"
|
||||||
|
@click="saveChanges"
|
||||||
|
>
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { EpTypes, createEndpoint } from '@/types/endpoints'
|
||||||
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
import Dial from '@/components/Dial.vue'
|
||||||
|
import Wireguard from '@/components/protocols/Wireguard.vue'
|
||||||
|
import HttpUtils from '@/plugins/httputil'
|
||||||
|
import { push } from 'notivue'
|
||||||
|
import { i18n } from '@/locales'
|
||||||
|
export default {
|
||||||
|
props: ['visible', 'data', 'id', 'tags'],
|
||||||
|
emits: ['close', 'save'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
endpoint: createEndpoint("wireguard",{ "tag": "" }),
|
||||||
|
title: "add",
|
||||||
|
tab: "t1",
|
||||||
|
link: "",
|
||||||
|
loading: false,
|
||||||
|
epTypes: EpTypes,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async updateData() {
|
||||||
|
if (this.$props.id > 0) {
|
||||||
|
const newData = JSON.parse(this.$props.data)
|
||||||
|
this.endpoint = createEndpoint(newData.type, newData)
|
||||||
|
this.title = "edit"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
|
const randomIPoctet = RandomUtil.randomIntRange(1, 255)
|
||||||
|
this.endpoint = createEndpoint("wireguard",{
|
||||||
|
tag: "wireguard-" + RandomUtil.randomSeq(3),
|
||||||
|
address: ['10.0.0.'+ randomIPoctet.toString() +'/32','fe80::'+ randomIPoctet.toString(16) +'/128'],
|
||||||
|
listen_port: port,
|
||||||
|
private_key: (await this.genWgKey()).private_key,
|
||||||
|
peers: [{
|
||||||
|
public_key: (await this.genWgKey()).public_key,
|
||||||
|
allowed_ips: ['0.0.0.0/0', '::/0']
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
this.title = "add"
|
||||||
|
}
|
||||||
|
this.tab = "t1"
|
||||||
|
},
|
||||||
|
changeType() {
|
||||||
|
// Tag change only in add endpoint
|
||||||
|
const tag = this.$props.id > 0 ? this.endpoint.tag : this.endpoint.type + "-" + RandomUtil.randomSeq(3)
|
||||||
|
// Use previous data
|
||||||
|
const prevConfig = { id: this.endpoint.id, tag: tag ,listen: this.endpoint.listen, listen_port: this.endpoint.listen_port }
|
||||||
|
this.endpoint = createEndpoint(this.endpoint.type, prevConfig)
|
||||||
|
},
|
||||||
|
closeModal() {
|
||||||
|
this.updateData() // reset
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
this.$emit('save', this.endpoint)
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
async genWgKey(){
|
||||||
|
this.loading = true
|
||||||
|
const msg = await HttpUtils.get('api/keypairs', { k: "wireguard" })
|
||||||
|
this.loading = false
|
||||||
|
let result = { private_key: "", public_key: "" }
|
||||||
|
if (msg.success) {
|
||||||
|
msg.obj.forEach((line:string) => {
|
||||||
|
if (line.startsWith("PrivateKey")){
|
||||||
|
result.private_key = line.substring(12)
|
||||||
|
}
|
||||||
|
if (line.startsWith("PublicKey")){
|
||||||
|
result.public_key = line.substring(11)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error') + ": " + msg.obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
async newWgKey(){
|
||||||
|
const newKeys = await this.genWgKey()
|
||||||
|
this.endpoint.private_key = newKeys.private_key
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.updateData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { Dial, Wireguard }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog transition="dialog-bottom-transition" width="800">
|
<v-dialog transition="dialog-bottom-transition" width="800" @after-enter="updateData">
|
||||||
<v-card class="rounded-lg">
|
<v-card class="rounded-lg" :loading="loading">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
{{ $t('actions.' + title) + " " + $t('objects.inbound') }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
<v-skeleton-loader
|
||||||
|
class="mx-auto border"
|
||||||
|
width="95%"
|
||||||
|
type="card, text, divider, list-item-two-line"
|
||||||
|
v-if="loading"
|
||||||
|
></v-skeleton-loader>
|
||||||
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
<v-card-text style="padding: 0 16px; overflow-y: scroll;">
|
||||||
<v-container style="padding: 0;">
|
<v-container style="padding: 0;" :hidden="loading">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-select
|
<v-select
|
||||||
@@ -34,7 +40,7 @@
|
|||||||
<v-window v-model="side" style="margin-top: 10px;">
|
<v-window v-model="side" style="margin-top: 10px;">
|
||||||
<v-window-item value="s">
|
<v-window-item value="s">
|
||||||
<Listen :inbound="inbound" :inTags="inTags" />
|
<Listen :inbound="inbound" :inTags="inTags" />
|
||||||
<Direct v-if="inbound.type == inTypes.Direct" direction="in" :data="inbound" />
|
<Direct v-if="inbound.type == inTypes.Direct" :data="inbound" />
|
||||||
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
<Shadowsocks v-if="inbound.type == inTypes.Shadowsocks" direction="in" :data="inbound" />
|
||||||
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
<Hysteria v-if="inbound.type == inTypes.Hysteria" direction="in" :data="inbound" />
|
||||||
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
<Hysteria2 v-if="inbound.type == inTypes.Hysteria2" direction="in" :data="inbound" />
|
||||||
@@ -45,21 +51,20 @@
|
|||||||
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
<TProxy v-if="inbound.type == inTypes.TProxy" :inbound="inbound" />
|
||||||
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
<Transport v-if="Object.hasOwn(inbound,'transport')" :data="inbound" />
|
||||||
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
<Users v-if="HasOptionalUser.includes(inbound.type)" :inbound="inbound" />
|
||||||
<InTls v-if="Object.hasOwn(inbound,'tls')" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="tls_id" />
|
<InTls v-if="HasTls.includes(inbound.type)" :inbound="inbound" :tlsConfigs="tlsConfigs" :tls_id="inbound.tls_id" />
|
||||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="in" :data="inbound" />
|
||||||
<v-switch v-model="inboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item value="c">
|
<v-window-item value="c">
|
||||||
<OutJsonVue :inData="inData" :type="inbound.type" />
|
<OutJsonVue :inData="inbound" :type="inbound.type" />
|
||||||
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inData.outJson" />
|
<Multiplex v-if="Object.hasOwn(inbound,'multiplex')" direction="out" :data="inbound.out_json" />
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-subtitle>{{ $t('in.multiDomain') }}
|
<v-card-subtitle>{{ $t('in.multiDomain') }}
|
||||||
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
<v-icon @click="add_addr" icon="mdi-plus"></v-icon>
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<template v-for="addr,index in inData.addrs">
|
<template v-for="addr,index in inbound.addrs">
|
||||||
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inData.addrs.splice(index,1)" />
|
{{ $t('in.addr') }} #{{ (index+1) }} <v-icon icon="mdi-delete" @click="inbound.addrs?.splice(index,1)" />
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<AddrVue :addr="addr" :hasTls="Object.hasOwn(inbound,'tls')" />
|
<AddrVue :addr="addr" :hasTls="HasTls.includes(inbound.type)" />
|
||||||
</template>
|
</template>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
@@ -79,6 +84,7 @@
|
|||||||
color="blue-darken-1"
|
color="blue-darken-1"
|
||||||
variant="text"
|
variant="text"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
:disabled="!validate"
|
||||||
@click="saveChanges"
|
@click="saveChanges"
|
||||||
>
|
>
|
||||||
{{ $t('actions.save') }}
|
{{ $t('actions.save') }}
|
||||||
@@ -89,8 +95,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { InTypes, createInbound } from '@/types/inbounds'
|
import { InTypes, createInbound, Addr } from '@/types/inbounds'
|
||||||
import { Addr, InData } from '@/plugins/inData'
|
|
||||||
import RandomUtil from '@/plugins/randomUtil'
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
|
|
||||||
import Listen from '@/components/Listen.vue'
|
import Listen from '@/components/Listen.vue'
|
||||||
@@ -109,19 +114,17 @@ import Multiplex from '@/components/Multiplex.vue'
|
|||||||
import Transport from '@/components/Transport.vue'
|
import Transport from '@/components/Transport.vue'
|
||||||
import AddrVue from '@/components/Addr.vue'
|
import AddrVue from '@/components/Addr.vue'
|
||||||
import OutJsonVue from '@/components/OutJson.vue'
|
import OutJsonVue from '@/components/OutJson.vue'
|
||||||
|
import Data from '@/store/modules/data'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'cData', 'index', 'stats', 'inTags', 'outTags', 'tlsConfigs'],
|
props: ['visible', 'id', 'inTags', 'outTags', 'tlsConfigs'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
inbound: createInbound("direct",{ "tag": "" }),
|
inbound: createInbound("direct",{ id:0, "tag": "" }),
|
||||||
inData: <InData>{},
|
|
||||||
title: "add",
|
title: "add",
|
||||||
loading: false,
|
loading: false,
|
||||||
side: "s",
|
side: "s",
|
||||||
inTypes: InTypes,
|
inTypes: InTypes,
|
||||||
inboundStats: false,
|
|
||||||
tls_id: { value: 0 },
|
|
||||||
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
HasOptionalUser: [InTypes.Mixed,InTypes.SOCKS,InTypes.HTTP,InTypes.Shadowsocks],
|
||||||
HasInData: [
|
HasInData: [
|
||||||
InTypes.SOCKS,
|
InTypes.SOCKS,
|
||||||
@@ -136,56 +139,65 @@ export default {
|
|||||||
InTypes.TUIC,
|
InTypes.TUIC,
|
||||||
InTypes.Hysteria2,
|
InTypes.Hysteria2,
|
||||||
InTypes.Naive,
|
InTypes.Naive,
|
||||||
]
|
],
|
||||||
|
HasTls: [
|
||||||
|
InTypes.HTTP,
|
||||||
|
InTypes.VMess,
|
||||||
|
InTypes.Trojan,
|
||||||
|
InTypes.Naive,
|
||||||
|
InTypes.Hysteria,
|
||||||
|
InTypes.TUIC,
|
||||||
|
InTypes.Hysteria2,
|
||||||
|
InTypes.VLESS,
|
||||||
|
],
|
||||||
|
OnlyTLS: [InTypes.Hysteria, InTypes.Hysteria2, InTypes.TUIC, InTypes.Naive ],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async loadData() {
|
||||||
|
this.loading = true
|
||||||
|
const inboundArray = await Data().loadInbounds([this.$props.id])
|
||||||
|
this.inbound = inboundArray[0]
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.index != -1) {
|
if (this.$props.id > 0) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
this.loadData()
|
||||||
this.inbound = createInbound(newData.type, newData)
|
|
||||||
this.tls_id.value = this.$props.tlsConfigs?.findLast((t:any) => t.inbounds?.includes(this.inbound.tag))?.id?? 0
|
|
||||||
if (this.HasInData.includes(this.inbound.type)){
|
|
||||||
this.inData = this.$props.cData?.length> 0 ? <InData>JSON.parse(this.$props.cData) : <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
|
||||||
} else {
|
|
||||||
this.inData = <InData>{id: -1}
|
|
||||||
}
|
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const port = RandomUtil.randomIntRange(10000, 60000)
|
const port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
this.inbound = createInbound("direct",{ tag: "direct-"+port ,listen: "::", listen_port: port })
|
this.inbound = createInbound("direct",{ id: 0, tag: "direct-"+port ,listen: "::", listen_port: port })
|
||||||
this.tls_id.value = 0
|
|
||||||
if (this.HasInData.includes(this.inbound.type)){
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
this.inData = <InData>{id: 0, tag: this.inbound.tag, addrs: [], outJson: {}}
|
this.inbound.addrs = []
|
||||||
|
this.inbound.out_json = {}
|
||||||
} else {
|
} else {
|
||||||
this.inData = <InData>{id: -1}
|
delete this.inbound.addrs
|
||||||
|
delete this.inbound.out_json
|
||||||
}
|
}
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
|
this.loading = false
|
||||||
}
|
}
|
||||||
this.inboundStats = this.$props.stats
|
|
||||||
this.side = "s"
|
this.side = "s"
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
if (!this.inbound.listen_port) this.inbound.listen_port = RandomUtil.randomIntRange(10000, 60000)
|
if (!this.inbound.listen_port) this.inbound.listen_port = RandomUtil.randomIntRange(10000, 60000)
|
||||||
// Tag change only in add inbound
|
// Tag change only in add inbound
|
||||||
const tag = this.$props.index != -1 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
const tag = this.$props.id > 0 ? this.inbound.tag : this.inbound.type + "-" + this.inbound.listen_port
|
||||||
// Use previous data
|
// Use previous data
|
||||||
const prevConfig = { tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
const prevConfig = { id: this.inbound.id, tag: tag ,listen: this.inbound.listen?? "::", listen_port: this.inbound.listen_port }
|
||||||
this.inbound = createInbound(this.inbound.type, this.inbound.type != this.inTypes.Tun ? prevConfig : { tag: tag })
|
this.inbound = createInbound(this.inbound.type, this.inbound.type != this.inTypes.Tun ? prevConfig : { tag: tag })
|
||||||
if (this.HasInData.includes(this.inbound.type)){
|
if (this.HasInData.includes(this.inbound.type)){
|
||||||
if (this.inData.id == -1) this.inData.id = 0
|
this.inbound.addrs = []
|
||||||
this.inData.addrs = []
|
this.inbound.out_json = {}
|
||||||
this.inData.outJson = {}
|
|
||||||
this.inData.tag = tag
|
|
||||||
} else {
|
} else {
|
||||||
this.inData = <InData>{id: -1}
|
delete this.inbound.addrs
|
||||||
|
delete this.inbound.out_json
|
||||||
}
|
}
|
||||||
this.tls_id.value = 0
|
|
||||||
this.side = "s"
|
this.side = "s"
|
||||||
},
|
},
|
||||||
add_addr() {
|
add_addr() {
|
||||||
this.inData.addrs.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
this.inbound.addrs?.push(<Addr>{ server: location.hostname, server_port: this.inbound.listen_port })
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.updateData() // reset
|
this.updateData() // reset
|
||||||
@@ -193,14 +205,23 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$emit('save', this.inbound, this.inboundStats, this.tls_id.value, this.inData)
|
this.$emit('save', this.inbound)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
validate() {
|
||||||
|
if (this.inbound == undefined) return false
|
||||||
|
if (this.inbound.tag == "") return false
|
||||||
|
if (this.inbound.listen_port > 65535 || this.inbound.listen_port < 1) return false
|
||||||
|
if (this.OnlyTLS.includes(this.inbound.type) && this.inbound.tls_id == 0) return false
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
visible(newValue) {
|
visible(newValue) {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
this.updateData()
|
this.loading = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<v-card class="rounded-lg">
|
<v-card class="rounded-lg">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('basic.log.title') + " - " + (logType == 's-ui'? "S-UI" : "Sing-Box") }}</v-col>
|
<v-col>{{ $t('basic.log.title') }}</v-col>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-icon icon="mdi-close" @click="$emit('close')" />
|
<v-icon icon="mdi-close" @click="$emit('close')" />
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
import HttpUtils from '@/plugins/httputil';
|
import HttpUtils from '@/plugins/httputil';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['logType', 'visible'],
|
props: ['visible'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async loadData() {
|
async loadData() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
const data = await HttpUtils.get('api/logs',{ s: this.$props.logType, c: this.logCount, l: this.logLevel })
|
const data = await HttpUtils.get('api/logs',{ c: this.logCount, l: this.logLevel })
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.lines = data.obj?? []
|
this.lines = data.obj?? []
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|||||||
@@ -48,13 +48,11 @@
|
|||||||
</v-text-field>
|
</v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<Direct v-if="outbound.type == outTypes.Direct" direction="out" :data="outbound" />
|
|
||||||
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
<Socks v-if="outbound.type == outTypes.SOCKS" :data="outbound" />
|
||||||
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
<Http v-if="outbound.type == outTypes.HTTP" :data="outbound" />
|
||||||
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
<Shadowsocks v-if="outbound.type == outTypes.Shadowsocks" direction="out" :data="outbound" />
|
||||||
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
<Vmess v-if="outbound.type == outTypes.VMess" :data="outbound" />
|
||||||
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
<Trojan v-if="outbound.type == outTypes.Trojan" :data="outbound" />
|
||||||
<Wireguard v-if="outbound.type == outTypes.Wireguard" :data="outbound" />
|
|
||||||
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
<Hysteria v-if="outbound.type == outTypes.Hysteria" direction="out" :data="outbound" />
|
||||||
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
<ShadowTls v-if="outbound.type == outTypes.ShadowTLS" :data="outbound" />
|
||||||
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
<Vless v-if="outbound.type == outTypes.VLESS" :data="outbound" />
|
||||||
@@ -69,7 +67,6 @@
|
|||||||
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
<OutTLS v-if="Object.hasOwn(outbound,'tls')" :outbound="outbound" />
|
||||||
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
<Multiplex v-if="Object.hasOwn(outbound,'multiplex')" direction="out" :data="outbound" />
|
||||||
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
<Dial v-if="!NoDial.includes(outbound.type)" :dial="outbound" :outTags="tags" />
|
||||||
<v-switch v-model="outboundStats" color="primary" :label="$t('stats.enable')" hide-details></v-switch>
|
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item value="t2">
|
<v-window-item value="t2">
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -131,7 +128,7 @@ import Selector from '@/components/protocols/Selector.vue'
|
|||||||
import UrlTest from '@/components/protocols/UrlTest.vue'
|
import UrlTest from '@/components/protocols/UrlTest.vue'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'id', 'stats', 'tags'],
|
props: ['visible', 'data', 'id', 'tags'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -141,14 +138,13 @@ export default {
|
|||||||
link: "",
|
link: "",
|
||||||
loading: false,
|
loading: false,
|
||||||
outTypes: OutTypes,
|
outTypes: OutTypes,
|
||||||
outboundStats: false,
|
NoDial: [OutTypes.Selector, OutTypes.URLTest],
|
||||||
NoDial: [OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest],
|
NoServer: [OutTypes.Direct, OutTypes.Selector, OutTypes.URLTest, OutTypes.Tor],
|
||||||
NoServer: [OutTypes.Direct, OutTypes.Block, OutTypes.DNS, OutTypes.Selector, OutTypes.URLTest, OutTypes.Tor],
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.id != -1) {
|
if (this.$props.id > 0) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
const newData = JSON.parse(this.$props.data)
|
||||||
this.outbound = createOutbound(newData.type, newData)
|
this.outbound = createOutbound(newData.type, newData)
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
@@ -158,13 +154,12 @@ export default {
|
|||||||
this.title = "add"
|
this.title = "add"
|
||||||
}
|
}
|
||||||
this.tab = "t1"
|
this.tab = "t1"
|
||||||
this.outboundStats = this.$props.stats
|
|
||||||
},
|
},
|
||||||
changeType() {
|
changeType() {
|
||||||
// Tag change only in add outbound
|
// Tag change only in add outbound
|
||||||
const tag = this.$props.id != -1 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
const tag = this.$props.id > 0 ? this.outbound.tag : this.outbound.type + "-" + RandomUtil.randomSeq(3)
|
||||||
// Use previous data
|
// Use previous data
|
||||||
const prevConfig = { tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
const prevConfig = { id: this.outbound.id, tag: tag ,listen: this.outbound.listen, listen_port: this.outbound.listen_port }
|
||||||
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
this.outbound = createOutbound(this.outbound.type, prevConfig)
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
@@ -173,7 +168,7 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$emit('save', this.outbound, this.outboundStats)
|
this.$emit('save', this.outbound)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
async linkConvert() {
|
async linkConvert() {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-col cols="auto" v-if="logical" justify="center" align="center">
|
<v-col cols="auto" v-if="logical" justify="center" align="center">
|
||||||
<v-btn color="primary" @click="ruleData.rules.push({})" hide-details>{{ $t('actions.add') + " " + $t('objects.rule') }}</v-btn>
|
<v-btn color="primary" @click="ruleData.rules.push(<rule>{})" hide-details>{{ $t('actions.add') + " " + $t('objects.rule') }}</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-card style="background-color: inherit; margin-bottom: 5px;" v-for="(r, index) in ruleData.rules" v-if="ruleData.type == 'logical'">
|
<v-card style="background-color: inherit; margin-bottom: 5px;" v-for="(r, index) in ruleData.rules" v-if="ruleData.type == 'logical'">
|
||||||
@@ -35,12 +35,12 @@
|
|||||||
:rsTags="rsTags" />
|
:rsTags="rsTags" />
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="6" md="4">
|
<v-col cols="12" sm="6" md="4">
|
||||||
<v-combobox
|
<v-select
|
||||||
v-model="ruleData.outbound"
|
v-model="ruleData.action"
|
||||||
:items="outTags"
|
:items="actions"
|
||||||
:label="$t('objects.outbound')"
|
:label="$t('admin.action')"
|
||||||
hide-details
|
hide-details
|
||||||
></v-combobox>
|
></v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="6" md="4" v-if="logical">
|
<v-col cols="12" sm="6" md="4" v-if="logical">
|
||||||
<v-combobox
|
<v-combobox
|
||||||
@@ -54,6 +54,95 @@
|
|||||||
<v-switch color="primary" v-model="ruleData.invert" :label="$t('rule.invert')" hide-details></v-switch>
|
<v-switch color="primary" v-model="ruleData.invert" :label="$t('rule.invert')" hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
<v-card subtitle="Route" v-if="ruleData.action == 'route'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleData.outbound"
|
||||||
|
:items="outTags"
|
||||||
|
:label="$t('objects.outbound')"
|
||||||
|
hide-details
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
<v-card subtitle="Route Option" v-if="ruleData.action == 'route-options'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="ruleData.override_address" :label="$t('types.direct.overrideAddr')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="ruleData.override_port"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="65534"
|
||||||
|
:label="$t('types.direct.overridePort')"
|
||||||
|
hide-details>
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="ruleData.udp_disable_domain_unmapping" :label="$t('rule.udpDisableDomainUnmapping')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="ruleData.udp_connect" :label="$t('rule.udpConnect')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="ruleData.udp_timeout" :label="$t('rule.udpTimeout')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
<v-card subtitle="Reject" v-if="ruleData.action == 'reject'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleData.method"
|
||||||
|
:items="[{ title: 'Default', value: 'default' },{ title: 'Drop', value: 'drop'}]"
|
||||||
|
:label="$t('rule.method')"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete ruleData.method"
|
||||||
|
hide-details>
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-switch v-model="ruleData.no_drop" :label="$t('rule.noDrop')" hide-details></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
<v-card subtitle="Sniff" v-if="ruleData.action == 'sniff'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleData.sniff"
|
||||||
|
:items="sniffers"
|
||||||
|
:label="$t('rule.sniffer')"
|
||||||
|
multiple
|
||||||
|
chips
|
||||||
|
hide-details>
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="ruleData.timeout" :label="$t('rule.timeout')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
<v-card subtitle="Resolve" v-if="ruleData.action == 'resolve'">
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-select
|
||||||
|
v-model="ruleData.strategy"
|
||||||
|
:items="strategies"
|
||||||
|
:label="$t('rule.strategy')"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete ruleData.strategy"
|
||||||
|
hide-details>
|
||||||
|
</v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="6" md="4">
|
||||||
|
<v-text-field v-model="ruleData.server" :label="$t('basic.dns.server')" hide-details></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@@ -78,8 +167,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { logicalRule, rule } from '@/types/rules'
|
import { logicalRule, rule, actionKeys } from '@/types/rules'
|
||||||
import RuleOptions from '@/components/Rule.vue'
|
import RuleOptions from '@/components/Rule.vue'
|
||||||
|
import { title } from 'process';
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'index', 'clients', 'inTags', 'outTags', 'rsTags'],
|
props: ['visible', 'data', 'index', 'clients', 'inTags', 'outTags', 'rsTags'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
@@ -87,13 +177,39 @@ export default {
|
|||||||
return {
|
return {
|
||||||
title: 'add',
|
title: 'add',
|
||||||
loading: false,
|
loading: false,
|
||||||
ruleData: <logicalRule>{
|
ruleData: <any>{
|
||||||
type: 'logical',
|
type: 'logical',
|
||||||
mode: 'and',
|
mode: 'and',
|
||||||
rules: <rule[]>[{}],
|
rules: <rule[]>[{}],
|
||||||
invert: false,
|
invert: false,
|
||||||
|
action: 'route',
|
||||||
outbound: 'direct',
|
outbound: 'direct',
|
||||||
}
|
},
|
||||||
|
actions: [
|
||||||
|
{ title: 'Route', value: 'route'},
|
||||||
|
{ title: 'Route Options', value: 'route-options'},
|
||||||
|
{ title: 'Reject', value: 'reject'},
|
||||||
|
{ title: 'Hijack DNS', value: 'hijack-dns'},
|
||||||
|
{ title: 'Sniff', value: 'sniff'},
|
||||||
|
{ title: 'Resolve', value: 'resolve'}
|
||||||
|
],
|
||||||
|
sniffers: [
|
||||||
|
{ title: 'HTTP', value: 'http' },
|
||||||
|
{ title: 'TLS', value: 'tls' },
|
||||||
|
{ title: 'QUIC', value: 'quic' },
|
||||||
|
{ title: 'STUN', value: 'stun' },
|
||||||
|
{ title: 'DNS', value: 'dns' },
|
||||||
|
{ title: 'BitTorrent', value: 'bittorrent' },
|
||||||
|
{ title: 'DTLS', value: 'dtls' },
|
||||||
|
{ title: 'SSH', value: 'ssh' },
|
||||||
|
{ title: 'RDP', value: 'rdp' },
|
||||||
|
],
|
||||||
|
strategies: [
|
||||||
|
{ title: 'Prefer IPv4', value: 'prefer_ipv4' },
|
||||||
|
{ title: 'Prefer IPv6', value: 'prefer_ipv6' },
|
||||||
|
{ title: 'IPv4 Only', value: 'ipv4_only' },
|
||||||
|
{ title: 'IPv6 Only', value: 'ipv6_only' },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -103,13 +219,18 @@ export default {
|
|||||||
if (newData.type) {
|
if (newData.type) {
|
||||||
this.ruleData = newData
|
this.ruleData = newData
|
||||||
} else {
|
} else {
|
||||||
this.ruleData = <logicalRule>{
|
this.ruleData = {
|
||||||
type: 'simple',
|
type: 'simple',
|
||||||
mode: 'and',
|
mode: 'and',
|
||||||
rules: <rule[]>[{...newData}],
|
rules: <rule[]>[{}],
|
||||||
invert: newData.invert,
|
|
||||||
outbound: newData.outbound,
|
|
||||||
}
|
}
|
||||||
|
Object.keys(newData).forEach(key => {
|
||||||
|
if (actionKeys.includes(key)) {
|
||||||
|
this.ruleData[key] = newData[key]
|
||||||
|
} else {
|
||||||
|
this.ruleData.rules[0][key] = newData[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.title = 'edit'
|
this.title = 'edit'
|
||||||
}
|
}
|
||||||
@@ -119,6 +240,7 @@ export default {
|
|||||||
mode: 'and',
|
mode: 'and',
|
||||||
rules: <rule[]>[{}],
|
rules: <rule[]>[{}],
|
||||||
invert: false,
|
invert: false,
|
||||||
|
action: 'route',
|
||||||
outbound: this.$props.outTags[0]?? 'direct',
|
outbound: this.$props.outTags[0]?? 'direct',
|
||||||
}
|
}
|
||||||
this.title = 'add'
|
this.title = 'add'
|
||||||
@@ -130,11 +252,48 @@ export default {
|
|||||||
},
|
},
|
||||||
saveChanges() {
|
saveChanges() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
if (this.ruleData.type == 'simple'){
|
let newRule = <any>{
|
||||||
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
action: this.ruleData.action,
|
||||||
this.ruleData.rules[0].invert = this.ruleData.invert
|
invert: this.ruleData.invert,
|
||||||
}
|
}
|
||||||
this.$emit('save', this.ruleData)
|
|
||||||
|
// Filter action data
|
||||||
|
switch (newRule.action){
|
||||||
|
case 'route':
|
||||||
|
newRule.outbound = this.ruleData.outbound
|
||||||
|
break
|
||||||
|
case 'route-options':
|
||||||
|
newRule.override_address = this.ruleData.override_address.length > 0 ? this.ruleData.override_address : undefined
|
||||||
|
newRule.override_port = this.ruleData.override_port > 0 ? this.ruleData.override_port : undefined
|
||||||
|
newRule.network_strategy = this.ruleData.network_strategy.length > 0 ? this.ruleData.network_strategy : undefined
|
||||||
|
newRule.fallback_delay = this.ruleData.fallback_delay.length > 0 ? this.ruleData.fallback_delay : undefined
|
||||||
|
newRule.udp_disable_domain_unmapping = this.ruleData.udp_disable_domain_unmapping? true : undefined
|
||||||
|
newRule.udp_connect = this.ruleData.udp_connect? true : undefined
|
||||||
|
newRule.udp_timeout = this.ruleData.udp_timeout.length > 0 ? this.ruleData.udp_timeout : undefined
|
||||||
|
break
|
||||||
|
case 'reject':
|
||||||
|
newRule.method = this.ruleData.method.length > 0 ? this.ruleData.method : undefined
|
||||||
|
newRule.no_drop = this.ruleData.no_drop? true : undefined
|
||||||
|
break
|
||||||
|
case 'sniff':
|
||||||
|
newRule.sniffer = this.ruleData.sniffer.length > 0 ? this.ruleData.sniffer : undefined
|
||||||
|
newRule.timeout = this.ruleData.timeout.length > 0 ? this.ruleData.timeout : undefined
|
||||||
|
break
|
||||||
|
case 'resolve':
|
||||||
|
newRule.strategy = this.ruleData.strategy.length > 0 ? this.ruleData.strategy : undefined
|
||||||
|
newRule.server = this.ruleData.server.length > 0 ? this.ruleData.server : undefined
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add rules
|
||||||
|
if (this.ruleData.type == 'simple'){
|
||||||
|
newRule = { ...this.ruleData.rules[0], ...newRule }
|
||||||
|
} else {
|
||||||
|
newRule.type = 'logical'
|
||||||
|
newRule.mode = this.ruleData.mode
|
||||||
|
newRule.rules = this.ruleData.rules
|
||||||
|
}
|
||||||
|
this.$emit('save', newRule)
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
deleteRule(index:number) {
|
deleteRule(index:number) {
|
||||||
@@ -145,14 +304,7 @@ export default {
|
|||||||
logical: {
|
logical: {
|
||||||
get() { return this.ruleData.type == 'logical' },
|
get() { return this.ruleData.type == 'logical' },
|
||||||
set(v:boolean) {
|
set(v:boolean) {
|
||||||
if (v) {
|
this.ruleData.type = v? 'logical' : 'simple'
|
||||||
this.ruleData.type = 'logical'
|
|
||||||
this.ruleData.outbound = this.ruleData.rules[0].outbound?? this.$props.outTags[0]
|
|
||||||
delete this.ruleData.rules[0].outbound
|
|
||||||
} else {
|
|
||||||
this.ruleData.type = 'simple'
|
|
||||||
this.ruleData.rules[0].outbound = this.ruleData.outbound
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -289,8 +289,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { iTls, defaultInTls } from '@/types/inTls'
|
import { tls, iTls, defaultInTls, oTls, defaultOutTls } from '@/types/tls'
|
||||||
import { oTls, defaultOutTls } from '@/types/outTls'
|
|
||||||
import AcmeVue from '@/components/tls/Acme.vue'
|
import AcmeVue from '@/components/tls/Acme.vue'
|
||||||
import EchVue from '@/components/tls/Ech.vue'
|
import EchVue from '@/components/tls/Ech.vue'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
@@ -298,11 +297,11 @@ import { push } from 'notivue'
|
|||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import RandomUtil from '@/plugins/randomUtil'
|
import RandomUtil from '@/plugins/randomUtil'
|
||||||
export default {
|
export default {
|
||||||
props: ['visible', 'data', 'index'],
|
props: ['visible', 'data', 'id'],
|
||||||
emits: ['close', 'save'],
|
emits: ['close', 'save'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tls: { id: -1, name: '', inbounds: [], server: <iTls>{ enabled: true }, client: <oTls>{} },
|
tls: <tls>{ id: 0, name: '', server: <iTls>{ enabled: true }, client: <oTls>{} },
|
||||||
title: "add",
|
title: "add",
|
||||||
loading: false,
|
loading: false,
|
||||||
menu: false,
|
menu: false,
|
||||||
@@ -354,15 +353,17 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateData() {
|
updateData() {
|
||||||
if (this.$props.index != -1) {
|
if (this.$props.id > 0) {
|
||||||
const newData = JSON.parse(this.$props.data)
|
const newData = <tls>JSON.parse(this.$props.data)
|
||||||
this.tls = newData
|
this.tls = newData
|
||||||
|
if (this.tls.server == null) this.tls.server = {}
|
||||||
|
if (this.tls.client == null) this.tls.client = {}
|
||||||
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
this.tlsType = newData.server?.reality == undefined ? 0 : 1
|
||||||
this.usePath = newData.server?.key == undefined ? 0 : 1
|
this.usePath = newData.server?.key == undefined ? 0 : 1
|
||||||
this.title = "edit"
|
this.title = "edit"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.tls = { id: 0, name: '', inbounds: [], server: {enabled: true}, client: {} }
|
this.tls = <tls>{ id: 0, name: '', server: {enabled: true}, client: {} }
|
||||||
this.tlsType = 0
|
this.tlsType = 0
|
||||||
this.usePath = 0
|
this.usePath = 0
|
||||||
this.title = "add"
|
this.title = "add"
|
||||||
@@ -461,10 +462,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
inTls(): iTls {
|
inTls(): iTls {
|
||||||
return <iTls> this.tls.server
|
return this.tls.server
|
||||||
},
|
},
|
||||||
outTls(): oTls {
|
outTls(): oTls {
|
||||||
return <oTls> this.tls.client
|
return this.tls.client
|
||||||
},
|
},
|
||||||
certText: {
|
certText: {
|
||||||
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
get(): string { return this.inTls.certificate ? this.inTls.certificate.join('\n') : '' },
|
||||||
@@ -476,11 +477,11 @@ export default {
|
|||||||
},
|
},
|
||||||
disableSni: {
|
disableSni: {
|
||||||
get() { return this.outTls.disable_sni ?? false },
|
get() { return this.outTls.disable_sni ?? false },
|
||||||
set(v: boolean) { this.outTls.disable_sni = v ? true : undefined }
|
set(v: boolean) { this.tls.client.disable_sni = v ? true : undefined }
|
||||||
},
|
},
|
||||||
insecure: {
|
insecure: {
|
||||||
get() { return this.outTls.insecure ?? false },
|
get() { return this.outTls.insecure ?? false },
|
||||||
set(v: boolean) { this.outTls.insecure = v ? true : undefined }
|
set(v: boolean) { this.tls.client.insecure = v ? true : undefined }
|
||||||
},
|
},
|
||||||
server_port: {
|
server_port: {
|
||||||
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
get() { return this.inTls.reality?.handshake?.server_port ? this.inTls.reality.handshake.server_port : 443 },
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { en } from "vuetify/lib/locale/index.mjs";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
message: "Welcome",
|
message: "Welcome",
|
||||||
success: "success",
|
success: "success",
|
||||||
@@ -37,6 +39,7 @@ export default {
|
|||||||
home: "Home",
|
home: "Home",
|
||||||
inbounds: "Inbounds",
|
inbounds: "Inbounds",
|
||||||
outbounds: "Outbounds",
|
outbounds: "Outbounds",
|
||||||
|
endpoints: "Endpoints",
|
||||||
clients: "Clients",
|
clients: "Clients",
|
||||||
rules: "Rules",
|
rules: "Rules",
|
||||||
tls: "TLS Settings",
|
tls: "TLS Settings",
|
||||||
@@ -75,6 +78,8 @@ export default {
|
|||||||
inbound: "Inbound",
|
inbound: "Inbound",
|
||||||
client: "Client",
|
client: "Client",
|
||||||
outbound: "Outbound",
|
outbound: "Outbound",
|
||||||
|
endpoint: "Endpoint",
|
||||||
|
config: "Config",
|
||||||
rule: "Rule",
|
rule: "Rule",
|
||||||
user: "User",
|
user: "User",
|
||||||
tag: "Tag",
|
tag: "Tag",
|
||||||
@@ -83,7 +88,6 @@ export default {
|
|||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "Multiplex",
|
multiplex: "Multiplex",
|
||||||
transport: "Transport",
|
transport: "Transport",
|
||||||
method: "Method",
|
|
||||||
headers: "Headers",
|
headers: "Headers",
|
||||||
key: "Key",
|
key: "Key",
|
||||||
value: "Value",
|
value: "Value",
|
||||||
@@ -91,6 +95,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "Action",
|
action: "Action",
|
||||||
add: "Add",
|
add: "Add",
|
||||||
|
addbulk: "Add Bulk",
|
||||||
new: "New",
|
new: "New",
|
||||||
edit: "Edit",
|
edit: "Edit",
|
||||||
del: "Delete",
|
del: "Delete",
|
||||||
@@ -168,7 +173,6 @@ export default {
|
|||||||
sub: "External Subscription",
|
sub: "External Subscription",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "Add Bulk",
|
|
||||||
order: "Order",
|
order: "Order",
|
||||||
random: "Random",
|
random: "Random",
|
||||||
},
|
},
|
||||||
@@ -228,7 +232,6 @@ export default {
|
|||||||
worker: "Workers",
|
worker: "Workers",
|
||||||
ifName: "Interface Name",
|
ifName: "Interface Name",
|
||||||
sysIf: "System Interface",
|
sysIf: "System Interface",
|
||||||
gso: "Segmentation Offload",
|
|
||||||
options: "Wireguard Options",
|
options: "Wireguard Options",
|
||||||
multiPeer: "Multi Peer",
|
multiPeer: "Multi Peer",
|
||||||
allowedIp: "Allowed IPs",
|
allowedIp: "Allowed IPs",
|
||||||
@@ -256,9 +259,6 @@ export default {
|
|||||||
mdOption: "Multi Domain Options",
|
mdOption: "Multi Domain Options",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "Sniffing",
|
|
||||||
sniffingTimeout: "Sniffing Timeout",
|
|
||||||
sniffingOverride: "Override Destation",
|
|
||||||
options: "Listen Options",
|
options: "Listen Options",
|
||||||
tcpOptions: "TCP Options",
|
tcpOptions: "TCP Options",
|
||||||
udpOptions: "UDP Options",
|
udpOptions: "UDP Options",
|
||||||
@@ -324,6 +324,14 @@ export default {
|
|||||||
domainRules: "Domain/IP",
|
domainRules: "Domain/IP",
|
||||||
srcIpRules: "Source IP",
|
srcIpRules: "Source IP",
|
||||||
srcPortRules: "Source Port",
|
srcPortRules: "Source Port",
|
||||||
|
udpDisableDomainUnmapping: "UDP Disable Domain Unmapping",
|
||||||
|
udpConnect: "UDP Connect",
|
||||||
|
udpTimeout: "UDP Timeout",
|
||||||
|
method: "Method",
|
||||||
|
noDrop: "No Drop",
|
||||||
|
sniffer: "Sniffer",
|
||||||
|
timeout: "Timeout",
|
||||||
|
strategy: "Strategy",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "Add Ruleset",
|
add: "Add Ruleset",
|
||||||
@@ -394,7 +402,6 @@ export default {
|
|||||||
download: "Download",
|
download: "Download",
|
||||||
volume: "Volume",
|
volume: "Volume",
|
||||||
usage: "Usage",
|
usage: "Usage",
|
||||||
enable: "Enable Statistics",
|
|
||||||
graphTitle: "Traffic Chart",
|
graphTitle: "Traffic Chart",
|
||||||
B: "B",
|
B: "B",
|
||||||
KB: "KB",
|
KB: "KB",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { config } from "process";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
message: "خوش آمدید",
|
message: "خوش آمدید",
|
||||||
success: "موفق",
|
success: "موفق",
|
||||||
@@ -37,6 +39,7 @@ export default {
|
|||||||
home: "خانه",
|
home: "خانه",
|
||||||
inbounds: "ورودیها",
|
inbounds: "ورودیها",
|
||||||
outbounds: "خروجیها",
|
outbounds: "خروجیها",
|
||||||
|
endpoints: "درگاهها",
|
||||||
clients: "کاربران",
|
clients: "کاربران",
|
||||||
rules: "قوانین",
|
rules: "قوانین",
|
||||||
tls: "رمزنگاریها",
|
tls: "رمزنگاریها",
|
||||||
@@ -75,6 +78,8 @@ export default {
|
|||||||
inbound: "ورودی",
|
inbound: "ورودی",
|
||||||
client: "کاربر",
|
client: "کاربر",
|
||||||
outbound: "خروجی",
|
outbound: "خروجی",
|
||||||
|
endpoint: "درگاه",
|
||||||
|
config: "پیکربندی",
|
||||||
rule: "قانون",
|
rule: "قانون",
|
||||||
user: "کاربر",
|
user: "کاربر",
|
||||||
tag: "برچسب",
|
tag: "برچسب",
|
||||||
@@ -90,6 +95,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "فرمان",
|
action: "فرمان",
|
||||||
add: "ایجاد",
|
add: "ایجاد",
|
||||||
|
addbulk: "ایجاد انبوه",
|
||||||
new: "جدید",
|
new: "جدید",
|
||||||
edit: "ویرایش",
|
edit: "ویرایش",
|
||||||
del: "حذف",
|
del: "حذف",
|
||||||
@@ -167,7 +173,6 @@ export default {
|
|||||||
sub: "سابسکریپشن خارجی",
|
sub: "سابسکریپشن خارجی",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "ایجاد انبوه",
|
|
||||||
order: "ترتیب",
|
order: "ترتیب",
|
||||||
random: "تصادفی",
|
random: "تصادفی",
|
||||||
},
|
},
|
||||||
@@ -227,7 +232,6 @@ export default {
|
|||||||
worker: "عملگرها",
|
worker: "عملگرها",
|
||||||
ifName: "نام اینترفیس",
|
ifName: "نام اینترفیس",
|
||||||
sysIf: "استفاده از اینترفیس سیستم",
|
sysIf: "استفاده از اینترفیس سیستم",
|
||||||
gso: "بارگذاری تقسیمبندی عمومی",
|
|
||||||
options: "گزینههای Wireguard",
|
options: "گزینههای Wireguard",
|
||||||
multiPeer: "چند همتایی",
|
multiPeer: "چند همتایی",
|
||||||
allowedIp: "آدرسهای مجاز",
|
allowedIp: "آدرسهای مجاز",
|
||||||
@@ -255,9 +259,6 @@ export default {
|
|||||||
mdOption: "گزینههای دامنه چندگانه",
|
mdOption: "گزینههای دامنه چندگانه",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "شنود آدرس",
|
|
||||||
sniffingTimeout: "مهلت شنود آدرس",
|
|
||||||
sniffingOverride: "جایگزینی مقصد",
|
|
||||||
options: "گزینههای گوشدادن",
|
options: "گزینههای گوشدادن",
|
||||||
tcpOptions: "گزینههای TCP",
|
tcpOptions: "گزینههای TCP",
|
||||||
udpOptions: "گزینههای UDP",
|
udpOptions: "گزینههای UDP",
|
||||||
@@ -323,6 +324,14 @@ export default {
|
|||||||
domainRules: "دامنه/آدرس",
|
domainRules: "دامنه/آدرس",
|
||||||
srcIpRules: "آدرس مبدا",
|
srcIpRules: "آدرس مبدا",
|
||||||
srcPortRules: "پورت مبدا",
|
srcPortRules: "پورت مبدا",
|
||||||
|
udpDisableDomainUnmapping: "عدم تبدیل مسیریابی دامنه",
|
||||||
|
udpConnect: "اتصال UDP",
|
||||||
|
udpTimeout: "مهلت UDP",
|
||||||
|
method: "روش",
|
||||||
|
noDrop: "عدم رهاکردن",
|
||||||
|
sniffer: "شنود کننده",
|
||||||
|
timeout: "مهلت",
|
||||||
|
strategy: "استراتژی",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "ایجاد مجموعه",
|
add: "ایجاد مجموعه",
|
||||||
@@ -393,7 +402,6 @@ export default {
|
|||||||
download: "دانلود",
|
download: "دانلود",
|
||||||
volume: "حجم",
|
volume: "حجم",
|
||||||
usage: "استفاده",
|
usage: "استفاده",
|
||||||
enable: "فعال سازی کنترل ترافیک",
|
|
||||||
graphTitle: "نمودار ترافیک",
|
graphTitle: "نمودار ترافیک",
|
||||||
B: "ب",
|
B: "ب",
|
||||||
KB: "کب",
|
KB: "کب",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { config } from "process";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
message: "Добро пожаловать",
|
message: "Добро пожаловать",
|
||||||
success: "успех",
|
success: "успех",
|
||||||
@@ -37,6 +39,7 @@ export default {
|
|||||||
home: "Главная",
|
home: "Главная",
|
||||||
inbounds: "Входящие",
|
inbounds: "Входящие",
|
||||||
outbounds: "Исходящие",
|
outbounds: "Исходящие",
|
||||||
|
endpoints: "Эндпоинты",
|
||||||
clients: "Клиенты",
|
clients: "Клиенты",
|
||||||
rules: "Правила",
|
rules: "Правила",
|
||||||
tls: "Настройки TLS",
|
tls: "Настройки TLS",
|
||||||
@@ -75,6 +78,8 @@ export default {
|
|||||||
inbound: "Входящий",
|
inbound: "Входящий",
|
||||||
client: "Клиент",
|
client: "Клиент",
|
||||||
outbound: "Исходящий",
|
outbound: "Исходящий",
|
||||||
|
endpoint: "Точка входа",
|
||||||
|
config: "Настройки",
|
||||||
rule: "Правило",
|
rule: "Правило",
|
||||||
user: "Пользователь",
|
user: "Пользователь",
|
||||||
tag: "Тег",
|
tag: "Тег",
|
||||||
@@ -83,7 +88,6 @@ export default {
|
|||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "Мультиплекс",
|
multiplex: "Мультиплекс",
|
||||||
transport: "Транспорт",
|
transport: "Транспорт",
|
||||||
method: "Метод",
|
|
||||||
headers: "Заголовки",
|
headers: "Заголовки",
|
||||||
key: "Ключ",
|
key: "Ключ",
|
||||||
value: "Значение",
|
value: "Значение",
|
||||||
@@ -91,6 +95,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "Действие",
|
action: "Действие",
|
||||||
add: "Добавить",
|
add: "Добавить",
|
||||||
|
addbulk: "Добавить пакетно",
|
||||||
new: "Новый",
|
new: "Новый",
|
||||||
edit: "Редактировать",
|
edit: "Редактировать",
|
||||||
del: "Удалить",
|
del: "Удалить",
|
||||||
@@ -168,7 +173,6 @@ export default {
|
|||||||
sub: "Внешняя подписка",
|
sub: "Внешняя подписка",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "Добавить пакетно",
|
|
||||||
order: "Порядок",
|
order: "Порядок",
|
||||||
random: "Случайный",
|
random: "Случайный",
|
||||||
},
|
},
|
||||||
@@ -228,7 +232,6 @@ export default {
|
|||||||
worker: "Работники",
|
worker: "Работники",
|
||||||
ifName: "Имя интерфейса",
|
ifName: "Имя интерфейса",
|
||||||
sysIf: "Системный интерфейс",
|
sysIf: "Системный интерфейс",
|
||||||
gso: "Отключение сегментации",
|
|
||||||
options: "Параметры Wireguard",
|
options: "Параметры Wireguard",
|
||||||
multiPeer: "Множественный пир",
|
multiPeer: "Множественный пир",
|
||||||
allowedIp: "Разрешенные IP",
|
allowedIp: "Разрешенные IP",
|
||||||
@@ -256,9 +259,6 @@ export default {
|
|||||||
mdOption: "Параметры мультидомена",
|
mdOption: "Параметры мультидомена",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "Обнаружение",
|
|
||||||
sniffingTimeout: "Таймаут обнаружения",
|
|
||||||
sniffingOverride: "Переопределение назначения",
|
|
||||||
options: "Параметры прослушивания",
|
options: "Параметры прослушивания",
|
||||||
tcpOptions: "Параметры TCP",
|
tcpOptions: "Параметры TCP",
|
||||||
udpOptions: "Параметры UDP",
|
udpOptions: "Параметры UDP",
|
||||||
@@ -324,6 +324,14 @@ export default {
|
|||||||
domainRules: "Домен/IP",
|
domainRules: "Домен/IP",
|
||||||
srcIpRules: "Источник IP",
|
srcIpRules: "Источник IP",
|
||||||
srcPortRules: "Источник порта",
|
srcPortRules: "Источник порта",
|
||||||
|
udpDisableDomainUnmapping: "Отключить перенос доменных имен",
|
||||||
|
udpConnect: "Подключение UDP",
|
||||||
|
udpTimeout: "Таймаут UDP",
|
||||||
|
method: "Метод",
|
||||||
|
noDrop: "Не сбрасывать",
|
||||||
|
sniffer: "Обнаружение",
|
||||||
|
timeout: "Таймаут",
|
||||||
|
strategy: "Стратегия",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "Добавить набор правил",
|
add: "Добавить набор правил",
|
||||||
@@ -394,7 +402,6 @@ export default {
|
|||||||
download: "Скачивание",
|
download: "Скачивание",
|
||||||
volume: "Объем",
|
volume: "Объем",
|
||||||
usage: "Использование",
|
usage: "Использование",
|
||||||
enable: "Включить статистику",
|
|
||||||
graphTitle: "График трафика",
|
graphTitle: "График трафика",
|
||||||
B: "Б",
|
B: "Б",
|
||||||
KB: "КБ",
|
KB: "КБ",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default {
|
|||||||
home: "Trang chủ",
|
home: "Trang chủ",
|
||||||
inbounds: "Đầu Vào",
|
inbounds: "Đầu Vào",
|
||||||
outbounds: "Đầu ra",
|
outbounds: "Đầu ra",
|
||||||
|
endpoints: "Câu hình",
|
||||||
clients: "Khách hàng",
|
clients: "Khách hàng",
|
||||||
rules: "Quy tắc",
|
rules: "Quy tắc",
|
||||||
tls: "Cài đặt TLS",
|
tls: "Cài đặt TLS",
|
||||||
@@ -75,6 +76,8 @@ export default {
|
|||||||
inbound: "Đầu Vào",
|
inbound: "Đầu Vào",
|
||||||
client: "Máy Khách hàng",
|
client: "Máy Khách hàng",
|
||||||
outbound: "Đầu Ra",
|
outbound: "Đầu Ra",
|
||||||
|
endpoint: "Điểm cuối",
|
||||||
|
config: "Câu hình",
|
||||||
rule: "Quy tắc",
|
rule: "Quy tắc",
|
||||||
user: "Người dùng",
|
user: "Người dùng",
|
||||||
tag: "Thẻ",
|
tag: "Thẻ",
|
||||||
@@ -83,7 +86,6 @@ export default {
|
|||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "Ghép đa truyền thông ",
|
multiplex: "Ghép đa truyền thông ",
|
||||||
transport: "Giao thông",
|
transport: "Giao thông",
|
||||||
method: "Phương pháp",
|
|
||||||
headers: "Tiêu đề",
|
headers: "Tiêu đề",
|
||||||
key: "Chìa khóa",
|
key: "Chìa khóa",
|
||||||
value: "Giá trị",
|
value: "Giá trị",
|
||||||
@@ -91,6 +93,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "Hành động",
|
action: "Hành động",
|
||||||
add: "Thêm",
|
add: "Thêm",
|
||||||
|
addbulk: "Thêm Hàng loạt",
|
||||||
new: "Mới",
|
new: "Mới",
|
||||||
edit: "Chỉnh sửa",
|
edit: "Chỉnh sửa",
|
||||||
del: "Xóa",
|
del: "Xóa",
|
||||||
@@ -168,7 +171,6 @@ export default {
|
|||||||
sub: "Đăng ký bên ngoài",
|
sub: "Đăng ký bên ngoài",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "Thêm Hàng loạt",
|
|
||||||
order: "Sắp xếp",
|
order: "Sắp xếp",
|
||||||
random: "Ngẫu nhiên",
|
random: "Ngẫu nhiên",
|
||||||
},
|
},
|
||||||
@@ -228,7 +230,6 @@ export default {
|
|||||||
worker: "Công nhân",
|
worker: "Công nhân",
|
||||||
ifName: "Tên Giao diện",
|
ifName: "Tên Giao diện",
|
||||||
sysIf: "Giao diện Hệ thống",
|
sysIf: "Giao diện Hệ thống",
|
||||||
gso: "Giao Thức GSO",
|
|
||||||
options: "Tùy chọn Wireguard",
|
options: "Tùy chọn Wireguard",
|
||||||
multiPeer: "Nhiều Đối tác",
|
multiPeer: "Nhiều Đối tác",
|
||||||
allowedIp: "IPs được Phép",
|
allowedIp: "IPs được Phép",
|
||||||
@@ -247,7 +248,6 @@ export default {
|
|||||||
in: {
|
in: {
|
||||||
addr: "Địa chỉ",
|
addr: "Địa chỉ",
|
||||||
port: "Cổng",
|
port: "Cổng",
|
||||||
sniffing: "Đang Sniffing",
|
|
||||||
clients: "Kích hoạt khách hàng",
|
clients: "Kích hoạt khách hàng",
|
||||||
ssMethod: "Phương thức",
|
ssMethod: "Phương thức",
|
||||||
sSide: "Phía Máy chủ",
|
sSide: "Phía Máy chủ",
|
||||||
@@ -257,9 +257,6 @@ export default {
|
|||||||
mdOption: "Tùy chọn Nhiều Tên miền",
|
mdOption: "Tùy chọn Nhiều Tên miền",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "Đang Sniffing",
|
|
||||||
sniffingTimeout: "Thời gian Chờ Sniffing",
|
|
||||||
sniffingOverride: "Ghi đè Đích",
|
|
||||||
options: "Tùy chọn Nghe",
|
options: "Tùy chọn Nghe",
|
||||||
tcpOptions: "Tùy chọn TCP",
|
tcpOptions: "Tùy chọn TCP",
|
||||||
udpOptions: "Tùy chọn UDP",
|
udpOptions: "Tùy chọn UDP",
|
||||||
@@ -325,6 +322,15 @@ export default {
|
|||||||
domainRules: "Tên miền/IP",
|
domainRules: "Tên miền/IP",
|
||||||
srcIpRules: "IP Nguồn",
|
srcIpRules: "IP Nguồn",
|
||||||
srcPortRules: "Cổng Nguồn",
|
srcPortRules: "Cổng Nguồn",
|
||||||
|
udpDisableDomainUnmapping: "Không màm mạng tiền lập tên miền",
|
||||||
|
udpFallbackDelay: "Thời gian Chờ Fallback",
|
||||||
|
udpConnect: "Kết nối UDP",
|
||||||
|
udpTimeout: "Thời gian Chờ UDP",
|
||||||
|
method: "Phương pháp",
|
||||||
|
noDrop: "Không Tháp",
|
||||||
|
sniffer: "Kiểm tra Sniffer",
|
||||||
|
timeout: "Thời gian Chờ Sniffing",
|
||||||
|
strategy: "Chiến lệ",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "Thêm Bộ quy tắc",
|
add: "Thêm Bộ quy tắc",
|
||||||
@@ -395,7 +401,6 @@ export default {
|
|||||||
download: "Tải xuống",
|
download: "Tải xuống",
|
||||||
volume: "Thể tích",
|
volume: "Thể tích",
|
||||||
usage: "Sử dụng",
|
usage: "Sử dụng",
|
||||||
enable: "Kích hoạt thống kê",
|
|
||||||
graphTitle: "Biểu đồ lưu lượng",
|
graphTitle: "Biểu đồ lưu lượng",
|
||||||
B: "B",
|
B: "B",
|
||||||
KB: "KB",
|
KB: "KB",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default {
|
|||||||
home: "主页",
|
home: "主页",
|
||||||
inbounds: "入站管理",
|
inbounds: "入站管理",
|
||||||
outbounds: "出站管理",
|
outbounds: "出站管理",
|
||||||
|
endpoints: "节点管理",
|
||||||
clients: "用户管理",
|
clients: "用户管理",
|
||||||
rules: "路由列表",
|
rules: "路由列表",
|
||||||
tls: "TLS 设置",
|
tls: "TLS 设置",
|
||||||
@@ -75,6 +76,8 @@ export default {
|
|||||||
inbound: "入站",
|
inbound: "入站",
|
||||||
client: "客户端",
|
client: "客户端",
|
||||||
outbound: "出站",
|
outbound: "出站",
|
||||||
|
endpoint: "节点",
|
||||||
|
config: "配置",
|
||||||
rule: "规则",
|
rule: "规则",
|
||||||
user: "用户",
|
user: "用户",
|
||||||
tag: "标签",
|
tag: "标签",
|
||||||
@@ -83,7 +86,6 @@ export default {
|
|||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "多路复用",
|
multiplex: "多路复用",
|
||||||
transport: "传输",
|
transport: "传输",
|
||||||
method: "方法",
|
|
||||||
headers: "标头",
|
headers: "标头",
|
||||||
key: "键",
|
key: "键",
|
||||||
value: "值",
|
value: "值",
|
||||||
@@ -91,6 +93,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "操作",
|
action: "操作",
|
||||||
add: "添加",
|
add: "添加",
|
||||||
|
addbulk: "批量添加",
|
||||||
new: "新建",
|
new: "新建",
|
||||||
edit: "编辑",
|
edit: "编辑",
|
||||||
del: "删除",
|
del: "删除",
|
||||||
@@ -168,7 +171,6 @@ export default {
|
|||||||
sub: "外部订阅",
|
sub: "外部订阅",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "批量添加",
|
|
||||||
order: "排序",
|
order: "排序",
|
||||||
random: "随机",
|
random: "随机",
|
||||||
},
|
},
|
||||||
@@ -228,7 +230,6 @@ export default {
|
|||||||
worker: "工作线程",
|
worker: "工作线程",
|
||||||
ifName: "接口名称",
|
ifName: "接口名称",
|
||||||
sysIf: "系统接口",
|
sysIf: "系统接口",
|
||||||
gso: "分段卸载",
|
|
||||||
options: "WireGuard 选项",
|
options: "WireGuard 选项",
|
||||||
multiPeer: "多对等体",
|
multiPeer: "多对等体",
|
||||||
allowedIp: "允许的 IP 地址",
|
allowedIp: "允许的 IP 地址",
|
||||||
@@ -247,7 +248,6 @@ export default {
|
|||||||
in: {
|
in: {
|
||||||
addr: "地址",
|
addr: "地址",
|
||||||
port: "端口",
|
port: "端口",
|
||||||
sniffing: "嗅探",
|
|
||||||
clients: "启用客户端",
|
clients: "启用客户端",
|
||||||
ssMethod: "方法",
|
ssMethod: "方法",
|
||||||
sSide: "服务器端",
|
sSide: "服务器端",
|
||||||
@@ -257,9 +257,6 @@ export default {
|
|||||||
mdOption: "多域名选项",
|
mdOption: "多域名选项",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "嗅探",
|
|
||||||
sniffingTimeout: "嗅探超时",
|
|
||||||
sniffingOverride: "覆盖目标地址",
|
|
||||||
options: "监听选项",
|
options: "监听选项",
|
||||||
tcpOptions: "TCP 选项",
|
tcpOptions: "TCP 选项",
|
||||||
udpOptions: "UDP 选项",
|
udpOptions: "UDP 选项",
|
||||||
@@ -325,6 +322,14 @@ export default {
|
|||||||
domainRules: "域名/IP",
|
domainRules: "域名/IP",
|
||||||
srcIpRules: "源 IP",
|
srcIpRules: "源 IP",
|
||||||
srcPortRules: "源端口",
|
srcPortRules: "源端口",
|
||||||
|
udpDisableDomainUnmapping: "禁用域名解析映射",
|
||||||
|
udpConnect: "启用 UDP 连接",
|
||||||
|
udpTimeout: "UDP 超时",
|
||||||
|
method: "方法",
|
||||||
|
noDrop: "不丢弃",
|
||||||
|
sniffer: "嗅探",
|
||||||
|
timeout: "超时",
|
||||||
|
strategy: "策略",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "添加规则集",
|
add: "添加规则集",
|
||||||
@@ -395,7 +400,6 @@ export default {
|
|||||||
download: "下载",
|
download: "下载",
|
||||||
volume: "流量",
|
volume: "流量",
|
||||||
usage: "已用",
|
usage: "已用",
|
||||||
enable: "启用统计",
|
|
||||||
graphTitle: "流量图表",
|
graphTitle: "流量图表",
|
||||||
B: "B",
|
B: "B",
|
||||||
KB: "KB",
|
KB: "KB",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export default {
|
|||||||
home: "主頁",
|
home: "主頁",
|
||||||
inbounds: "入站管理",
|
inbounds: "入站管理",
|
||||||
outbounds: "出站管理",
|
outbounds: "出站管理",
|
||||||
|
endpoints: "端點管理",
|
||||||
clients: "用戶管理",
|
clients: "用戶管理",
|
||||||
rules: "路由列表",
|
rules: "路由列表",
|
||||||
tls: "TLS 設置",
|
tls: "TLS 設置",
|
||||||
@@ -76,6 +77,8 @@ export default {
|
|||||||
inbound: "入站",
|
inbound: "入站",
|
||||||
client: "客戶端",
|
client: "客戶端",
|
||||||
outbound: "出站",
|
outbound: "出站",
|
||||||
|
endpoint: "端點",
|
||||||
|
config: "配置",
|
||||||
rule: "規則",
|
rule: "規則",
|
||||||
user: "用戶",
|
user: "用戶",
|
||||||
tag: "標簽",
|
tag: "標簽",
|
||||||
@@ -84,7 +87,6 @@ export default {
|
|||||||
tls: "TLS",
|
tls: "TLS",
|
||||||
multiplex: "多路復用",
|
multiplex: "多路復用",
|
||||||
transport: "傳輸",
|
transport: "傳輸",
|
||||||
method: "方法",
|
|
||||||
headers: "方法",
|
headers: "方法",
|
||||||
key: "鑰匙",
|
key: "鑰匙",
|
||||||
value: "價值",
|
value: "價值",
|
||||||
@@ -92,6 +94,7 @@ export default {
|
|||||||
actions: {
|
actions: {
|
||||||
action: "操作",
|
action: "操作",
|
||||||
add: "添加",
|
add: "添加",
|
||||||
|
addbulk: "批量添加",
|
||||||
new: "新建",
|
new: "新建",
|
||||||
edit: "編輯",
|
edit: "編輯",
|
||||||
del: "刪除",
|
del: "刪除",
|
||||||
@@ -169,7 +172,6 @@ export default {
|
|||||||
sub: "外部訂閱",
|
sub: "外部訂閱",
|
||||||
},
|
},
|
||||||
bulk: {
|
bulk: {
|
||||||
add: "批量添加",
|
|
||||||
order: "排序",
|
order: "排序",
|
||||||
random: "隨機",
|
random: "隨機",
|
||||||
},
|
},
|
||||||
@@ -229,7 +231,6 @@ export default {
|
|||||||
worker: "工作線程",
|
worker: "工作線程",
|
||||||
ifName: "介面名稱",
|
ifName: "介面名稱",
|
||||||
sysIf: "系統介面",
|
sysIf: "系統介面",
|
||||||
gso: "分段卸載",
|
|
||||||
options: "Wireguard 選項",
|
options: "Wireguard 選項",
|
||||||
multiPeer: "多對等方",
|
multiPeer: "多對等方",
|
||||||
allowedIp: "允許的 IP",
|
allowedIp: "允許的 IP",
|
||||||
@@ -248,7 +249,6 @@ export default {
|
|||||||
in: {
|
in: {
|
||||||
addr: "地址",
|
addr: "地址",
|
||||||
port: "端口",
|
port: "端口",
|
||||||
sniffing: "嗅探",
|
|
||||||
clients: "啟用客戶端",
|
clients: "啟用客戶端",
|
||||||
ssMethod: "方法",
|
ssMethod: "方法",
|
||||||
sSide: "服務器端",
|
sSide: "服務器端",
|
||||||
@@ -258,9 +258,6 @@ export default {
|
|||||||
mdOption: "多域名選項",
|
mdOption: "多域名選項",
|
||||||
},
|
},
|
||||||
listen: {
|
listen: {
|
||||||
sniffing: "嗅探",
|
|
||||||
sniffingTimeout: "嗅探超時",
|
|
||||||
sniffingOverride: "覆蓋目的地",
|
|
||||||
options: "監聽選項",
|
options: "監聽選項",
|
||||||
tcpOptions: "TCP 選項",
|
tcpOptions: "TCP 選項",
|
||||||
udpOptions: "UDP 選項",
|
udpOptions: "UDP 選項",
|
||||||
@@ -326,6 +323,14 @@ export default {
|
|||||||
domainRules: "域名/IP",
|
domainRules: "域名/IP",
|
||||||
srcIpRules: "源 IP",
|
srcIpRules: "源 IP",
|
||||||
srcPortRules: "源端口",
|
srcPortRules: "源端口",
|
||||||
|
udpDisableDomainUnmapping: "禁用域名解析映射",
|
||||||
|
udpConnect: "啟用 UDP 連接",
|
||||||
|
udpTimeout: "UDP 超時",
|
||||||
|
method: "方法",
|
||||||
|
noDrop: "不丟弃",
|
||||||
|
sniffer: "嗅探",
|
||||||
|
timeout: "超時",
|
||||||
|
strategy: "策略",
|
||||||
},
|
},
|
||||||
ruleset: {
|
ruleset: {
|
||||||
add: "添加規則集",
|
add: "添加規則集",
|
||||||
@@ -396,7 +401,6 @@ export default {
|
|||||||
download: "下載",
|
download: "下載",
|
||||||
volume: "流量",
|
volume: "流量",
|
||||||
usage: "已用",
|
usage: "已用",
|
||||||
enable: "啟用統計",
|
|
||||||
graphTitle: "流量圖表",
|
graphTitle: "流量圖表",
|
||||||
B: "B",
|
B: "B",
|
||||||
KB: "KB",
|
KB: "KB",
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
export interface Addr {
|
|
||||||
server: string
|
|
||||||
server_port: number
|
|
||||||
tls?: boolean
|
|
||||||
insecure?: boolean
|
|
||||||
server_name?: string
|
|
||||||
remark?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InData {
|
|
||||||
id: number
|
|
||||||
tag: string
|
|
||||||
addrs: Addr[]
|
|
||||||
outJson: any
|
|
||||||
}
|
|
||||||
@@ -1,465 +0,0 @@
|
|||||||
import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds"
|
|
||||||
import { HTTP, WebSocket, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport"
|
|
||||||
import RandomUtil from "./randomUtil"
|
|
||||||
import { Client } from "@/types/clients"
|
|
||||||
|
|
||||||
export interface Link {
|
|
||||||
type: "local" | "external" | "sub"
|
|
||||||
remark?: string
|
|
||||||
uri: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function utf8ToBase64(utf8String: string): string {
|
|
||||||
const encodedUtf8 = encodeURIComponent(utf8String).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16)))
|
|
||||||
return btoa(encodedUtf8)
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace LinkUtil {
|
|
||||||
export function linkGenerator(user: Client, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] {
|
|
||||||
switch(inbound.type){
|
|
||||||
case InTypes.Shadowsocks:
|
|
||||||
return shadowsocksLink(user,<Shadowsocks>inbound, addrs)
|
|
||||||
case InTypes.Naive:
|
|
||||||
return naiveLink(user,<Naive>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.Hysteria:
|
|
||||||
return hysteriaLink(user,<Hysteria>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.Hysteria2:
|
|
||||||
return hysteria2Link(user,<Hysteria2>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.TUIC:
|
|
||||||
return tuicLink(user,<TUIC>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.VLESS:
|
|
||||||
return vlessLink(user,<VLESS>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.Trojan:
|
|
||||||
return trojanLink(user,<Trojan>inbound, addrs, tlsClient)
|
|
||||||
case InTypes.VMess:
|
|
||||||
return vmessLink(user,<VMess>inbound, addrs, tlsClient)
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadowsocksLink(user: Client, inbound: Shadowsocks, addrs: any[]): string[] {
|
|
||||||
const userPass = inbound.method == "2022-blake3-aes-128-gcm" ? user.config.shadowsocks16?.password : user.config.shadowsocks?.password
|
|
||||||
const password = [userPass]
|
|
||||||
if (inbound.method.startsWith('2022')) password.push(inbound.password)
|
|
||||||
const params = {
|
|
||||||
tfo: inbound.tcp_fast_open? 1 : null,
|
|
||||||
network: inbound.network?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const auth = user.config.hysteria.auth_str
|
|
||||||
const params = {
|
|
||||||
upmbps: inbound.up_mbps?? null,
|
|
||||||
downmbps: inbound.down_mbps?? null,
|
|
||||||
auth: auth?? null,
|
|
||||||
peer: inbound.tls.server_name?? null,
|
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
|
||||||
obfsParam: inbound.obfs?? null,
|
|
||||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
|
||||||
insecure: tlsClient?.insecure ? 1 : null
|
|
||||||
}
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`hysteria://${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`hysteria://${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
uri.searchParams.set('peer', a.server_name)
|
|
||||||
} else {
|
|
||||||
inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer')
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
uri.searchParams.set('insecure', '1')
|
|
||||||
} else {
|
|
||||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const password = user.config.hysteria2.password
|
|
||||||
const params = {
|
|
||||||
upmbps: inbound.up_mbps?? null,
|
|
||||||
downmbps: inbound.down_mbps?? null,
|
|
||||||
sni: inbound.tls.server_name?? null,
|
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
|
||||||
obfs: inbound.obfs?.type?? null,
|
|
||||||
'obfs-password': inbound.obfs?.password?? null,
|
|
||||||
fastopen: inbound.tcp_fast_open? 1 : 0,
|
|
||||||
insecure: tlsClient?.insecure ? 1 : null
|
|
||||||
}
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`hysteria2://${password}@${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`hysteria2://${password}@${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
uri.searchParams.set('sni', a.server_name)
|
|
||||||
} else {
|
|
||||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
uri.searchParams.set('insecure', '1')
|
|
||||||
} else {
|
|
||||||
tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure')
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function naiveLink(user: Client, inbound: Naive, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const password = user.config.naive.password
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const params = {
|
|
||||||
padding: 1,
|
|
||||||
peer: inbound.tls.server_name?? null,
|
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
|
||||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : null
|
|
||||||
}
|
|
||||||
const uri = `http2://${utf8ToBase64(user.name + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}`
|
|
||||||
const paramsArray = []
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag)
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const params = {
|
|
||||||
padding: 1,
|
|
||||||
peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null,
|
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
|
||||||
tfo: inbound.tcp_fast_open? 1 : 0,
|
|
||||||
allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null
|
|
||||||
}
|
|
||||||
const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}`
|
|
||||||
const paramsArray = []
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function tuicLink(user: Client, inbound: TUIC, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const u = user.config.tuic
|
|
||||||
const params = {
|
|
||||||
sni: inbound.tls.server_name?? null,
|
|
||||||
alpn: inbound.tls.alpn?.join(',')?? null,
|
|
||||||
congestion_control: inbound.congestion_control?? null,
|
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
|
||||||
disable_sni: tlsClient?.disable_sni ? 1 : null
|
|
||||||
}
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries(params)){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
uri.searchParams.set('sni', a.server_name)
|
|
||||||
} else {
|
|
||||||
inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
uri.searchParams.set('allowInsecure', '1')
|
|
||||||
} else {
|
|
||||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTransportParams(t:Transport): any {
|
|
||||||
if (Object.keys(t).length == 0) return {}
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
host: <string|null>'',
|
|
||||||
path: <string|null>'',
|
|
||||||
serviceName: <string|null>'',
|
|
||||||
}
|
|
||||||
switch (t.type){
|
|
||||||
case TrspTypes.HTTP:
|
|
||||||
const th = <HTTP>t
|
|
||||||
params.host = th.host?.join(',')?? null
|
|
||||||
params.path = th.path?? null
|
|
||||||
break
|
|
||||||
case TrspTypes.WebSocket:
|
|
||||||
const tw = <WebSocket>t
|
|
||||||
params.path = tw.path?? null
|
|
||||||
params.host = tw.headers?.Host?? null
|
|
||||||
break
|
|
||||||
case TrspTypes.gRPC:
|
|
||||||
const tg = <gRPC>t
|
|
||||||
params.serviceName = tg.service_name?? null
|
|
||||||
break
|
|
||||||
case TrspTypes.HTTPUpgrade:
|
|
||||||
const tu = <HTTPUpgrade>t
|
|
||||||
params.host = tu.host?? null
|
|
||||||
params.path = tu.path?? null
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
function vlessLink(user: Client, inbound: VLESS, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const u = user.config.vless
|
|
||||||
const transport = <Transport>inbound.transport
|
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
type: transport?.type?? 'tcp',
|
|
||||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
|
||||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
|
||||||
sni: inbound.tls?.server_name?? null,
|
|
||||||
flow: inbound.tls?.enabled ? u?.flow?? null : null,
|
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
|
||||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
|
||||||
pbk: tlsClient?.reality?.public_key?? null,
|
|
||||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
|
||||||
}
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`vless://${u?.uuid}@${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`vless://${u?.uuid}@${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.tls != undefined){
|
|
||||||
if (a.tls) {
|
|
||||||
uri.searchParams.set('security','tls')
|
|
||||||
} else {
|
|
||||||
uri.searchParams.delete('security')
|
|
||||||
uri.searchParams.delete('sni')
|
|
||||||
uri.searchParams.delete('alpn')
|
|
||||||
uri.searchParams.delete('allowInsecure')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
uri.searchParams.set('sni', a.server_name)
|
|
||||||
} else {
|
|
||||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
uri.searchParams.set('allowInsecure', '1')
|
|
||||||
} else {
|
|
||||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function trojanLink(user: Client, inbound: Trojan, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const u = user.config.trojan
|
|
||||||
const transport = <Transport>inbound.transport
|
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
type: transport?.type?? 'tcp',
|
|
||||||
security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null,
|
|
||||||
alpn: inbound.tls?.alpn?.join(',')?? null,
|
|
||||||
sni: inbound.tls?.server_name?? null,
|
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : null,
|
|
||||||
fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null,
|
|
||||||
pbk: tlsClient?.reality?.public_key?? null,
|
|
||||||
sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
const uri = new URL(`trojan://${u?.password}@${location.hostname}:${inbound.listen_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
const uri = new URL(`trojan://${u?.password}@${a.server}:${a.server_port}`)
|
|
||||||
for (const [key, value] of Object.entries({...params, ...tParams})){
|
|
||||||
if (value) {
|
|
||||||
uri.searchParams.set(key, value.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.tls != undefined){
|
|
||||||
if (a.tls) {
|
|
||||||
uri.searchParams.set('security','tls')
|
|
||||||
} else {
|
|
||||||
uri.searchParams.delete('security')
|
|
||||||
uri.searchParams.delete('sni')
|
|
||||||
uri.searchParams.delete('alpn')
|
|
||||||
uri.searchParams.delete('allowInsecure')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
uri.searchParams.set('sni', a.server_name)
|
|
||||||
} else {
|
|
||||||
inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni')
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
uri.searchParams.set('allowInsecure', '1')
|
|
||||||
} else {
|
|
||||||
tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure')
|
|
||||||
}
|
|
||||||
uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)
|
|
||||||
links.push(uri.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
function vmessLink(user: Client, inbound: VMess, addrs: any[], tlsClient: any): string[] {
|
|
||||||
const u = user.config.vmess
|
|
||||||
const transport = <Transport>inbound.transport
|
|
||||||
|
|
||||||
const tParams = getTransportParams(transport)
|
|
||||||
if (transport.type == TrspTypes.gRPC) tParams.path = tParams.serviceName
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
v: 2,
|
|
||||||
add: location.hostname,
|
|
||||||
aid: u?.alterId,
|
|
||||||
host: tParams.host?? undefined,
|
|
||||||
id: u?.uuid,
|
|
||||||
net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type,
|
|
||||||
type: transport?.type == 'http' ? 'http' : undefined,
|
|
||||||
path: tParams.path?? undefined,
|
|
||||||
port: inbound.listen_port,
|
|
||||||
ps: inbound.tag,
|
|
||||||
sni: inbound.tls.server_name?? undefined,
|
|
||||||
tls: Object.keys(inbound.tls).length>0? 'tls' : 'none',
|
|
||||||
allowInsecure: tlsClient?.insecure ? 1 : undefined
|
|
||||||
}
|
|
||||||
let links = <string[]>[]
|
|
||||||
if (addrs.length == 0) {
|
|
||||||
links.push('vmess://' + utf8ToBase64(JSON.stringify(params, null, 2)))
|
|
||||||
} else {
|
|
||||||
addrs.forEach(a => {
|
|
||||||
let newParams = {...params}
|
|
||||||
newParams.add = a.server
|
|
||||||
newParams.port = a.server_port
|
|
||||||
if (a.tls != undefined){
|
|
||||||
if (a.tls) {
|
|
||||||
newParams.tls = 'tls'
|
|
||||||
} else {
|
|
||||||
newParams.tls = 'none'
|
|
||||||
delete newParams.sni
|
|
||||||
delete newParams.allowInsecure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (a.server_name?.length>0) {
|
|
||||||
newParams.sni = a.server_name
|
|
||||||
}
|
|
||||||
if (a.insecure) {
|
|
||||||
newParams.allowInsecure = 1
|
|
||||||
}
|
|
||||||
newParams.ps = inbound.tag + (a.remark??'')
|
|
||||||
links.push('vmess://' + utf8ToBase64(JSON.stringify(newParams, null, 2)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { Hysteria, Hysteria2, Inbound, InTypes, Shadowsocks, Trojan, TUIC, VLESS, VMess, ShadowTLS } from "@/types/inbounds"
|
|
||||||
import { iTls } from "@/types/inTls"
|
|
||||||
import { oTls } from "@/types/outTls"
|
|
||||||
import RandomUtil from "./randomUtil"
|
|
||||||
|
|
||||||
export function fillData(out: any, inbound: Inbound, tlsClient: any) {
|
|
||||||
if (Object.hasOwn(inbound, 'tls')) {
|
|
||||||
const inb = <any>inbound
|
|
||||||
addTls(out,inb.tls,tlsClient)
|
|
||||||
} else {
|
|
||||||
delete out.tls
|
|
||||||
}
|
|
||||||
out.type = inbound.type
|
|
||||||
out.tag = inbound.tag
|
|
||||||
out.server = location.hostname
|
|
||||||
out.server_port = inbound.listen_port
|
|
||||||
switch(inbound.type){
|
|
||||||
case InTypes.HTTP: case InTypes.SOCKS: case InTypes.Mixed:
|
|
||||||
return
|
|
||||||
case InTypes.Shadowsocks:
|
|
||||||
shadowsocksOut(out, <Shadowsocks>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.ShadowTLS:
|
|
||||||
shadowTlsOut(out, <ShadowTLS>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.Hysteria:
|
|
||||||
hysteriaOut(out, <Hysteria>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.Hysteria2:
|
|
||||||
hysteria2Out(out, <Hysteria2>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.TUIC:
|
|
||||||
tuicOut(out, <TUIC>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.VLESS:
|
|
||||||
vlessOut(out, <VLESS>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.Trojan:
|
|
||||||
trojanOut(out, <Trojan>inbound)
|
|
||||||
return
|
|
||||||
case InTypes.VMess:
|
|
||||||
vmessOut(out, <VMess>inbound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Object.keys(out).forEach(key => delete out[key])
|
|
||||||
}
|
|
||||||
|
|
||||||
function addTls(out: any, tls: iTls, tlsClient: oTls){
|
|
||||||
out.tls = tlsClient
|
|
||||||
if(tls.enabled) out.tls.enabled = tls.enabled
|
|
||||||
if(tls.server_name) out.tls.server_name = tls.server_name
|
|
||||||
if(tls.alpn) out.tls.alpn = tls.alpn
|
|
||||||
if(tls.min_version) out.tls.min_version = tls.min_version
|
|
||||||
if(tls.max_version) out.tls.max_version = tls.max_version
|
|
||||||
if(tls.cipher_suites) out.tls.cipher_suites = tls.cipher_suites
|
|
||||||
if(tls.reality?.enabled){
|
|
||||||
out.tls.reality.enabled = true
|
|
||||||
out.tls.reality.short_id = tls.reality.short_id[RandomUtil.randomInt(tls.reality.short_id.length)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadowsocksOut(out: any, inbound: Shadowsocks) {
|
|
||||||
out.method = inbound.method
|
|
||||||
}
|
|
||||||
|
|
||||||
function shadowTlsOut(out: any, inbound: ShadowTLS) {
|
|
||||||
if (inbound.version == 3) {
|
|
||||||
out.version = 3
|
|
||||||
} else {
|
|
||||||
Object.keys(out).forEach(key => delete out[key])
|
|
||||||
}
|
|
||||||
out.tls = { enabled: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
function hysteriaOut(out: any, inbound: Hysteria) {
|
|
||||||
out.up_mbps = inbound.down_mbps
|
|
||||||
out.down_mbps = inbound.up_mbps
|
|
||||||
out.obfs = inbound.obfs
|
|
||||||
out.recv_window_conn = inbound.recv_window_conn
|
|
||||||
out.disable_mtu_discovery = inbound.disable_mtu_discovery
|
|
||||||
}
|
|
||||||
|
|
||||||
function hysteria2Out(out: any, inbound: Hysteria2) {
|
|
||||||
out.up_mbps = inbound.down_mbps
|
|
||||||
out.down_mbps = inbound.up_mbps
|
|
||||||
out.obfs = inbound.obfs
|
|
||||||
}
|
|
||||||
|
|
||||||
function tuicOut(out: any, inbound: TUIC) {
|
|
||||||
out.congestion_control = inbound.congestion_control?? "cubic"
|
|
||||||
out.zero_rtt_handshake = inbound.zero_rtt_handshake
|
|
||||||
out.heartbeat = inbound.heartbeat
|
|
||||||
}
|
|
||||||
|
|
||||||
function vlessOut(out: any, inbound: VLESS) {
|
|
||||||
out.transport = inbound.transport
|
|
||||||
}
|
|
||||||
|
|
||||||
function trojanOut(out: any, inbound: Trojan) {
|
|
||||||
out.transport = inbound.transport
|
|
||||||
}
|
|
||||||
|
|
||||||
function vmessOut(out: any, inbound: VMess) {
|
|
||||||
out.transport = inbound.transport
|
|
||||||
}
|
|
||||||
@@ -144,10 +144,10 @@ export const HumanReadable = {
|
|||||||
const remain = Math.floor((second/3600) - (day*24))
|
const remain = Math.floor((second/3600) - (day*24))
|
||||||
return day + i18n.global.t('date.d') + (remain > 0 ? ' ' + remain + i18n.global.t('date.h') : '')
|
return day + i18n.global.t('date.d') + (remain > 0 ? ' ' + remain + i18n.global.t('date.h') : '')
|
||||||
},
|
},
|
||||||
remainedDays(exp:number): number|null {
|
remainedDays(exp:number): string {
|
||||||
if (exp == 0) return -1
|
if (exp == 0) return i18n.global.t('unlimited')
|
||||||
const now = Date.now()/1000
|
const now = Date.now()/1000
|
||||||
if (exp < now) return null
|
if (exp < now) return i18n.global.t('date.expired')
|
||||||
return Math.floor((exp - now) / (3600*24))
|
return Math.floor((exp - now) / (3600*24)) + " " + i18n.global.t('date.d')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,11 @@ const routes = [
|
|||||||
name: 'pages.outbounds',
|
name: 'pages.outbounds',
|
||||||
component: () => import('@/views/Outbounds.vue'),
|
component: () => import('@/views/Outbounds.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/endpoints',
|
||||||
|
name: 'pages.endpoints',
|
||||||
|
component: () => import('@/views/Endpoints.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/rules',
|
path: '/rules',
|
||||||
name: 'pages.rules',
|
name: 'pages.rules',
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import HttpUtils from '@/plugins/httputil'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
|
import { Inbound } from '@/types/inbounds'
|
||||||
|
import { Outbound } from '@/types/outbounds'
|
||||||
|
import { Endpoint } from '@/types/endpoints'
|
||||||
|
|
||||||
const Data = defineStore('Data', {
|
const Data = defineStore('Data', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -10,23 +13,17 @@ const Data = defineStore('Data', {
|
|||||||
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
reloadItems: localStorage.getItem("reloadItems")?.split(',')?? <string[]>[],
|
||||||
subURI: "",
|
subURI: "",
|
||||||
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
onlines: {inbound: <string[]>[], outbound: <string[]>[], user: <string[]>[]},
|
||||||
oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{},
|
|
||||||
config: <any>{},
|
config: <any>{},
|
||||||
|
inbounds: <Inbound[]>[],
|
||||||
|
outbounds: <Outbound[]>[],
|
||||||
|
endpoints: <Endpoint[]>[],
|
||||||
clients: [],
|
clients: [],
|
||||||
tlsConfigs: [],
|
tlsConfigs: <any[]>[],
|
||||||
inData: [],
|
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async loadData() {
|
async loadData() {
|
||||||
const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} )
|
const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} )
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
|
||||||
|
|
||||||
// Set new data
|
|
||||||
if (msg.obj.config) this.oldData.config = msg.obj.config
|
|
||||||
if (msg.obj.clients) this.oldData.clients = msg.obj.clients
|
|
||||||
if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls
|
|
||||||
if (msg.obj.inData) this.oldData.inData = msg.obj.inData
|
|
||||||
this.onlines = msg.obj.onlines
|
this.onlines = msg.obj.onlines
|
||||||
if (msg.obj.lastLog) {
|
if (msg.obj.lastLog) {
|
||||||
push.error({
|
push.error({
|
||||||
@@ -37,84 +34,49 @@ const Data = defineStore('Data', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.obj.config) {
|
if (msg.obj.config) {
|
||||||
// To avoid ref copy
|
this.setNewData(msg.obj)
|
||||||
const data = JSON.parse(JSON.stringify(msg.obj))
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setNewData(data: any) {
|
||||||
|
this.lastLoad = Math.floor((new Date()).getTime()/1000)
|
||||||
if (data.subURI) this.subURI = data.subURI
|
if (data.subURI) this.subURI = data.subURI
|
||||||
if (data.config) this.config = data.config
|
if (data.config) this.config = data.config
|
||||||
if (data.clients) this.clients = data.clients
|
if (data.clients) this.clients = data.clients
|
||||||
|
if (data.inbounds) this.inbounds = data.inbounds
|
||||||
|
if (data.outbounds) this.outbounds = data.outbounds
|
||||||
|
if (data.endpoints) this.endpoints = data.endpoints
|
||||||
if (data.tls) this.tlsConfigs = data.tls
|
if (data.tls) this.tlsConfigs = data.tls
|
||||||
if (data.inData) this.inData = data.inData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async pushData() {
|
async loadInbounds(ids: number[]): Promise<Inbound[]> {
|
||||||
const diff = {
|
const options = ids.length > 0 ? {id: ids.join(",")} : {}
|
||||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2),
|
const msg = await HttpUtils.get('api/inbounds', options)
|
||||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
|
||||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
|
||||||
inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2),
|
|
||||||
}
|
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
|
||||||
if(msg.success) {
|
if(msg.success) {
|
||||||
this.lastLoad = 0
|
return msg.obj.inbounds
|
||||||
this.loadData()
|
|
||||||
}
|
}
|
||||||
|
return <Inbound[]>[]
|
||||||
},
|
},
|
||||||
async delInbound(index: number) {
|
async save (object: string, action: string, data: any, userLinks: any[] | null = null, outJsons: any[] | null = null): Promise<boolean> {
|
||||||
const diff = {
|
let postData = {
|
||||||
config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]),
|
object: object,
|
||||||
clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2),
|
action: action,
|
||||||
tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2),
|
data: JSON.stringify(data, null, 2),
|
||||||
inData: <string|undefined> undefined,
|
userLinks: userLinks == null ? undefined : JSON.stringify(userLinks),
|
||||||
}
|
}
|
||||||
|
if (userLinks == null) {
|
||||||
// Validate inData
|
delete postData.userLinks
|
||||||
let invalidInData = <any[]>[]
|
}
|
||||||
this.inData.forEach((d:any) => {
|
const msg = await HttpUtils.post('api/save', postData)
|
||||||
const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag)
|
if (msg.success) {
|
||||||
if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null})
|
const objectName = ['tls', 'config'].includes(object) ? object : object.substring(0, object.length - 1)
|
||||||
|
push.success({
|
||||||
|
title: i18n.global.t('success'),
|
||||||
|
duration: 5000,
|
||||||
|
message: i18n.global.t('actions.' + action) + " " + i18n.global.t('objects.' + objectName)
|
||||||
})
|
})
|
||||||
if (invalidInData.length>0) {
|
this.setNewData(msg.obj)
|
||||||
diff.inData = JSON.stringify(invalidInData)
|
|
||||||
}
|
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
|
||||||
if(msg.success) {
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async delInData(id: number) {
|
|
||||||
const diff = {
|
|
||||||
inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}])
|
|
||||||
}
|
|
||||||
await HttpUtils.post('api/save',diff)
|
|
||||||
},
|
|
||||||
async delOutbound(index: number) {
|
|
||||||
const diff = {
|
|
||||||
config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]),
|
|
||||||
}
|
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
|
||||||
if(msg.success) {
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async delClient(id: number) {
|
|
||||||
const diff = {
|
|
||||||
config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)),
|
|
||||||
clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]),
|
|
||||||
}
|
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
|
||||||
if(msg.success) {
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async delTls(id: number) {
|
|
||||||
const diff = {
|
|
||||||
tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]),
|
|
||||||
}
|
|
||||||
const msg = await HttpUtils.post('api/save',diff)
|
|
||||||
if(msg.success) {
|
|
||||||
this.loadData()
|
|
||||||
}
|
}
|
||||||
|
return msg.success
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { Link } from "@/plugins/link"
|
|
||||||
import RandomUtil from "@/plugins/randomUtil"
|
import RandomUtil from "@/plugins/randomUtil"
|
||||||
|
|
||||||
|
export interface Link {
|
||||||
|
type: "local" | "external" | "sub"
|
||||||
|
remark?: string
|
||||||
|
uri: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Client {
|
export interface Client {
|
||||||
id?: number
|
id?: number
|
||||||
enable: boolean
|
enable: boolean
|
||||||
name: string
|
name: string
|
||||||
config: Config
|
config: Config
|
||||||
inbounds: string[]
|
inbounds: number[]
|
||||||
links: Link[]
|
links: Link[]
|
||||||
volume: number
|
volume: number
|
||||||
expiry: number
|
expiry: number
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ interface RouteRuleSet {
|
|||||||
|
|
||||||
interface Experimental {
|
interface Experimental {
|
||||||
cache_file?: CacheFile
|
cache_file?: CacheFile
|
||||||
clash_api?: ClashApi
|
|
||||||
v2ray_api: V2rayApi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CacheFile {
|
interface CacheFile {
|
||||||
@@ -94,27 +92,6 @@ interface CacheFile {
|
|||||||
store_fakeip?: boolean
|
store_fakeip?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface V2rayApi {
|
|
||||||
listen: string
|
|
||||||
stats: V2rayApiStats
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface V2rayApiStats {
|
|
||||||
enabled: boolean
|
|
||||||
inbounds: string[]
|
|
||||||
outbounds: string[]
|
|
||||||
users: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ClashApi {
|
|
||||||
external_controller?: string
|
|
||||||
external_ui?: string
|
|
||||||
external_ui_download_url?: string
|
|
||||||
external_ui_download_detour?: string
|
|
||||||
secret?: string
|
|
||||||
default_mode?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
log: Log
|
log: Log
|
||||||
dns: Dns
|
dns: Dns
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Dial } from "./outbounds"
|
||||||
|
|
||||||
|
export const EpTypes = {
|
||||||
|
Wireguard: 'wireguard',
|
||||||
|
}
|
||||||
|
|
||||||
|
type EpType = typeof EpTypes[keyof typeof EpTypes]
|
||||||
|
|
||||||
|
interface EndpointBasics {
|
||||||
|
id: number
|
||||||
|
type: EpType
|
||||||
|
tag: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WgPeer {
|
||||||
|
address: string
|
||||||
|
port: number
|
||||||
|
public_key: string
|
||||||
|
pre_shared_key?: string
|
||||||
|
allowed_ips?: string[]
|
||||||
|
persistent_keepalive_interval?: number
|
||||||
|
reserved?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WireGuard extends EndpointBasics, Dial {
|
||||||
|
system?: boolean
|
||||||
|
name?: string
|
||||||
|
mtu?: number
|
||||||
|
address: string[]
|
||||||
|
private_key: string
|
||||||
|
listen_port: number,
|
||||||
|
peers: WgPeer[]
|
||||||
|
udp_timeout?: string,
|
||||||
|
workers?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create interfaces dynamically based on EpTypes keys
|
||||||
|
type InterfaceMap = {
|
||||||
|
[Key in keyof typeof EpTypes]: {
|
||||||
|
type: string
|
||||||
|
[otherProperties: string]: any; // You can add other properties as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create union type from InterfaceMap
|
||||||
|
export type Endpoint = InterfaceMap[keyof InterfaceMap]
|
||||||
|
|
||||||
|
// Create defaultValues object dynamically
|
||||||
|
const defaultValues: Record<EpType, Endpoint> = {
|
||||||
|
wireguard: { type: EpTypes.Wireguard, address: ['10.0.0.2/32','fe80::2/128'], private_key: '', listen_port: 0, peers: [{ address: '', port: 0, public_key: ''}] },
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEndpoint<T extends Endpoint>(type: string,json?: Partial<T>): Endpoint {
|
||||||
|
const defaultObject: Endpoint = { ...defaultValues[type], ...(json || {}) }
|
||||||
|
return defaultObject
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { iMultiplex } from "./multiplex"
|
import { iMultiplex } from "./multiplex"
|
||||||
import { iTls } from "./inTls"
|
import { iTls } from "./tls"
|
||||||
import { Dial } from "./outbounds"
|
import { Dial } from "./outbounds"
|
||||||
import { Transport } from "./transport"
|
import { Transport } from "./transport"
|
||||||
|
|
||||||
@@ -24,6 +24,15 @@ export const InTypes = {
|
|||||||
|
|
||||||
type InType = typeof InTypes[keyof typeof InTypes]
|
type InType = typeof InTypes[keyof typeof InTypes]
|
||||||
|
|
||||||
|
export interface Addr {
|
||||||
|
server: string
|
||||||
|
server_port: number
|
||||||
|
tls?: boolean
|
||||||
|
insecure?: boolean
|
||||||
|
server_name?: string
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface Listen {
|
export interface Listen {
|
||||||
listen: string
|
listen: string
|
||||||
listen_port: number
|
listen_port: number
|
||||||
@@ -32,15 +41,15 @@ export interface Listen {
|
|||||||
udp_fragment?: boolean
|
udp_fragment?: boolean
|
||||||
udp_timeout?: string
|
udp_timeout?: string
|
||||||
detour?: string
|
detour?: string
|
||||||
sniff?: boolean
|
|
||||||
sniff_override_destination?: boolean
|
|
||||||
sniff_timeout?: string
|
|
||||||
domain_strategy?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InboundBasics extends Listen {
|
interface InboundBasics extends Listen {
|
||||||
|
id: number
|
||||||
type: InType
|
type: InType
|
||||||
tag: string
|
tag: string
|
||||||
|
tls_id: number
|
||||||
|
addrs?: Addr[]
|
||||||
|
out_json?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UsernamePass {
|
interface UsernamePass {
|
||||||
@@ -87,7 +96,6 @@ export interface SOCKS extends InboundBasics {
|
|||||||
}
|
}
|
||||||
export interface HTTP extends InboundBasics {
|
export interface HTTP extends InboundBasics {
|
||||||
users?: UsernamePass[]
|
users?: UsernamePass[]
|
||||||
tls?: iTls,
|
|
||||||
}
|
}
|
||||||
export interface Shadowsocks extends InboundBasics {
|
export interface Shadowsocks extends InboundBasics {
|
||||||
method: string
|
method: string
|
||||||
@@ -125,7 +133,6 @@ export interface Hysteria extends InboundBasics {
|
|||||||
recv_window_client?: number
|
recv_window_client?: number
|
||||||
max_conn_client?: number
|
max_conn_client?: number
|
||||||
disable_mtu_discovery?: boolean
|
disable_mtu_discovery?: boolean
|
||||||
tls: iTls
|
|
||||||
}
|
}
|
||||||
export interface ShadowTLS extends InboundBasics {
|
export interface ShadowTLS extends InboundBasics {
|
||||||
version: 1|2|3
|
version: 1|2|3
|
||||||
@@ -139,7 +146,6 @@ export interface ShadowTLS extends InboundBasics {
|
|||||||
}
|
}
|
||||||
export interface VLESS extends InboundBasics {
|
export interface VLESS extends InboundBasics {
|
||||||
users: VlessUser[]
|
users: VlessUser[]
|
||||||
tls?: iTls
|
|
||||||
multiplex?: iMultiplex
|
multiplex?: iMultiplex
|
||||||
transport?: Transport
|
transport?: Transport
|
||||||
}
|
}
|
||||||
@@ -149,7 +155,6 @@ export interface TUIC extends InboundBasics {
|
|||||||
auth_timeout?: string
|
auth_timeout?: string
|
||||||
zero_rtt_handshake?: boolean
|
zero_rtt_handshake?: boolean
|
||||||
heartbeat?: string
|
heartbeat?: string
|
||||||
tls: iTls
|
|
||||||
}
|
}
|
||||||
export interface Hysteria2 extends InboundBasics {
|
export interface Hysteria2 extends InboundBasics {
|
||||||
up_mbps?: number
|
up_mbps?: number
|
||||||
@@ -160,8 +165,15 @@ export interface Hysteria2 extends InboundBasics {
|
|||||||
}
|
}
|
||||||
users: NamePass[]
|
users: NamePass[]
|
||||||
ignore_client_bandwidth?: boolean
|
ignore_client_bandwidth?: boolean
|
||||||
tls: iTls
|
masquerade?: string | {
|
||||||
masquerade?: string
|
type: string
|
||||||
|
directory?: string
|
||||||
|
url?: string
|
||||||
|
rewrite_host?: string
|
||||||
|
status_code?: number
|
||||||
|
headers?: Headers[]
|
||||||
|
content?: string
|
||||||
|
}
|
||||||
brutal_debug?: boolean
|
brutal_debug?: boolean
|
||||||
}
|
}
|
||||||
export interface Tun extends InboundBasics {
|
export interface Tun extends InboundBasics {
|
||||||
@@ -172,7 +184,6 @@ export interface Tun extends InboundBasics {
|
|||||||
udp_timeout?: string
|
udp_timeout?: string
|
||||||
stack?: string
|
stack?: string
|
||||||
auto_route?: boolean
|
auto_route?: boolean
|
||||||
// gso?: boolean
|
|
||||||
// strict_route?: boolean
|
// strict_route?: boolean
|
||||||
// iproute2_table_index?: number
|
// iproute2_table_index?: number
|
||||||
// iproute2_rule_index?: number
|
// iproute2_rule_index?: number
|
||||||
@@ -234,24 +245,26 @@ type userEnabledTypes = {
|
|||||||
vless: VLESS
|
vless: VLESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const inboundWithUsers = ['mixed', 'socks:', 'http', 'shadowsocks', 'vmess', 'trojan', 'naive', 'hysteria', 'shadowtls', 'tuic', 'hysteria2', 'vless']
|
||||||
|
|
||||||
// Create union type from userEnabledTypes
|
// Create union type from userEnabledTypes
|
||||||
export type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
type InboundWithUser = userEnabledTypes[keyof userEnabledTypes]
|
||||||
|
|
||||||
// Create defaultValues object dynamically
|
// Create defaultValues object dynamically
|
||||||
const defaultValues: Record<InType, Inbound> = {
|
const defaultValues: Record<InType, Inbound> = {
|
||||||
direct: <Direct>{ type: InTypes.Direct },
|
direct: <Direct>{ type: InTypes.Direct },
|
||||||
mixed: <Mixed>{ type: InTypes.Mixed },
|
mixed: <Mixed>{ type: InTypes.Mixed },
|
||||||
socks: <SOCKS>{ type: InTypes.SOCKS },
|
socks: <SOCKS>{ type: InTypes.SOCKS },
|
||||||
http: <HTTP>{ type: InTypes.HTTP, tls: {} },
|
http: <HTTP>{ type: InTypes.HTTP, tls_id: 0 },
|
||||||
shadowsocks: <Shadowsocks>{ type: InTypes.Shadowsocks, method: 'none', multiplex: {} },
|
shadowsocks: <Shadowsocks>{ type: InTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||||
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
vmess: <VMess>{ type: InTypes.VMess, users: <VmessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||||
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls: {}, multiplex: {}, transport: {} },
|
trojan: <Trojan>{ type: InTypes.Trojan, users: <NamePass[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||||
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls: { enabled: true } },
|
naive: <Naive>{ type: InTypes.Naive, users: <UsernamePass[]>[], tls_id: 0 },
|
||||||
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
hysteria: <Hysteria>{ type: InTypes.Hysteria, users: <NameAuth[]>[], up_mbps: 100, down_mbps: 100, tls_id: 0 },
|
||||||
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, users: <NamePass[]>[], handshake: {}, handshake_for_server_name: {} },
|
shadowtls: <ShadowTLS>{ type: InTypes.ShadowTLS, version: 3, users: <NamePass[]>[], handshake: {}, handshake_for_server_name: {} },
|
||||||
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls: { enabled: true } },
|
tuic: <TUIC>{ type: InTypes.TUIC, users: <TuicUser[]>[], congestion_control: "cubic", tls_id: 0 },
|
||||||
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls: { enabled: true } },
|
hysteria2: <Hysteria2>{ type: InTypes.Hysteria2, users: <NamePass[]>[], tls_id: 0 },
|
||||||
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls: {}, multiplex: {}, transport: {} },
|
vless: <VLESS>{ type: InTypes.VLESS, users: <VlessUser[]>[], tls_id: 0, multiplex: {}, transport: {} },
|
||||||
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
tun: <Tun>{ type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false },
|
||||||
redirect: <Redirect>{ type: InTypes.Redirect },
|
redirect: <Redirect>{ type: InTypes.Redirect },
|
||||||
tproxy: <TProxy>{ type: InTypes.TProxy },
|
tproxy: <TProxy>{ type: InTypes.TProxy },
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
export interface oTls {
|
|
||||||
enabled?: boolean
|
|
||||||
disable_sni?: boolean
|
|
||||||
server_name?: string
|
|
||||||
insecure?: boolean
|
|
||||||
alpn?: string[]
|
|
||||||
min_version?: string
|
|
||||||
max_version?: string
|
|
||||||
cipher_suites?: string[]
|
|
||||||
certificate?: string
|
|
||||||
certificate_path?: string
|
|
||||||
ech?: {
|
|
||||||
enabled: boolean
|
|
||||||
pq_signature_schemes_enabled?: boolean
|
|
||||||
dynamic_record_sizing_disabled?: boolean
|
|
||||||
config?: string[],
|
|
||||||
config_path?: string
|
|
||||||
},
|
|
||||||
utls?: {
|
|
||||||
enabled: boolean
|
|
||||||
fingerprint: string
|
|
||||||
},
|
|
||||||
reality?: {
|
|
||||||
enabled: boolean
|
|
||||||
public_key: string
|
|
||||||
short_id: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultOutTls: oTls = {
|
|
||||||
alpn: ['h3', 'h2', 'http/1.1'],
|
|
||||||
min_version: "1.2",
|
|
||||||
max_version: "1.3",
|
|
||||||
cipher_suites: [],
|
|
||||||
utls: {
|
|
||||||
enabled: true,
|
|
||||||
fingerprint: "chrome",
|
|
||||||
},
|
|
||||||
reality: {
|
|
||||||
enabled: true,
|
|
||||||
public_key: "",
|
|
||||||
short_id: "",
|
|
||||||
},
|
|
||||||
ech: {
|
|
||||||
enabled: true,
|
|
||||||
pq_signature_schemes_enabled: false,
|
|
||||||
dynamic_record_sizing_disabled: false,
|
|
||||||
config_path: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
import { oTls } from "./outTls"
|
import { oTls } from "./tls"
|
||||||
import { oMultiplex } from "./multiplex"
|
import { oMultiplex } from "./multiplex"
|
||||||
import { Transport } from "./transport"
|
import { Transport } from "./transport"
|
||||||
|
|
||||||
export const OutTypes = {
|
export const OutTypes = {
|
||||||
Direct: 'direct',
|
Direct: 'direct',
|
||||||
Block: 'block',
|
|
||||||
SOCKS: 'socks',
|
SOCKS: 'socks',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
Shadowsocks: 'shadowsocks',
|
Shadowsocks: 'shadowsocks',
|
||||||
VMess: 'vmess',
|
VMess: 'vmess',
|
||||||
Trojan: 'trojan',
|
Trojan: 'trojan',
|
||||||
Wireguard: 'wireguard',
|
|
||||||
Hysteria: 'hysteria',
|
Hysteria: 'hysteria',
|
||||||
VLESS: 'vless',
|
VLESS: 'vless',
|
||||||
ShadowTLS: 'shadowtls',
|
ShadowTLS: 'shadowtls',
|
||||||
@@ -18,7 +16,6 @@ export const OutTypes = {
|
|||||||
Hysteria2: 'hysteria2',
|
Hysteria2: 'hysteria2',
|
||||||
Tor: 'tor',
|
Tor: 'tor',
|
||||||
SSH: 'ssh',
|
SSH: 'ssh',
|
||||||
DNS: 'dns',
|
|
||||||
Selector: 'selector',
|
Selector: 'selector',
|
||||||
URLTest: 'urltest',
|
URLTest: 'urltest',
|
||||||
}
|
}
|
||||||
@@ -41,6 +38,7 @@ export interface Dial {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface OutboundBasics {
|
interface OutboundBasics {
|
||||||
|
id: number
|
||||||
type: OutType
|
type: OutType
|
||||||
tag: string
|
tag: string
|
||||||
}
|
}
|
||||||
@@ -54,12 +52,7 @@ export interface WgPeer {
|
|||||||
reserved?: number[]
|
reserved?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Direct extends OutboundBasics, Dial {
|
export interface Direct extends OutboundBasics, Dial {}
|
||||||
override_address?: string
|
|
||||||
override_port?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Block extends OutboundBasics {}
|
|
||||||
|
|
||||||
export interface SOCKS extends OutboundBasics, Dial {
|
export interface SOCKS extends OutboundBasics, Dial {
|
||||||
server: string
|
server: string
|
||||||
@@ -124,23 +117,6 @@ export interface Trojan extends OutboundBasics, Dial {
|
|||||||
transport?: Transport
|
transport?: Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WireGuard extends OutboundBasics, Dial {
|
|
||||||
server?: string
|
|
||||||
server_port?: number
|
|
||||||
system_interface?: boolean
|
|
||||||
gso?: boolean
|
|
||||||
interface_name?: string
|
|
||||||
local_address: string[]
|
|
||||||
private_key: string
|
|
||||||
peers?: WgPeer[]
|
|
||||||
peer_public_key?: string
|
|
||||||
pre_shared_key?: string
|
|
||||||
reserved?: number[]
|
|
||||||
workers?: number
|
|
||||||
mtu?: number
|
|
||||||
network?: "udp" | "tcp"
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Hysteria extends OutboundBasics, Dial {
|
export interface Hysteria extends OutboundBasics, Dial {
|
||||||
server: string
|
server: string
|
||||||
server_port: number
|
server_port: number
|
||||||
@@ -192,6 +168,8 @@ export interface TUIC extends OutboundBasics, Dial {
|
|||||||
export interface Hysteria2 extends OutboundBasics, Dial {
|
export interface Hysteria2 extends OutboundBasics, Dial {
|
||||||
server: string
|
server: string
|
||||||
server_port: number
|
server_port: number
|
||||||
|
server_ports?: string[]
|
||||||
|
hop_interval: string
|
||||||
up_mbps?: number
|
up_mbps?: number
|
||||||
down_mbps?: number
|
down_mbps?: number
|
||||||
obfs?: {
|
obfs?: {
|
||||||
@@ -226,8 +204,6 @@ export interface SSH extends OutboundBasics, Dial {
|
|||||||
client_version?: string
|
client_version?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DNS extends OutboundBasics {}
|
|
||||||
|
|
||||||
export interface Selector extends OutboundBasics {
|
export interface Selector extends OutboundBasics {
|
||||||
outbounds: string[]
|
outbounds: string[]
|
||||||
url?: string
|
url?: string
|
||||||
@@ -257,13 +233,11 @@ export type Outbound = InterfaceMap[keyof InterfaceMap]
|
|||||||
// Create defaultValues object dynamically
|
// Create defaultValues object dynamically
|
||||||
const defaultValues: Record<OutType, Outbound> = {
|
const defaultValues: Record<OutType, Outbound> = {
|
||||||
direct: { type: OutTypes.Direct },
|
direct: { type: OutTypes.Direct },
|
||||||
block: { type: OutTypes.Block },
|
|
||||||
socks: { type: OutTypes.SOCKS, version: "5" },
|
socks: { type: OutTypes.SOCKS, version: "5" },
|
||||||
http: { type: OutTypes.HTTP, tls: {} },
|
http: { type: OutTypes.HTTP, tls: {} },
|
||||||
shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} },
|
shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} },
|
||||||
vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false },
|
vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false },
|
||||||
trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} },
|
trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} },
|
||||||
wireguard: { type: OutTypes.Wireguard, local_address: ['10.0.0.2/32','fe80::2/128'], private_key: '' },
|
|
||||||
hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } },
|
||||||
shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } },
|
shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } },
|
||||||
vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} },
|
vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} },
|
||||||
@@ -271,7 +245,6 @@ const defaultValues: Record<OutType, Outbound> = {
|
|||||||
hysteria2: { type: OutTypes.Hysteria2, tls: { enabled: true } },
|
hysteria2: { type: OutTypes.Hysteria2, tls: { enabled: true } },
|
||||||
tor: { type: OutTypes.Tor, executable_path: './tor', data_directory: '$HOME/.cache/tor', torrc: { ClientOnly: 1 } },
|
tor: { type: OutTypes.Tor, executable_path: './tor', data_directory: '$HOME/.cache/tor', torrc: { ClientOnly: 1 } },
|
||||||
ssh: { type: OutTypes.SSH },
|
ssh: { type: OutTypes.SSH },
|
||||||
dns: { type: OutTypes.DNS },
|
|
||||||
selector: { type: OutTypes.Selector },
|
selector: { type: OutTypes.Selector },
|
||||||
urltest: { type: OutTypes.URLTest },
|
urltest: { type: OutTypes.URLTest },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,43 @@
|
|||||||
export interface logicalRule {
|
interface generalRule {
|
||||||
|
invert: boolean
|
||||||
|
action: 'route' | 'route-options' | 'reject' | 'hijack-dns' | 'sniff' | 'resolve'
|
||||||
|
outbound?: string
|
||||||
|
override_address?: string
|
||||||
|
override_port?: number
|
||||||
|
udp_disable_domain_unmapping?: boolean
|
||||||
|
udp_connect?: boolean
|
||||||
|
udp_timeout?: string
|
||||||
|
method?: string
|
||||||
|
no_drop?: boolean
|
||||||
|
sniffer: string[]
|
||||||
|
timeout: string
|
||||||
|
strategy: string
|
||||||
|
server: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actionKeys = [
|
||||||
|
'invert',
|
||||||
|
'action',
|
||||||
|
'outbound',
|
||||||
|
'override_address',
|
||||||
|
'override_port',
|
||||||
|
'udp_disable_domain_unmapping',
|
||||||
|
'udp_connect',
|
||||||
|
'udp_timeout',
|
||||||
|
'method',
|
||||||
|
'no_drop',
|
||||||
|
'sniffer',
|
||||||
|
'timeout',
|
||||||
|
'strategy',
|
||||||
|
'server'
|
||||||
|
]
|
||||||
|
export interface logicalRule extends generalRule {
|
||||||
type: 'logical' | 'simple'
|
type: 'logical' | 'simple'
|
||||||
mode: 'and' | 'or'
|
mode: 'and' | 'or'
|
||||||
rules: rule[]
|
rules: rule[]
|
||||||
invert: boolean
|
|
||||||
outbound: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface rule {
|
export interface rule extends generalRule {
|
||||||
inbound?: string[]
|
inbound?: string[]
|
||||||
ip_version?: 4 | 6
|
ip_version?: 4 | 6
|
||||||
network?: string[]
|
network?: string[]
|
||||||
@@ -32,8 +63,6 @@ export interface rule {
|
|||||||
clash_mode?: string
|
clash_mode?: string
|
||||||
rule_set?: string[]
|
rule_set?: string[]
|
||||||
rule_set_ipcidr_match_source?: boolean
|
rule_set_ipcidr_match_source?: boolean
|
||||||
invert?: boolean
|
|
||||||
outbound?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ruleset {
|
export interface ruleset {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { Dial } from "./dial"
|
import { Dial } from "./dial"
|
||||||
|
|
||||||
|
export interface tls {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
server: iTls
|
||||||
|
client: oTls
|
||||||
|
}
|
||||||
|
|
||||||
export interface iTls {
|
export interface iTls {
|
||||||
enabled?: boolean
|
enabled?: boolean
|
||||||
server_name?: string
|
server_name?: string
|
||||||
@@ -63,3 +70,54 @@ export const defaultInTls: iTls = {
|
|||||||
max_version: "1.3",
|
max_version: "1.3",
|
||||||
cipher_suites: [],
|
cipher_suites: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface oTls {
|
||||||
|
enabled?: boolean
|
||||||
|
disable_sni?: boolean
|
||||||
|
server_name?: string
|
||||||
|
insecure?: boolean
|
||||||
|
alpn?: string[]
|
||||||
|
min_version?: string
|
||||||
|
max_version?: string
|
||||||
|
cipher_suites?: string[]
|
||||||
|
certificate?: string
|
||||||
|
certificate_path?: string
|
||||||
|
ech?: {
|
||||||
|
enabled: boolean
|
||||||
|
pq_signature_schemes_enabled?: boolean
|
||||||
|
dynamic_record_sizing_disabled?: boolean
|
||||||
|
config?: string[],
|
||||||
|
config_path?: string
|
||||||
|
},
|
||||||
|
utls?: {
|
||||||
|
enabled: boolean
|
||||||
|
fingerprint: string
|
||||||
|
},
|
||||||
|
reality?: {
|
||||||
|
enabled: boolean
|
||||||
|
public_key: string
|
||||||
|
short_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOutTls: oTls = {
|
||||||
|
alpn: ['h3', 'h2', 'http/1.1'],
|
||||||
|
min_version: "1.2",
|
||||||
|
max_version: "1.3",
|
||||||
|
cipher_suites: [],
|
||||||
|
utls: {
|
||||||
|
enabled: true,
|
||||||
|
fingerprint: "chrome",
|
||||||
|
},
|
||||||
|
reality: {
|
||||||
|
enabled: true,
|
||||||
|
public_key: "",
|
||||||
|
short_id: "",
|
||||||
|
},
|
||||||
|
ech: {
|
||||||
|
enabled: true,
|
||||||
|
pq_signature_schemes_enabled: false,
|
||||||
|
dynamic_record_sizing_disabled: false,
|
||||||
|
config_path: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
@close="closeEditModal"
|
@close="closeEditModal"
|
||||||
@save="saveEditModal"
|
@save="saveEditModal"
|
||||||
/>
|
/>
|
||||||
<ChngModal
|
<ChangeModal
|
||||||
v-model="changesModal.visible"
|
v-model="changesModal.visible"
|
||||||
:visible="changesModal.visible"
|
:visible="changesModal.visible"
|
||||||
:admins="users.map((u:any) => u.username)"
|
:admins="users.map((u:any) => u.username)"
|
||||||
@@ -27,19 +27,19 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('admin.date') }}</v-col>
|
<v-col>{{ $t('admin.date') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.loginDate }}
|
{{ item.loginDate }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('admin.time') }}</v-col>
|
<v-col>{{ $t('admin.time') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.loginTime }}
|
{{ item.loginTime }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>IP</v-col>
|
<v-col>IP</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.ip }}
|
{{ item.ip }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import AdminModal from '@/layouts/modals/Admin.vue'
|
import AdminModal from '@/layouts/modals/Admin.vue'
|
||||||
import ChngModal from '@/layouts/modals/Changes.vue'
|
import ChangeModal from '@/layouts/modals/Changes.vue'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import HttpUtils from '@/plugins/httputil'
|
import HttpUtils from '@/plugins/httputil'
|
||||||
import { Ref, ref, inject, onMounted } from 'vue'
|
import { Ref, ref, inject, onMounted } from 'vue'
|
||||||
|
|||||||
+35
-109
@@ -1,4 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<v-row style="margin-bottom: 10px;">
|
||||||
|
<v-col cols="12" justify="center" align="center">
|
||||||
|
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
<v-expansion-panels>
|
<v-expansion-panels>
|
||||||
<v-expansion-panel :title="$t('basic.log.title')">
|
<v-expansion-panel :title="$t('basic.log.title')">
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
@@ -11,6 +18,8 @@
|
|||||||
hide-details
|
hide-details
|
||||||
:label="$t('basic.log.level')"
|
:label="$t('basic.log.level')"
|
||||||
:items="levels"
|
:items="levels"
|
||||||
|
clearable
|
||||||
|
@click:clear="delete appConfig.log.level"
|
||||||
v-model="appConfig.log.level">
|
v-model="appConfig.log.level">
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
@@ -215,128 +224,49 @@
|
|||||||
hide-details></v-switch>
|
hide-details></v-switch>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
Clash API
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-switch v-model="enableClashApi" color="primary" :label="$t('enable')" hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.external_controller"
|
|
||||||
hide-details
|
|
||||||
label="External Controller"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.external_ui"
|
|
||||||
hide-details
|
|
||||||
label="External UI"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.external_ui_download_url"
|
|
||||||
hide-details
|
|
||||||
label="UI Download URL"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.external_ui_download_detour"
|
|
||||||
hide-details
|
|
||||||
label="UI Download detour"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.secret"
|
|
||||||
hide-details
|
|
||||||
label="Secret"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2" v-if="appConfig.experimental.clash_api">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.clash_api.default_mode"
|
|
||||||
hide-details
|
|
||||||
label="Default Mode"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
V2Ray API
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-text-field
|
|
||||||
v-model="appConfig.experimental.v2ray_api.listen"
|
|
||||||
hide-details
|
|
||||||
:label="$t('objects.listen')"
|
|
||||||
></v-text-field>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6" md="3" lg="2">
|
|
||||||
<v-switch v-model="appConfig.experimental.v2ray_api.stats.enabled"
|
|
||||||
color="primary"
|
|
||||||
:label="$t('stats.enable')"
|
|
||||||
hide-details></v-switch>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row v-if="appConfig.experimental.v2ray_api.stats.enabled">
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('pages.inbounds')"
|
|
||||||
multiple chips closable-chips
|
|
||||||
:items="inboundTags"
|
|
||||||
v-model="appConfig.experimental.v2ray_api.stats.inbounds">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('pages.outbounds')"
|
|
||||||
multiple chips closable-chips
|
|
||||||
:items="outboundTags"
|
|
||||||
v-model="appConfig.experimental.v2ray_api.stats.outbounds">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" sm="6">
|
|
||||||
<v-select
|
|
||||||
hide-details
|
|
||||||
:label="$t('pages.clients')"
|
|
||||||
multiple chips closable-chips
|
|
||||||
:items="clientNames"
|
|
||||||
v-model="appConfig.experimental.v2ray_api.stats.users">
|
|
||||||
</v-select>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Data from '@/store/modules/data';
|
import Data from '@/store/modules/data'
|
||||||
import Dial from '@/components/Dial.vue';
|
import Dial from '@/components/Dial.vue'
|
||||||
import { computed } from 'vue';
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { Config, Ntp } from '@/types/config';
|
import { Config, Ntp } from '@/types/config'
|
||||||
import { Client } from '@/types/clients';
|
import { Client } from '@/types/clients'
|
||||||
|
import { FindDiff } from '@/plugins/utils'
|
||||||
|
|
||||||
|
const oldConfig = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
const appConfig = computed((): Config => {
|
||||||
return <Config> Data().config
|
return <Config> Data().config
|
||||||
})
|
})
|
||||||
|
|
||||||
const inboundTags = computed((): string[] => {
|
onMounted(async () => {
|
||||||
return appConfig.value.inbounds.map(i => i.tag)
|
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const stateChange = computed(() => {
|
||||||
|
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveConfig = async () => {
|
||||||
|
loading.value = true
|
||||||
|
const success = await Data().save("config", "set", appConfig.value)
|
||||||
|
if (success) {
|
||||||
|
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const outboundTags = computed((): string[] => {
|
const outboundTags = computed((): string[] => {
|
||||||
return appConfig.value.outbounds.map(o => o.tag)
|
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||||
})
|
})
|
||||||
|
|
||||||
const clientNames = computed((): string[] => {
|
const clientNames = computed((): string[] => {
|
||||||
const clients = <Client[]>Data().clients
|
return Data().clients.map((c:any) => c.name)
|
||||||
return clients?.map(c => c.name)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const levels = ["trace", "debug", "info", "warn", "error", "fatal", "panic"]
|
const levels = ["trace", "debug", "info", "warn", "error", "fatal", "panic"]
|
||||||
@@ -384,8 +314,4 @@ const enableCacheFile = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const enableClashApi = computed({
|
|
||||||
get() { return appConfig.value.experimental.clash_api != undefined },
|
|
||||||
set(v:boolean) { v ? appConfig.value.experimental.clash_api = {} : delete appConfig.value.experimental.clash_api }
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
+47
-142
@@ -3,10 +3,9 @@
|
|||||||
<ClientModal
|
<ClientModal
|
||||||
v-model="modal.visible"
|
v-model="modal.visible"
|
||||||
:visible="modal.visible"
|
:visible="modal.visible"
|
||||||
:index="modal.index"
|
:id="modal.id"
|
||||||
:data="modal.data"
|
:data="modal.data"
|
||||||
:groups="groups"
|
:groups="groups"
|
||||||
:stats="modal.stats"
|
|
||||||
:inboundTags="inboundTags"
|
:inboundTags="inboundTags"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
@save="saveModal"
|
@save="saveModal"
|
||||||
@@ -34,7 +33,7 @@
|
|||||||
/>
|
/>
|
||||||
<v-row justify="center" align="center">
|
<v-row justify="center" align="center">
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-menu v-model="actionMenu" :close-on-content-click="false" location="bottom center">
|
<v-menu v-model="actionMenu" :close-on-content-click="false" location="bottom center">
|
||||||
@@ -48,7 +47,7 @@
|
|||||||
<template v-slot:prepend>
|
<template v-slot:prepend>
|
||||||
<v-icon icon="mdi-account-multiple-plus"></v-icon>
|
<v-icon icon="mdi-account-multiple-plus"></v-icon>
|
||||||
</template>
|
</template>
|
||||||
<v-list-item-title v-text="$t('bulk.add')"></v-list-item-title>
|
<v-list-item-title v-text="$t('actions.addbulk')"></v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -147,7 +146,6 @@
|
|||||||
<v-col cols="auto">
|
<v-col cols="auto">
|
||||||
<v-switch color="primary"
|
<v-switch color="primary"
|
||||||
v-model="item.enable"
|
v-model="item.enable"
|
||||||
@update:model-value="buildInboundsUsers(item.inbounds)"
|
|
||||||
hideDetails density="compact" />
|
hideDetails density="compact" />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -160,28 +158,28 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds != ''">
|
||||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
<span v-for="i in item.inbounds">{{ inbounds.find(inb => inb.id == i)?.tag }}<br /></span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
{{ item.inbounds.length }}
|
{{ item.inbounds.length }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('stats.volume') }}</v-col>
|
<v-col>{{ $t('stats.volume') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.volume == 0 ? $t('unlimited') : HumanReadable.sizeFormat(item.volume) }}
|
{{ item.volume == 0 ? $t('unlimited') : HumanReadable.sizeFormat(item.volume) }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('date.expiry') }}</v-col>
|
<v-col>{{ $t('date.expiry') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.expiry == 0 ? $t('unlimited') : HumanReadable.remainedDays(item.expiry)?? $t('date.expired') }}
|
{{ HumanReadable.remainedDays(item.expiry) }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('stats.usage') }}</v-col>
|
<v-col>{{ $t('stats.usage') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<v-tooltip activator="parent" location="bottom">
|
<v-tooltip activator="parent" location="bottom">
|
||||||
{{ $t('stats.upload') }}:{{ HumanReadable.sizeFormat(item.up) }}<br />
|
{{ $t('stats.upload') }}:{{ HumanReadable.sizeFormat(item.up) }}<br />
|
||||||
{{ $t('stats.download') }}:{{ HumanReadable.sizeFormat(item.down) }}<br />
|
{{ $t('stats.download') }}:{{ HumanReadable.sizeFormat(item.down) }}<br />
|
||||||
@@ -194,7 +192,7 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('online') }}</v-col>
|
<v-col>{{ $t('online') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<template v-if="isOnline(item.name).value">
|
<template v-if="isOnline(item.name).value">
|
||||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||||
</template>
|
</template>
|
||||||
@@ -230,7 +228,7 @@
|
|||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" text="QR-Code"></v-tooltip>
|
<v-tooltip activator="parent" location="top" text="QR-Code"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
<v-btn icon="mdi-chart-line" @click="showStats(item.name)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -277,7 +275,7 @@
|
|||||||
<v-chip
|
<v-chip
|
||||||
size="small"
|
size="small"
|
||||||
label
|
label
|
||||||
>{{ item.expiry == 0 ? $t('unlimited') : HumanReadable.remainedDays(item.expiry)?? $t('date.expired') }}</v-chip>
|
>{{ HumanReadable.remainedDays(item.expiry) }}</v-chip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:item.online="{ item }">
|
<template v-slot:item.online="{ item }">
|
||||||
@@ -324,7 +322,7 @@
|
|||||||
>
|
>
|
||||||
mdi-qrcode
|
mdi-qrcode
|
||||||
</v-icon>
|
</v-icon>
|
||||||
<v-icon icon="mdi-chart-line" @click="showStats(item.name)" v-if="v2rayStats.users.includes(item.name)">
|
<v-icon icon="mdi-chart-line" @click="showStats(item.name)">
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||||
</v-icon>
|
</v-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -347,11 +345,9 @@ import ClientModal from '@/layouts/modals/Client.vue'
|
|||||||
import ClientBulk from '@/layouts/modals/ClientBulk.vue'
|
import ClientBulk from '@/layouts/modals/ClientBulk.vue'
|
||||||
import QrCode from '@/layouts/modals/QrCode.vue'
|
import QrCode from '@/layouts/modals/QrCode.vue'
|
||||||
import Stats from '@/layouts/modals/Stats.vue'
|
import Stats from '@/layouts/modals/Stats.vue'
|
||||||
import { Client, createClient } from '@/types/clients'
|
import { Client } from '@/types/clients'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { Config, V2rayApiStats } from '@/types/config'
|
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||||
import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
|
||||||
import { Link, LinkUtil } from '@/plugins/link'
|
|
||||||
import { HumanReadable } from '@/plugins/utils'
|
import { HumanReadable } from '@/plugins/utils'
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
@@ -367,21 +363,13 @@ const isOnline = (cname: string) => computed(() => {
|
|||||||
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
|
return Data().onlines?.user ? Data().onlines.user.includes(cname) : false
|
||||||
})
|
})
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
|
||||||
return <Config> Data().config
|
|
||||||
})
|
|
||||||
|
|
||||||
const v2rayStats = computed((): V2rayApiStats => {
|
|
||||||
return <V2rayApiStats> appConfig.value.experimental.v2ray_api.stats
|
|
||||||
})
|
|
||||||
|
|
||||||
const inbounds = computed((): Inbound[] => {
|
const inbounds = computed((): Inbound[] => {
|
||||||
return <Inbound[]> appConfig.value?.inbounds
|
return <Inbound[]> Data().inbounds?? []
|
||||||
})
|
})
|
||||||
|
|
||||||
const inboundTags = computed((): string[] => {
|
const inboundTags = computed((): any[] => {
|
||||||
if (!inbounds.value) return []
|
if (!inbounds.value) return []
|
||||||
return inbounds.value?.filter(i => i.tag != "" && Object.hasOwn(i,'users')).map(i => i.tag)
|
return inbounds.value?.filter(i => i.tag != "" && inboundWithUsers.includes(i.type)).map(i => { return { title: i.tag, value: i.id } })
|
||||||
})
|
})
|
||||||
|
|
||||||
const groups = computed((): string[] => {
|
const groups = computed((): string[] => {
|
||||||
@@ -430,128 +418,39 @@ const groupBy = [
|
|||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
index: -1,
|
id: 0,
|
||||||
data: "",
|
data: "",
|
||||||
stats: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const delOverlay = ref(new Array<boolean>(clients.value.length).fill(false))
|
const delOverlay = ref(new Array<boolean>(clients.value.length).fill(false))
|
||||||
|
|
||||||
const showModal = (id: number) => {
|
const showModal = async (id: number) => {
|
||||||
const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id)
|
modal.value.id = id
|
||||||
modal.value.index = index
|
modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id))
|
||||||
modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index])
|
|
||||||
modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name)
|
|
||||||
modal.value.visible = true
|
modal.value.visible = true
|
||||||
}
|
}
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:any, stats:boolean) => {
|
const saveModal = async (data:any) => {
|
||||||
// Check duplicate name
|
// Check duplicate name
|
||||||
const oldName = modal.value.index != -1 ? clients.value[modal.value.index].name : null
|
const oldName = modal.value.id > 0 ? clients.value.findLast(i => i.id == modal.value.id)?.name : null
|
||||||
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
|
if (data.name != oldName && clients.value.findIndex(c => c.name == data.name) != -1) {
|
||||||
push.error({
|
push.error({
|
||||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(modal.value.index == -1) {
|
|
||||||
clients.value.push(data)
|
// save data
|
||||||
} else {
|
const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data)
|
||||||
clients.value[modal.value.index] = data
|
if (success) modal.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild affected inbounds
|
const delClient = async (id: number) => {
|
||||||
buildInboundsUsers(data.inbounds)
|
const index = clients.value.findIndex(c => c.id === id)
|
||||||
|
const success = await Data().save("clients", "del", id)
|
||||||
// Rebuild links
|
if (success) delOverlay.value[index] = false
|
||||||
data.links = updateLinks(data)
|
|
||||||
|
|
||||||
// Set Client Stats
|
|
||||||
const sIndex = v2rayStats.value.users.findIndex(i => i == data.name) // Find if new user exists
|
|
||||||
|
|
||||||
if (oldName != data.name) {
|
|
||||||
v2rayStats.value.users = v2rayStats.value.users.filter(item => item != oldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats) {
|
|
||||||
// Add if dos not exist
|
|
||||||
if (data.name.length>0 && sIndex == -1) v2rayStats.value.users.push(data.name)
|
|
||||||
} else {
|
|
||||||
// Delete if exists
|
|
||||||
if (sIndex != -1) v2rayStats.value.users.splice(sIndex,1)
|
|
||||||
}
|
|
||||||
|
|
||||||
modal.value.visible = false
|
|
||||||
}
|
|
||||||
const buildInboundsUsers = (inboundTags:string[]) => {
|
|
||||||
inboundTags.forEach(tag => {
|
|
||||||
const inbound_index = inbounds.value.findIndex(i => i.tag == tag)
|
|
||||||
if (inbound_index != -1){
|
|
||||||
const users = <any>[]
|
|
||||||
const newInbound = <InboundWithUser>inbounds.value[inbound_index]
|
|
||||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(tag))
|
|
||||||
inboundClients.forEach(c => {
|
|
||||||
// Remove flow in non tls VLESS
|
|
||||||
if (newInbound.type == InTypes.VLESS) {
|
|
||||||
const vlessInbound = <VLESS>newInbound
|
|
||||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
|
||||||
}
|
|
||||||
users.push(c.config[newInbound.type])
|
|
||||||
})
|
|
||||||
newInbound.users = users
|
|
||||||
|
|
||||||
// Exceptions for Naive and ShadowTLSv3
|
|
||||||
if (users.length == 0){
|
|
||||||
if (newInbound.type == InTypes.Naive) {
|
|
||||||
newInbound.users = <any>[{}]
|
|
||||||
} else {
|
|
||||||
if (newInbound.type == InTypes.ShadowTLS){
|
|
||||||
const ssTls = <ShadowTLS>newInbound
|
|
||||||
if (ssTls.version == 3) newInbound.users = <any>[{}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds.value[inbound_index] = newInbound
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const updateLinks = (c:Client):Link[] => {
|
|
||||||
const clientInbounds = <Inbound[]>inbounds.value.filter(i => c.inbounds.includes(i.tag))
|
|
||||||
const newLinks = <Link[]>[]
|
|
||||||
clientInbounds.forEach(i =>{
|
|
||||||
const tlsConfig = <any>Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag))
|
|
||||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
|
||||||
const addrs = cData ? <any[]>cData.addrs : []
|
|
||||||
const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs)
|
|
||||||
if (uris.length>0){
|
|
||||||
uris.forEach(uri => {
|
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let links = c.links && c.links.length>0? c.links : <Link[]>[]
|
|
||||||
links = [...newLinks, ...links.filter(l => l.type != 'local')]
|
|
||||||
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
const delClient = (id: number) => {
|
|
||||||
const clientIndex = clients.value.findIndex(c => c.id === id)
|
|
||||||
const oldData = createClient(clients.value[clientIndex])
|
|
||||||
|
|
||||||
// Delete stats if exists and will be orphaned
|
|
||||||
const tagCounts = clients.value.filter(i => i.name == oldData.name).length
|
|
||||||
const sIndex = v2rayStats.value.users.findIndex(i => i == oldData.name)
|
|
||||||
if (tagCounts == 1 && sIndex != -1){
|
|
||||||
v2rayStats.value.users.splice(sIndex,1)
|
|
||||||
}
|
|
||||||
|
|
||||||
clients.value.splice(clientIndex,1)
|
|
||||||
buildInboundsUsers(oldData.inbounds)
|
|
||||||
if (id>0) Data().delClient(id)
|
|
||||||
delOverlay.value[clientIndex] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const qrcode = ref({
|
const qrcode = ref({
|
||||||
@@ -603,7 +502,7 @@ const doFilter = () => {
|
|||||||
filteredClients = filteredClients.filter(c => c.enable == false)
|
filteredClients = filteredClients.filter(c => c.enable == false)
|
||||||
break
|
break
|
||||||
case "expired":
|
case "expired":
|
||||||
filteredClients = filteredClients.filter(c => HumanReadable.remainedDays(c.expiry) == null)
|
filteredClients = filteredClients.filter(c => c.expiry > 0 && c.expiry < (Date.now()/1000) )
|
||||||
break
|
break
|
||||||
case "online":
|
case "online":
|
||||||
filteredClients = filteredClients.filter(c => Data().onlines?.user?.includes(c.name))
|
filteredClients = filteredClients.filter(c => Data().onlines?.user?.includes(c.name))
|
||||||
@@ -636,14 +535,20 @@ const closeBulk = () => {
|
|||||||
addBulkModal.value = false
|
addBulkModal.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => {
|
const saveBulk = async (bulkClients: Client[]) => {
|
||||||
bulkClients.forEach((c,c_index) => {
|
// Check duplicate name
|
||||||
bulkClients[c_index].links = updateLinks(c)
|
const oldNames = new Set(clients.value.map(c => c.name))
|
||||||
|
const newNames = new Set(bulkClients.map(c => c.name))
|
||||||
|
const allNames = new Set([...clients.value.map(c => c.name), ...bulkClients.map(c => c.name)])
|
||||||
|
if (newNames.size != bulkClients.length || oldNames.size + newNames.size != allNames.size) {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name')
|
||||||
})
|
})
|
||||||
clients.value.push(...bulkClients)
|
return
|
||||||
buildInboundsUsers(clientInbounds)
|
}
|
||||||
// Stats
|
|
||||||
if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name))
|
// save data
|
||||||
closeBulk()
|
const success = await Data().save("clients", "addbulk", bulkClients)
|
||||||
|
if (success) closeBulk()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<EndpointVue
|
||||||
|
v-model="modal.visible"
|
||||||
|
:visible="modal.visible"
|
||||||
|
:id="modal.id"
|
||||||
|
:data="modal.data"
|
||||||
|
:tags="endpointTags"
|
||||||
|
@close="closeModal"
|
||||||
|
@save="saveModal"
|
||||||
|
/>
|
||||||
|
<Stats
|
||||||
|
v-model="stats.visible"
|
||||||
|
:visible="stats.visible"
|
||||||
|
:resource="stats.resource"
|
||||||
|
:tag="stats.tag"
|
||||||
|
@close="closeStats"
|
||||||
|
/>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" justify="center" align="center">
|
||||||
|
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>endpoints" :key="item.tag">
|
||||||
|
<v-card rounded="xl" elevation="5" min-width="200" :title="item.tag">
|
||||||
|
<v-card-subtitle style="margin-top: -20px;">
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ item.type }}</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('in.addr') }}</v-col>
|
||||||
|
<v-col>
|
||||||
|
{{ item.address?.length>0 ? item.address[0] : '-' }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('in.port') }}</v-col>
|
||||||
|
<v-col>
|
||||||
|
{{ item.listen_port?? '-' }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('types.wg.peers') }}</v-col>
|
||||||
|
<v-col>
|
||||||
|
{{ item.peers.length?? '-' }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('online') }}</v-col>
|
||||||
|
<v-col>
|
||||||
|
<template v-if="onlines.includes(item.tag)">
|
||||||
|
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||||
|
</template>
|
||||||
|
<template v-else>-</template>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-actions style="padding: 0;">
|
||||||
|
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
<v-overlay
|
||||||
|
v-model="delOverlay[index]"
|
||||||
|
contained
|
||||||
|
class="align-center justify-center"
|
||||||
|
>
|
||||||
|
<v-card :title="$t('actions.del')" rounded="lg">
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="error" variant="outlined" @click="delEndpoint(item.tag)">{{ $t('yes') }}</v-btn>
|
||||||
|
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-overlay>
|
||||||
|
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||||
|
<v-icon />
|
||||||
|
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Data from '@/store/modules/data'
|
||||||
|
import EndpointVue from '@/layouts/modals/Endpoint.vue'
|
||||||
|
import Stats from '@/layouts/modals/Stats.vue'
|
||||||
|
import { Endpoint } from '@/types/endpoints';
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { i18n } from '@/locales';
|
||||||
|
import { push } from 'notivue';
|
||||||
|
|
||||||
|
const endpoints = computed((): Endpoint[] => {
|
||||||
|
return <Endpoint[]> Data().endpoints
|
||||||
|
})
|
||||||
|
|
||||||
|
const endpointTags = computed((): any[] => {
|
||||||
|
return endpoints.value?.map((o:Endpoint) => o.tag)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onlines = computed(() => {
|
||||||
|
return [...Data().onlines.inbound?? [], ...Data().onlines.outbound??[] ]
|
||||||
|
})
|
||||||
|
|
||||||
|
const modal = ref({
|
||||||
|
visible: false,
|
||||||
|
id: 0,
|
||||||
|
data: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
let delOverlay = ref(new Array<boolean>)
|
||||||
|
|
||||||
|
const showModal = (id: number) => {
|
||||||
|
modal.value.id = id
|
||||||
|
modal.value.data = id == 0 ? '' : JSON.stringify(endpoints.value.findLast(o => o.id == id))
|
||||||
|
modal.value.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
modal.value.visible = false
|
||||||
|
}
|
||||||
|
const saveModal = async (data:Endpoint) => {
|
||||||
|
// Check duplicate tag
|
||||||
|
const oldTag = modal.value.id > 0 ? endpoints.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||||
|
if (data.tag != oldTag && endpointTags.value.includes(data.tag)) {
|
||||||
|
push.error({
|
||||||
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// save data
|
||||||
|
const success = await Data().save("endpoints", modal.value.id == 0 ? "new" : "edit", data)
|
||||||
|
if (success) modal.value.visible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = ref({
|
||||||
|
visible: false,
|
||||||
|
resource: "endpoint",
|
||||||
|
tag: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const delEndpoint = async (tag: string) => {
|
||||||
|
const index = endpoints.value.findIndex(i => i.tag == tag)
|
||||||
|
const success = await Data().save("endpoints", "del", tag)
|
||||||
|
if (success) delOverlay.value[index] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const showStats = (tag: string) => {
|
||||||
|
stats.value.tag = tag
|
||||||
|
stats.value.visible = true
|
||||||
|
}
|
||||||
|
const closeStats = () => {
|
||||||
|
stats.value.visible = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+38
-215
@@ -2,10 +2,7 @@
|
|||||||
<InboundVue
|
<InboundVue
|
||||||
v-model="modal.visible"
|
v-model="modal.visible"
|
||||||
:visible="modal.visible"
|
:visible="modal.visible"
|
||||||
:index="modal.index"
|
:id="modal.id"
|
||||||
:stats="modal.stats"
|
|
||||||
:data="modal.data"
|
|
||||||
:cData="modal.cData"
|
|
||||||
:inTags="inTags"
|
:inTags="inTags"
|
||||||
:outTags="outTags"
|
:outTags="outTags"
|
||||||
:tlsConfigs="tlsConfigs"
|
:tlsConfigs="tlsConfigs"
|
||||||
@@ -21,7 +18,7 @@
|
|||||||
/>
|
/>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" justify="center" align="center">
|
<v-col cols="12" justify="center" align="center">
|
||||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -35,35 +32,38 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('in.addr') }}</v-col>
|
<v-col>{{ $t('in.addr') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.listen }}
|
{{ item.listen }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('in.port') }}</v-col>
|
<v-col>{{ $t('in.port') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.listen_port }}
|
{{ item.listen_port }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
{{ item.tls_id > 0 ? $t('enable') : $t('disable') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('pages.clients') }}</v-col>
|
<v-col>{{ $t('pages.clients') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="Object.hasOwn(item,'users')">
|
<template v-if="inboundWithUsers.includes(item.type)">
|
||||||
<span v-for="u in findInbounsUsers(item)">{{ u }}<br /></span>
|
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="findInboundUsers(item).length > 0">
|
||||||
|
<span v-for="u in findInboundUsers(item)">{{ u }}<br /></span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
{{ Array.isArray(item.users) ? item.users.length : '-' }}
|
{{ findInboundUsers(item).length }}
|
||||||
|
</template>
|
||||||
|
<template v-else>-</template>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('online') }}</v-col>
|
<v-col>{{ $t('online') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<template v-if="onlines[index]">
|
<template v-if="onlines.includes(item.tag)">
|
||||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>-</template>
|
<template v-else>-</template>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions style="padding: 0;">
|
<v-card-actions style="padding: 0;">
|
||||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -89,12 +89,12 @@
|
|||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="error" variant="outlined" @click="delInbound(index)">{{ $t('yes') }}</v-btn>
|
<v-btn color="error" variant="outlined" @click="delInbound(item.id)">{{ $t('yes') }}</v-btn>
|
||||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.inbounds.includes(item.tag)">
|
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -108,31 +108,25 @@
|
|||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import InboundVue from '@/layouts/modals/Inbound.vue'
|
import InboundVue from '@/layouts/modals/Inbound.vue'
|
||||||
import Stats from '@/layouts/modals/Stats.vue'
|
import Stats from '@/layouts/modals/Stats.vue'
|
||||||
import { Config, V2rayApiStats } from '@/types/config'
|
import { Config } from '@/types/config'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { InTypes, Inbound, InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds'
|
import { Inbound, inboundWithUsers } from '@/types/inbounds'
|
||||||
import { Client } from '@/types/clients'
|
import { Client } from '@/types/clients'
|
||||||
import { Link, LinkUtil } from '@/plugins/link'
|
|
||||||
import { i18n } from '@/locales'
|
import { i18n } from '@/locales'
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
import { fillData } from '@/plugins/outJson'
|
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
const appConfig = computed((): Config => {
|
||||||
return <Config> Data().config
|
return <Config> Data().config
|
||||||
})
|
})
|
||||||
|
|
||||||
const inbounds = computed((): Inbound[] => {
|
const inbounds = computed((): Inbound[] => {
|
||||||
return <Inbound[]> appConfig.value.inbounds
|
return <Inbound[]> Data().inbounds
|
||||||
})
|
})
|
||||||
|
|
||||||
const tlsConfigs = computed((): any[] => {
|
const tlsConfigs = computed((): any[] => {
|
||||||
return <any[]> Data().tlsConfigs
|
return <any[]> Data().tlsConfigs
|
||||||
})
|
})
|
||||||
|
|
||||||
const inData = computed((): any[] => {
|
|
||||||
return <any[]> Data().inData
|
|
||||||
})
|
|
||||||
|
|
||||||
const inTags = computed((): string[] => {
|
const inTags = computed((): string[] => {
|
||||||
return inbounds.value?.map(i => i.tag)
|
return inbounds.value?.map(i => i.tag)
|
||||||
})
|
})
|
||||||
@@ -149,216 +143,45 @@ const onlines = computed(() => {
|
|||||||
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
|
return Data().onlines.inbound ? inbounds.value.map(i => Data().onlines.inbound.includes(i.tag)) : []
|
||||||
})
|
})
|
||||||
|
|
||||||
const v2rayStats = computed((): V2rayApiStats => {
|
|
||||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
|
||||||
})
|
|
||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
index: -1,
|
id: 0,
|
||||||
data: "",
|
|
||||||
cData: "",
|
|
||||||
stats: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let delOverlay = ref(new Array<boolean>)
|
let delOverlay = ref(new Array<boolean>)
|
||||||
|
|
||||||
const showModal = (index: number) => {
|
const showModal = (id: number) => {
|
||||||
modal.value.index = index
|
modal.value.id = id
|
||||||
if (index == -1){
|
|
||||||
modal.value.data = ''
|
|
||||||
modal.value.cData = ''
|
|
||||||
modal.value.stats = false
|
|
||||||
} else {
|
|
||||||
modal.value.data = JSON.stringify(inbounds.value[index])
|
|
||||||
modal.value.stats = v2rayStats.value.inbounds.includes(inbounds.value[index].tag)
|
|
||||||
const inDataIndex = inData.value.findIndex(d => d.tag == inbounds.value[index].tag)
|
|
||||||
modal.value.cData = inDataIndex == -1 ? '' : JSON.stringify(inData.value[inDataIndex])
|
|
||||||
}
|
|
||||||
modal.value.visible = true
|
modal.value.visible = true
|
||||||
}
|
}
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:Inbound, stats: boolean, tls_id: number, cData: any) => {
|
const saveModal = async (data:Inbound) => {
|
||||||
// Check duplicate tag
|
// Check duplicate tag
|
||||||
const oldTag = modal.value.index != -1 ? inbounds.value[modal.value.index].tag : null
|
const oldInbound = modal.value.id > 0 ? inbounds.value.findLast(i => i.id == modal.value.id) : null
|
||||||
if (data.tag != oldTag && inTags.value.includes(data.tag)) {
|
if (data.tag != oldInbound?.tag && inTags.value.includes(data.tag)) {
|
||||||
push.error({
|
push.error({
|
||||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (cData.id != -1) {
|
|
||||||
cData.tag = data.tag
|
// save data
|
||||||
fillData(cData.outJson, data,tls_id>0 ? tlsConfigs.value.findLast(t => t.id == tls_id).client : {})
|
const success = await Data().save("inbounds", modal.value.id == 0 ? "new" : "edit", data)
|
||||||
|
if (success) modal.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// New or Edit
|
const delInbound = async (id: number) => {
|
||||||
if (modal.value.index == -1) {
|
const index = inbounds.value.findIndex(i => i.id == id)
|
||||||
inbounds.value.push(data)
|
const tag = inbounds.value[index].tag
|
||||||
if (stats && data.tag.length>0) {
|
|
||||||
v2rayStats.value.inbounds.push(data.tag)
|
|
||||||
}
|
|
||||||
if (cData.id != -1){
|
|
||||||
inData.value.push(cData)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const oldTag = inbounds.value[modal.value.index].tag
|
|
||||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
|
||||||
|
|
||||||
// Update tls preset
|
const success = await Data().save("inbounds", "del", tag)
|
||||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(oldTag))
|
if (success) delOverlay.value[index] = false
|
||||||
if (oldTlsConfigIndex != -1){
|
|
||||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != oldTag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldTag != data.tag) {
|
const findInboundUsers = (i: Inbound): string[] => {
|
||||||
v2rayStats.value.inbounds = v2rayStats.value.inbounds.filter(item => item != oldTag)
|
return clients.value.filter(c => c.inbounds.includes(i.id)).map(c => c.name)
|
||||||
changeClientInboundsTag(oldTag,data.tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats) {
|
|
||||||
// Add if dos not exist
|
|
||||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.inbounds.push(data.tag)
|
|
||||||
} else {
|
|
||||||
// Delete if exists
|
|
||||||
if (sIndex != -1) v2rayStats.value.inbounds.splice(sIndex,1)
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds.value[modal.value.index] = data
|
|
||||||
const inDataIndex = inData.value.findIndex(indata => indata.tag == oldTag)
|
|
||||||
if (cData.id != -1) {
|
|
||||||
if (inDataIndex == -1){
|
|
||||||
inData.value.push(cData)
|
|
||||||
} else {
|
|
||||||
inData.value[inDataIndex] = cData
|
|
||||||
}
|
|
||||||
} else if (inDataIndex != -1) {
|
|
||||||
Data().delInData(inData.value[inDataIndex].id)
|
|
||||||
inData.value.splice(inDataIndex,1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update tls preset
|
|
||||||
if (tls_id>0) {
|
|
||||||
tlsConfigs.value.findLast(t => t.id == tls_id).inbounds.push(data.tag)
|
|
||||||
tlsConfigs.value.sort()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.hasOwn(data,'users')) {
|
|
||||||
// Set users
|
|
||||||
data = buildInboundsUsers(data)
|
|
||||||
// Update links
|
|
||||||
updateLinks(data)
|
|
||||||
}
|
|
||||||
modal.value.visible = false
|
|
||||||
}
|
|
||||||
const updateLinks = (i: any) => {
|
|
||||||
if(i.users){
|
|
||||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
|
||||||
uClients.forEach((u:Client) => {
|
|
||||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => u.inbounds.includes(inb.tag))
|
|
||||||
const newLinks = <Link[]>[]
|
|
||||||
clientInbounds.forEach(i =>{
|
|
||||||
const tlsClient = tlsConfigs?.value.findLast((t:any) => t.inbounds.includes(i.tag))?.client?? {}
|
|
||||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
|
||||||
const addrs = cData ? <any[]>cData.addrs : []
|
|
||||||
const uris = LinkUtil.linkGenerator(u,i, tlsClient, addrs)
|
|
||||||
if (uris.length>0){
|
|
||||||
uris.forEach(uri => {
|
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let links = u.links && u.links.length>0? u.links : <Link[]>[]
|
|
||||||
links = [...newLinks, ...links.filter(l => l.type != 'local')]
|
|
||||||
|
|
||||||
u.links = links
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const delInbound = (index: number) => {
|
|
||||||
const inb = inbounds.value[index]
|
|
||||||
inbounds.value.splice(index,1)
|
|
||||||
const tag = inb.tag
|
|
||||||
|
|
||||||
if (Object.hasOwn(inb,'users')) {
|
|
||||||
const inbU = <InboundWithUser>inb
|
|
||||||
if (inbU.users && inbU.users.length>0){
|
|
||||||
inbU.users.forEach((u:any) => {
|
|
||||||
const c_index = clients.value.findIndex(c => u.username? u.username == c.name : u.name == c.name)
|
|
||||||
if (c_index != -1) {
|
|
||||||
clients.value[c_index].inbounds = clients.value[c_index].inbounds.filter((x:string) => x!=tag)
|
|
||||||
clients.value[c_index].links = clients.value[c_index].links.filter((x:any) => x.remark!=tag)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete binded tls if exists
|
|
||||||
if (Object.hasOwn(inb,'tls')) {
|
|
||||||
const oldTlsConfigIndex = tlsConfigs?.value.findIndex(t => t.inbounds?.includes(inb.tag))
|
|
||||||
if (oldTlsConfigIndex != -1){
|
|
||||||
tlsConfigs.value[oldTlsConfigIndex].inbounds = tlsConfigs?.value[oldTlsConfigIndex].inbounds.filter((i:string) => i != inb.tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete stats if exists and will be orphaned
|
|
||||||
const tagCounts = inbounds.value.filter(i => i.tag == inb.tag).length
|
|
||||||
const sIndex = v2rayStats.value.inbounds.findIndex(i => i == inb.tag)
|
|
||||||
if (tagCounts == 1 && sIndex != -1){
|
|
||||||
v2rayStats.value.inbounds.splice(sIndex,1)
|
|
||||||
}
|
|
||||||
if (index < Data().oldData.config.inbounds.length){
|
|
||||||
Data().delInbound(index)
|
|
||||||
} else {
|
|
||||||
// Delete new inbound's inData if exists
|
|
||||||
const inDataIndex = Data().inData.findIndex((d:any) => d.tag == tag)
|
|
||||||
if (inDataIndex != -1) Data().inData.splice(inDataIndex, 1)
|
|
||||||
}
|
|
||||||
delOverlay.value[index] = false
|
|
||||||
}
|
|
||||||
const buildInboundsUsers = (inbound:any):Inbound => {
|
|
||||||
const users = <any>[]
|
|
||||||
const inboundClients = clients.value.filter(c => c.enable && c.inbounds.includes(inbound.tag))
|
|
||||||
inboundClients.forEach(c => {
|
|
||||||
// Remove flow in non tls VLESS
|
|
||||||
if (inbound.type == InTypes.VLESS) {
|
|
||||||
const vlessInbound = <VLESS>inbound
|
|
||||||
if (!vlessInbound.tls?.enabled || vlessInbound.transport?.type) delete(c.config?.vless?.flow)
|
|
||||||
}
|
|
||||||
users.push(c.config[inbound.type])
|
|
||||||
})
|
|
||||||
inbound.users = users
|
|
||||||
|
|
||||||
// Exceptions for Naive and ShadowTLSv3
|
|
||||||
if (users.length == 0){
|
|
||||||
if (inbound.type == InTypes.Naive){
|
|
||||||
inbound.users = <any>[{}]
|
|
||||||
} else {
|
|
||||||
if (inbound.type == InTypes.ShadowTLS){
|
|
||||||
const ssTls = <ShadowTLS>inbound
|
|
||||||
if (ssTls.version == 3) inbound.users = <any>[{}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Inbound>inbound
|
|
||||||
}
|
|
||||||
const changeClientInboundsTag = (oldtag: string, newTag:string) => {
|
|
||||||
clients.value.forEach((c, c_index) => {
|
|
||||||
const inbound_index = c.inbounds.findIndex(i => i == oldtag)
|
|
||||||
if (inbound_index != -1) {
|
|
||||||
c.inbounds[inbound_index] = newTag
|
|
||||||
clients.value[c_index].inbounds = c.inbounds
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const findInbounsUsers = (inbound: InboundWithUser): string[] => {
|
|
||||||
if (inbound.users === null || !Array.isArray(inbound.users) || inbound.users.length == 0) return []
|
|
||||||
|
|
||||||
const users = inbound.users.map(user => "username" in user ? user.username : user.name)
|
|
||||||
return users
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stats = ref({
|
const stats = ref({
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
v-model="modal.visible"
|
v-model="modal.visible"
|
||||||
:visible="modal.visible"
|
:visible="modal.visible"
|
||||||
:id="modal.id"
|
:id="modal.id"
|
||||||
:stats="modal.stats"
|
|
||||||
:data="modal.data"
|
:data="modal.data"
|
||||||
:tags="outboundTags"
|
:tags="outboundTags"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
@@ -18,7 +17,7 @@
|
|||||||
/>
|
/>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" justify="center" align="center">
|
<v-col cols="12" justify="center" align="center">
|
||||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -32,26 +31,26 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('in.addr') }}</v-col>
|
<v-col>{{ $t('in.addr') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.server?? '-' }}
|
{{ item.server?? '-' }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('in.port') }}</v-col>
|
<v-col>{{ $t('in.port') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.server_port?? '-' }}
|
{{ item.server_port?? '-' }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('objects.tls') }}</v-col>
|
<v-col>{{ $t('objects.tls') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
{{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('online') }}</v-col>
|
<v-col>{{ $t('online') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<template v-if="onlines[index]">
|
<template v-if="onlines.includes(item.tag)">
|
||||||
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
<v-chip density="comfortable" size="small" color="success" variant="flat">{{ $t('online') }}</v-chip>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>-</template>
|
<template v-else>-</template>
|
||||||
@@ -60,7 +59,7 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions style="padding: 0;">
|
<v-card-actions style="padding: 0;">
|
||||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -77,12 +76,12 @@
|
|||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="error" variant="outlined" @click="delOutbound(index)">{{ $t('yes') }}</v-btn>
|
<v-btn color="error" variant="outlined" @click="delOutbound(item.tag)">{{ $t('yes') }}</v-btn>
|
||||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)" v-if="v2rayStats.outbounds.includes(item.tag)">
|
<v-btn icon="mdi-chart-line" @click="showStats(item.tag)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('stats.graphTitle')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -96,79 +95,56 @@
|
|||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import OutboundVue from '@/layouts/modals/Outbound.vue'
|
import OutboundVue from '@/layouts/modals/Outbound.vue'
|
||||||
import Stats from '@/layouts/modals/Stats.vue'
|
import Stats from '@/layouts/modals/Stats.vue'
|
||||||
import { Config, V2rayApiStats } from '@/types/config';
|
|
||||||
import { Outbound } from '@/types/outbounds';
|
import { Outbound } from '@/types/outbounds';
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { i18n } from '@/locales';
|
import { i18n } from '@/locales';
|
||||||
import { push } from 'notivue';
|
import { push } from 'notivue';
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
|
||||||
return <Config> Data().config
|
|
||||||
})
|
|
||||||
|
|
||||||
const outbounds = computed((): Outbound[] => {
|
const outbounds = computed((): Outbound[] => {
|
||||||
return <Outbound[]> appConfig.value.outbounds
|
return <Outbound[]> Data().outbounds
|
||||||
})
|
})
|
||||||
|
|
||||||
const outboundTags = computed((): string[] => {
|
const outboundTags = computed((): any[] => {
|
||||||
return outbounds.value?.map((o:Outbound) => o.tag)
|
return outbounds.value?.map((o:Outbound) => o.tag)
|
||||||
})
|
})
|
||||||
|
|
||||||
const onlines = computed(() => {
|
const onlines = computed(() => {
|
||||||
return Data().onlines.outbound ? outbounds.value.map(i => Data().onlines.outbound.includes(i.tag)) : []
|
return Data().onlines.outbound?? []
|
||||||
})
|
|
||||||
|
|
||||||
const v2rayStats = computed((): V2rayApiStats => {
|
|
||||||
return <V2rayApiStats> appConfig.value.experimental?.v2ray_api.stats
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
id: -1,
|
id: 0,
|
||||||
data: "",
|
data: "",
|
||||||
stats: false,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let delOverlay = ref(new Array<boolean>)
|
let delOverlay = ref(new Array<boolean>)
|
||||||
|
|
||||||
const showModal = (id: number) => {
|
const showModal = (id: number) => {
|
||||||
modal.value.id = id
|
modal.value.id = id
|
||||||
modal.value.data = id == -1 ? '' : JSON.stringify(outbounds.value[id])
|
modal.value.data = id == 0 ? '' : JSON.stringify(outbounds.value.findLast(o => o.id == id))
|
||||||
modal.value.stats = id == -1 ? false : v2rayStats.value.outbounds.includes(outbounds.value[id].tag)
|
|
||||||
modal.value.visible = true
|
modal.value.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:Outbound, stats: boolean) => {
|
const saveModal = async (data:Outbound) => {
|
||||||
// Check duplicate tag
|
// Check duplicate tag
|
||||||
const oldTag = modal.value.id != -1 ? outbounds.value[modal.value.id].tag : null
|
const oldTag = modal.value.id > 0 ? outbounds.value.findLast(i => i.id == modal.value.id)?.tag : null
|
||||||
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
|
if (data.tag != oldTag && outboundTags.value.includes(data.tag)) {
|
||||||
push.error({
|
push.error({
|
||||||
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
message: i18n.global.t('error.dplData') + ": " + i18n.global.t('objects.tag')
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// New or Edit
|
|
||||||
if (modal.value.id == -1) {
|
|
||||||
outbounds.value.push(data)
|
|
||||||
if (stats && data.tag.length>0) {
|
|
||||||
v2rayStats.value.outbounds.push(data.tag)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == data.tag) // Find if new tag exists
|
|
||||||
|
|
||||||
if (stats) {
|
// save data
|
||||||
// Add if dos not exist
|
const success = await Data().save("outbounds", modal.value.id == 0 ? "new" : "edit", data)
|
||||||
if (data.tag.length>0 && sIndex == -1) v2rayStats.value.outbounds.push(data.tag)
|
if (!success) {
|
||||||
} else {
|
return
|
||||||
// Delete if exists
|
|
||||||
if (sIndex != -1) v2rayStats.value.outbounds.splice(sIndex,1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
outbounds.value[modal.value.id] = data
|
|
||||||
}
|
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,21 +154,10 @@ const stats = ref({
|
|||||||
tag: "",
|
tag: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
const delOutbound = (index: number) => {
|
const delOutbound = async (tag: string) => {
|
||||||
const inb = outbounds.value[index]
|
const index = outbounds.value.findIndex(i => i.tag == tag)
|
||||||
outbounds.value.splice(index,1)
|
const success = await Data().save("outbounds", "del", tag)
|
||||||
const tag = inb.tag
|
if (success) delOverlay.value[index] = false
|
||||||
|
|
||||||
// Delete stats if exists and will be orphaned
|
|
||||||
const tagCounts = outbounds.value.filter(i => i.tag == inb.tag).length
|
|
||||||
const sIndex = v2rayStats.value.outbounds.findIndex(i => i == inb.tag)
|
|
||||||
if (tagCounts == 1 && sIndex != -1){
|
|
||||||
v2rayStats.value.outbounds.splice(sIndex,1)
|
|
||||||
}
|
|
||||||
if (index < Data().oldData.config.outbounds.length){
|
|
||||||
Data().delOutbound(index)
|
|
||||||
}
|
|
||||||
delOverlay.value[index] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const showStats = (tag: string) => {
|
const showStats = (tag: string) => {
|
||||||
@@ -202,4 +167,8 @@ const showStats = (tag: string) => {
|
|||||||
const closeStats = () => {
|
const closeStats = () => {
|
||||||
stats.value.visible = false
|
stats.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function awaitData() {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
<v-col cols="12" justify="center" align="center">
|
<v-col cols="12" justify="center" align="center">
|
||||||
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('rule.add') }}</v-btn>
|
<v-btn color="primary" @click="showRuleModal(-1)" style="margin: 0 5px;">{{ $t('rule.add') }}</v-btn>
|
||||||
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('ruleset.add') }}</v-btn>
|
<v-btn color="primary" @click="showRulesetModal(-1)" style="margin: 0 5px;">{{ $t('ruleset.add') }}</v-btn>
|
||||||
|
<v-btn variant="outlined" color="warning" @click="saveConfig" :loading="loading" :disabled="stateChange">
|
||||||
|
{{ $t('actions.save') }}
|
||||||
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
@@ -38,13 +41,13 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('ruleset.format') }}</v-col>
|
<v-col>{{ $t('ruleset.format') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.format }}
|
{{ item.format }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('actions.update') }}</v-col>
|
<v-col>{{ $t('actions.update') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.update_interval?? '-' }}
|
{{ item.update_interval?? '-' }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -93,21 +96,27 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col>{{ $t('admin.action') }}</v-col>
|
||||||
|
<v-col>
|
||||||
|
{{ item.action }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('objects.outbound') }}</v-col>
|
<v-col>{{ $t('objects.outbound') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.outbound }}
|
{{ item.outbound?? '-' }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('pages.rules') }}</v-col>
|
<v-col>{{ $t('pages.rules') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ item.rules ? item.rules.length : Object.keys(item).filter(r => !["rule_set_ipcidr_match_source","invert","outbound"].includes(r)).length }}
|
{{ item.rules ? item.rules.length : Object.keys(item).filter(r => !actionKeys.includes(r)).length }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('rule.invert') }}</v-col>
|
<v-col>{{ $t('rule.invert') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
|
{{ $t( (item.invert?? false)? 'yes' : 'no') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
@@ -144,16 +153,37 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import RuleVue from '@/layouts/modals/Rule.vue'
|
import RuleVue from '@/layouts/modals/Rule.vue'
|
||||||
import RulesetVue from '@/layouts/modals/Ruleset.vue'
|
import RulesetVue from '@/layouts/modals/Ruleset.vue'
|
||||||
import { Config } from '@/types/config'
|
import { Config } from '@/types/config'
|
||||||
import { logicalRule, ruleset } from '@/types/rules'
|
import { actionKeys, ruleset } from '@/types/rules'
|
||||||
|
import { FindDiff } from '@/plugins/utils'
|
||||||
|
|
||||||
|
const oldConfig = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
const appConfig = computed((): Config => {
|
const appConfig = computed((): Config => {
|
||||||
return <Config> Data().config
|
return <Config> Data().config
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||||
|
})
|
||||||
|
|
||||||
|
const stateChange = computed(() => {
|
||||||
|
return FindDiff.deepCompare(appConfig.value,oldConfig.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveConfig = async () => {
|
||||||
|
loading.value = true
|
||||||
|
const success = await Data().save("config", "set", appConfig.value)
|
||||||
|
if (success) {
|
||||||
|
oldConfig.value = JSON.parse(JSON.stringify(Data().config))
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const clients = computed((): string[] => {
|
const clients = computed((): string[] => {
|
||||||
return Data().clients.map((c:any) => c.name)
|
return Data().clients.map((c:any) => c.name)
|
||||||
})
|
})
|
||||||
@@ -189,11 +219,11 @@ const rulesetTags = computed((): any[] => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const outboundTags = computed((): string[] => {
|
const outboundTags = computed((): string[] => {
|
||||||
return appConfig.value.outbounds?.map((o:any) => o.tag)
|
return [...Data().outbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||||
})
|
})
|
||||||
|
|
||||||
const inboundTags = computed((): string[] => {
|
const inboundTags = computed((): string[] => {
|
||||||
return appConfig.value.inbounds?.map((i:any) => i.tag)
|
return [...Data().inbounds?.map((o:any) => o.tag), ...Data().endpoints?.map((e:any) => e.tag)]
|
||||||
})
|
})
|
||||||
|
|
||||||
let delRuleOverlay = ref(new Array<boolean>)
|
let delRuleOverlay = ref(new Array<boolean>)
|
||||||
@@ -215,15 +245,12 @@ const closeRuleModal = () => {
|
|||||||
ruleModal.value.visible = false
|
ruleModal.value.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRuleModal = (data:logicalRule) => {
|
const saveRuleModal = (data:any) => {
|
||||||
// Logical or simple
|
|
||||||
const ruleData = data.type == 'logical' ? data : data.rules[0]
|
|
||||||
|
|
||||||
// New or Edit
|
// New or Edit
|
||||||
if (ruleModal.value.index == -1) {
|
if (ruleModal.value.index == -1) {
|
||||||
rules.value.push(ruleData)
|
rules.value.push(data)
|
||||||
} else {
|
} else {
|
||||||
rules.value[ruleModal.value.index] = ruleData
|
rules.value[ruleModal.value.index] = data
|
||||||
}
|
}
|
||||||
ruleModal.value.visible = false
|
ruleModal.value.visible = false
|
||||||
}
|
}
|
||||||
@@ -268,7 +295,7 @@ const draggedItemIndex = ref(null);
|
|||||||
|
|
||||||
const onDragStart = (index: any) => {
|
const onDragStart = (index: any) => {
|
||||||
draggedItemIndex.value = index;
|
draggedItemIndex.value = index;
|
||||||
};
|
}
|
||||||
|
|
||||||
const onDrop = (index: any) => {
|
const onDrop = (index: any) => {
|
||||||
if (draggedItemIndex.value !== null) {
|
if (draggedItemIndex.value !== null) {
|
||||||
@@ -278,5 +305,5 @@ const onDrop = (index: any) => {
|
|||||||
rules.value.splice(index, 0, draggedItem);
|
rules.value.splice(index, 0, draggedItem);
|
||||||
draggedItemIndex.value = null;
|
draggedItemIndex.value = null;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
+36
-78
@@ -2,58 +2,61 @@
|
|||||||
<TlsVue
|
<TlsVue
|
||||||
v-model="modal.visible"
|
v-model="modal.visible"
|
||||||
:visible="modal.visible"
|
:visible="modal.visible"
|
||||||
:index="modal.index"
|
:id="modal.id"
|
||||||
:data="modal.data"
|
:data="modal.data"
|
||||||
@close="closeModal"
|
@close="closeModal"
|
||||||
@save="saveModal"
|
@save="saveModal"
|
||||||
/>
|
/>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" justify="center" align="center">
|
<v-col cols="12" justify="center" align="center">
|
||||||
<v-btn color="primary" @click="showModal(-1)">{{ $t('actions.add') }}</v-btn>
|
<v-btn color="primary" @click="showModal(0)">{{ $t('actions.add') }}</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>tlsConfigs" :key="item.id">
|
<v-col cols="12" sm="4" md="3" lg="2" v-for="(item, index) in <any[]>tlsConfigs" :key="item.id">
|
||||||
<v-card rounded="xl" elevation="5" min-width="200" :title="(item.id? item.id + '. ' : '*') + item.name">
|
<v-card rounded="xl" elevation="5" min-width="200" :title="item.name">
|
||||||
<v-card-subtitle style="margin-top: -20px;">
|
<v-card-subtitle style="margin-top: -20px;">
|
||||||
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
|
{{ item.server?.server_name?.length>0 ? item.server.server_name : "-" }}
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
<v-col>{{ $t('pages.inbounds') }}</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
<v-tooltip activator="parent" dir="ltr" location="bottom" v-if="item.inbounds?.length>0">
|
<template v-if="tlsInbounds(item.id).length>0">
|
||||||
<span v-for="i in item.inbounds">{{ i }}<br /></span>
|
<v-tooltip activator="parent" dir="ltr" location="bottom">
|
||||||
|
<span v-for="i in tlsInbounds(item.id)">{{ i }}<br /></span>
|
||||||
</v-tooltip>
|
</v-tooltip>
|
||||||
{{ item.inbounds?.length }}
|
{{ tlsInbounds.length }}
|
||||||
|
</template>
|
||||||
|
<template v-else>-</template>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>ACME</v-col>
|
<v-col>ACME</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ $t(item.server?.acme == undefined ? 'no' : 'yes') }}
|
{{ $t(item.server?.acme == undefined ? 'no' : 'yes') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>ECH</v-col>
|
<v-col>ECH</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ $t(item.server?.ech == undefined ? 'no' : 'yes') }}
|
{{ $t(item.server?.ech == undefined ? 'no' : 'yes') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>Reality</v-col>
|
<v-col>Reality</v-col>
|
||||||
<v-col dir="ltr">
|
<v-col>
|
||||||
{{ $t(item.server?.reality == undefined ? 'no' : 'yes') }}
|
{{ $t(item.server?.reality == undefined ? 'no' : 'yes') }}
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-actions style="padding: 0;">
|
<v-card-actions style="padding: 0;">
|
||||||
<v-btn icon="mdi-file-edit" @click="showModal(index)">
|
<v-btn icon="mdi-file-edit" @click="showModal(item.id)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('actions.edit')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn v-if="item.inbounds?.length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
<v-btn v-if="tlsInbounds(item.id).length == 0" icon="mdi-file-remove" style="margin-inline-start:0;" color="warning" @click="delOverlay[index] = true">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('actions.del')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -66,12 +69,12 @@
|
|||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
<v-card-text>{{ $t('confirm') }}</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="error" variant="outlined" @click="delTls(index)">{{ $t('yes') }}</v-btn>
|
<v-btn color="error" variant="outlined" @click="delTls(item.id)">{{ $t('yes') }}</v-btn>
|
||||||
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
<v-btn color="success" variant="outlined" @click="delOverlay[index] = false">{{ $t('no') }}</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<v-btn icon="mdi-content-duplicate" @click="clone(index)">
|
<v-btn icon="mdi-content-duplicate" @click="clone(item)">
|
||||||
<v-icon />
|
<v-icon />
|
||||||
<v-tooltip activator="parent" location="top" :text="$t('actions.clone')"></v-tooltip>
|
<v-tooltip activator="parent" location="top" :text="$t('actions.clone')"></v-tooltip>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
@@ -85,23 +88,21 @@
|
|||||||
import TlsVue from '@/layouts/modals/Tls.vue'
|
import TlsVue from '@/layouts/modals/Tls.vue'
|
||||||
import Data from '@/store/modules/data'
|
import Data from '@/store/modules/data'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { Config } from '@/types/config'
|
|
||||||
import { Inbound } from '@/types/inbounds'
|
import { Inbound } from '@/types/inbounds'
|
||||||
import { Client } from '@/types/clients'
|
import { Client } from '@/types/clients'
|
||||||
import { Link, LinkUtil } from '@/plugins/link'
|
import { tls } from '@/types/tls'
|
||||||
import { fillData } from '@/plugins/outJson'
|
|
||||||
|
|
||||||
const tlsConfigs = computed((): any[] => {
|
const tlsConfigs = computed((): any[] => {
|
||||||
return Data().tlsConfigs
|
return Data().tlsConfigs
|
||||||
})
|
})
|
||||||
|
|
||||||
const inbounds = computed((): any[] => {
|
const inbounds = computed((): Inbound[] => {
|
||||||
return <any[]>(<Config>Data().config)?.inbounds
|
return Data().inbounds
|
||||||
})
|
})
|
||||||
|
|
||||||
const inData = computed((): any[] => {
|
const tlsInbounds = (id: number): string[] => {
|
||||||
return <any[]> Data().inData
|
return inbounds.value.filter(i => i.tls_id == id).map(i => i.tag)
|
||||||
})
|
}
|
||||||
|
|
||||||
const clients = computed((): any[] => {
|
const clients = computed((): any[] => {
|
||||||
return <Client[]>Data().clients
|
return <Client[]>Data().clients
|
||||||
@@ -109,21 +110,20 @@ const clients = computed((): any[] => {
|
|||||||
|
|
||||||
const modal = ref({
|
const modal = ref({
|
||||||
visible: false,
|
visible: false,
|
||||||
index: -1,
|
id: 0,
|
||||||
data: "",
|
data: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
const delOverlay = ref(new Array<boolean>(tlsConfigs.value.length).fill(false))
|
const delOverlay = ref(new Array<boolean>(tlsConfigs.value.length).fill(false))
|
||||||
|
|
||||||
const showModal = (index: number) => {
|
const showModal = (id: number) => {
|
||||||
modal.value.index = index
|
modal.value.id = id
|
||||||
modal.value.data = index == -1 ? '{}' : JSON.stringify(tlsConfigs.value[index])
|
modal.value.data = id == 0 ? '{}' : JSON.stringify(tlsConfigs.value.findLast(t => t.id == id))
|
||||||
modal.value.visible = true
|
modal.value.visible = true
|
||||||
}
|
}
|
||||||
const clone = (index: number) => {
|
const clone = (obj: any) => {
|
||||||
let data = JSON.parse(JSON.stringify(tlsConfigs.value[index]))
|
let data = JSON.parse(JSON.stringify(obj))
|
||||||
data.id = 0
|
data.id = 0
|
||||||
data.inbounds = []
|
|
||||||
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
|
while (tlsConfigs.value.findIndex(t => t.name == data.name) != -1){
|
||||||
data.name += "-copy"
|
data.name += "-copy"
|
||||||
}
|
}
|
||||||
@@ -132,57 +132,15 @@ const clone = (index: number) => {
|
|||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
modal.value.visible = false
|
modal.value.visible = false
|
||||||
}
|
}
|
||||||
const saveModal = (data:any) => {
|
const saveModal = async (data:tls) => {
|
||||||
// New or Edit
|
const success = await Data().save("tls", data.id == 0 ? "new" : "edit", data)
|
||||||
if (modal.value.index == -1) {
|
if (success) modal.value.visible = false
|
||||||
tlsConfigs.value.push(data)
|
|
||||||
} else {
|
|
||||||
tlsConfigs.value[modal.value.index] = data
|
|
||||||
inbounds?.value.filter(i => tlsConfigs.value[modal.value.index].inbounds.includes(i.tag)).forEach(i =>{
|
|
||||||
if (i.tls != undefined) i.tls = data.server
|
|
||||||
updateInData(i,data.client)
|
|
||||||
updateLinks(i,data.client)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
modal.value.visible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const delTls = (index: number) => {
|
const delTls = async (id: number) => {
|
||||||
if (index < Data().oldData.tlsConfigs.length){
|
const index = tlsConfigs.value.findIndex(t => t.id == id)
|
||||||
Data().delTls(tlsConfigs.value[index].id)
|
const success = await Data().save("tls", "del", id)
|
||||||
}
|
if (success) delOverlay.value[index] = false
|
||||||
tlsConfigs.value.splice(index,1)
|
|
||||||
delOverlay.value[index] = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateLinks = (i:any,tlsClient:any) => {
|
|
||||||
if(i.users){
|
|
||||||
const uClients = clients.value.filter(c => c.inbounds.includes(i.tag))
|
|
||||||
uClients.forEach((client:any) => {
|
|
||||||
const clientInbounds = <Inbound[]>inbounds.value.filter(inb => client?.inbounds.includes(inb.tag))
|
|
||||||
const newLinks = <Link[]>[]
|
|
||||||
clientInbounds.forEach(i =>{
|
|
||||||
const cData = <any>Data().inData?.findLast((d:any) => d.tag == i.tag)
|
|
||||||
const addrs = cData ? <any[]>cData.addrs : []
|
|
||||||
const uris = LinkUtil.linkGenerator(client,i, tlsClient, addrs)
|
|
||||||
if (uris.length>0){
|
|
||||||
uris.forEach(uri => {
|
|
||||||
newLinks.push(<Link>{ type: 'local', remark: i.tag, uri: uri })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let links = client.links && client.links.length>0? client.links : <Link[]>[]
|
|
||||||
links = [...newLinks, ...links.filter((l:Link) => l.type != 'local')]
|
|
||||||
|
|
||||||
client.links = links
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateInData = (i:any, c:any) => {
|
|
||||||
const inDataIndex = inData.value.findIndex(d => d.tag == i.tag)
|
|
||||||
if (inDataIndex != -1) {
|
|
||||||
fillData(inData.value[inDataIndex].outJson, i, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user