diff --git a/.github/workflows/docker-core.yml b/.github/workflows/docker-core.yml deleted file mode 100644 index b0ada90..0000000 --- a/.github/workflows/docker-core.yml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa8eecf..a992777 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,7 +60,7 @@ jobs: cd .. mv frontend/dist backend/web/html - - name: Build s-ui & singbox + - name: Build s-ui run: | export CGO_ENABLED=1 export GOOS=linux @@ -88,27 +88,15 @@ jobs: export CC=s390x-linux-gnu-gcc 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 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 .. mkdir s-ui cp sui s-ui/ cp s-ui.service s-ui/ - cp sing-box.service s-ui/ - mkdir s-ui/bin - cp sing-box/sing-box s-ui/bin/ - cp core/runSingbox.sh s-ui/bin/ + cp s-ui.sh s-ui/ - name: Package run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui diff --git a/Dockerfile b/Dockerfile index 4d4a10e..eb54353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ ENV GOARCH=$TARGETARCH RUN apk update && apk --no-cache --update add build-base gcc wget unzip COPY backend/ ./ 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 LABEL org.opencontainers.image.authors="alireza7@gmail.com" diff --git a/backend/api/api.go b/backend/api/api.go index 0457b04..992139e 100644 --- a/backend/api/api.go +++ b/backend/api/api.go @@ -1,10 +1,11 @@ package api import ( + "encoding/json" "s-ui/logger" "s-ui/service" - "s-ui/singbox" "s-ui/util" + "s-ui/util/common" "strconv" "strings" @@ -17,11 +18,12 @@ type APIHandler struct { service.ConfigService service.ClientService service.TlsService - service.InDataService + service.InboundService + service.OutboundService + service.EndpointService service.PanelService service.StatsService service.ServerService - singbox.Controller } func NewAPIHandler(g *gin.RouterGroup) { @@ -45,6 +47,8 @@ func (a *APIHandler) postHandler(c *gin.Context) { var err error action := c.Param("postAction") remoteIP := getRemoteIp(c) + loginUser := GetLoginUser(c) + hostname := getHostname(c) switch action { case "login": @@ -81,25 +85,31 @@ func (a *APIHandler) postHandler(c *gin.Context) { jsonMsg(c, "", err) } case "save": - loginUser := GetLoginUser(c) - data := map[string]string{} - err = c.ShouldBind(&data) - if err == nil { - err = a.ConfigService.SaveChanges(data, loginUser) + obj := c.Request.FormValue("object") + act := c.Request.FormValue("action") + data := c.Request.FormValue("data") + objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), loginUser, hostname) + if err != nil { + jsonMsg(c, "save", err) + return } - jsonMsg(c, "save", err) + err = a.loadPartialData(c, objs) + if err != nil { + jsonMsg(c, obj, err) + } + return case "restartApp": err = a.PanelService.RestartPanel(3) jsonMsg(c, "restartApp", err) case "restartSb": - err = a.Controller.Restart() + err = a.ConfigService.RestartCore() jsonMsg(c, "restartSb", err) case "linkConvert": link := c.Request.FormValue("link") result, _, err := util.GetOutbound(link, 0) jsonObj(c, result, err) 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 } 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": users, err := a.UserService.GetUsers() if err != nil { @@ -156,10 +172,9 @@ func (a *APIHandler) getHandler(c *gin.Context) { onlines, err := a.StatsService.GetOnlines() jsonObj(c, onlines, err) case "logs": - service := c.Query("s") count := c.Query("c") level := c.Query("l") - logs := a.ServerService.GetLogs(service, count, level) + logs := a.ServerService.GetLogs(count, level) jsonObj(c, logs, nil) case "changes": actor := c.Query("a") @@ -173,7 +188,7 @@ func (a *APIHandler) getHandler(c *gin.Context) { keypair := a.ServerService.GenKeypair(kType, options) jsonObj(c, keypair, nil) 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() if sysInfo["running"] == false { - logs := a.ServerService.GetLogs("sing-box", "1", "debug") + logs := a.ServerService.GetLogs("1", "debug") if len(logs) > 0 { data["lastLog"] = logs[0] } @@ -198,7 +213,7 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { return "", err } if isUpdated { - config, err := a.ConfigService.GetConfig() + config, err := a.SettingService.GetConfig() if err != nil { return "", err } @@ -210,7 +225,15 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { if err != nil { 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 { return "", err } @@ -218,10 +241,12 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { if err != nil { return "", err } - data["config"] = *config + data["config"] = json.RawMessage(config) data["clients"] = clients data["tls"] = tlsConfigs - data["inData"] = inData + data["inbounds"] = inbounds + data["outbounds"] = outbounds + data["endpoints"] = endpoints data["subURI"] = subURI data["onlines"] = onlines } else { @@ -230,3 +255,61 @@ func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { 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 +} diff --git a/backend/api/utils.go b/backend/api/utils.go index 07aa2a7..e26230e 100644 --- a/backend/api/utils.go +++ b/backend/api/utils.go @@ -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) { jsonMsgObj(c, msg, nil, err) } @@ -46,7 +54,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { } } else { m.Success = false - m.Msg = msg + err.Error() + m.Msg = msg + ": " + err.Error() logger.Warning("failed :", err) } c.JSON(http.StatusOK, m) diff --git a/backend/app/app.go b/backend/app/app.go index 1b4c767..502240b 100644 --- a/backend/app/app.go +++ b/backend/app/app.go @@ -3,6 +3,7 @@ package app import ( "log" "s-ui/config" + "s-ui/core" "s-ui/cronjob" "s-ui/database" "s-ui/logger" @@ -15,9 +16,12 @@ import ( type APP struct { service.SettingService - webServer *web.Server - subServer *sub.Server - cronJob *cronjob.CronJob + configService *service.ConfigService + webServer *web.Server + subServer *sub.Server + cronJob *cronjob.CronJob + logger *logging.Logger + core *core.Core } func NewApp() *APP { @@ -34,15 +38,17 @@ func (a *APP) Init() error { return err } + // Init Setting + a.SettingService.GetAllSetting() + + a.core = core.NewCore() + a.cronJob = cronjob.NewCronJob() a.webServer = web.NewServer() a.subServer = sub.NewServer() - configService := service.NewConfigService() - err = configService.InitConfig() - if err != nil { - return err - } + a.configService = service.NewConfigService(a.core) + return nil } @@ -72,6 +78,11 @@ func (a *APP) Start() error { return err } + err = a.configService.StartCore("") + if err != nil { + logger.Error(err) + } + return nil } @@ -85,6 +96,10 @@ func (a *APP) Stop() { if err != nil { logger.Warning("stop Web Server err:", err) } + err = a.configService.StopCore() + if err != nil { + logger.Warning("stop Core err:", err) + } } func (a *APP) initLog() { @@ -106,3 +121,7 @@ func (a *APP) RestartApp() { a.Stop() a.Start() } + +func (a *APP) GetCore() *core.Core { + return a.core +} diff --git a/backend/cmd/cmd.go b/backend/cmd/cmd.go index 361b349..5a3dc53 100644 --- a/backend/cmd/cmd.go +++ b/backend/cmd/cmd.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "os" + "s-ui/cmd/migration" "s-ui/config" ) @@ -40,6 +41,7 @@ func ParseCmd() { fmt.Println() fmt.Println("Commands:") fmt.Println(" admin set/reset/show first admin credentials") + fmt.Println(" uri Show panel URI") fmt.Println(" migrate migrate form older version") fmt.Println(" setting set/reset/show settings") fmt.Println() @@ -71,8 +73,11 @@ func ParseCmd() { showAdmin() } + case "uri": + getPanelURI() + case "migrate": - migrateDb() + migration.MigrateDb() case "setting": err := settingCmd.Parse(os.Args[2:]) diff --git a/backend/cmd/migration.go b/backend/cmd/migration/1_1.go similarity index 74% rename from backend/cmd/migration.go rename to backend/cmd/migration/1_1.go index 2aef0ac..a3453f7 100644 --- a/backend/cmd/migration.go +++ b/backend/cmd/migration/1_1.go @@ -1,50 +1,14 @@ -package cmd +package migration import ( "encoding/json" "fmt" - "log" - "os" - "s-ui/config" "s-ui/database/model" "strings" - "gorm.io/driver/sqlite" "gorm.io/gorm" ) -func migrateDb() { - // void running on first install - path := config.GetDBPath() - _, err := os.Stat(path) - if err != nil { - return - } - - db, err := gorm.Open(sqlite.Open(path)) - if err != nil { - log.Fatal(err) - } - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - fmt.Println("Start migrating database...") - err = migrateClientSchema(tx) - if err != nil { - log.Fatal(err) - } - err = changesObj(tx) - if err != nil { - log.Fatal(err) - } - fmt.Println("Migration done!") -} - func migrateClientSchema(db *gorm.DB) error { rows, err := db.Raw("PRAGMA table_info(clients)").Rows() if err != nil { @@ -95,10 +59,21 @@ func migrateClientSchema(db *gorm.DB) error { } } } - db.AutoMigrate(model.Client{}) return nil } func changesObj(db *gorm.DB) error { return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error } + +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 +} diff --git a/backend/cmd/migration/1_2.go b/backend/cmd/migration/1_2.go new file mode 100644 index 0000000..0e138a7 --- /dev/null +++ b/backend/cmd/migration/1_2.go @@ -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) +} diff --git a/backend/cmd/migration/main.go b/backend/cmd/migration/main.go new file mode 100644 index 0000000..3291909 --- /dev/null +++ b/backend/cmd/migration/main.go @@ -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!") +} diff --git a/backend/cmd/setting.go b/backend/cmd/setting.go index 4b7f0d5..366d8b3 100644 --- a/backend/cmd/setting.go +++ b/backend/cmd/setting.go @@ -2,9 +2,14 @@ package cmd import ( "fmt" + "io" + "net/http" "s-ui/config" "s-ui/database" "s-ui/service" + "strings" + + "github.com/shirou/gopsutil/v4/net" ) func resetSetting() { @@ -103,3 +108,64 @@ func showSetting() { 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) + } + } +} diff --git a/backend/config/config.go b/backend/config/config.go index 8917bd3..85b2859 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -4,6 +4,7 @@ import ( _ "embed" "fmt" "os" + "path/filepath" "strings" ) @@ -13,9 +14,6 @@ var version string //go:embed name var name string -//go:embed config.json -var defaultConfig string - type LogLevel string const ( @@ -48,18 +46,14 @@ func IsDebug() bool { return os.Getenv("SUI_DEBUG") == "true" } -func GetBinFolderPath() string { - binFolderPath := os.Getenv("SUI_BIN_FOLDER") - if binFolderPath == "" { - binFolderPath = "bin" - } - return binFolderPath -} - func GetDBFolderPath() string { dbFolderPath := os.Getenv("SUI_DB_FOLDER") if dbFolderPath == "" { - dbFolderPath = "/usr/local/s-ui/db" + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + dbFolderPath = "/usr/local/s-ui/db" + } + dbFolderPath = dir + "/db" } return dbFolderPath } @@ -67,24 +61,3 @@ func GetDBFolderPath() string { func GetDBPath() string { 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" -} diff --git a/backend/config/config.json b/backend/config/config.json deleted file mode 100644 index f5441f9..0000000 --- a/backend/config/config.json +++ /dev/null @@ -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": [] - } - } - } -} \ No newline at end of file diff --git a/backend/core/box.go b/backend/core/box.go new file mode 100644 index 0000000..da269ce --- /dev/null +++ b/backend/core/box.go @@ -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 +} diff --git a/backend/core/conntracker.go b/backend/core/conntracker.go new file mode 100644 index 0000000..5f99adc --- /dev/null +++ b/backend/core/conntracker.go @@ -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 +} diff --git a/backend/core/endpoint.go b/backend/core/endpoint.go new file mode 100644 index 0000000..d3c3a3c --- /dev/null +++ b/backend/core/endpoint.go @@ -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) +} diff --git a/backend/core/log.go b/backend/core/log.go new file mode 100644 index 0000000..46ac701 --- /dev/null +++ b/backend/core/log.go @@ -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) +} diff --git a/backend/core/main.go b/backend/core/main.go new file mode 100644 index 0000000..ef3ae29 --- /dev/null +++ b/backend/core/main.go @@ -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 +} diff --git a/backend/core/register.go b/backend/core/register.go new file mode 100644 index 0000000..aaf3e01 --- /dev/null +++ b/backend/core/register.go @@ -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 +} diff --git a/backend/cronjob/checkCoreJob.go b/backend/cronjob/checkCoreJob.go new file mode 100644 index 0000000..0f90613 --- /dev/null +++ b/backend/cronjob/checkCoreJob.go @@ -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("") +} diff --git a/backend/cronjob/cronJob.go b/backend/cronjob/cronJob.go index df5853c..4bf3eff 100644 --- a/backend/cronjob/cronJob.go +++ b/backend/cronjob/cronJob.go @@ -25,6 +25,8 @@ func (c *CronJob) Start(loc *time.Location, trafficAge int) error { c.cron.AddJob("@every 1m", NewDepleteJob()) // Start deleting old stats c.cron.AddJob("@daily", NewDelStatsJob(trafficAge)) + // Start core if it is not running + c.cron.AddJob("@every 5s", NewCheckCoreJob()) }() return nil diff --git a/backend/cronjob/depleteJob.go b/backend/cronjob/depleteJob.go index 0b0eb9b..d7adcffa 100644 --- a/backend/cronjob/depleteJob.go +++ b/backend/cronjob/depleteJob.go @@ -6,7 +6,7 @@ import ( ) type DepleteJob struct { - service.ConfigService + service.ClientService } func NewDepleteJob() *DepleteJob { @@ -14,7 +14,7 @@ func NewDepleteJob() *DepleteJob { } func (s *DepleteJob) Run() { - err := s.ConfigService.DepleteClients() + err := s.ClientService.DepleteClients() if err != nil { logger.Warning("Disable depleted users failed: ", err) return diff --git a/backend/cronjob/statsJob.go b/backend/cronjob/statsJob.go index 84a4124..dcf72f7 100644 --- a/backend/cronjob/statsJob.go +++ b/backend/cronjob/statsJob.go @@ -6,15 +6,15 @@ import ( ) type StatsJob struct { - service.SingBoxService + service.StatsService } func NewStatsJob() *StatsJob { - return new(StatsJob) + return &StatsJob{} } func (s *StatsJob) Run() { - err := s.SingBoxService.GetStats() + err := s.StatsService.SaveStats() if err != nil { logger.Warning("Get stats failed: ", err) return diff --git a/backend/database/db.go b/backend/database/db.go index 9e7c5b2..6f6e75c 100644 --- a/backend/database/db.go +++ b/backend/database/db.go @@ -1,6 +1,7 @@ package database import ( + "encoding/json" "os" "path" "s-ui/config" @@ -48,6 +49,10 @@ func OpenDB(dbPath string) error { Logger: gormLogger, } db, err = gorm.Open(sqlite.Open(dbPath), c) + + if config.IsDebug() { + db = db.Debug() + } return err } @@ -57,10 +62,22 @@ func InitDB(dbPath string) error { 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( &model.Setting{}, &model.Tls{}, - &model.InboundData{}, + &model.Inbound{}, + &model.Outbound{}, + &model.Endpoint{}, &model.User{}, &model.Stats{}, &model.Client{}, diff --git a/backend/database/model/endpoints.go b/backend/database/model/endpoints.go new file mode 100644 index 0000000..43de727 --- /dev/null +++ b/backend/database/model/endpoints.go @@ -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) +} diff --git a/backend/database/model/inbounds.go b/backend/database/model/inbounds.go new file mode 100644 index 0000000..72e09ec --- /dev/null +++ b/backend/database/model/inbounds.go @@ -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 +} diff --git a/backend/database/model/model.go b/backend/database/model/model.go index d027aa3..0295e0e 100644 --- a/backend/database/model/model.go +++ b/backend/database/model/model.go @@ -9,18 +9,10 @@ type Setting struct { } type Tls struct { - Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - Name string `json:"name" form:"name"` - Inbounds json.RawMessage `json:"inbounds" form:"inbounds"` - Server json.RawMessage `json:"server" form:"server"` - Client json.RawMessage `json:"client" form:"client"` -} - -type InboundData struct { - Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - Tag string `json:"tag" form:"tag"` - Addrs json.RawMessage `json:"addrs" form:"addrs"` - OutJson json.RawMessage `json:"outJson" form:"outJson"` + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" form:"name"` + Server json.RawMessage `json:"server" form:"server"` + Client json.RawMessage `json:"client" form:"client"` } type User struct { diff --git a/backend/database/model/outbounds.go b/backend/database/model/outbounds.go new file mode 100644 index 0000000..75b6fc2 --- /dev/null +++ b/backend/database/model/outbounds.go @@ -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) +} diff --git a/backend/go.mod b/backend/go.mod index 9574084..22ac54e 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -7,62 +7,125 @@ require ( github.com/gin-gonic/gin v1.10.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 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/v2fly/v2ray-core/v5 v5.17.1 - google.golang.org/grpc v1.67.1 - gorm.io/driver/sqlite v1.5.6 + golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 + gorm.io/driver/sqlite v1.5.7 gorm.io/gorm v1.25.12 ) 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/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/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/gin-contrib/sessions v1.0.1 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-playground/locales v0.14.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-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/gofrs/uuid/v5 v5.3.0 // 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/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // 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/now v1.1.5 // indirect + github.com/josharian/native v1.1.0 // 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/kr/text v0.2.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/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/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/pires/go-proxyproto v0.8.0 // 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/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // 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/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 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/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.31.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/protobuf v1.35.1 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 47c3ce0..401981c 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -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/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 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/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.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= 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/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 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/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= -github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU= -github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM= +github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE= +github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +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/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= 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/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= 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.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 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-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/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/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 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.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +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/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/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/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 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/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= 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/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -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/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= 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/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 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/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= 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.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 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/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/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/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/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/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/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/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-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 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/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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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/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/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= 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/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/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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/quic-go/quic-go v0.46.0 h1:uuwLClEEyk1DNvchH8uCByQVjo3yKL9opKulExNDs7Y= -github.com/quic-go/quic-go v0.46.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= -github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= -github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= -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/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= +github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/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/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ= -github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E= -github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= -github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= +github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= +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/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/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc= -github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY= -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/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= -go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +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/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4= 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.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +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/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +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/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 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= 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.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/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/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/backend/logger/logger.go b/backend/logger/logger.go index a9d7a0c..83622a9 100644 --- a/backend/logger/logger.go +++ b/backend/logger/logger.go @@ -3,7 +3,6 @@ package logger import ( "fmt" "os" - "s-ui/config" "time" "github.com/op/go-logging" @@ -26,10 +25,10 @@ func InitLogger(level logging.Level) { backend, err = logging.NewSyslogBackend("") if err != nil { - println("Unable to use syslog: " + err.Error()) + fmt.Println("Unable to use syslog: " + err.Error()) 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}`) } else { format = logging.MustStringFormatter(`%{level} - %{message}`) @@ -43,6 +42,10 @@ func InitLogger(level logging.Level) { logger = newLogger } +func GetLogger() *logging.Logger { + return logger +} + func Debug(args ...interface{}) { logger.Debug(args...) addToBuffer("DEBUG", fmt.Sprint(args...)) diff --git a/backend/service/client.go b/backend/service/client.go index f66835d..939c628 100644 --- a/backend/service/client.go +++ b/backend/service/client.go @@ -5,12 +5,15 @@ import ( "s-ui/database" "s-ui/database/model" "s-ui/logger" + "s-ui/util" + "s-ui/util/common" "time" "gorm.io/gorm" ) type ClientService struct { + InboundService } func (s *ClientService) GetAll() ([]model.Client, error) { @@ -23,48 +26,236 @@ func (s *ClientService) GetAll() ([]model.Client, error) { 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 - for _, change := range changes { - client := model.Client{} - err = json.Unmarshal(change.Obj, &client) + var inboundIds []uint + + switch act { + case "new", "edit": + var client model.Client + err = json.Unmarshal(data, &client) if err != nil { - return err + return nil, err } - switch change.Action { - case "new": - err = tx.Create(&client).Error - case "del": - err = tx.Where("id = ?", change.Index).Delete(model.Client{}).Error - default: - err = tx.Save(client).Error + 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 + } + case "del": + var id uint + err = json.Unmarshal(data, &id) + if err != nil { + return nil, err + } + var client model.Client + err = tx.Where("id = ?", id).First(&client).Error + if err != nil { + return nil, 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) + } + + 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 } } - 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) DepleteClients() ([]string, []string, error) { +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 clients []model.Client var changes []model.Changes + var users []string + var inboundIds []uint + now := time.Now().Unix() 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 { - return nil, nil, err + return err } dt := time.Now().Unix() - var users, inbounds []string for _, client := range clients { logger.Debug("Client ", client.Name, " is going to be disabled") users = append(users, client.Name) - var userInbounds []string + var userInbounds []uint json.Unmarshal(client.Inbounds, &userInbounds) - inbounds = append(inbounds, userInbounds...) + inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds) changes = append(changes, model.Changes{ DateTime: dt, Actor: "DepleteJob", @@ -76,16 +267,32 @@ func (s *ClientService) DepleteClients() ([]string, []string, error) { // Save changes 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 { - 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 { - return nil, nil, err + return err } 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 } diff --git a/backend/service/config.go b/backend/service/config.go index 91daab0..e9f6fbc 100644 --- a/backend/service/config.go +++ b/backend/service/config.go @@ -2,26 +2,27 @@ package service import ( "encoding/json" - "os" - "s-ui/config" + "s-ui/core" "s-ui/database" "s-ui/database/model" "s-ui/logger" - "s-ui/singbox" + "s-ui/util/common" "strconv" "time" ) -var ApiAddr string -var LastUpdate int64 -var IsSystemd bool +var ( + LastUpdate int64 + corePtr *core.Core +) type ConfigService struct { ClientService TlsService - InDataService - singbox.Controller SettingService + InboundService + OutboundService + EndpointService } type SingBoxConfig struct { @@ -30,90 +31,95 @@ type SingBoxConfig struct { Ntp json.RawMessage `json:"ntp"` Inbounds []json.RawMessage `json:"inbounds"` Outbounds []json.RawMessage `json:"outbounds"` + Endpoints []json.RawMessage `json:"endpoints"` Route json.RawMessage `json:"route"` Experimental json.RawMessage `json:"experimental"` } -func NewConfigService() *ConfigService { +func NewConfigService(core *core.Core) *ConfigService { + corePtr = core return &ConfigService{} } -func (s *ConfigService) InitConfig() error { - IsSystemd = config.IsSystemd() - configPath := config.GetBinFolderPath() - data, err := os.ReadFile(configPath + "/config.json") - 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 +func (s *ConfigService) GetConfig(data string) (*SingBoxConfig, error) { + var err error + if len(data) == 0 { + data, err = s.SettingService.GetConfig() + if err != nil { + return nil, 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") + singboxConfig := SingBoxConfig{} + err = json.Unmarshal([]byte(data), &singboxConfig) if err != nil { return nil, err } - singboxConfig := SingBoxConfig{} - err = json.Unmarshal(data, &singboxConfig) + + 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 { return nil, err } 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 clientChanges, tlsChanges, inChanges, settingChanges, configChanges []model.Changes - if _, ok := changes["clients"]; ok { - 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 - } - } + var inboundIds []uint + var inboundId uint db := database.GetDB() tx := db.Begin() @@ -125,99 +131,96 @@ func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) } }() - if len(clientChanges) > 0 { - err = s.ClientService.Save(tx, clientChanges) + switch obj { + case "clients": + inboundIds, err = s.ClientService.Save(tx, act, data, hostname) + case "tls": + inboundIds, err = s.TlsService.Save(tx, act, data) + case "inbounds": + inboundId, err = s.InboundService.Save(tx, act, data, hostname) + case "outbounds": + err = s.OutboundService.Save(tx, act, data) + case "endpoints": + err = s.EndpointService.Save(tx, act, data) + case "config": + err = s.SettingService.SaveConfig(tx, data) if err != nil { - return err + return nil, err } + err = s.restartCoreWithConfig(data) + default: + return nil, common.NewError("unknown object: ", obj) } - 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": - if change.Action == "edit" { - 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": - if change.Action == "edit" { - newConfig.Outbounds[change.Index] = rawObject - } else if change.Action == "del" { - newConfig.Outbounds = append(newConfig.Outbounds[:change.Index], newConfig.Outbounds[change.Index+1:]...) - } else { - newConfig.Outbounds = append(newConfig.Outbounds, rawObject) - } - } - } - - err = s.Save(&newConfig) - if err != nil { - return err - } + if err != nil { + return nil, err } - // Log changes dt := time.Now().Unix() - allChanges := append(clientChanges, settingChanges...) - allChanges = append(allChanges, configChanges...) - allChanges = append(allChanges, tlsChanges...) - allChanges = append(allChanges, inChanges...) - if len(allChanges) > 0 { - for index := range allChanges { - allChanges[index].DateTime = dt - allChanges[index].Actor = loginUser - } - err = tx.Model(model.Changes{}).Create(&allChanges).Error + err = tx.Create(&model.Changes{ + DateTime: dt, + Actor: loginUser, + Key: obj, + Action: act, + Obj: data, + }).Error + if err != nil { + 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 err + 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) { @@ -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 { c, _ := strconv.Atoi(count) whereString := "`id`>0" diff --git a/backend/service/endpoints.go b/backend/service/endpoints.go new file mode 100644 index 0000000..8ca910e --- /dev/null +++ b/backend/service/endpoints.go @@ -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 +} diff --git a/backend/service/inData.go b/backend/service/inData.go deleted file mode 100644 index 296b3bc..0000000 --- a/backend/service/inData.go +++ /dev/null @@ -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 -} diff --git a/backend/service/inbounds.go b/backend/service/inbounds.go new file mode 100644 index 0000000..480bbfb --- /dev/null +++ b/backend/service/inbounds.go @@ -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 +} diff --git a/backend/service/outbounds.go b/backend/service/outbounds.go new file mode 100644 index 0000000..07b606c --- /dev/null +++ b/backend/service/outbounds.go @@ -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 +} diff --git a/backend/service/server.go b/backend/service/server.go index 33bc5a0..a089edd 100644 --- a/backend/service/server.go +++ b/backend/service/server.go @@ -1,24 +1,24 @@ package service import ( - "bytes" + "encoding/base64" "os" - "os/exec" "runtime" "s-ui/config" "s-ui/logger" "strconv" "strings" + "time" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/host" - "github.com/shirou/gopsutil/v3/mem" - "github.com/shirou/gopsutil/v3/net" + "github.com/sagernet/sing-box/common/tls" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/host" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/net" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) -type ServerService struct { - SingBoxService -} +type ServerService struct{} func (s *ServerService) GetStatus(request string) *map[string]interface{} { status := make(map[string]interface{}, 0) @@ -91,15 +91,21 @@ func (s *ServerService) GetNetInfo() map[string]interface{} { } func (s *ServerService) GetSingboxInfo() map[string]interface{} { - info := make(map[string]interface{}, 0) - sysStats, err := s.SingBoxService.GetSysStats() - if err == nil { - info["running"] = true - info["stats"] = sysStats - } else { - info["running"] = s.SingBoxService.IsRunning() + var rtm runtime.MemStats + runtime.ReadMemStats(&rtm) + isRunning := corePtr.IsRunning() + uptime := uint32(0) + if isRunning { + uptime = corePtr.GetInstance().Uptime() + } + 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{} { @@ -139,48 +145,60 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} { 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) - - if service == "s-ui" { - return logger.GetLogs(c, level) - } - var lines []string - var cmdArgs []string - if IsSystemd { - cmdArgs = []string{"journalctl", "-u", service, "--no-pager", "-n", count, "-p", level} - } else { - cmdArgs = []string{"tail", "/logs/" + service + ".log", "-n", count} - } - // Run the command - cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - return []string{"Failed to get logs!", err.Error()} - } - lines = strings.Split(out.String(), "\n") - - return lines + return logger.GetLogs(c, level) } func (s *ServerService) GenKeypair(keyType string, options string) []string { if len(keyType) == 0 { return []string{"No keypair to generate"} } - sbExec := s.GetBinaryPath() - cmdArgs := []string{"generate", keyType + "-keypair"} - if keyType == "tls" || keyType == "ech" { - cmdArgs = append(cmdArgs, options) + + switch keyType { + case "ech": + 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 strings.Split(out.String(), "\n") + + return []string{"Failed to generate keypair"} +} + +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()} } diff --git a/backend/service/setting.go b/backend/service/setting.go index ea48a9f..3ca4f0d 100644 --- a/backend/service/setting.go +++ b/backend/service/setting.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "os" + "s-ui/config" "s-ui/database" "s-ui/database/model" "s-ui/logger" @@ -14,6 +15,25 @@ import ( "gorm.io/gorm" ) +var defaultConfig = `{ + "log": { + "level": "info" + }, + "dns": {}, + "route": { + "rules": [ + { + "protocol": [ + "dns" + ], + "outbound": "dns-out", + "action": "route" + } + ] + }, + "experimental": {} +}` + var defaultValueMap = map[string]string{ "webListen": "", "webDomain": "", @@ -37,6 +57,8 @@ var defaultValueMap = map[string]string{ "subShowInfo": "false", "subURI": "", "subJsonExt": "", + "config": defaultConfig, + "version": config.GetVersion(), } type SettingService struct { @@ -67,6 +89,8 @@ func (s *SettingService) GetAllSetting() (*map[string]string, error) { // Due to security principles delete(allSetting, "secret") + delete(allSetting, "config") + delete(allSetting, "version") return &allSetting, nil } @@ -311,6 +335,22 @@ func (s *SettingService) GetFinalSubURI(host string) (string, error) { 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 { var err error for _, change := range changes { diff --git a/backend/service/sinxbox.go b/backend/service/sinxbox.go deleted file mode 100644 index ce64a4f..0000000 --- a/backend/service/sinxbox.go +++ /dev/null @@ -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 -} diff --git a/backend/service/stats.go b/backend/service/stats.go index fd7bf36..876b1e1 100644 --- a/backend/service/stats.go +++ b/backend/service/stats.go @@ -19,18 +19,22 @@ var onlineResources = &onlines{} type StatsService struct { } -func (s *StatsService) SaveStats(stats []*model.Stats) error { - var err error +func (s *StatsService) SaveStats() error { + if !corePtr.IsRunning() { + return nil + } + stats := corePtr.GetInstance().ConnTracker().GetStats() // Reset onlines onlineResources.Inbound = nil onlineResources.Outbound = nil onlineResources.User = nil - if len(stats) == 0 { + if len(*stats) == 0 { return nil } + var err error db := database.GetDB() tx := db.Begin() 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.Direction { err = tx.Model(model.Client{}).Where("name = ?", stat.Tag). @@ -70,7 +74,7 @@ func (s *StatsService) SaveStats(stats []*model.Stats) error { 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 result []model.Stats @@ -78,7 +82,11 @@ func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model. timeDiff := currentTime - (int64(limit) * 3600) 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 { return nil, err } diff --git a/backend/service/tls.go b/backend/service/tls.go index ada8cb2..11d23ab 100644 --- a/backend/service/tls.go +++ b/backend/service/tls.go @@ -4,11 +4,13 @@ import ( "encoding/json" "s-ui/database" "s-ui/database/model" + "s-ui/util/common" "gorm.io/gorm" ) type TlsService struct { + InboundService } func (s *TlsService) GetAll() ([]model.Tls, error) { @@ -22,25 +24,45 @@ func (s *TlsService) GetAll() ([]model.Tls, error) { 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 - for _, change := range changes { - tlsConfig := model.Tls{} - err = json.Unmarshal(change.Obj, &tlsConfig) + var inboundIds []uint + + switch action { + case "new", "edit": + var tls model.Tls + err = json.Unmarshal(data, &tls) if err != nil { - return err - } - switch change.Action { - case "new": - err = tx.Create(&tlsConfig).Error - case "del": - err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error - default: - err = tx.Save(tlsConfig).Error + return nil, err } + err = tx.Save(&tls).Error if err != nil { - return err + 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": + var id uint + err = json.Unmarshal(data, &id) + if err != nil { + 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 } diff --git a/backend/singbox/controller.go b/backend/singbox/controller.go deleted file mode 100644 index d5ca5b3..0000000 --- a/backend/singbox/controller.go +++ /dev/null @@ -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") -} diff --git a/backend/singbox/v2rayApi.go b/backend/singbox/v2rayApi.go deleted file mode 100644 index 571edf9..0000000 --- a/backend/singbox/v2rayApi.go +++ /dev/null @@ -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 -} diff --git a/backend/sub/jsonService.go b/backend/sub/jsonService.go index 48c5c93..496f8d7 100644 --- a/backend/sub/jsonService.go +++ b/backend/sub/jsonService.go @@ -87,27 +87,27 @@ func (j *JsonService) GetJson(subId string, format string) (*string, error) { 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() client := &model.Client{} err := db.Model(model.Client{}).Where("enable = true and name = ?", subId).First(client).Error if err != nil { return nil, nil, err } - var inbounds []string - err = json.Unmarshal(client.Inbounds, &inbounds) + var clientInbounds []uint + err = json.Unmarshal(client.Inbounds, &clientInbounds) if err != nil { return nil, nil, err } - inDatas := &[]model.InboundData{} - err = db.Model(model.InboundData{}).Where("tag in ?", inbounds).Find(&inDatas).Error + var inbounds []*model.Inbound + err = db.Model(model.Inbound{}).Where("id in ?", clientInbounds).Find(&inbounds).Error if err != nil { 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 configs map[string]interface{} var outTags []string @@ -116,7 +116,7 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode if err != nil { return nil, nil, err } - for _, inData := range *inDatas { + for _, inData := range inbounds { if len(inData.OutJson) < 5 { continue } @@ -161,22 +161,14 @@ func (j *JsonService) getOutbounds(clientConfig json.RawMessage, inDatas *[]mode newOut["server_port"] = int(port) // Override TLS - newTls, overrideTls := addr["tls"].(bool) - if overrideTls { - tlsIf := map[string]interface{}{} - if newTls { - tlsIf["enabled"] = true - newSNI, overrideSNI := addr["server_name"].(string) - if overrideSNI { - tlsIf["server_name"] = newSNI - } - newInsecure, overrideInsecure := addr["insecure"].(bool) - if overrideInsecure { - tlsIf["insecure"] = newInsecure - } + outTls, _ := newOut["tls"].(map[string]interface{}) + if addrTls, ok := addr["tls"].(map[string]interface{}); ok { + for key, value := range addrTls { + outTls[key] = value } - newOut["tls"] = tlsIf } + newOut["tls"] = outTls + remark, _ := addr["remark"].(string) newTag := fmt.Sprintf("%d.%s%s", index+1, tag, remark) newOut["tag"] = newTag diff --git a/backend/util/genLink.go b/backend/util/genLink.go new file mode 100644 index 0000000..98b06e4 --- /dev/null +++ b/backend/util/genLink.go @@ -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 +} diff --git a/backend/util/outJson.go b/backend/util/outJson.go new file mode 100644 index 0000000..aaa38ea --- /dev/null +++ b/backend/util/outJson.go @@ -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 + } +} diff --git a/build.sh b/build.sh index bb00705..e18494a 100755 --- a/build.sh +++ b/build.sh @@ -12,4 +12,4 @@ mkdir -p web/html rm -fr 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 diff --git a/docker-compose.yml b/docker-compose.yml index 167c0d3..f78f5a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,15 +3,10 @@ services: s-ui: image: alireza7/s-ui container_name: s-ui - hostname: "S-UI docker" + hostname: "s-ui" volumes: - - "singbox:/app/bin" - "./db:/app/db" - "./cert:/app/cert" - - "logs:/logs" - environment: - SINGBOX_API: "sing-box:1080" - SUI_DB_FOLDER: "db" tty: true restart: unless-stopped ports: @@ -19,64 +14,9 @@ services: - "2096:2096" networks: - s-ui - links: - - syslog - logging: - driver: syslog - options: - tag: "s-ui" - syslog-address: "udp://127.0.0.1:1514" entrypoint: "./entrypoint.sh" - depends_on: - - syslog - - sing-box: - 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: s-ui: driver: bridge - -volumes: - logs: - singbox: \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cf837f0..e246658 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -58,12 +58,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz", - "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -73,9 +73,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -497,14 +497,14 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -513,20 +513,23 @@ } }, "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", "dev": true, "license": "Apache-2.0", "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "peer": true, @@ -549,9 +552,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "peer": true, @@ -560,9 +563,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -571,9 +574,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.2.tgz", - "integrity": "sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -610,6 +613,21 @@ "node": ">=18.18.0" } }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -626,9 +644,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -641,13 +659,13 @@ } }, "node_modules/@intlify/core-base": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.1.tgz", - "integrity": "sha512-rG5/hlNW6Qfve41go37szEf0mVLcfhYuOu83JcY0jZKasnwsrcZYYWDzebCcuO5I/6Sy1JFWo9p+nvkQS1Dy+w==", + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.2.tgz", + "integrity": "sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==", "license": "MIT", "dependencies": { - "@intlify/message-compiler": "9.14.1", - "@intlify/shared": "9.14.1" + "@intlify/message-compiler": "9.14.2", + "@intlify/shared": "9.14.2" }, "engines": { "node": ">= 16" @@ -657,12 +675,12 @@ } }, "node_modules/@intlify/message-compiler": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.1.tgz", - "integrity": "sha512-MY8hwukJBnXvGAncVKlHsqKDQ5ZcQx4peqEmI8wBUTXn4pezrtTGYXNoz81cLyEEHB+L/zlKWVBSh5TiX4gYoQ==", + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.2.tgz", + "integrity": "sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==", "license": "MIT", "dependencies": { - "@intlify/shared": "9.14.1", + "@intlify/shared": "9.14.2", "source-map-js": "^1.0.2" }, "engines": { @@ -673,9 +691,9 @@ } }, "node_modules/@intlify/shared": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.1.tgz", - "integrity": "sha512-XjHu6PEQup9MnP1x0W9y0nXXfq9jFftAYSfV11hryjtH4XqXP8HrzMvXI+ZVifF+jZLszaTzIhvukllplxTQTg==", + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.2.tgz", + "integrity": "sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==", "license": "MIT", "engines": { "node": ">= 16" @@ -691,9 +709,9 @@ "license": "MIT" }, "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, "node_modules/@mdi/font": { @@ -741,9 +759,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz", - "integrity": "sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.2.tgz", + "integrity": "sha512-s/8RiF4bdmGnc/J0N7lHAr5ZFJj+NdJqJ/Hj29K+c4lEdoVlukzvWXB9XpWZCdakVT0YAw8iyIqUP2iFRz5/jA==", "cpu": [ "arm" ], @@ -754,9 +772,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz", - "integrity": "sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.2.tgz", + "integrity": "sha512-mKRlVj1KsKWyEOwR6nwpmzakq6SgZXW4NUHNWlYSiyncJpuXk7wdLzuKdWsRoR1WLbWsZBKvsUCdCTIAqRn9cA==", "cpu": [ "arm64" ], @@ -767,9 +785,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz", - "integrity": "sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.2.tgz", + "integrity": "sha512-vJX+vennGwygmutk7N333lvQ/yKVAHnGoBS2xMRQgXWW8tvn46YWuTDOpKroSPR9BEW0Gqdga2DHqz8Pwk6X5w==", "cpu": [ "arm64" ], @@ -780,9 +798,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz", - "integrity": "sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.2.tgz", + "integrity": "sha512-e2rW9ng5O6+Mt3ht8fH0ljfjgSCC6ffmOipiLUgAnlK86CHIaiCdHCzHzmTkMj6vEkqAiRJ7ss6Ibn56B+RE5w==", "cpu": [ "x64" ], @@ -793,9 +811,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz", - "integrity": "sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.2.tgz", + "integrity": "sha512-/xdNwZe+KesG6XJCK043EjEDZTacCtL4yurMZRLESIgHQdvtNyul3iz2Ab03ZJG0pQKbFTu681i+4ETMF9uE/Q==", "cpu": [ "arm64" ], @@ -806,9 +824,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz", - "integrity": "sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.2.tgz", + "integrity": "sha512-eXKvpThGzREuAbc6qxnArHh8l8W4AyTcL8IfEnmx+bcnmaSGgjyAHbzZvHZI2csJ+e0MYddl7DX0X7g3sAuXDQ==", "cpu": [ "x64" ], @@ -819,9 +837,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz", - "integrity": "sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.2.tgz", + "integrity": "sha512-h4VgxxmzmtXLLYNDaUcQevCmPYX6zSj4SwKuzY7SR5YlnCBYsmvfYORXgiU8axhkFCDtQF3RW5LIXT8B14Qykg==", "cpu": [ "arm" ], @@ -832,9 +850,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz", - "integrity": "sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.2.tgz", + "integrity": "sha512-EObwZ45eMmWZQ1w4N7qy4+G1lKHm6mcOwDa+P2+61qxWu1PtQJ/lz2CNJ7W3CkfgN0FQ7cBUy2tk6D5yR4KeXw==", "cpu": [ "arm" ], @@ -845,9 +863,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz", - "integrity": "sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.2.tgz", + "integrity": "sha512-Z7zXVHEXg1elbbYiP/29pPwlJtLeXzjrj4241/kCcECds8Zg9fDfURWbZHRIKrEriAPS8wnVtdl4ZJBvZr325w==", "cpu": [ "arm64" ], @@ -858,9 +876,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz", - "integrity": "sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.2.tgz", + "integrity": "sha512-TF4kxkPq+SudS/r4zGPf0G08Bl7+NZcFrUSR3484WwsHgGgJyPQRLCNrQ/R5J6VzxfEeQR9XRpc8m2t7lD6SEQ==", "cpu": [ "arm64" ], @@ -870,10 +888,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.2.tgz", + "integrity": "sha512-kO9Fv5zZuyj2zB2af4KA29QF6t7YSxKrY7sxZXfw8koDQj9bx5Tk5RjH+kWKFKok0wLGTi4bG117h31N+TIBEg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz", - "integrity": "sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.2.tgz", + "integrity": "sha512-gIh776X7UCBaetVJGdjXPFurGsdWwHHinwRnC5JlLADU8Yk0EdS/Y+dMO264OjJFo7MXQ5PX4xVFbxrwK8zLqA==", "cpu": [ "ppc64" ], @@ -884,9 +915,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz", - "integrity": "sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.2.tgz", + "integrity": "sha512-YgikssQ5UNq1GoFKZydMEkhKbjlUq7G3h8j6yWXLBF24KyoA5BcMtaOUAXq5sydPmOPEqB6kCyJpyifSpCfQ0w==", "cpu": [ "riscv64" ], @@ -897,9 +928,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz", - "integrity": "sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.2.tgz", + "integrity": "sha512-9ouIR2vFWCyL0Z50dfnon5nOrpDdkTG9lNDs7MRaienQKlTyHcDxplmk3IbhFlutpifBSBr2H4rVILwmMLcaMA==", "cpu": [ "s390x" ], @@ -910,9 +941,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz", - "integrity": "sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.2.tgz", + "integrity": "sha512-ckBBNRN/F+NoSUDENDIJ2U9UWmIODgwDB/vEXCPOMcsco1niTkxTXa6D2Y/pvCnpzaidvY2qVxGzLilNs9BSzw==", "cpu": [ "x64" ], @@ -923,9 +954,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz", - "integrity": "sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.2.tgz", + "integrity": "sha512-jycl1wL4AgM2aBFJFlpll/kGvAjhK8GSbEmFT5v3KC3rP/b5xZ1KQmv0vQQ8Bzb2ieFQ0kZFPRMbre/l3Bu9JA==", "cpu": [ "x64" ], @@ -936,9 +967,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz", - "integrity": "sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.2.tgz", + "integrity": "sha512-S2V0LlcOiYkNGlRAWZwwUdNgdZBfvsDHW0wYosYFV3c7aKgEVcbonetZXsHv7jRTTX+oY5nDYT4W6B1oUpMNOg==", "cpu": [ "arm64" ], @@ -949,9 +980,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz", - "integrity": "sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.2.tgz", + "integrity": "sha512-pW8kioj9H5f/UujdoX2atFlXNQ9aCfAxFRaa+mhczwcsusm6gGrSo4z0SLvqLF5LwFqFTjiLCCzGkNK/LE0utQ==", "cpu": [ "ia32" ], @@ -962,9 +993,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz", - "integrity": "sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.2.tgz", + "integrity": "sha512-p6fTArexECPf6KnOHvJXRpAEq0ON1CBtzG/EY4zw08kCHk/kivBc5vUEtnCFNCHOpJZ2ne77fxwRLIKD4wuW2Q==", "cpu": [ "x64" ], @@ -990,9 +1021,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "20.17.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.2.tgz", - "integrity": "sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==", + "version": "20.17.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz", + "integrity": "sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1000,96 +1031,96 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", - "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "node_modules/@volar/language-core": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.8.tgz", - "integrity": "sha512-K/GxMOXGq997bO00cdFhTNuR85xPxj0BEEAy+BaqqayTmy9Tmhfgmq2wpJcVspRhcwfgPoE2/mEJa26emUhG/g==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", + "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/source-map": "2.4.8" + "@volar/source-map": "2.4.11" } }, "node_modules/@volar/source-map": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.8.tgz", - "integrity": "sha512-jeWJBkC/WivdelMwxKkpFL811uH/jJ1kVxa+c7OvG48DXc3VrP7pplSWPP2W1dLMqBxD+awRlg55FQQfiup4cA==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", + "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", "dev": true, "license": "MIT" }, "node_modules/@volar/typescript": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.8.tgz", - "integrity": "sha512-6xkIYJ5xxghVBhVywMoPMidDDAFT1OoQeXwa27HSgJ6AiIKRe61RXLoik+14Z7r0JvnblXVsjsRLmCr42SGzqg==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", + "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "2.4.8", + "@volar/language-core": "2.4.11", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", - "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", - "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", - "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.12", - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", - "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-vue2": { @@ -1110,17 +1141,17 @@ "license": "MIT" }, "node_modules/@vue/language-core": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.8.tgz", - "integrity": "sha512-DtPUKrIRqqzY1joGfVHxHWZoxXZbCQLmVtW+QTifuPInfcs1R/3UAdlJXDp+lpSpP9lI5m+jMYYlwDXXu3KSTg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", + "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", "dev": true, "license": "MIT", "dependencies": { - "@volar/language-core": "~2.4.8", + "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", - "alien-signals": "^0.2.0", + "alien-signals": "^0.4.9", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" @@ -1161,53 +1192,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.12" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", - "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", - "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", - "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.5.12" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "license": "MIT" }, "node_modules/@vuetify/loader-shared": { @@ -1266,9 +1297,9 @@ } }, "node_modules/alien-signals": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.0.tgz", - "integrity": "sha512-StlonZhBBrsPPwrDjiPAiVTf/rolxffLxVPT60Qv/t88BZ81BvUVzHgGqEFvJ1ii8HXtm1+zU2Icr59tfWEcag==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.12.tgz", + "integrity": "sha512-Og0PgAihxlp1R22bsoBsyhhMG4+qhU+fkkLPoGBQkYVc3qt9rYnrwYTf+M6kqUqUZpf3rXDnpL90iKa0QcSVVg==", "dev": true, "license": "MIT" }, @@ -1318,9 +1349,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -1407,9 +1438,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.6.tgz", - "integrity": "sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz", + "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -1508,9 +1539,9 @@ "license": "MIT" }, "node_modules/core-js": { - "version": "3.38.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", - "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", + "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -1519,9 +1550,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "peer": true, @@ -1561,9 +1592,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1667,33 +1698,33 @@ } }, "node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", - "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", + "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1707,8 +1738,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -1729,9 +1759,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.30.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.30.0.tgz", - "integrity": "sha512-CyqlRgShvljFkOeYK8wN5frh/OGTvkj1S7wlr2Q2pUvwq+X5VYiLd6ZjujpgSgLnys2W8qrBLkXQ41SUYaoPIQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz", + "integrity": "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -1768,9 +1798,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", - "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -1786,9 +1816,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -1800,16 +1830,16 @@ } }, "node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "peer": true, "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1880,9 +1910,9 @@ "peer": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -1890,7 +1920,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1926,9 +1956,9 @@ "peer": true }, "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, "license": "ISC", "dependencies": { @@ -1996,9 +2026,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true, "license": "ISC", "peer": true @@ -2357,9 +2387,9 @@ "peer": true }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -2477,9 +2507,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -2680,9 +2710,9 @@ } }, "node_modules/pinia": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.4.tgz", - "integrity": "sha512-K7ZhpMY9iJ9ShTC0cR2+PnxdQRuwVIsXDO/WIEV/RnMC/vmSoKDTKW/exNQYPI+4ij10UjXqdNiEHwn47McANQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.0.tgz", + "integrity": "sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.3", @@ -2692,49 +2722,19 @@ "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "@vue/composition-api": "^1.4.0", "typescript": ">=4.4.4", - "vue": "^2.6.14 || ^3.3.0" + "vue": "^2.7.0 || ^3.5.11" }, "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - }, "typescript": { "optional": true } } }, - "node_modules/pinia/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -2752,7 +2752,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -2802,9 +2802,9 @@ } }, "node_modules/qrcode.vue": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.5.1.tgz", - "integrity": "sha512-Mek5hpUgYP2KsRW4mnyPMUttknuXSe37UorUzymYi3rr/74rV0aTvejl2gF2phrxwAEm6zhpSvkGzIxftxj5Tg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz", + "integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==", "license": "MIT", "peerDependencies": { "vue": "^3.0.0" @@ -2889,9 +2889,9 @@ "license": "Apache-2.0" }, "node_modules/rollup": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz", - "integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.2.tgz", + "integrity": "sha512-tJXpsEkzsEzyAKIaB3qv3IuvTVcTN7qBw1jL4SPPXM3vzDrJgiLGFY6+HodgFaUHAJ2RYJ94zV5MKRJCoQzQeA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -2905,24 +2905,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.2", - "@rollup/rollup-android-arm64": "4.24.2", - "@rollup/rollup-darwin-arm64": "4.24.2", - "@rollup/rollup-darwin-x64": "4.24.2", - "@rollup/rollup-freebsd-arm64": "4.24.2", - "@rollup/rollup-freebsd-x64": "4.24.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.2", - "@rollup/rollup-linux-arm-musleabihf": "4.24.2", - "@rollup/rollup-linux-arm64-gnu": "4.24.2", - "@rollup/rollup-linux-arm64-musl": "4.24.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.2", - "@rollup/rollup-linux-riscv64-gnu": "4.24.2", - "@rollup/rollup-linux-s390x-gnu": "4.24.2", - "@rollup/rollup-linux-x64-gnu": "4.24.2", - "@rollup/rollup-linux-x64-musl": "4.24.2", - "@rollup/rollup-win32-arm64-msvc": "4.24.2", - "@rollup/rollup-win32-ia32-msvc": "4.24.2", - "@rollup/rollup-win32-x64-msvc": "4.24.2", + "@rollup/rollup-android-arm-eabi": "4.29.2", + "@rollup/rollup-android-arm64": "4.29.2", + "@rollup/rollup-darwin-arm64": "4.29.2", + "@rollup/rollup-darwin-x64": "4.29.2", + "@rollup/rollup-freebsd-arm64": "4.29.2", + "@rollup/rollup-freebsd-x64": "4.29.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.29.2", + "@rollup/rollup-linux-arm-musleabihf": "4.29.2", + "@rollup/rollup-linux-arm64-gnu": "4.29.2", + "@rollup/rollup-linux-arm64-musl": "4.29.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.29.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.29.2", + "@rollup/rollup-linux-riscv64-gnu": "4.29.2", + "@rollup/rollup-linux-s390x-gnu": "4.29.2", + "@rollup/rollup-linux-x64-gnu": "4.29.2", + "@rollup/rollup-linux-x64-musl": "4.29.2", + "@rollup/rollup-win32-arm64-msvc": "4.29.2", + "@rollup/rollup-win32-ia32-msvc": "4.29.2", + "@rollup/rollup-win32-x64-msvc": "4.29.2", "fsevents": "~2.3.2" } }, @@ -3049,14 +3050,6 @@ "node": ">=8" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -3104,9 +3097,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -3125,9 +3118,9 @@ "license": "MIT" }, "node_modules/unplugin": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.15.0.tgz", - "integrity": "sha512-jTPIs63W+DUEDW207ztbaoO7cQ4p5aVaB823LSlxpsFEU3Mykwxf3ZGC/wzxFJeZlASZYgVrWeo7LgOrqJZ8RA==", + "version": "2.0.0-beta.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.0.0-beta.1.tgz", + "integrity": "sha512-2qzQo5LN2DmUZXkWDHvGKLF5BP0WN+KthD6aPnPJ8plRBIjv4lh5O07eYcSxgO2znNw9s4MNhEO1sB+JDllDbQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3135,30 +3128,22 @@ "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } + "node": ">=18.12.0" } }, "node_modules/unplugin-fonts": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.1.1.tgz", - "integrity": "sha512-/Aw/rL9D2aslGGM0vi+2R2aG508RSwawLnnBuo+JDSqYc4cHJO1R1phllhN6GysEhBp/6a4B6+vSFPVapWyAAw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/unplugin-fonts/-/unplugin-fonts-1.3.1.tgz", + "integrity": "sha512-GmaJWPAWH6lBI4fP8xKdbMZJwTgsnr8PGJOfQE52jlod8QkqSO4M529Nox2L8zYapjB5hox2wCu4N3c/LOal/A==", "dev": true, "license": "MIT", "dependencies": { - "fast-glob": "^3.2.12", - "unplugin": "^1.3.1" + "fast-glob": "^3.3.2", + "unplugin": "2.0.0-beta.1" }, "peerDependencies": { "@nuxt/kit": "^3.0.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" }, "peerDependenciesMeta": { "@nuxt/kit": { @@ -3196,9 +3181,9 @@ "license": "MIT" }, "node_modules/vite": { - "version": "5.4.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3283,16 +3268,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", - "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-sfc": "3.5.12", - "@vue/runtime-dom": "3.5.12", - "@vue/server-renderer": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -3304,15 +3289,41 @@ } }, "node_modules/vue-chartjs": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.1.tgz", - "integrity": "sha512-rZjqcHBxKiHrBl0CIvcOlVEBwRhpWAVf6rDU3vUfa7HuSRmGtCslc0Oc8m16oAVuk0erzc1FCtH1VCriHsrz+A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.2.tgz", + "integrity": "sha512-NrkbRRoYshbXbWqJkTN6InoDVwVb90C0R7eAVgMWcB9dPikbruaOoTFjFYHE/+tNPdIe6qdLCDjfjPHQ0fw4jw==", "license": "MIT", "peerDependencies": { "chart.js": "^4.1.1", "vue": "^3.0.0-0 || ^2.7.0" } }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-eslint-parser": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", @@ -3387,13 +3398,13 @@ } }, "node_modules/vue-i18n": { - "version": "9.14.1", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.1.tgz", - "integrity": "sha512-xjxV0LYc1xQ8TbAVfIyZiOSS8qoU1R0YwV7V5I8I6Fd64+zvsTsdPgtylPsie3Vdt9wekeYhr+smKDeaK6RBuA==", + "version": "9.14.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.2.tgz", + "integrity": "sha512-JK9Pm80OqssGJU2Y6F7DcM8RFHqVG4WkuCqOZTVsXkEzZME7ABejAUqUdA931zEBedc4thBgSUWxeQh4uocJAQ==", "license": "MIT", "dependencies": { - "@intlify/core-base": "9.14.1", - "@intlify/shared": "9.14.1", + "@intlify/core-base": "9.14.2", + "@intlify/shared": "9.14.2", "@vue/devtools-api": "^6.5.0" }, "engines": { @@ -3407,9 +3418,9 @@ } }, "node_modules/vue-router": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", - "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.4" @@ -3422,15 +3433,14 @@ } }, "node_modules/vue-tsc": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.8.tgz", - "integrity": "sha512-6+vjb7JLxKIzeD/1ktoUBZGAr+148FQoEFl8Lv5EpDJLO2PrUalhp7atMEuzEkLnoooM5bg3pJqjZI+oobxIaQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz", + "integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==", "dev": true, "license": "MIT", "dependencies": { - "@volar/typescript": "~2.4.8", - "@vue/language-core": "2.1.8", - "semver": "^7.5.4" + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.0" }, "bin": { "vue-tsc": "bin/vue-tsc.js" @@ -3449,9 +3459,9 @@ } }, "node_modules/vuetify": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.3.tgz", - "integrity": "sha512-bpuvBpZl1/+nLlXDgdVXekvMNR6W/ciaoa8CYlpeAzAARbY8zUFSoBq05JlLhkIHI58AnzKVy4c09d0OtfYAPg==", + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.6.tgz", + "integrity": "sha512-lol0Va5HtMIqZfjccSD5DLv5v31R/asJXzc6s7ULy51PHr1DjXxWylZejhq0kVpMGW64MiV1FmA/p8eYQfOWfQ==", "license": "MIT", "engines": { "node": "^12.20 || >=14.13" diff --git a/frontend/src/components/Addr.vue b/frontend/src/components/Addr.vue index 6f79b6c..98ccbec 100644 --- a/frontend/src/components/Addr.vue +++ b/frontend/src/components/Addr.vue @@ -24,30 +24,7 @@ - - - - - - - - - - - - + @@ -63,12 +40,6 @@ - - - - - - @@ -77,6 +48,7 @@ \ No newline at end of file diff --git a/frontend/src/components/Listen.vue b/frontend/src/components/Listen.vue index 2b29110..d037edb 100644 --- a/frontend/src/components/Listen.vue +++ b/frontend/src/components/Listen.vue @@ -14,6 +14,8 @@ :label="$t('in.port')" hide-details type="number" + min="1" + max="65535" required v-model.number="inbound.listen_port"> @@ -27,24 +29,6 @@ v-model="inbound.detour"> - - - - - - - - - - - @@ -68,16 +52,6 @@ v-model.number="udpTimeout"> - - - - - - @@ -95,9 +69,6 @@ - - - @@ -118,10 +89,6 @@ export default { 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' } }, - 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: { get(): boolean { return this.$props.inbound.tcp_fast_open != undefined && @@ -145,10 +112,6 @@ export default { optionDetour: { get(): boolean { return this.$props.inbound.detour != 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 } } } } diff --git a/frontend/src/components/Main.vue b/frontend/src/components/Main.vue index 583d480..9fa97c0 100644 --- a/frontend/src/components/Main.vue +++ b/frontend/src/components/Main.vue @@ -2,7 +2,6 @@ @@ -93,7 +92,7 @@ v{{ tilesData.sys?.appVersion }} - + {{ $t('basic.log.title') + " - S-UI" }} @@ -110,12 +109,6 @@ {{ $t('yes') }} {{ $t('no') }} - - - {{ $t('basic.log.title') + " - Sing-Box" }} - - - {{ $t('actions.restartSb') }} @@ -244,16 +237,13 @@ onBeforeUnmount(() => { const logModal = ref({ visible: false, - logType: "s-ui" }) -const openLogs = (logType: string) => { - logModal.value.logType = logType +const openLogs = () => { logModal.value.visible = true } const closeLogs = () => { - logModal.value.logType = "s-ui" logModal.value.visible = false } diff --git a/frontend/src/components/OutJson.vue b/frontend/src/components/OutJson.vue index 35dbf74..afbc382 100644 --- a/frontend/src/components/OutJson.vue +++ b/frontend/src/components/OutJson.vue @@ -6,20 +6,20 @@ hide-details :items="['4','4a','5']" :label="$t('version')" - v-model="inData.outJson.version"> + v-model="inData.out_json.version"> - + - + + v-model="inData.out_json.path"> @@ -36,14 +36,14 @@ hide-details :label="$t('types.vmess.security')" :items="vmessSecurities" - v-model="inData.outJson.security"> + v-model="inData.out_json.security"> - + - + @@ -52,7 +52,7 @@ hide-details type="number" min="0" - v-model.number="inData.outJson.recv_window"> + v-model.number="inData.out_json.recv_window"> - + @@ -114,8 +114,8 @@ export default { needNetwork():boolean { return this.haveNetwork.includes(this.$props.type) }, needUot():boolean { return this.havUoT.includes(this.$props.type) }, packet_encoding: { - get() { return this.$props.inData.outJson.packet_encoding != undefined ? this.$props.inData.outJson.packet_encoding : 'none'; }, - set(v:string) { this.$props.inData.outJson.packet_encoding = v != "none" ? v : undefined } + get() { return this.$props.inData.out_json.packet_encoding != undefined ? this.$props.inData.out_json.packet_encoding : 'none'; }, + set(v:string) { this.$props.inData.out_json.packet_encoding = v != "none" ? v : undefined } }, }, components: { Network, UoT, Headers } diff --git a/frontend/src/components/WgPeer.vue b/frontend/src/components/WgPeer.vue index 6dffcc1..695fe3f 100644 --- a/frontend/src/components/WgPeer.vue +++ b/frontend/src/components/WgPeer.vue @@ -4,7 +4,7 @@ + v-model="address"> @@ -13,7 +13,16 @@ type="number" min="0" hide-details - v-model="data.server_port"> + v-model="port"> + + + + @@ -36,6 +45,8 @@ \ No newline at end of file diff --git a/frontend/src/components/protocols/Direct.vue b/frontend/src/components/protocols/Direct.vue index c88423a..34823fc 100644 --- a/frontend/src/components/protocols/Direct.vue +++ b/frontend/src/components/protocols/Direct.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/layouts/default/Drawer.vue b/frontend/src/layouts/default/Drawer.vue index 4701039..13e8943 100644 --- a/frontend/src/layouts/default/Drawer.vue +++ b/frontend/src/layouts/default/Drawer.vue @@ -53,6 +53,7 @@ const menu = [ { title: 'pages.inbounds', icon: 'mdi-cloud-download', path: '/inbounds' }, { title: 'pages.clients', icon: 'mdi-account-multiple', path: '/clients' }, { 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.tls', icon: 'mdi-certificate', path: '/tls' }, { title: 'pages.basics', icon: 'mdi-application-cog', path: '/basics' }, diff --git a/frontend/src/layouts/modals/Client.vue b/frontend/src/layouts/modals/Client.vue index cae827a..47f28ac 100644 --- a/frontend/src/layouts/modals/Client.vue +++ b/frontend/src/layouts/modals/Client.vue @@ -41,7 +41,7 @@ - +
@@ -70,19 +70,14 @@ - - - - - - + > @@ -183,13 +178,12 @@ \ No newline at end of file diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue index cfd8cdf..e085d8d 100644 --- a/frontend/src/layouts/modals/Inbound.vue +++ b/frontend/src/layouts/modals/Inbound.vue @@ -1,12 +1,18 @@ @@ -347,11 +345,9 @@ import ClientModal from '@/layouts/modals/Client.vue' import ClientBulk from '@/layouts/modals/ClientBulk.vue' import QrCode from '@/layouts/modals/QrCode.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 { Config, V2rayApiStats } from '@/types/config' -import { InTypes, Inbound,InboundWithUser, ShadowTLS, VLESS } from '@/types/inbounds' -import { Link, LinkUtil } from '@/plugins/link' +import { Inbound, inboundWithUsers } from '@/types/inbounds' import { HumanReadable } from '@/plugins/utils' import { i18n } from '@/locales' import { push } from 'notivue' @@ -367,21 +363,13 @@ const isOnline = (cname: string) => computed(() => { return Data().onlines?.user ? Data().onlines.user.includes(cname) : false }) -const appConfig = computed((): Config => { - return Data().config -}) - -const v2rayStats = computed((): V2rayApiStats => { - return appConfig.value.experimental.v2ray_api.stats -}) - const inbounds = computed((): Inbound[] => { - return appConfig.value?.inbounds + return Data().inbounds?? [] }) -const inboundTags = computed((): string[] => { +const inboundTags = computed((): any[] => { 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[] => { @@ -430,128 +418,39 @@ const groupBy = [ const modal = ref({ visible: false, - index: -1, + id: 0, data: "", - stats: false, }) const delOverlay = ref(new Array(clients.value.length).fill(false)) -const showModal = (id: number) => { - const index = id == -1 ? -1 : clients.value.findIndex(c => c.id == id) - modal.value.index = index - modal.value.data = index == -1 ? '' : JSON.stringify(clients.value[index]) - modal.value.stats = index == -1 ? false : v2rayStats.value.users.includes(clients.value[index].name) +const showModal = async (id: number) => { + modal.value.id = id + modal.value.data = id == 0 ? '' : JSON.stringify(clients.value.findLast(o => o.id == id)) modal.value.visible = true } const closeModal = () => { modal.value.visible = false } -const saveModal = (data:any, stats:boolean) => { +const saveModal = async (data:any) => { // 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) { push.error({ message: i18n.global.t('error.dplData') + ": " + i18n.global.t('client.name') }) return } - if(modal.value.index == -1) { - clients.value.push(data) - } else { - clients.value[modal.value.index] = data - } - // Rebuild affected inbounds - buildInboundsUsers(data.inbounds) - - // Rebuild links - 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 + // save data + const success = await Data().save("clients", modal.value.id == 0 ? "new" : "edit", data) + if (success) 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 = [] - const newInbound = 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 = 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 = [{}] - } else { - if (newInbound.type == InTypes.ShadowTLS){ - const ssTls = newInbound - if (ssTls.version == 3) newInbound.users = [{}] - } - } - } - - inbounds.value[inbound_index] = newInbound - } - }) -} -const updateLinks = (c:Client):Link[] => { - const clientInbounds = inbounds.value.filter(i => c.inbounds.includes(i.tag)) - const newLinks = [] - clientInbounds.forEach(i =>{ - const tlsConfig = Data().tlsConfigs?.findLast((t:any) => t.inbounds.includes(i.tag)) - const cData = Data().inData?.findLast((d:any) => d.tag == i.tag) - const addrs = cData ? cData.addrs : [] - const uris = LinkUtil.linkGenerator(c,i, tlsConfig?.client?? {}, addrs) - if (uris.length>0){ - uris.forEach(uri => { - newLinks.push({ type: 'local', remark: i.tag, uri: uri }) - }) - } - }) - let links = c.links && c.links.length>0? c.links : [] - 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 delClient = async (id: number) => { + const index = clients.value.findIndex(c => c.id === id) + const success = await Data().save("clients", "del", id) + if (success) delOverlay.value[index] = false } const qrcode = ref({ @@ -603,7 +502,7 @@ const doFilter = () => { filteredClients = filteredClients.filter(c => c.enable == false) break case "expired": - filteredClients = filteredClients.filter(c => HumanReadable.remainedDays(c.expiry) == null) + filteredClients = filteredClients.filter(c => c.expiry > 0 && c.expiry < (Date.now()/1000) ) break case "online": filteredClients = filteredClients.filter(c => Data().onlines?.user?.includes(c.name)) @@ -636,14 +535,20 @@ const closeBulk = () => { addBulkModal.value = false } -const saveBulk = (bulkClients: Client[], clientInbounds: string[], clientStats: boolean) => { - bulkClients.forEach((c,c_index) => { - bulkClients[c_index].links = updateLinks(c) - }) - clients.value.push(...bulkClients) - buildInboundsUsers(clientInbounds) - // Stats - if (clientStats) v2rayStats.value.users.push(...bulkClients.map(bc => bc.name)) - closeBulk() +const saveBulk = async (bulkClients: Client[]) => { + // Check duplicate name + 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') + }) + return + } + + // save data + const success = await Data().save("clients", "addbulk", bulkClients) + if (success) closeBulk() } \ No newline at end of file diff --git a/frontend/src/views/Endpoints.vue b/frontend/src/views/Endpoints.vue new file mode 100644 index 0000000..6e2c40c --- /dev/null +++ b/frontend/src/views/Endpoints.vue @@ -0,0 +1,166 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue index 17c647d..eec2206 100644 --- a/frontend/src/views/Inbounds.vue +++ b/frontend/src/views/Inbounds.vue @@ -2,10 +2,7 @@ - {{ $t('actions.add') }} + {{ $t('actions.add') }} @@ -35,35 +32,38 @@ {{ $t('in.addr') }} - + {{ item.listen }} {{ $t('in.port') }} - + {{ item.listen_port }} {{ $t('objects.tls') }} - - {{ Object.hasOwn(item,'tls') ? $t(item.tls?.enabled ? 'enable' : 'disable') : '-' }} + + {{ item.tls_id > 0 ? $t('enable') : $t('disable') }} {{ $t('pages.clients') }} - - - {{ u }}
-
- {{ Array.isArray(item.users) ? item.users.length : '-' }} + + +
{{ $t('online') }} - -