diff --git a/backend/.gitignore b/.dockerignore similarity index 97% rename from backend/.gitignore rename to .dockerignore index d9c57cc..7b13414 100644 --- a/backend/.gitignore +++ b/.dockerignore @@ -3,6 +3,7 @@ dist/ release/ backup/ bin/ +db/ sui web/html main diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f25e629..5445036 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,9 +1,5 @@ version: 2 updates: - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "daily" - package-ecosystem: "gomod" directory: "/" schedule: 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/docker.yml b/.github/workflows/docker.yml index 95bdda1..55a129e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,10 +7,13 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4.1.1 + with: + submodules: recursive - name: Docker meta id: meta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa8eecf..4feb9f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,16 +18,18 @@ jobs: - armv5 - 386 - s390x - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v4.1.1 + with: + submodules: recursive - name: Setup Go uses: actions/setup-go@v5 with: cache: false - go-version-file: backend/go.mod + go-version-file: go.mod - name: Setup Node.js uses: actions/setup-node@v4 @@ -58,9 +60,10 @@ jobs: npm install npm run build cd .. - mv frontend/dist backend/web/html + mv frontend/dist web/html + rm -fr frontend - - name: Build s-ui & singbox + - name: Build s-ui run: | export CGO_ENABLED=1 export GOOS=linux @@ -88,27 +91,13 @@ 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 - cd .. + 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 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/.gitignore b/.gitignore index ff60407..35e1c36 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,12 @@ backup/ bin/ db/ sui +web/html main tmp .sync* *.tar.gz +frontend/ # local env files .env.local diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..89c4063 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "frontend"] + path = frontend + url = https://github.com/alireza0/s-ui-frontend + branch = main diff --git a/Dockerfile b/Dockerfile index 4d4a10e..b57f518 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,9 +10,9 @@ ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" ENV CGO_ENABLED=1 ENV GOARCH=$TARGETARCH RUN apk update && apk --no-cache --update add build-base gcc wget unzip -COPY backend/ ./ +COPY . . 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/README.md b/README.md index b86a63d..35d9ea5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ![](https://img.shields.io/github/v/release/alireza0/s-ui.svg) ![S-UI Docker pull](https://img.shields.io/docker/pulls/alireza7/s-ui.svg) ![S-UI-Singbox Docker pull](https://img.shields.io/docker/pulls/alireza7/s-ui-singbox.svg) +[![Go Report Card](https://goreportcard.com/badge/github.com/alireza0/s-ui)](https://goreportcard.com/report/github.com/alireza0/s-ui) [![Downloads](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg)](https://img.shields.io/github/downloads/alireza0/s-ui/total.svg) [![License](https://img.shields.io/badge/license-GPL%20V3-blue.svg?longCache=true)](https://www.gnu.org/licenses/gpl-3.0.en.html) @@ -25,7 +26,11 @@ | Client & Traffic & System Status | :heavy_check_mark: | | Subscription Service (link/json + info)| :heavy_check_mark: | | Dark/Light Theme | :heavy_check_mark: | +| API Interface | :heavy_check_mark: | +## API Documentation + +[API-Documentation Wiki](https://github.com/alireza0/s-ui/wiki/API-Documentation) ## Default Installation Information - Panel Port: 2095 @@ -63,16 +68,13 @@ VERSION=1.0.0 && bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui ```sh sudo -i -systemctl disable sing-box --now systemctl disable s-ui --now -rm -f /etc/systemd/system/s-ui.service rm -f /etc/systemd/system/sing-box.service systemctl daemon-reload rm -fr /usr/local/s-ui rm /usr/bin/s-ui - ``` ## Install using Docker @@ -98,7 +100,7 @@ wget -q https://raw.githubusercontent.com/alireza0/s-ui/master/docker-compose.ym docker compose up -d ``` -> Use docker for s-ui only +> Use docker ```shell mkdir s-ui && cd s-ui @@ -113,6 +115,8 @@ docker run -itd \ > Build your own image ```shell +git clone https://github.com/alireza0/s-ui +git submodule update --init --recursive docker build -t s-ui . ``` @@ -128,37 +132,30 @@ docker build -t s-ui . ./runSUI.sh ``` +### Clone the repository +```shell +# clone repository +git clone https://github.com/alireza0/s-ui +# clone submodules +git submodule update --init --recursive +``` + + ### - Frontend -Frontend codes are in `frontend` folder in the root of repository. - -To run it locally for instant development you can use (apply automatic changes on file save): -```shell -cd frontend -npm run dev -``` -> By this command it will run a `vite` web server on separate port `3000`, with backend proxy to `http://localhost:2095`. You can change it in `frontend/vite.config.mts`. - -To build frontend: -```shell -cd frontend -npm run build -``` +Visit [s-ui-frontend](https://github.com/alireza0/s-ui-frontend) for frontend code ### - Backend -Backend codes are in `backend` folder in the root of repository. > Please build frontend once before! To build backend: ```shell -cd backend - # remove old frontend compiled files rm -fr web/html/* # apply new frontend compiled files -cp -R ../frontend/dist/ web/html/ +cp -R frontend/dist/ web/html/ # build -go build -o ../sui main.go +go build -o sui main.go ``` To run backend (from root folder of repository): @@ -182,7 +179,7 @@ To run backend (from root folder of repository): - Supported protocols: - General: Mixed, SOCKS, HTTP, HTTPS, Direct, Redirect, TProxy - V2Ray based: VLESS, VMess, Trojan, Shadowsocks - - Other protocols: ShadowTLS, Hysteria, Hysteri2, Naive, TUIC + - Other protocols: ShadowTLS, Hysteria, Hysteria2, Naive, TUIC - Supports XTLS protocols - An advanced interface for routing traffic, incorporating PROXY Protocol, External, and Transparent Proxy, SSL Certificate, and Port - An advanced interface for inbound and outbound configuration diff --git a/api/apiHandler.go b/api/apiHandler.go new file mode 100644 index 0000000..7244e17 --- /dev/null +++ b/api/apiHandler.go @@ -0,0 +1,100 @@ +package api + +import ( + "s-ui/util/common" + "strings" + + "github.com/gin-gonic/gin" +) + +type APIHandler struct { + ApiService + apiv2 *APIv2Handler +} + +func NewAPIHandler(g *gin.RouterGroup, a2 *APIv2Handler) { + a := &APIHandler{ + apiv2: a2, + } + a.initRouter(g) +} + +func (a *APIHandler) initRouter(g *gin.RouterGroup) { + g.Use(func(c *gin.Context) { + path := c.Request.URL.Path + if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") { + checkLogin(c) + } + }) + g.POST("/:postAction", a.postHandler) + g.GET("/:getAction", a.getHandler) +} + +func (a *APIHandler) postHandler(c *gin.Context) { + loginUser := GetLoginUser(c) + action := c.Param("postAction") + + switch action { + case "login": + a.ApiService.Login(c) + case "changePass": + a.ApiService.ChangePass(c) + case "save": + a.ApiService.Save(c, loginUser) + case "restartApp": + a.ApiService.RestartApp(c) + case "restartSb": + a.ApiService.RestartSb(c) + case "linkConvert": + a.ApiService.LinkConvert(c) + case "importdb": + a.ApiService.ImportDb(c) + case "addToken": + a.ApiService.AddToken(c) + a.apiv2.ReloadTokens() + case "deleteToken": + a.ApiService.DeleteToken(c) + a.apiv2.ReloadTokens() + default: + jsonMsg(c, "failed", common.NewError("unknown action: ", action)) + } +} + +func (a *APIHandler) getHandler(c *gin.Context) { + action := c.Param("getAction") + + switch action { + case "logout": + a.ApiService.Logout(c) + case "load": + a.ApiService.LoadData(c) + case "inbounds", "outbounds", "endpoints", "tls", "clients", "config": + err := a.ApiService.LoadPartialData(c, []string{action}) + if err != nil { + jsonMsg(c, action, err) + } + return + case "users": + a.ApiService.GetUsers(c) + case "settings": + a.ApiService.GetSettings(c) + case "stats": + a.ApiService.GetStats(c) + case "status": + a.ApiService.GetStatus(c) + case "onlines": + a.ApiService.GetOnlines(c) + case "logs": + a.ApiService.GetLogs(c) + case "changes": + a.ApiService.CheckChanges(c) + case "keypairs": + a.ApiService.GetKeypairs(c) + case "getdb": + a.ApiService.GetDb(c) + case "tokens": + a.ApiService.GetTokens(c) + default: + jsonMsg(c, "failed", common.NewError("unknown action: ", action)) + } +} diff --git a/api/apiService.go b/api/apiService.go new file mode 100644 index 0000000..013f5fd --- /dev/null +++ b/api/apiService.go @@ -0,0 +1,363 @@ +package api + +import ( + "encoding/json" + "s-ui/database" + "s-ui/logger" + "s-ui/service" + "s-ui/util" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +type ApiService struct { + service.SettingService + service.UserService + service.ConfigService + service.ClientService + service.TlsService + service.InboundService + service.OutboundService + service.EndpointService + service.PanelService + service.StatsService + service.ServerService +} + +func (a *ApiService) LoadData(c *gin.Context) { + data, err := a.getData(c) + if err != nil { + jsonMsg(c, "", err) + return + } + jsonObj(c, data, nil) +} + +func (a *ApiService) getData(c *gin.Context) (interface{}, error) { + data := make(map[string]interface{}, 0) + lu := c.Query("lu") + isUpdated, err := a.ConfigService.CheckChanges(lu) + if err != nil { + return "", err + } + onlines, err := a.StatsService.GetOnlines() + + sysInfo := a.ServerService.GetSingboxInfo() + if sysInfo["running"] == false { + logs := a.ServerService.GetLogs("1", "debug") + if len(logs) > 0 { + data["lastLog"] = logs[0] + } + } + + if err != nil { + return "", err + } + if isUpdated { + config, err := a.SettingService.GetConfig() + if err != nil { + return "", err + } + clients, err := a.ClientService.GetAll() + if err != nil { + return "", err + } + tlsConfigs, err := a.TlsService.GetAll() + if err != nil { + return "", err + } + 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 + } + subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0]) + if err != nil { + return "", err + } + data["config"] = json.RawMessage(config) + data["clients"] = clients + data["tls"] = tlsConfigs + data["inbounds"] = inbounds + data["outbounds"] = outbounds + data["endpoints"] = endpoints + data["subURI"] = subURI + data["onlines"] = onlines + } else { + data["onlines"] = onlines + } + + return data, nil +} + +func (a *ApiService) LoadPartialData(c *gin.Context, objs []string) error { + data := make(map[string]interface{}, 0) + id := c.Query("id") + + for _, obj := range objs { + switch obj { + case "inbounds": + 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.Get(id) + 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) + case "settings": + settings, err := a.SettingService.GetAllSetting() + if err != nil { + return err + } + data[obj] = settings + } + } + + jsonObj(c, data, nil) + return nil +} + +func (a *ApiService) GetUsers(c *gin.Context) { + users, err := a.UserService.GetUsers() + if err != nil { + jsonMsg(c, "", err) + return + } + jsonObj(c, *users, nil) +} + +func (a *ApiService) GetSettings(c *gin.Context) { + data, err := a.SettingService.GetAllSetting() + if err != nil { + jsonMsg(c, "", err) + return + } + jsonObj(c, data, err) +} + +func (a *ApiService) GetStats(c *gin.Context) { + resource := c.Query("resource") + tag := c.Query("tag") + limit, err := strconv.Atoi(c.Query("limit")) + if err != nil { + limit = 100 + } + data, err := a.StatsService.GetStats(resource, tag, limit) + if err != nil { + jsonMsg(c, "", err) + return + } + jsonObj(c, data, err) +} + +func (a *ApiService) GetStatus(c *gin.Context) { + request := c.Query("r") + result := a.ServerService.GetStatus(request) + jsonObj(c, result, nil) +} + +func (a *ApiService) GetOnlines(c *gin.Context) { + onlines, err := a.StatsService.GetOnlines() + jsonObj(c, onlines, err) +} + +func (a *ApiService) GetLogs(c *gin.Context) { + count := c.Query("c") + level := c.Query("l") + logs := a.ServerService.GetLogs(count, level) + jsonObj(c, logs, nil) +} + +func (a *ApiService) CheckChanges(c *gin.Context) { + actor := c.Query("a") + chngKey := c.Query("k") + count := c.Query("c") + changes := a.ConfigService.GetChanges(actor, chngKey, count) + jsonObj(c, changes, nil) +} + +func (a *ApiService) GetKeypairs(c *gin.Context) { + kType := c.Query("k") + options := c.Query("o") + keypair := a.ServerService.GenKeypair(kType, options) + jsonObj(c, keypair, nil) +} + +func (a *ApiService) GetDb(c *gin.Context) { + exclude := c.Query("exclude") + db, err := database.GetDb(exclude) + if err != nil { + jsonMsg(c, "", err) + return + } + c.Header("Content-Type", "application/octet-stream") + c.Header("Content-Disposition", "attachment; filename=s-ui_"+time.Now().Format("20060102-150405")+".db") + c.Writer.Write(db) +} + +func (a *ApiService) 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 +} + +func (a *ApiService) Login(c *gin.Context) { + remoteIP := getRemoteIp(c) + loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP) + if err != nil { + jsonMsg(c, "", err) + return + } + + sessionMaxAge, err := a.SettingService.GetSessionMaxAge() + if err != nil { + logger.Infof("Unable to get session's max age from DB") + } + + err = SetLoginUser(c, loginUser, sessionMaxAge) + if err == nil { + logger.Info("user ", loginUser, " login success") + } else { + logger.Warning("login failed: ", err) + } + + jsonMsg(c, "", nil) +} + +func (a *ApiService) ChangePass(c *gin.Context) { + id := c.Request.FormValue("id") + oldPass := c.Request.FormValue("oldPass") + newUsername := c.Request.FormValue("newUsername") + newPass := c.Request.FormValue("newPass") + err := a.UserService.ChangePass(id, oldPass, newUsername, newPass) + if err == nil { + logger.Info("change user credentials success") + jsonMsg(c, "save", nil) + } else { + logger.Warning("change user credentials failed:", err) + jsonMsg(c, "", err) + } +} + +func (a *ApiService) Save(c *gin.Context, loginUser string) { + hostname := getHostname(c) + obj := c.Request.FormValue("object") + act := c.Request.FormValue("action") + data := c.Request.FormValue("data") + initUsers := c.Request.FormValue("initUsers") + objs, err := a.ConfigService.Save(obj, act, json.RawMessage(data), initUsers, loginUser, hostname) + if err != nil { + jsonMsg(c, "save", err) + return + } + err = a.LoadPartialData(c, objs) + if err != nil { + jsonMsg(c, obj, err) + } +} + +func (a *ApiService) RestartApp(c *gin.Context) { + err := a.PanelService.RestartPanel(3) + jsonMsg(c, "restartApp", err) +} + +func (a *ApiService) RestartSb(c *gin.Context) { + err := a.ConfigService.RestartCore() + jsonMsg(c, "restartSb", err) +} + +func (a *ApiService) LinkConvert(c *gin.Context) { + link := c.Request.FormValue("link") + result, _, err := util.GetOutbound(link, 0) + jsonObj(c, result, err) +} + +func (a *ApiService) ImportDb(c *gin.Context) { + file, _, err := c.Request.FormFile("db") + if err != nil { + jsonMsg(c, "", err) + return + } + defer file.Close() + err = database.ImportDB(file) + jsonMsg(c, "", err) +} + +func (a *ApiService) Logout(c *gin.Context) { + loginUser := GetLoginUser(c) + if loginUser != "" { + logger.Infof("user %s logout", loginUser) + } + ClearSession(c) + jsonMsg(c, "", nil) +} + +func (a *ApiService) LoadTokens() ([]byte, error) { + return a.UserService.LoadTokens() +} + +func (a *ApiService) GetTokens(c *gin.Context) { + loginUser := GetLoginUser(c) + tokens, err := a.UserService.GetUserTokens(loginUser) + jsonObj(c, tokens, err) +} + +func (a *ApiService) AddToken(c *gin.Context) { + loginUser := GetLoginUser(c) + expiry := c.Request.FormValue("expiry") + expiryInt, err := strconv.ParseInt(expiry, 10, 64) + if err != nil { + jsonMsg(c, "", err) + return + } + desc := c.Request.FormValue("desc") + token, err := a.UserService.AddToken(loginUser, expiryInt, desc) + jsonObj(c, token, err) +} + +func (a *ApiService) DeleteToken(c *gin.Context) { + tokenId := c.Request.FormValue("id") + err := a.UserService.DeleteToken(tokenId) + jsonMsg(c, "", err) +} diff --git a/api/apiV2Handler.go b/api/apiV2Handler.go new file mode 100644 index 0000000..464d24a --- /dev/null +++ b/api/apiV2Handler.go @@ -0,0 +1,129 @@ +package api + +import ( + "encoding/json" + "s-ui/logger" + "s-ui/util/common" + "time" + + "github.com/gin-gonic/gin" +) + +type TokenInMemory struct { + Token string + Expiry int64 + Username string +} + +type APIv2Handler struct { + ApiService + tokens *[]TokenInMemory +} + +func NewAPIv2Handler(g *gin.RouterGroup) *APIv2Handler { + a := &APIv2Handler{} + a.ReloadTokens() + a.initRouter(g) + return a +} + +func (a *APIv2Handler) initRouter(g *gin.RouterGroup) { + g.Use(func(c *gin.Context) { + a.checkToken(c) + }) + g.POST("/:postAction", a.postHandler) + g.GET("/:getAction", a.getHandler) +} + +func (a *APIv2Handler) postHandler(c *gin.Context) { + username := a.findUsername(c) + action := c.Param("postAction") + + switch action { + case "save": + a.ApiService.Save(c, username) + case "restartApp": + a.ApiService.RestartApp(c) + case "restartSb": + a.ApiService.RestartSb(c) + case "linkConvert": + a.ApiService.LinkConvert(c) + case "importdb": + a.ApiService.ImportDb(c) + default: + jsonMsg(c, "failed", common.NewError("unknown action: ", action)) + } +} + +func (a *APIv2Handler) getHandler(c *gin.Context) { + action := c.Param("getAction") + + switch action { + case "load": + a.ApiService.LoadData(c) + case "inbounds", "outbounds", "endpoints", "tls", "clients", "config": + err := a.ApiService.LoadPartialData(c, []string{action}) + if err != nil { + jsonMsg(c, action, err) + } + return + case "users": + a.ApiService.GetUsers(c) + case "settings": + a.ApiService.GetSettings(c) + case "stats": + a.ApiService.GetStats(c) + case "status": + a.ApiService.GetStatus(c) + case "onlines": + a.ApiService.GetOnlines(c) + case "logs": + a.ApiService.GetLogs(c) + case "changes": + a.ApiService.CheckChanges(c) + case "keypairs": + a.ApiService.GetKeypairs(c) + case "getdb": + a.ApiService.GetDb(c) + default: + jsonMsg(c, "failed", common.NewError("unknown action: ", action)) + } +} + +func (a *APIv2Handler) findUsername(c *gin.Context) string { + token := c.Request.Header.Get("Token") + for index, t := range *a.tokens { + if t.Expiry > 0 && t.Expiry < time.Now().Unix() { + (*a.tokens) = append((*a.tokens)[:index], (*a.tokens)[index+1:]...) + continue + } + if t.Token == token { + return t.Username + } + } + return "" +} + +func (a *APIv2Handler) checkToken(c *gin.Context) { + username := a.findUsername(c) + if username != "" { + c.Next() + return + } + jsonMsg(c, "", common.NewError("invalid token")) + c.Abort() +} + +func (a *APIv2Handler) ReloadTokens() { + tokens, err := a.ApiService.LoadTokens() + if err == nil { + var newTokens []TokenInMemory + err = json.Unmarshal(tokens, &newTokens) + if err != nil { + logger.Error("unable to load tokens: ", err) + } + a.tokens = &newTokens + } else { + logger.Error("unable to load tokens: ", err) + } +} diff --git a/backend/api/session.go b/api/session.go similarity index 100% rename from backend/api/session.go rename to api/session.go diff --git a/backend/api/utils.go b/api/utils.go similarity index 85% rename from backend/api/utils.go rename to api/utils.go index 07aa2a7..e26230e 100644 --- a/backend/api/utils.go +++ b/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/app/app.go similarity index 75% rename from backend/app/app.go rename to app/app.go index 1b4c767..502240b 100644 --- a/backend/app/app.go +++ b/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/api/api.go b/backend/api/api.go deleted file mode 100644 index 0457b04..0000000 --- a/backend/api/api.go +++ /dev/null @@ -1,232 +0,0 @@ -package api - -import ( - "s-ui/logger" - "s-ui/service" - "s-ui/singbox" - "s-ui/util" - "strconv" - "strings" - - "github.com/gin-gonic/gin" -) - -type APIHandler struct { - service.SettingService - service.UserService - service.ConfigService - service.ClientService - service.TlsService - service.InDataService - service.PanelService - service.StatsService - service.ServerService - singbox.Controller -} - -func NewAPIHandler(g *gin.RouterGroup) { - a := &APIHandler{} - a.initRouter(g) -} - -func (a *APIHandler) initRouter(g *gin.RouterGroup) { - g.Use(func(c *gin.Context) { - path := c.Request.URL.Path - if !strings.HasSuffix(path, "login") && !strings.HasSuffix(path, "logout") { - checkLogin(c) - } - }) - - g.POST("/:postAction", a.postHandler) - g.GET("/:getAction", a.getHandler) -} - -func (a *APIHandler) postHandler(c *gin.Context) { - var err error - action := c.Param("postAction") - remoteIP := getRemoteIp(c) - - switch action { - case "login": - loginUser, err := a.UserService.Login(c.Request.FormValue("user"), c.Request.FormValue("pass"), remoteIP) - if err != nil { - jsonMsg(c, "", err) - return - } - - sessionMaxAge, err := a.SettingService.GetSessionMaxAge() - if err != nil { - logger.Infof("Unable to get session's max age from DB") - } - - err = SetLoginUser(c, loginUser, sessionMaxAge) - if err == nil { - logger.Info("user ", loginUser, " login success") - } else { - logger.Warning("login failed: ", err) - } - - jsonMsg(c, "", nil) - case "changePass": - id := c.Request.FormValue("id") - oldPass := c.Request.FormValue("oldPass") - newUsername := c.Request.FormValue("newUsername") - newPass := c.Request.FormValue("newPass") - err = a.UserService.ChangePass(id, oldPass, newUsername, newPass) - if err == nil { - logger.Info("change user credentials success") - jsonMsg(c, "save", nil) - } else { - logger.Warning("change user credentials failed:", err) - jsonMsg(c, "", err) - } - case "save": - loginUser := GetLoginUser(c) - data := map[string]string{} - err = c.ShouldBind(&data) - if err == nil { - err = a.ConfigService.SaveChanges(data, loginUser) - } - jsonMsg(c, "save", err) - case "restartApp": - err = a.PanelService.RestartPanel(3) - jsonMsg(c, "restartApp", err) - case "restartSb": - err = a.Controller.Restart() - 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) - } -} - -func (a *APIHandler) getHandler(c *gin.Context) { - action := c.Param("getAction") - - switch action { - case "logout": - loginUser := GetLoginUser(c) - if loginUser != "" { - logger.Infof("user %s logout", loginUser) - } - ClearSession(c) - jsonMsg(c, "", nil) - case "load": - data, err := a.loadData(c) - if err != nil { - jsonMsg(c, "", err) - return - } - jsonObj(c, data, nil) - case "users": - users, err := a.UserService.GetUsers() - if err != nil { - jsonMsg(c, "", err) - return - } - jsonObj(c, *users, nil) - case "setting": - data, err := a.SettingService.GetAllSetting() - if err != nil { - jsonMsg(c, "", err) - return - } - jsonObj(c, data, err) - case "stats": - resource := c.Query("resource") - tag := c.Query("tag") - limit, err := strconv.Atoi(c.Query("limit")) - if err != nil { - limit = 100 - } - data, err := a.StatsService.GetStats(resource, tag, limit) - if err != nil { - jsonMsg(c, "", err) - return - } - jsonObj(c, data, err) - case "status": - request := c.Query("r") - result := a.ServerService.GetStatus(request) - jsonObj(c, result, nil) - case "onlines": - 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) - jsonObj(c, logs, nil) - case "changes": - actor := c.Query("a") - chngKey := c.Query("k") - count := c.Query("c") - changes := a.ConfigService.GetChanges(actor, chngKey, count) - jsonObj(c, changes, nil) - case "keypairs": - kType := c.Query("k") - options := c.Query("o") - keypair := a.ServerService.GenKeypair(kType, options) - jsonObj(c, keypair, nil) - default: - jsonMsg(c, "API call", nil) - } -} - -func (a *APIHandler) loadData(c *gin.Context) (interface{}, error) { - data := make(map[string]interface{}, 0) - lu := c.Query("lu") - isUpdated, err := a.ConfigService.CheckChanges(lu) - if err != nil { - return "", err - } - onlines, err := a.StatsService.GetOnlines() - - sysInfo := a.ServerService.GetSingboxInfo() - if sysInfo["running"] == false { - logs := a.ServerService.GetLogs("sing-box", "1", "debug") - if len(logs) > 0 { - data["lastLog"] = logs[0] - } - } - - if err != nil { - return "", err - } - if isUpdated { - config, err := a.ConfigService.GetConfig() - if err != nil { - return "", err - } - clients, err := a.ClientService.GetAll() - if err != nil { - return "", err - } - tlsConfigs, err := a.TlsService.GetAll() - if err != nil { - return "", err - } - inData, err := a.InDataService.GetAll() - if err != nil { - return "", err - } - subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0]) - if err != nil { - return "", err - } - data["config"] = *config - data["clients"] = clients - data["tls"] = tlsConfigs - data["inData"] = inData - data["subURI"] = subURI - data["onlines"] = onlines - } else { - data["onlines"] = onlines - } - - return data, nil -} 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/config/version b/backend/config/version deleted file mode 100644 index 1cc5f65..0000000 --- a/backend/config/version +++ /dev/null @@ -1 +0,0 @@ -1.1.0 \ No newline at end of file diff --git a/backend/go.mod b/backend/go.mod deleted file mode 100644 index 9574084..0000000 --- a/backend/go.mod +++ /dev/null @@ -1,68 +0,0 @@ -module s-ui - -go 1.23.2 - -require ( - github.com/gin-contrib/gzip v1.0.1 - 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/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 - gorm.io/gorm v1.25.12 -) - -require ( - github.com/adrg/xdg v0.5.0 // indirect - github.com/bytedance/sonic v1.12.3 // indirect - github.com/bytedance/sonic/loader v0.2.1 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect - github.com/cloudwego/iasm v0.2.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-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/goccy/go-json v0.10.3 // indirect - github.com/golang/protobuf v1.5.4 // 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/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/json-iterator/go v1.1.12 // 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/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/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/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/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/yusufpapurcu/wmi v1.2.4 // 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/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 - 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 -) diff --git a/backend/go.sum b/backend/go.sum deleted file mode 100644 index 47c3ce0..0000000 --- a/backend/go.sum +++ /dev/null @@ -1,256 +0,0 @@ -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/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/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/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= -github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4= -github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= -github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= -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/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/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/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= -github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= -github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= -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/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/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.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/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 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -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/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/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/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/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/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= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.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.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= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= -github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/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/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= -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/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/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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= -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= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -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/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/service/client.go b/backend/service/client.go deleted file mode 100644 index f66835d..0000000 --- a/backend/service/client.go +++ /dev/null @@ -1,91 +0,0 @@ -package service - -import ( - "encoding/json" - "s-ui/database" - "s-ui/database/model" - "s-ui/logger" - "time" - - "gorm.io/gorm" -) - -type ClientService struct { -} - -func (s *ClientService) GetAll() ([]model.Client, error) { - db := database.GetDB() - clients := []model.Client{} - err := db.Model(model.Client{}).Scan(&clients).Error - if err != nil { - return nil, err - } - return clients, nil -} - -func (s *ClientService) Save(tx *gorm.DB, changes []model.Changes) error { - var err error - for _, change := range changes { - client := model.Client{} - err = json.Unmarshal(change.Obj, &client) - if err != nil { - return 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 - } - if err != nil { - return err - } - } - return err -} - -func (s *ClientService) DepleteClients() ([]string, []string, error) { - var err error - var clients []model.Client - var changes []model.Changes - 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 - if err != nil { - return nil, nil, 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 - json.Unmarshal(client.Inbounds, &userInbounds) - inbounds = append(inbounds, userInbounds...) - changes = append(changes, model.Changes{ - DateTime: dt, - Actor: "DepleteJob", - Key: "clients", - Action: "disable", - Obj: json.RawMessage("\"" + client.Name + "\""), - }) - } - - // 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 - if err != nil { - return nil, nil, err - } - err = db.Model(model.Changes{}).Create(&changes).Error - if err != nil { - return nil, nil, err - } - LastUpdate = dt - } - - return users, inbounds, nil -} diff --git a/backend/service/config.go b/backend/service/config.go deleted file mode 100644 index 91daab0..0000000 --- a/backend/service/config.go +++ /dev/null @@ -1,383 +0,0 @@ -package service - -import ( - "encoding/json" - "os" - "s-ui/config" - "s-ui/database" - "s-ui/database/model" - "s-ui/logger" - "s-ui/singbox" - "strconv" - "time" -) - -var ApiAddr string -var LastUpdate int64 -var IsSystemd bool - -type ConfigService struct { - ClientService - TlsService - InDataService - singbox.Controller - SettingService -} - -type SingBoxConfig struct { - Log json.RawMessage `json:"log"` - Dns json.RawMessage `json:"dns"` - Ntp json.RawMessage `json:"ntp"` - Inbounds []json.RawMessage `json:"inbounds"` - Outbounds []json.RawMessage `json:"outbounds"` - Route json.RawMessage `json:"route"` - Experimental json.RawMessage `json:"experimental"` -} - -func NewConfigService() *ConfigService { - 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 - } - } - var singboxConfig SingBoxConfig - err = json.Unmarshal(data, &singboxConfig) - if err != nil { - return err - } - - return s.RefreshApiAddr(&singboxConfig) -} - -func (s *ConfigService) GetConfig() (*SingBoxConfig, error) { - configPath := config.GetBinFolderPath() - data, err := os.ReadFile(configPath + "/config.json") - if err != nil { - return nil, err - } - singboxConfig := SingBoxConfig{} - err = json.Unmarshal(data, &singboxConfig) - if err != nil { - return nil, err - } - return &singboxConfig, nil -} - -func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error { - 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 - } - } - - db := database.GetDB() - tx := db.Begin() - defer func() { - if err == nil { - tx.Commit() - } else { - tx.Rollback() - } - }() - - if len(clientChanges) > 0 { - err = s.ClientService.Save(tx, clientChanges) - if err != nil { - return err - } - } - 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 - } - } - - // 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 - if err != nil { - return err - } - } - - LastUpdate = dt - - return nil -} - -func (s *ConfigService) CheckChanges(lu string) (bool, error) { - if lu == "" { - return true, nil - } - if LastUpdate == 0 { - db := database.GetDB() - var count int64 - err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error - if err == nil { - LastUpdate = time.Now().Unix() - } - return count > 0, err - } else { - intLu, err := strconv.ParseInt(lu, 10, 64) - return LastUpdate > intLu, err - } -} - -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" - if len(actor) > 0 { - whereString += " and `actor`='" + actor + "'" - } - if len(chngKey) > 0 { - whereString += " and `key`='" + chngKey + "'" - } - db := database.GetDB() - var chngs []model.Changes - err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error - if err != nil { - logger.Warning(err) - } - return chngs -} 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/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/tls.go b/backend/service/tls.go deleted file mode 100644 index ada8cb2..0000000 --- a/backend/service/tls.go +++ /dev/null @@ -1,46 +0,0 @@ -package service - -import ( - "encoding/json" - "s-ui/database" - "s-ui/database/model" - - "gorm.io/gorm" -) - -type TlsService struct { -} - -func (s *TlsService) GetAll() ([]model.Tls, error) { - db := database.GetDB() - tlsConfig := []model.Tls{} - err := db.Model(model.Tls{}).Scan(&tlsConfig).Error - if err != nil { - return nil, err - } - - return tlsConfig, nil -} - -func (s *TlsService) Save(tx *gorm.DB, changes []model.Changes) error { - var err error - for _, change := range changes { - tlsConfig := model.Tls{} - err = json.Unmarshal(change.Obj, &tlsConfig) - if err != nil { - return err - } - switch change.Action { - case "new": - err = tx.Create(&tlsConfig).Error - case "del": - err = tx.Where("id = ?", change.Index).Delete(model.Tls{}).Error - default: - err = tx.Save(tlsConfig).Error - } - if err != nil { - return err - } - } - return err -} 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/build.sh b/build.sh index bb00705..d36b167 100755 --- a/build.sh +++ b/build.sh @@ -5,11 +5,10 @@ npm i npm run build cd .. -cd backend echo "Backend" mkdir -p web/html rm -fr web/html/* -cp -R ../frontend/dist/* web/html/ +cp -R frontend/dist/* web/html/ -go build -o ../sui main.go +go build -ldflags "-w -s" -tags "with_quic,with_grpc,with_ech,with_utls,with_reality_server,with_acme,with_gvisor" -o sui main.go diff --git a/backend/cmd/admin.go b/cmd/admin.go similarity index 100% rename from backend/cmd/admin.go rename to cmd/admin.go diff --git a/backend/cmd/cmd.go b/cmd/cmd.go similarity index 94% rename from backend/cmd/cmd.go rename to cmd/cmd.go index 361b349..5a3dc53 100644 --- a/backend/cmd/cmd.go +++ b/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/cmd/migration/1_1.go similarity index 75% rename from backend/cmd/migration.go rename to cmd/migration/1_1.go index 2aef0ac..d4e68e2 100644 --- a/backend/cmd/migration.go +++ b/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,29 @@ func migrateClientSchema(db *gorm.DB) error { } } } - db.AutoMigrate(model.Client{}) return nil } +func deleteOldWebSecret(db *gorm.DB) error { + return db.Exec("DELETE FROM settings WHERE key = ?", "webSecret").Error +} + func changesObj(db *gorm.DB) error { return db.Exec("UPDATE changes SET obj = CAST('\"' || CAST(obj AS TEXT) || '\"' AS BLOB) WHERE actor = ? and obj not like ?", "DepleteJob", "\"%\"").Error } + +func to1_1(db *gorm.DB) error { + err := migrateClientSchema(db) + if err != nil { + return err + } + err = deleteOldWebSecret(db) + if err != nil { + return err + } + err = changesObj(db) + if err != nil { + return err + } + return nil +} diff --git a/cmd/migration/1_2.go b/cmd/migration/1_2.go new file mode 100644 index 0000000..8bf9d7a --- /dev/null +++ b/cmd/migration/1_2.go @@ -0,0 +1,316 @@ +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 { + newTls := &model.Tls{ + Name: tag, + Server: tls_server, + Client: json.RawMessage("{}"), + } + 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 + } + err := db.Migrator().DropColumn(&model.Tls{}, "inbounds") + if err != nil { + return err + } + var tlsConfig []model.Tls + err = db.Model(model.Tls{}).Scan(&tlsConfig).Error + if err != nil { + return err + } + + for index, tls := range tlsConfig { + var tlsClient map[string]interface{} + err = json.Unmarshal(tls.Client, &tlsClient) + if err != nil { + continue + } + for key := range tlsClient { + switch key { + case "insecure", "disable_sni", "utls", "ech", "reality": + continue + default: + delete(tlsClient, key) + } + } + tlsConfig[index].Client, _ = json.MarshalIndent(tlsClient, "", " ") + } + + return db.Save(&tlsConfig).Error +} + +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 migrateChanges(db *gorm.DB) error { + return db.Migrator().DropColumn(&model.Changes{}, "index") +} + +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 + } + err = migrateClients(db) + if err != nil { + return err + } + return migrateChanges(db) +} diff --git a/cmd/migration/main.go b/cmd/migration/main.go new file mode 100644 index 0000000..3291909 --- /dev/null +++ b/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/cmd/setting.go similarity index 59% rename from backend/cmd/setting.go rename to cmd/setting.go index 4b7f0d5..366d8b3 100644 --- a/backend/cmd/setting.go +++ b/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/config/config.go similarity index 56% rename from backend/config/config.go rename to config/config.go index 8917bd3..85b2859 100644 --- a/backend/config/config.go +++ b/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/name b/config/name similarity index 100% rename from backend/config/name rename to config/name diff --git a/config/version b/config/version new file mode 100644 index 0000000..9fb0f0c --- /dev/null +++ b/config/version @@ -0,0 +1 @@ +1.2.0-rc0 \ No newline at end of file diff --git a/core/Dockerfile b/core/Dockerfile deleted file mode 100644 index 5b01136..0000000 --- a/core/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS singbox-builder -LABEL maintainer="Alireza " -WORKDIR /app -ARG TARGETOS TARGETARCH -ARG SINGBOX_VER=v1.10.1 -ARG SINGBOX_TAGS="with_quic,with_grpc,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_v2ray_api,with_clash_api,with_gvisor" -ARG GOPROXY="" -ENV GOPROXY ${GOPROXY} -ENV CGO_ENABLED=0 -ENV GOOS=$TARGETOS -ENV GOARCH=$TARGETARCH -RUN apk --no-cache --update add build-base gcc wget unzip git -RUN set -ex \ - && git clone --depth 1 --branch $SINGBOX_VER https://github.com/SagerNet/sing-box.git \ - && cd sing-box \ - && go build -v -trimpath -tags \ - $SINGBOX_TAGS \ - -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$SINGBOX_VER\" -s -w -buildid=" \ - ./cmd/sing-box - -FROM --platform=$TARGETPLATFORM alpine -LABEL maintainer="Alireza " -ENV TZ=Asia/Tehran -WORKDIR /app -RUN apk add --no-cache --update ca-certificates tzdata bash -COPY --from=singbox-builder /app/sing-box/sing-box . -COPY runSingbox.sh . -ENTRYPOINT [ "./runSingbox.sh" ] \ No newline at end of file diff --git a/core/box.go b/core/box.go new file mode 100644 index 0000000..da269ce --- /dev/null +++ b/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/core/conntracker.go b/core/conntracker.go new file mode 100644 index 0000000..5f99adc --- /dev/null +++ b/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/core/endpoint.go b/core/endpoint.go new file mode 100644 index 0000000..d3c3a3c --- /dev/null +++ b/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/core/log.go b/core/log.go new file mode 100644 index 0000000..46ac701 --- /dev/null +++ b/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/core/main.go b/core/main.go new file mode 100644 index 0000000..ef3ae29 --- /dev/null +++ b/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/core/register.go b/core/register.go new file mode 100644 index 0000000..aaf3e01 --- /dev/null +++ b/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/core/runSingbox.sh b/core/runSingbox.sh deleted file mode 100755 index 78cb32f..0000000 --- a/core/runSingbox.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -set -e - -tokill=$$ - -runSingbox(){ - ./sing-box run & - tokill=$! -} - -terminateSingbox() -{ - if kill -0 $tokill > /dev/null 2>&1; then - echo "Terminating singbox PID=$tokill" - kill $tokill - while kill -0 $tokill > /dev/null 2>&1; do - sleep 1 - done - fi -} - -reloadSingbox() -{ - if kill -0 $tokill > /dev/null 2>&1; then - kill -HUP $tokill - else - runSingbox - fi -} - -trap terminateSingbox SIGINT SIGTERM SIGKILL -trap reloadSingbox SIGHUP - -runSingbox - -while true -do - sleep 5 - if [ -f "signal" ]; then - signal=`cat signal` - echo "Signal received: $signal" - # Remove singnal file - rm -f signal >> /dev/null 2>&1 - case ${signal} in - "stop") - terminateSingbox - ;; - "restart") - reloadSingbox - ;; - esac - fi - - # Check if sin-box crashed - if ! kill -0 $tokill > /dev/null 2>&1; then - if [ "$signal" != "stop" ]; then - echo "Sing-Box with PID $tokill crashed. Breaking the loop..." - exit 1 - fi - fi -done \ No newline at end of file diff --git a/cronjob/checkCoreJob.go b/cronjob/checkCoreJob.go new file mode 100644 index 0000000..0f90613 --- /dev/null +++ b/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/cronjob/cronJob.go similarity index 88% rename from backend/cronjob/cronJob.go rename to cronjob/cronJob.go index df5853c..4bf3eff 100644 --- a/backend/cronjob/cronJob.go +++ b/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/delStatsJob.go b/cronjob/delStatsJob.go similarity index 100% rename from backend/cronjob/delStatsJob.go rename to cronjob/delStatsJob.go diff --git a/backend/cronjob/depleteJob.go b/cronjob/depleteJob.go similarity index 80% rename from backend/cronjob/depleteJob.go rename to cronjob/depleteJob.go index 0b0eb9b..d7adcffa 100644 --- a/backend/cronjob/depleteJob.go +++ b/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/cronjob/statsJob.go similarity index 73% rename from backend/cronjob/statsJob.go rename to cronjob/statsJob.go index 84a4124..dcf72f7 100644 --- a/backend/cronjob/statsJob.go +++ b/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/database/backup.go b/database/backup.go new file mode 100644 index 0000000..53846a0 --- /dev/null +++ b/database/backup.go @@ -0,0 +1,275 @@ +package database + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "os" + "path/filepath" + "s-ui/cmd/migration" + "s-ui/config" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util/common" + "strings" + "syscall" + "time" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func GetDb(exclude string) ([]byte, error) { + exclude_changes, exclude_stats := false, false + for _, table := range strings.Split(exclude, ",") { + if table == "changes" { + exclude_changes = true + } else if table == "stats" { + exclude_stats = true + } + } + + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + if err != nil { + return nil, err + } + dbPath := dir + config.GetName() + "_" + time.Now().Format("20060102-200203") + ".db" + + backupDb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) + if err != nil { + return nil, err + } + + err = backupDb.AutoMigrate( + &model.Setting{}, + &model.Tls{}, + &model.Inbound{}, + &model.Outbound{}, + &model.Endpoint{}, + &model.User{}, + &model.Stats{}, + &model.Client{}, + &model.Changes{}, + ) + if err != nil { + return nil, err + } + + var settings []model.Setting + var tls []model.Tls + var inbound []model.Inbound + var outbound []model.Outbound + var endpoint []model.Endpoint + var users []model.User + var clients []model.Client + var stats []model.Stats + var changes []model.Changes + + // Perform scans and handle errors + if err := db.Model(&model.Setting{}).Scan(&settings).Error; err != nil { + return nil, err + } + if err := db.Model(&model.Tls{}).Scan(&tls).Error; err != nil { + return nil, err + } + if err := db.Model(&model.Inbound{}).Scan(&inbound).Error; err != nil { + return nil, err + } + if err := db.Model(&model.Outbound{}).Scan(&outbound).Error; err != nil { + return nil, err + } + if err := db.Model(&model.Endpoint{}).Scan(&endpoint).Error; err != nil { + return nil, err + } + if err := db.Model(&model.User{}).Scan(&users).Error; err != nil { + return nil, err + } + if err := db.Model(&model.Client{}).Scan(&clients).Error; err != nil { + return nil, err + } + + // Save each model + for _, mdl := range []interface{}{settings, tls, inbound, outbound, endpoint, users, clients} { + if err := backupDb.Save(mdl).Error; err != nil { + return nil, err + } + } + + if !exclude_stats { + if err := db.Model(&model.Stats{}).Scan(&stats).Error; err != nil { + return nil, err + } + if len(stats) > 0 { + if err := backupDb.Save(stats).Error; err != nil { + return nil, err + } + } + } + if !exclude_changes { + if err := db.Model(&model.Changes{}).Scan(&changes).Error; err != nil { + return nil, err + } + if len(changes) > 0 { + if err := backupDb.Save(changes).Error; err != nil { + return nil, err + } + } + } + + // Update WAL + err = backupDb.Exec("PRAGMA wal_checkpoint;").Error + if err != nil { + return nil, err + } + + bdb, _ := backupDb.DB() + bdb.Close() + + // Open the file for reading + file, err := os.Open(dbPath) + if err != nil { + return nil, err + } + defer file.Close() + defer os.Remove(dbPath) + + // Read the file contents + fileContents, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + return fileContents, nil +} + +func ImportDB(file multipart.File) error { + // Check if the file is a SQLite database + isValidDb, err := IsSQLiteDB(file) + if err != nil { + return common.NewErrorf("Error checking db file format: %v", err) + } + if !isValidDb { + return common.NewError("Invalid db file format") + } + + // Reset the file reader to the beginning + _, err = file.Seek(0, 0) + if err != nil { + return common.NewErrorf("Error resetting file reader: %v", err) + } + + // Save the file as temporary file + tempPath := fmt.Sprintf("%s.temp", config.GetDBPath()) + // Remove the existing fallback file (if any) before creating one + _, err = os.Stat(tempPath) + if err == nil { + errRemove := os.Remove(tempPath) + if errRemove != nil { + return common.NewErrorf("Error removing existing temporary db file: %v", errRemove) + } + } + // Create the temporary file + tempFile, err := os.Create(tempPath) + if err != nil { + return common.NewErrorf("Error creating temporary db file: %v", err) + } + defer tempFile.Close() + + // Remove temp file before returning + defer os.Remove(tempPath) + + // Close old DB + old_db, _ := db.DB() + old_db.Close() + + // Save uploaded file to temporary file + _, err = io.Copy(tempFile, file) + if err != nil { + return common.NewErrorf("Error saving db: %v", err) + } + + // Check if we can init db or not + newDb, err := gorm.Open(sqlite.Open(tempPath), &gorm.Config{}) + if err != nil { + return common.NewErrorf("Error checking db: %v", err) + } + newDb_db, _ := newDb.DB() + newDb_db.Close() + + // Backup the current database for fallback + fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath()) + // Remove the existing fallback file (if any) + _, err = os.Stat(fallbackPath) + if err == nil { + errRemove := os.Remove(fallbackPath) + if errRemove != nil { + return common.NewErrorf("Error removing existing fallback db file: %v", errRemove) + } + } + // Move the current database to the fallback location + err = os.Rename(config.GetDBPath(), fallbackPath) + if err != nil { + return common.NewErrorf("Error backing up temporary db file: %v", err) + } + + // Remove the temporary file before returning + defer os.Remove(fallbackPath) + + // Move temp to DB path + err = os.Rename(tempPath, config.GetDBPath()) + if err != nil { + errRename := os.Rename(fallbackPath, config.GetDBPath()) + if errRename != nil { + return common.NewErrorf("Error moving db file and restoring fallback: %v", errRename) + } + return common.NewErrorf("Error moving db file: %v", err) + } + + // Migrate DB + migration.MigrateDb() + err = InitDB(config.GetDBPath()) + if err != nil { + errRename := os.Rename(fallbackPath, config.GetDBPath()) + if errRename != nil { + return common.NewErrorf("Error migrating db and restoring fallback: %v", errRename) + } + return common.NewErrorf("Error migrating db: %v", err) + } + + // Restart app + err = SendSighup() + if err != nil { + return common.NewErrorf("Error restarting app: %v", err) + } + + return nil +} + +func IsSQLiteDB(file io.Reader) (bool, error) { + signature := []byte("SQLite format 3\x00") + buf := make([]byte, len(signature)) + _, err := file.Read(buf) + if err != nil { + return false, err + } + return bytes.Equal(buf, signature), nil +} + +func SendSighup() error { + // Get the current process + process, err := os.FindProcess(os.Getpid()) + if err != nil { + return err + } + + // Send SIGHUP to the current process + go func() { + time.Sleep(3 * time.Second) + err := process.Signal(syscall.SIGHUP) + if err != nil { + logger.Error("send signal SIGHUP failed:", err) + } + }() + return nil +} diff --git a/backend/database/db.go b/database/db.go similarity index 72% rename from backend/database/db.go rename to database/db.go index 9e7c5b2..20988ad 100644 --- a/backend/database/db.go +++ b/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,11 +62,24 @@ 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.Tokens{}, &model.Stats{}, &model.Client{}, &model.Changes{}, diff --git a/database/model/endpoints.go b/database/model/endpoints.go new file mode 100644 index 0000000..960c963 --- /dev/null +++ b/database/model/endpoints.go @@ -0,0 +1,63 @@ +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:"-"` + Ext json.RawMessage `json:"ext" form:"ext"` +} + +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") + o.Ext, _ = json.MarshalIndent(raw["ext"], "", " ") + delete(raw, "ext") + + // Remaining fields + o.Options, err = json.MarshalIndent(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{}) + switch o.Type { + case "warp": + combined["type"] = "wireguard" + default: + 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/database/model/inbounds.go b/database/model/inbounds.go new file mode 100644 index 0000000..72e09ec --- /dev/null +++ b/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/database/model/model.go similarity index 61% rename from backend/database/model/model.go rename to database/model/model.go index d027aa3..5685460 100644 --- a/backend/database/model/model.go +++ b/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 { @@ -34,9 +26,9 @@ type Client struct { Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` Enable bool `json:"enable" form:"enable"` Name string `json:"name" form:"name"` - Config json.RawMessage `json:"config" form:"config"` + Config json.RawMessage `json:"config,omitempty" form:"config"` Inbounds json.RawMessage `json:"inbounds" form:"inbounds"` - Links json.RawMessage `json:"links" form:"links"` + Links json.RawMessage `json:"links,omitempty" form:"links"` Volume int64 `json:"volume" form:"volume"` Expiry int64 `json:"expiry" form:"expiry"` Down int64 `json:"down" form:"down"` @@ -57,9 +49,17 @@ type Stats struct { type Changes struct { Id uint64 `json:"id" gorm:"primaryKey;autoIncrement"` DateTime int64 `json:"dateTime"` - Actor string `json:"Actor"` - Key string `json:"key" form:"key"` - Action string `json:"action" form:"action"` - Index uint `json:"index" form:"index"` - Obj json.RawMessage `json:"obj" form:"obj"` + Actor string `json:"actor"` + Key string `json:"key"` + Action string `json:"action"` + Obj json.RawMessage `json:"obj"` +} + +type Tokens struct { + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Desc string `json:"desc" form:"desc"` + Token string `json:"token" form:"token"` + Expiry int64 `json:"expiry" form:"expiry"` + UserId uint `json:"userId" form:"userId"` + User *User `json:"user" gorm:"foreignKey:UserId;references:Id"` } diff --git a/database/model/outbounds.go b/database/model/outbounds.go new file mode 100644 index 0000000..9aec2ae --- /dev/null +++ b/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.MarshalIndent(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/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 b/frontend new file mode 160000 index 0000000..0d3496c --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 0d3496cfd418b047203cb9dc3f96287b02ef5691 diff --git a/frontend/.browserslistrc b/frontend/.browserslistrc deleted file mode 100644 index dc3bc09..0000000 --- a/frontend/.browserslistrc +++ /dev/null @@ -1,4 +0,0 @@ -> 1% -last 2 versions -not dead -not ie 11 diff --git a/frontend/.editorconfig b/frontend/.editorconfig deleted file mode 100644 index 7053c49..0000000 --- a/frontend/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -[*.{js,jsx,ts,tsx,vue}] -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js deleted file mode 100644 index 5f3949b..0000000 --- a/frontend/.eslintrc.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - env: { - node: true, - }, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-typescript', - ], - rules: { - 'vue/multi-word-component-names': 'off', - }, -} diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index 1b57351..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -.DS_Store -node_modules -/dist -/bin - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index cea000b..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# base - -## Project setup - -``` -# yarn -yarn - -# npm -npm install - -# pnpm -pnpm install - -# bun -bun install -``` - -### Compiles and hot-reloads for development - -``` -# yarn -yarn dev - -# npm -npm run dev - -# pnpm -pnpm dev - -# bun -pnpm run dev -``` - -### Compiles and minifies for production - -``` -# yarn -yarn build - -# npm -npm run build - -# pnpm -pnpm build - -# bun -pnpm run build -``` - -### Lints and fixes files - -``` -# yarn -yarn lint - -# npm -npm run lint - -# pnpm -pnpm lint - -# bun -pnpm run lint -``` - -### Customize configuration - -See [Configuration Reference](https://vitejs.dev/config/). diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index cafa407..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - S-UI - - - -
- - - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index cf837f0..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,3547 +0,0 @@ -{ - "name": "frontend", - "version": "1.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "frontend", - "version": "1.1.0", - "dependencies": { - "@mdi/font": "7.4.47", - "axios": "^1.7.4", - "chart.js": "^4.4.3", - "clipboard": "^2.0.11", - "core-js": "^3.37.1", - "moment": "^2.30.1", - "notivue": "^2.4.4", - "pinia": "^2.1.7", - "qrcode.vue": "^3.4.1", - "roboto-fontface": "^0.10.0", - "vue": "^3.4.31", - "vue-chartjs": "^5.3.1", - "vue-i18n": "^9.13.1", - "vue-router": "^4.4.0", - "vue3-persian-datetime-picker": "^1.2.2", - "vuetify": "^3.6.10" - }, - "devDependencies": { - "@babel/types": "^7.24.7", - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "eslint-plugin-vue": "^9.26.0", - "material-design-icons-iconfont": "^6.7.0", - "sass": "1.77.6", - "typescript": "^5.5.2", - "unplugin-fonts": "^1.1.1", - "vite": "^5.4.6", - "vite-plugin-vuetify": "^2.0.3", - "vue-tsc": "^2.0.22" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", - "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "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==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "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/@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==", - "license": "MIT", - "dependencies": { - "@intlify/message-compiler": "9.14.1", - "@intlify/shared": "9.14.1" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@intlify/shared": "9.14.1", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "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==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "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==", - "license": "MIT" - }, - "node_modules/@mdi/font": { - "version": "7.4.47", - "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", - "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", - "license": "Apache-2.0" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "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==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "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==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "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==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "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==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "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==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "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==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "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==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT", - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.8" - } - }, - "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==", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.8", - "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==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", - "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==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" - } - }, - "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==", - "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", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.47", - "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==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" - } - }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "~2.4.8", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^0.2.0", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/language-core/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.12" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", - "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==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" - }, - "peerDependencies": { - "vue": "3.5.12" - } - }, - "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==", - "license": "MIT" - }, - "node_modules/@vuetify/loader-shared": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.0.3.tgz", - "integrity": "sha512-Ss3GC7eJYkp2SF6xVzsT7FAruEmdihmn4OCk2+UocREerlXKWgOKKzTN5PN3ZVN5q05jHHrsNhTuWbhN61Bpdg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "upath": "^2.0.1" - }, - "peerDependencies": { - "vue": "^3.0.0", - "vuetify": "^3.0.0" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0", - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/clipboard": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", - "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", - "license": "MIT", - "dependencies": { - "good-listener": "^1.2.2", - "select": "^1.1.2", - "tiny-emitter": "^2.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "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==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "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==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/delegate": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", - "license": "MIT" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.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", - "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", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "globals": "^13.24.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^6.0.15", - "semver": "^7.6.3", - "vue-eslint-parser": "^9.4.3", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT", - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/good-listener": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", - "license": "MIT", - "dependencies": { - "delegate": "^3.1.2" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/jalaali-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.7.tgz", - "integrity": "sha512-gE+YHWSbygYAoJa+Xg8LWxGILqFOxZSBQQw39ghel01fVFUxV7bjL0x1JFsHcLQ3uPjvn81HQMa+kxwyPWnxGQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT", - "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==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/material-design-icons-iconfont": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz", - "integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/moment-jalaali": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.9.6.tgz", - "integrity": "sha512-v8wXjQplvk5ez+sUqgsWIrafwIf1BEXXvzTYwsg1wHcqh27nSgKPCJ6FnZRrCz03MoNyB9N31L0oms+vE8Rq7g==", - "license": "MIT", - "dependencies": { - "jalaali-js": "^1.1.0", - "moment": "^2.22.2", - "moment-timezone": "^0.5.21", - "rimraf": "^3.0.2" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.46", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz", - "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==", - "license": "MIT", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/notivue": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/notivue/-/notivue-2.4.5.tgz", - "integrity": "sha512-7yBdaKesUZIwdcQP3nv1oWYyisI2bURkZ+D9KfLgeNqguHUzkQ1WdhGcTj59PBZa8mqa1/K5Mh8YsphSToMKcQ==", - "license": "MIT", - "peerDependencies": { - "@nuxt/kit": ">=3.5.0", - "@nuxt/schema": ">=3.5.0", - "defu": ">=6" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "@nuxt/schema": { - "optional": true - }, - "defu": { - "optional": true - } - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.3", - "vue-demi": "^0.14.10" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "@vue/composition-api": "^1.4.0", - "typescript": ">=4.4.4", - "vue": "^2.6.14 || ^3.3.0" - }, - "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==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/roboto-fontface": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz", - "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==", - "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==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.6" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "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", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/select": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "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", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "devOptional": true, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.14.0", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "webpack-sources": "^3" - }, - "peerDependenciesMeta": { - "webpack-sources": { - "optional": true - } - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.12", - "unplugin": "^1.3.1" - }, - "peerDependencies": { - "@nuxt/kit": "^3.0.0", - "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/upath": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", - "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "5.4.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", - "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-vuetify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.0.4.tgz", - "integrity": "sha512-A4cliYUoP/u4AWSRVRvAPKgpgR987Pss7LpFa7s1GvOe8WjgDq92Rt3eVXrvgxGCWvZsPKziVqfHHdCMqeDhfw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@vuetify/loader-shared": "^2.0.3", - "debug": "^4.3.3", - "upath": "^2.0.1" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": ">=5", - "vue": "^3.0.0", - "vuetify": "^3.0.0" - } - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "dev": true, - "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==", - "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" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "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==", - "license": "MIT", - "peerDependencies": { - "chart.js": "^4.1.1", - "vue": "^3.0.0-0 || ^2.7.0" - } - }, - "node_modules/vue-eslint-parser": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", - "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", - "lodash": "^4.17.21", - "semver": "^7.3.6" - }, - "engines": { - "node": "^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=6.0.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@intlify/core-base": "9.14.1", - "@intlify/shared": "9.14.1", - "@vue/devtools-api": "^6.5.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "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==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/typescript": "~2.4.8", - "@vue/language-core": "2.1.8", - "semver": "^7.5.4" - }, - "bin": { - "vue-tsc": "bin/vue-tsc.js" - }, - "peerDependencies": { - "typescript": ">=5.0.0" - } - }, - "node_modules/vue3-persian-datetime-picker": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vue3-persian-datetime-picker/-/vue3-persian-datetime-picker-1.2.2.tgz", - "integrity": "sha512-d7nkj5vgtUvEXZboSdRmP1uwBfXvXgXqdvsOOMQb34jiMZU/aBDrTYWTEe1N+XKF9pvTTJn8Rws9ttJmyhK/hw==", - "license": "MIT", - "dependencies": { - "moment-jalaali": "^0.9.4" - } - }, - "node_modules/vuetify": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.3.tgz", - "integrity": "sha512-bpuvBpZl1/+nLlXDgdVXekvMNR6W/ciaoa8CYlpeAzAARbY8zUFSoBq05JlLhkIHI58AnzKVy4c09d0OtfYAPg==", - "license": "MIT", - "engines": { - "node": "^12.20 || >=14.13" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/johnleider" - }, - "peerDependencies": { - "typescript": ">=4.7", - "vite-plugin-vuetify": ">=1.0.0", - "vue": "^3.3.0", - "webpack-plugin-vuetify": ">=2.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "vite-plugin-vuetify": { - "optional": true - }, - "webpack-plugin-vuetify": { - "optional": true - } - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index 42bd476..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "frontend", - "version": "1.1.0", - "private": true, - "scripts": { - "dev": "vite --host", - "build": "vue-tsc --noEmit && vite build", - "preview": "vite preview", - "lint": "eslint . --fix --ignore-path .gitignore" - }, - "dependencies": { - "@mdi/font": "7.4.47", - "axios": "^1.7.4", - "chart.js": "^4.4.3", - "clipboard": "^2.0.11", - "core-js": "^3.37.1", - "moment": "^2.30.1", - "notivue": "^2.4.4", - "pinia": "^2.1.7", - "qrcode.vue": "^3.4.1", - "roboto-fontface": "^0.10.0", - "vue": "^3.4.31", - "vue-chartjs": "^5.3.1", - "vue-i18n": "^9.13.1", - "vue-router": "^4.4.0", - "vue3-persian-datetime-picker": "^1.2.2", - "vuetify": "^3.6.10" - }, - "devDependencies": { - "@babel/types": "^7.24.7", - "@types/node": "^20.14.9", - "@vitejs/plugin-vue": "^5.0.5", - "eslint-plugin-vue": "^9.26.0", - "material-design-icons-iconfont": "^6.7.0", - "sass": "1.77.6", - "typescript": "^5.5.2", - "unplugin-fonts": "^1.1.1", - "vite": "^5.4.6", - "vite-plugin-vuetify": "^2.0.3", - "vue-tsc": "^2.0.22" - } -} diff --git a/frontend/public/assets/favicon.ico b/frontend/public/assets/favicon.ico deleted file mode 100644 index 2e25018..0000000 Binary files a/frontend/public/assets/favicon.ico and /dev/null differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue deleted file mode 100644 index 0ec30e4..0000000 --- a/frontend/src/App.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/assets/Vazirmatn-UI-NL-Regular.woff2 b/frontend/src/assets/Vazirmatn-UI-NL-Regular.woff2 deleted file mode 100644 index 3ce577a..0000000 Binary files a/frontend/src/assets/Vazirmatn-UI-NL-Regular.woff2 and /dev/null differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png deleted file mode 100644 index e982f82..0000000 Binary files a/frontend/src/assets/logo.png and /dev/null differ diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg deleted file mode 100644 index c3e063b..0000000 --- a/frontend/src/assets/logo.svg +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/src/components/Addr.vue b/frontend/src/components/Addr.vue deleted file mode 100644 index 6f79b6c..0000000 --- a/frontend/src/components/Addr.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/DateTime.vue b/frontend/src/components/DateTime.vue deleted file mode 100644 index 0a73ea7..0000000 --- a/frontend/src/components/DateTime.vue +++ /dev/null @@ -1,146 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/Dial.vue b/frontend/src/components/Dial.vue deleted file mode 100644 index c24604c..0000000 --- a/frontend/src/components/Dial.vue +++ /dev/null @@ -1,215 +0,0 @@ - - - diff --git a/frontend/src/components/Headers.vue b/frontend/src/components/Headers.vue deleted file mode 100644 index 62a75c6..0000000 --- a/frontend/src/components/Headers.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Listen.vue b/frontend/src/components/Listen.vue deleted file mode 100644 index 2b29110..0000000 --- a/frontend/src/components/Listen.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Main.vue b/frontend/src/components/Main.vue deleted file mode 100644 index 583d480..0000000 --- a/frontend/src/components/Main.vue +++ /dev/null @@ -1,265 +0,0 @@ - - - diff --git a/frontend/src/components/Multiplex.vue b/frontend/src/components/Multiplex.vue deleted file mode 100644 index 1f94517..0000000 --- a/frontend/src/components/Multiplex.vue +++ /dev/null @@ -1,129 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Network.vue b/frontend/src/components/Network.vue deleted file mode 100644 index d050bbe..0000000 --- a/frontend/src/components/Network.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/OutJson.vue b/frontend/src/components/OutJson.vue deleted file mode 100644 index 35dbf74..0000000 --- a/frontend/src/components/OutJson.vue +++ /dev/null @@ -1,123 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Rule.vue b/frontend/src/components/Rule.vue deleted file mode 100644 index 1ccdb58..0000000 --- a/frontend/src/components/Rule.vue +++ /dev/null @@ -1,382 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/SubJsonExt.vue b/frontend/src/components/SubJsonExt.vue deleted file mode 100644 index 489924a..0000000 --- a/frontend/src/components/SubJsonExt.vue +++ /dev/null @@ -1,452 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Transport.vue b/frontend/src/components/Transport.vue deleted file mode 100644 index decdd18..0000000 --- a/frontend/src/components/Transport.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/UoT.vue b/frontend/src/components/UoT.vue deleted file mode 100644 index b6dc292..0000000 --- a/frontend/src/components/UoT.vue +++ /dev/null @@ -1,29 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/Users.vue b/frontend/src/components/Users.vue deleted file mode 100644 index 13c7200..0000000 --- a/frontend/src/components/Users.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/WgPeer.vue b/frontend/src/components/WgPeer.vue deleted file mode 100644 index 6dffcc1..0000000 --- a/frontend/src/components/WgPeer.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/message.vue b/frontend/src/components/message.vue deleted file mode 100644 index 968ef94..0000000 --- a/frontend/src/components/message.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Direct.vue b/frontend/src/components/protocols/Direct.vue deleted file mode 100644 index c88423a..0000000 --- a/frontend/src/components/protocols/Direct.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Http.vue b/frontend/src/components/protocols/Http.vue deleted file mode 100644 index e028b48..0000000 --- a/frontend/src/components/protocols/Http.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Hysteria.vue b/frontend/src/components/protocols/Hysteria.vue deleted file mode 100644 index 4be9ca2..0000000 --- a/frontend/src/components/protocols/Hysteria.vue +++ /dev/null @@ -1,159 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Hysteria2.vue b/frontend/src/components/protocols/Hysteria2.vue deleted file mode 100644 index 1f22031..0000000 --- a/frontend/src/components/protocols/Hysteria2.vue +++ /dev/null @@ -1,109 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Naive.vue b/frontend/src/components/protocols/Naive.vue deleted file mode 100644 index 005ef19..0000000 --- a/frontend/src/components/protocols/Naive.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/OutShadowTls.vue b/frontend/src/components/protocols/OutShadowTls.vue deleted file mode 100644 index 024ef86..0000000 --- a/frontend/src/components/protocols/OutShadowTls.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Selector.vue b/frontend/src/components/protocols/Selector.vue deleted file mode 100644 index 7e28131..0000000 --- a/frontend/src/components/protocols/Selector.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/ShadowTls.vue b/frontend/src/components/protocols/ShadowTls.vue deleted file mode 100644 index 2296136..0000000 --- a/frontend/src/components/protocols/ShadowTls.vue +++ /dev/null @@ -1,153 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Shadowsocks.vue b/frontend/src/components/protocols/Shadowsocks.vue deleted file mode 100644 index cfac78a..0000000 --- a/frontend/src/components/protocols/Shadowsocks.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Socks.vue b/frontend/src/components/protocols/Socks.vue deleted file mode 100644 index de605f2..0000000 --- a/frontend/src/components/protocols/Socks.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Ssh.vue b/frontend/src/components/protocols/Ssh.vue deleted file mode 100644 index 97a8964..0000000 --- a/frontend/src/components/protocols/Ssh.vue +++ /dev/null @@ -1,151 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/TProxy.vue b/frontend/src/components/protocols/TProxy.vue deleted file mode 100644 index 036bde7..0000000 --- a/frontend/src/components/protocols/TProxy.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Tor.vue b/frontend/src/components/protocols/Tor.vue deleted file mode 100644 index b3e8326..0000000 --- a/frontend/src/components/protocols/Tor.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Trojan.vue b/frontend/src/components/protocols/Trojan.vue deleted file mode 100644 index b3637b8..0000000 --- a/frontend/src/components/protocols/Trojan.vue +++ /dev/null @@ -1,24 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Tuic.vue b/frontend/src/components/protocols/Tuic.vue deleted file mode 100644 index 99fd707..0000000 --- a/frontend/src/components/protocols/Tuic.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Tun.vue b/frontend/src/components/protocols/Tun.vue deleted file mode 100644 index 49edabc..0000000 --- a/frontend/src/components/protocols/Tun.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/UrlTest.vue b/frontend/src/components/protocols/UrlTest.vue deleted file mode 100644 index 298c97e..0000000 --- a/frontend/src/components/protocols/UrlTest.vue +++ /dev/null @@ -1,121 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Vless.vue b/frontend/src/components/protocols/Vless.vue deleted file mode 100644 index 8b09e00..0000000 --- a/frontend/src/components/protocols/Vless.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Vmess.vue b/frontend/src/components/protocols/Vmess.vue deleted file mode 100644 index 03b18d3..0000000 --- a/frontend/src/components/protocols/Vmess.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/protocols/Wireguard.vue b/frontend/src/components/protocols/Wireguard.vue deleted file mode 100644 index 4c2b7c2..0000000 --- a/frontend/src/components/protocols/Wireguard.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/tiles/Gauge.vue b/frontend/src/components/tiles/Gauge.vue deleted file mode 100644 index 9903fea..0000000 --- a/frontend/src/components/tiles/Gauge.vue +++ /dev/null @@ -1,110 +0,0 @@ - - - - - diff --git a/frontend/src/components/tiles/History.vue b/frontend/src/components/tiles/History.vue deleted file mode 100644 index 4069e5b..0000000 --- a/frontend/src/components/tiles/History.vue +++ /dev/null @@ -1,200 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/tls/Acme.vue b/frontend/src/components/tls/Acme.vue deleted file mode 100644 index fea944f..0000000 --- a/frontend/src/components/tls/Acme.vue +++ /dev/null @@ -1,252 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/tls/Ech.vue b/frontend/src/components/tls/Ech.vue deleted file mode 100644 index 16255fa..0000000 --- a/frontend/src/components/tls/Ech.vue +++ /dev/null @@ -1,160 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/tls/InTLS.vue b/frontend/src/components/tls/InTLS.vue deleted file mode 100644 index 9a54cf5..0000000 --- a/frontend/src/components/tls/InTLS.vue +++ /dev/null @@ -1,243 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/tls/OutTLS.vue b/frontend/src/components/tls/OutTLS.vue deleted file mode 100644 index 6421bd1..0000000 --- a/frontend/src/components/tls/OutTLS.vue +++ /dev/null @@ -1,341 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/transports/Http.vue b/frontend/src/components/transports/Http.vue deleted file mode 100644 index c6f8b0b..0000000 --- a/frontend/src/components/transports/Http.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/transports/HttpUpgrade.vue b/frontend/src/components/transports/HttpUpgrade.vue deleted file mode 100644 index 06a15dc..0000000 --- a/frontend/src/components/transports/HttpUpgrade.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/transports/WebSocket.vue b/frontend/src/components/transports/WebSocket.vue deleted file mode 100644 index cf9f669..0000000 --- a/frontend/src/components/transports/WebSocket.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/components/transports/gRPC.vue b/frontend/src/components/transports/gRPC.vue deleted file mode 100644 index 3fc80d7..0000000 --- a/frontend/src/components/transports/gRPC.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/default/AppBar.vue b/frontend/src/layouts/default/AppBar.vue deleted file mode 100644 index d59e895..0000000 --- a/frontend/src/layouts/default/AppBar.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/frontend/src/layouts/default/Default.vue b/frontend/src/layouts/default/Default.vue deleted file mode 100644 index 677235e..0000000 --- a/frontend/src/layouts/default/Default.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/layouts/default/Drawer.vue b/frontend/src/layouts/default/Drawer.vue deleted file mode 100644 index 4701039..0000000 --- a/frontend/src/layouts/default/Drawer.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/default/View.vue b/frontend/src/layouts/default/View.vue deleted file mode 100644 index f17623b..0000000 --- a/frontend/src/layouts/default/View.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Admin.vue b/frontend/src/layouts/modals/Admin.vue deleted file mode 100644 index 8344503..0000000 --- a/frontend/src/layouts/modals/Admin.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Changes.vue b/frontend/src/layouts/modals/Changes.vue deleted file mode 100644 index 6c2bf30..0000000 --- a/frontend/src/layouts/modals/Changes.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Client.vue b/frontend/src/layouts/modals/Client.vue deleted file mode 100644 index cae827a..0000000 --- a/frontend/src/layouts/modals/Client.vue +++ /dev/null @@ -1,273 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/ClientBulk.vue b/frontend/src/layouts/modals/ClientBulk.vue deleted file mode 100644 index aaa3971..0000000 --- a/frontend/src/layouts/modals/ClientBulk.vue +++ /dev/null @@ -1,199 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue deleted file mode 100644 index cfd8cdf..0000000 --- a/frontend/src/layouts/modals/Inbound.vue +++ /dev/null @@ -1,213 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Logs.vue b/frontend/src/layouts/modals/Logs.vue deleted file mode 100644 index e2dc9ef..0000000 --- a/frontend/src/layouts/modals/Logs.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Outbound.vue b/frontend/src/layouts/modals/Outbound.vue deleted file mode 100644 index 7119b71..0000000 --- a/frontend/src/layouts/modals/Outbound.vue +++ /dev/null @@ -1,204 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/QrCode.vue b/frontend/src/layouts/modals/QrCode.vue deleted file mode 100644 index 5c42b9e..0000000 --- a/frontend/src/layouts/modals/QrCode.vue +++ /dev/null @@ -1,134 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Rule.vue b/frontend/src/layouts/modals/Rule.vue deleted file mode 100644 index 256bb9c..0000000 --- a/frontend/src/layouts/modals/Rule.vue +++ /dev/null @@ -1,169 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Ruleset.vue b/frontend/src/layouts/modals/Ruleset.vue deleted file mode 100644 index a1842d6..0000000 --- a/frontend/src/layouts/modals/Ruleset.vue +++ /dev/null @@ -1,133 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Stats.vue b/frontend/src/layouts/modals/Stats.vue deleted file mode 100644 index 6a4a7e4..0000000 --- a/frontend/src/layouts/modals/Stats.vue +++ /dev/null @@ -1,210 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/layouts/modals/Tls.vue b/frontend/src/layouts/modals/Tls.vue deleted file mode 100644 index 7b73406..0000000 --- a/frontend/src/layouts/modals/Tls.vue +++ /dev/null @@ -1,551 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts deleted file mode 100644 index 7963840..0000000 --- a/frontend/src/locales/en.ts +++ /dev/null @@ -1,422 +0,0 @@ -export default { - message: "Welcome", - success: "success", - failed: "failed", - enable: "Enable", - disable: "Disable", - none: "None", - all: "All", - filter: "Filter", - loading: "Loading...", - confirm: "Are you sure ?", - yes: "yes", - no: "no", - unlimited: "infinite", - remained: "Remained", - type: "Type", - protocol: "Protocol", - submit: "Submit", - reset: "Reset", - now: "Now", - network: "Network", - copyToClipboard: "Copy to clipboard", - noData: "No data!", - invalidLogin: "Invalid Login!", - online: "Online", - version: "Version", - email: "Email", - commaSeparated: "(comma separated)", - count: "Count", - template: "Template", - error: { - dplData: "Duplicate Data", - core: "Sing-Box Error", - }, - pages: { - login: "Login", - home: "Home", - inbounds: "Inbounds", - outbounds: "Outbounds", - clients: "Clients", - rules: "Rules", - tls: "TLS Settings", - basics: "Basics", - admins: "Admins", - settings: "Settings", - }, - main: { - tiles: "Tiles", - gauges: "Gauges", - charts: "Charts", - infos: "Information", - gauge: { - cpu: "CPU Gauge", - mem: "RAM Gauge", - }, - chart: { - cpu: "CPU Monitor", - mem: "RAM Monitor", - net: "Network Bandwidth", - pnet: "Network Packets", - }, - info: { - sys: "System Info", - sbd: "Sing-Box Info", - host: "Host", - cpu: "CPU", - core: "Core", - uptime: "Uptime", - threads: "Threads", - memory: "Memory", - running: "Running" - } - }, - objects: { - inbound: "Inbound", - client: "Client", - outbound: "Outbound", - rule: "Rule", - user: "User", - tag: "Tag", - listen: "Listen", - dial: "Dial", - tls: "TLS", - multiplex: "Multiplex", - transport: "Transport", - method: "Method", - headers: "Headers", - key: "Key", - value: "Value", - }, - actions: { - action: "Action", - add: "Add", - new: "New", - edit: "Edit", - del: "Delete", - clone: "Clone", - save: "Save", - update: "Update", - submit: "Submit", - set: "Set", - generate: "Generate", - disable: "Disable", - close: "Close", - restartApp: "Restart App", - restartSb: "Restart Singbox", - }, - login: { - title: "Login", - username: "Username", - unRules: "Username can not be empty", - password: "Password", - pwRules: "Password can not be empty", - }, - menu: { - logout: "Logout", - }, - admin: { - changeCred: "Change credentials", - oldPass: "Current Password", - newUname: "New Username", - newPass: "New Password", - lastLogin: "Last login", - date: "Date", - time: "Time", - changes: "Changes", - actor: "Actor", - key: "Key", - action: "Action", - }, - setting: { - interface: "Interface", - sub: "Subscription", - addr: "Address", - port: "Port", - webPath: "Base URI", - domain: "Domain", - sslKey: "SSL Key Path", - sslCert: "SSL Certificate Path", - webUri: "Panel URI", - sessionAge: "Session Maximum Age", - trafficAge: "Traffic Maximum Age", - timeLoc: "Timezone Location", - subEncode: "Enable Encoding", - subInfo: "Enable Client Info", - path: "Default Path", - update: "Automatic Update Time", - subUri: "Subscription URI", - jsonSub: "JSON Subscription", - toDirect: "Route to Direct", - toBlock: "Route to Block", - timestamp: "Timestamp", - globalDns: "Global DNS", - directDns: "Direct DNS", - toDirectDns: "Route to Direct DNS", - jsonSubOptions: "Other Options", - excludePkg: "Exclude Packages", - }, - client: { - name: "Name", - desc: "Description", - group: "Group", - inboundTags: "Inbound Tags", - basics: "Basics", - config: "Config", - links: "Links", - external: "External Link", - sub: "External Subscription", - }, - bulk: { - add: "Add Bulk", - order: "Order", - random: "Random", - }, - types: { - un: "Username", - pw: "Password", - direct: { - overrideAddr: "Override Address", - overridePort: "Override Port", - }, - hy: { - obfs: "Obfuscated Password", - auth: "Authentication Password", - hyOptions: "Hysteria Options", - hy2Options: "Hysteria2 Options", - ignoreBw: "Ignore Client Bandwidth", - }, - shdwTls: { - hs: "Handshake Server", - addHS: "Add Handshake Server", - }, - ssh: { - passphrase: "Passphrase", - hostKey: "Host Keys", - algorithm: "Key Algorithms", - clientVer: "Client Version", - options: "SSH Options", - }, - tor: { - execPath: "Executable File Path", - dataDir: "Data Directory", - extArgs: "Extra Args", - }, - tuic: { - congControl: "Congestion Control", - authTimeout: "Authentication Timeout", - hb: "Heartbeat", - }, - tun: { - addr: "Addresses", - ifName: "Interface Name", - }, - vless: { - flow: "Flow", - udpEnc: "UDP Packet Encoding", - }, - vmess: { - security: "Security", - globalPadding: "Global Padding", - authLen: "Encryptrd Length", - }, - wg: { - privKey: "Private Key", - pubKey: "Peer Public Key", - psk: "Pre-Shared Key", - localIp: "Local IPs", - worker: "Workers", - ifName: "Interface Name", - sysIf: "System Interface", - gso: "Segmentation Offload", - options: "Wireguard Options", - multiPeer: "Multi Peer", - allowedIp: "Allowed IPs", - peer: "Peer", - peers: "Peers", - }, - lb: { - defaultOut: "Default Outbound", - interruptConn: "Interrupt exist connections", - testUrl: "Test URL", - interval: "Interval", - tolerance: "Tolerance", - urlTestOptions: "URLTest Options" - } - }, - in: { - addr: "Address", - port: "Port", - clients: "Enable Clients", - ssMethod: "Method", - sSide: "Server Side", - cSide: "Client Side", - multiDomain: "Multi Domain", - remark: "Remark", - mdOption: "Multi Domain Options", - }, - listen: { - sniffing: "Sniffing", - sniffingTimeout: "Sniffing Timeout", - sniffingOverride: "Override Destation", - options: "Listen Options", - tcpOptions: "TCP Options", - udpOptions: "UDP Options", - detour: "Detour", - detourText: "Forward to inbound", - domainStrategy: "Domain Strategy", - }, - dial: { - bindIf: "Bind to Network Interface", - bindIp4: "Bind to IPv4", - bindIp6: "Bind to IPv6", - reuseAddr: "Reuse Listener Address", - connTimeout: "Connection Timeout", - fbTimeout: "Fallback Timeout", - options: "Dial Options", - detourText: "Forward to outbound", - }, - transport: { - enable: "Enable Transport", - host: "Host", - hosts: "Hosts", - path: "Path", - httpMethod: "Request Method", - idleTimeout: "Idle Timeout", - pingTimeout: "Ping Timeout", - grpcServiceName: "Service Name", - grpcPws: "Permit Without Stream", - }, - mux: { - enable: "Enable Multiplex", - maxConn: "Max Connections", - minStr: "Min Streams", - maxStr: "Max Streams", - padding: "Only padding", - enableBrutal: "Enable Brutal", - }, - out: { - addr: "Server Address", - port: "Server Port", - }, - rule: { - add: "Add Rule", - simple: "Simple", - logical: "Logical", - mode: "Mode", - invert: "Invert", - ipVer: "IP Version", - domain: "Domains", - domainSufix: "Domain Suffixes", - domainKw: "Domain Keywords", - domainRgx: "Domain Regexes", - ip: "IP CIDRs", - privateIp: "Invalid IP Ranges", - port: "Ports", - portRange: "Port Ranges", - srcCidr: "Source IP CIDRs", - srcPrivateIp: "Invalid Source IPs", - srcPort: "Source Ports", - srcPortRange: "Source Port Ranges", - ruleset: "Rulesets", - rulesetMatchSrc: "Ruleset IPcidr Match Source", - options: "Rule Options", - domainRules: "Domain/IP", - srcIpRules: "Source IP", - srcPortRules: "Source Port", - }, - ruleset: { - add: "Add Ruleset", - format: "Data Format", - interval: "Update Intervals", - remote: "Remote", - local: "Local", - }, - basic: { - log: { - title: "Logs", - level: "Level", - output: "Output", - timestamp: "Enable Timestamp", - }, - dns: { - final: "Final", - server: "Server", - firstServer: "First Server", - addrResolver: "Address Resolver", - }, - routing: { - title: "Routing", - defaultOut: "Default Outbound", - defaultIf: "Default NIC", - defaultRm: "Default Routing Mark", - autoBind: "Auto Bind NIC", - }, - exp: { - storeFakeIp: "Store Fake IP", - }, - }, - tls: { - enable: "Enable TLS", - usePath: "Use Path", - useText: "Use Text", - certPath: "Certificate File Path", - keyPath: "Key File Path", - cert: "Certificate", - key: "Key", - options: "TLS Options", - minVer: "Minimum Version", - maxVer: "Maximum Version", - cs: "Cipher suits", - privKey: "Private Key", - pubKey: "Public Key", - disableSni: "Disable SNI", - insecure: "Allow Insecure", - acme: { - options: "ACME Options", - dataDir: "Data Directory", - defaultDomain: "Default Domain", - disableChallenges: "Disable Challenges", - httpChallenge: "Disable HTTP Challenge", - tlsChallenge: "Disable TLS Challenge", - altPorts: "Alternative Ports", - altHport: "Alternative HTTP Port", - altTport: "Alternative TLS Port", - caProvider: "CA Provider", - customCa: "Custom CA Provider", - extAcc: "External Account", - dns01: "DNS01 Challenge", - dns01Provider: "DNS01 Challenge Provider", - }, - }, - stats: { - upload: "Upload", - download: "Download", - volume: "Volume", - usage: "Usage", - enable: "Enable Statistics", - graphTitle: "Traffic Chart", - B: "B", - KB: "KB", - MB: "MB", - GB: "GB", - TB: "TB", - PB: "PB", - p: "p", - Kp: "Kp", - Mp: "Mp", - Gb: "Gb", - bps: "bps", - Kbps: "Kbps", - Mbps: "Mbps", - }, - date: { - expiry: "Expiry", - expired: "Expired", - d: "d", - h: "h", - m: "m", - s: "s", - ms: "ms", - }, -} \ No newline at end of file diff --git a/frontend/src/locales/fa.ts b/frontend/src/locales/fa.ts deleted file mode 100644 index c50e2d7..0000000 --- a/frontend/src/locales/fa.ts +++ /dev/null @@ -1,421 +0,0 @@ -export default { - message: "خوش آمدید", - success: "موفق", - failed: "خطا", - enable: "فعال", - disable: "غیرفعال", - none: "هیچ", - all: "همه", - filter: "فیلتر", - loading: "در حال بارگذاری...", - confirm: "آیا مطمئن هستید ؟", - yes: "بله", - no: "خیر", - unlimited: "نامحدود", - remained: "باقیمانده", - type: "مدل", - protocol: "پروتکل", - submit: "تایید", - reset: "ریست", - now: "اکنون", - network: "شبکه", - copyToClipboard: "کپی در حافظه", - noData: "بدون داده!", - invalidLogin: "ورود نامعتبر!", - online: "آنلاین", - version: "نسخه", - email: "ایمیل", - commaSeparated: "(جداشده با کاما)", - count: "تعداد", - template: "الگو", - error: { - dplData: "داده تکراری", - core: "خطا در سینگ‌باکس", - }, - pages: { - login: "ورود", - home: "خانه", - inbounds: "ورودی‌ها", - outbounds: "خروجی‌ها", - clients: "کاربران", - rules: "قوانین", - tls: "رمزنگاری‌ها", - basics: "ترازها", - admins: "ادمین‌ها", - settings: "پیکربندی", - }, - main: { - tiles: "کاشی‌ها", - gauges: "سنجش‌ها", - charts: "نمودارها", - infos: "داده‌ها", - gauge: { - cpu: "سنجش پردازنده", - mem: "سنجش حافظه", - }, - chart: { - cpu: "نمودار پردازنده", - mem: "نمودار حافظه", - net: "ترافیک شبکه", - pnet: "بسته‌های شبکه", - }, - info: { - sys: "داده‌های سیستم", - sbd: "داده‌های سینگ‌باکس", - host: "نام", - cpu: "پردازنده", - core: "هسته", - uptime: "مدت‌", - threads: "نخ‌ها", - memory: "حافظه", - running: "اجرا" - } - }, - objects: { - inbound: "ورودی‌", - client: "کاربر", - outbound: "خروجی‌", - rule: "قانون", - user: "کاربر", - tag: "برچسب", - listen: "گوش‌دادن", - dial: "تماس", - tls: "رمزنگاری", - multiplex: "تسهیم", - transport: "انتقال", - headers: "سربرگ‌ها", - key: "نام", - value: "مقدار", - }, - actions: { - action: "فرمان", - add: "ایجاد", - new: "جدید", - edit: "ویرایش", - del: "حذف", - clone: "شبیه‌سازی", - save: "ذخیره", - update: "بروزرسانی", - submit: "ارسال", - set: "تنظیم", - generate: "تولید", - disable: "غیرفعال", - close: "بستن", - restartApp: "ریستارت پنل", - restartSb: "ریستارت سینگ‌باکس", - }, - login: { - title: "ورود", - username: "نام کاربری", - unRules: "نام کاربری نمی‌تواند خالی باشد", - password: "کلمه عبور", - pwRules: "کلمه عبور نمی‌تواند خالی باشد", - }, - menu: { - logout: "خروج", - }, - admin: { - changeCred: "ویرایش داده‌ها", - oldPass: "رمز کنونی", - newUname: "نام کاربری جدید", - newPass: "رمز جدید", - lastLogin: "آخرین ورود", - date: "تاریخ", - time: "ساعت", - changes: "تغییرات", - actor: "مجری", - key: "کلید", - action: "عمل", - }, - setting: { - interface: "نما", - sub: "سابسکریپشن", - addr: "آدرس", - port: "پورت", - webPath: "مسیر پایه", - domain: "دامنه", - sslKey: "مسیر فایل کلید", - sslCert: "مسیر فایل گواهی", - webUri: "آدرس نهایی پنل", - sessionAge: "بیشینه زمان لاگین ماندن", - trafficAge: "بیشینه زمان ذخیره ترافیک", - timeLoc: "منطقه زمانی", - subEncode: "رمزگذاری", - subInfo: "نمایش اطلاعات کاربر", - path: "مسیر پیشفرض", - update: "زمان بروزرسانی خودکار", - subUri: "آدرس نهایی سابسکریپشن", - jsonSub: "سابسکریپشن JSON", - toDirect: "هدایت مستقیم", - toBlock: "بستن مسیر", - timestamp: "نمایش زمان", - globalDns: "DNS کلی", - directDns: "DNS مستقیم", - toDirectDns: "هدایت به DNS مستقیم", - jsonSubOptions: "گزینه‌های دیگر", - excludePkg: "نرم‌افزارهای استثنا", - }, - client: { - name: "نام", - desc: "شرح", - group: "گروه", - inboundTags: "برچسب‌های ورودی", - basics: "پایه", - config: "تنظیم", - links: "لینک‌ها", - external: "لینک‌ خارجی", - sub: "سابسکریپشن خارجی", - }, - bulk: { - add: "ایجاد انبوه", - order: "ترتیب", - random: "تصادفی", - }, - types: { - un: "نام کاربری", - pw: "رمز", - direct: { - overrideAddr: "جایگزین آدرس", - overridePort: "جایگزین پورت", - }, - hy: { - obfs: "رمز مبهم کننده", - auth: "رمز احراز هویت", - hyOptions: "گزینه‌های Hysteria", - hy2Options: "گزینه‌های Hysteria2", - ignoreBw: "نادیده‌گرفتن پهنای‌باند کاربر", - }, - shdwTls: { - hs: "سرور دست‌تکانی", - addHS: "افزودن سرور دست‌تکانی", - }, - ssh: { - passphrase: "عبارت عبور", - hostKey: "کلیدهای هاست‌ها", - algorithm: "الگوریتم‌ها", - clientVer: "نسخه کلاینت", - options: "گزینه‌های SSH", - }, - tor: { - execPath: "مسیر فایل اجرایی", - dataDir: "پوشه داده‌ها", - extArgs: "آرگومان‌های اضافی", - }, - tuic: { - congControl: "کنترل ازدحام", - authTimeout: "مهلت احراز هویت", - hb: "ضربان قلب", - }, - tun: { - addr: "آدرس‌ها", - ifName: "نام اینترفیس", - }, - vless: { - flow: "جریان", - udpEnc: "کدگذاری بسته UDP", - }, - vmess: { - security: "امنیت", - globalPadding: "لایه بندی کلی", - authLen: "رمزگذاری اندازه بسته", - }, - wg: { - privKey: "کلید خصوصی", - pubKey: "کلید عمومی همتا", - psk: "کلید مشترک", - localIp: "آدرس‌های محلی", - worker: "عملگرها", - ifName: "نام اینترفیس", - sysIf: "استفاده از اینترفیس سیستم", - gso: "بارگذاری تقسیم‌بندی عمومی", - options: "گزینه‌های Wireguard", - multiPeer: "چند همتایی", - allowedIp: "آدرس‌های مجاز", - peer: "همتا", - peers: "همتاها", - }, - lb: { - defaultOut: "خروجی پیش‌فرض", - interruptConn: "قطع ارتباط موجود", - testUrl: "URL تست", - interval: "فاصله زمانی", - tolerance: "تحمل", - urlTestOptions: "گزینه‌های URLTest" - } - }, - in: { - addr: "آدرس", - port: "پورت", - clients: "فعال‌سازی کاربران", - ssMethod: "روش", - sSide: "سمت سرور", - cSide: "سمت کاربر", - multiDomain: "دامنه چندگانه", - remark: "شرح", - mdOption: "گزینه‌های دامنه چندگانه", - }, - listen: { - sniffing: "شنود آدرس", - sniffingTimeout: "مهلت شنود آدرس", - sniffingOverride: "جایگزینی مقصد", - options: "گزینه‌های گوش‌دادن", - tcpOptions: "گزینه‌های TCP", - udpOptions: "گزینه‌های UDP", - detour: "انحراف مسیر", - detourText: "ارسال به ورودی دیگر", - domainStrategy: "استراتژی دامنه", - }, - dial: { - bindIf: "اتصال به کارت شبکه", - bindIp4: "اتصال به IPv4", - bindIp6: "اتصال به IPv6", - reuseAddr: "استفاده مجدد از آدرس", - connTimeout: "مهلت ارتباط", - fbTimeout: "مهلت فالبک", - options: "گزینه‌های تماس", - detourText: "ارسال به خروجی دیگر", - }, - transport: { - enable: "فعال‌سازی انتقال", - host: "دامنه", - hosts: "دامنه‌ها", - path: "مسیر", - httpMethod: "متد درخواست", - idleTimeout: "مهلت بیکاری", - pingTimeout: "مهلت پینگ", - grpcServiceName: "نام سرویس", - grpcPws: "حفظ ارتباط بدون دیتا", - }, - mux: { - enable: "فعال‌سازی تسهیم", - maxConn: "بیشینه ارتباطات", - minStr: "کمینه استریم", - maxStr: "بیشینه استریم", - padding: "فقط با پدینگ", - enableBrutal: "فعال‌سازی شدت", - }, - out: { - addr: "آدرس سرور", - port: "پورت سرور", - }, - rule: { - add: "ایجاد قانون", - simple: "ساده", - logical: "منطقی", - mode: "حالت", - invert: "برعکس", - ipVer: "نسخه IP", - domain: "دامنه‌ها", - domainSufix: "پسوند‌های دامنه", - domainKw: "کلمات کلیدی دامنه", - domainRgx: "رجکس دامنه", - ip: "محدوده‌های IP", - privateIp: "آدرس های IP نامعتبر", - port: "پورت‌ها", - portRange: "محدوده‌های پورت", - srcCidr: "محدوده‌های آدرس IP مبدا", - srcPrivateIp: "آدرس‌های IP مبدا نامعتبر", - srcPort: "پورت‌های مبدا", - srcPortRange: "محدوده پورتهای منبع", - ruleset: "مجموعه‌ها", - rulesetMatchSrc: "تطابق آدرس‌های مبدا با مجموعه قوانین", - options: "گزینه‌های قوانین", - domainRules: "دامنه/آدرس", - srcIpRules: "آدرس مبدا", - srcPortRules: "پورت مبدا", - }, - ruleset: { - add: "ایجاد مجموعه", - format: "فرمت داده‌ها", - interval: "بازه بروزرسانی‌ها", - remote: "راه دور", - local: "محلی", - }, - basic: { - log: { - title: "گزارش‌ها", - level: "سطح", - output: "خروجی", - timestamp: "فعال‌سازی ثبت زمان", - }, - dns: { - final: "سرور نهایی", - server: "سرور", - firstServer: "سرور نخست", - addrResolver: "حل کننده دامنه", - }, - routing: { - title: "مسیریابی", - defaultOut: "خروجی پیش‌فرض", - defaultIf: "کارت شبکه پیش‌فرض", - defaultRm: "Routing Mark پیش‌فرض", - autoBind: "انتخاب اتوماتیک کارت شبکه", - }, - exp: { - storeFakeIp: "ذخیره آدرس‌های نامعتبر", - }, - }, - tls: { - enable: "فعالسازی رمزنگاری", - usePath: "مسیر فایل", - useText: "متن گواهی", - certPath: "مسیر فایل گواهی", - keyPath: "مسیر فایل کلید", - cert: "گواهی", - key: "کلید", - options: "گزینه‌های رمز‌نگاری", - minVer: "کمینه نسخه", - maxVer: "بیشینه نسخه", - cs: "مدل‌های رمزنگاری", - privKey: "کلید خصوصی", - pubKey: "کلید عمومی", - disableSni: "غیرفعال‌سازی SNI", - insecure: "تایید ارتباط ناامن", - acme: { - options: "گزینه‌های ACME", - dataDir: "مسیر داده‌ها", - defaultDomain: "دامنه پیش‌فرض", - disableChallenges: "بستن چالش‌ها", - httpChallenge: "بستن چالش HTTP", - tlsChallenge: "بستن چالش TLS", - altPorts: "پورت‌های جایگزین", - altHport: "پورت جایگزین HTTP", - altTport: "پورت جایگزین TLS", - caProvider: "فراهم کننده گواهی", - customCa: "فراهم کننده دیگر", - extAcc: "حساب خارجی", - dns01: "چالش DNS01", - dns01Provider: "فراهم کننده چالش DNS01", - }, - }, - stats: { - upload: "آپلود", - download: "دانلود", - volume: "حجم", - usage: "استفاده", - enable: "فعال سازی کنترل ترافیک", - graphTitle: "نمودار ترافیک", - B: "ب", - KB: "ک‌ب", - MB: "م‌ب", - GB: "گ‌ب", - TB: "ت‌ب", - PB: "پ‌ب", - p: "پ", - Kp: "ک‌پ", - Mp: "م‌پ", - Gp: "گ‌پ", - bps: "ب/ث", - Kbps: "ک‌ب/ث", - Mbps: "م‌ب/ث", - }, - date: { - expiry: "انقضا", - expired: "منقضی", - d: "ر", - h: "س", - m: "د", - s: "ث", - ms: "م‌ث", - } -} \ No newline at end of file diff --git a/frontend/src/locales/index.ts b/frontend/src/locales/index.ts deleted file mode 100644 index 7049e4d..0000000 --- a/frontend/src/locales/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createI18n } from 'vue-i18n' -import en from './en' -import fa from './fa' -import vi from './vi' -import zhcn from './zhcn' -import zhtw from './zhtw' -import ru from './ru' - -export const i18n = createI18n({ - legacy: false, - locale: localStorage.getItem("locale") ?? 'en', - fallbackLocale: 'en', - messages: { - en: en, - fa: fa, - vi: vi, - zhHans: zhcn, - zhHant: zhtw, - ru: ru - }, -}) - -export const languages = [ - { title: 'English', value: 'en' }, - { title: 'فارسی', value: 'fa' }, - { title: 'Tiếng Việt', value: 'vi' }, - { title: '简体中文', value: 'zhHans' }, - { title: '繁體中文', value: 'zhHant' }, - { title: 'Русский', value: 'ru' }, -] diff --git a/frontend/src/locales/ru.ts b/frontend/src/locales/ru.ts deleted file mode 100644 index eb7b410..0000000 --- a/frontend/src/locales/ru.ts +++ /dev/null @@ -1,422 +0,0 @@ -export default { - message: "Добро пожаловать", - success: "успех", - failed: "ошибка", - enable: "Включить", - disable: "Отключить", - none: "Никакие", - all: "Все", - filter: "Фильтр", - loading: "Загрузка...", - confirm: "Вы уверены?", - yes: "да", - no: "нет", - unlimited: "бесконечный", - remained: "Осталось", - type: "Тип", - protocol: "Протокол", - submit: "Отправить", - reset: "Сбросить", - now: "Сейчас", - network: "Сеть", - copyToClipboard: "Копировать в буфер обмена", - noData: "Нет данных!", - invalidLogin: "Неверный логин!", - online: "В сети", - version: "Версия", - email: "Электронная почта", - commaSeparated: "(разделено запятыми)", - count: "Количество", - template: "Шаблон", - error: { - dplData: "Дублирующие данные", - core: "Ошибка Sing-Box", - }, - pages: { - login: "Вход", - home: "Главная", - inbounds: "Входящие", - outbounds: "Исходящие", - clients: "Клиенты", - rules: "Правила", - tls: "Настройки TLS", - basics: "Основы", - admins: "Администраторы", - settings: "Настройки", - }, - main: { - tiles: "Плитки", - gauges: "Датчики", - charts: "Графики", - infos: "Информация", - gauge: { - cpu: "Загрузка ЦП", - mem: "Загрузка ОЗУ", - }, - chart: { - cpu: "Мониторинг ЦП", - mem: "Мониторинг ОЗУ", - net: "Сетевой трафик", - pnet: "Сетевые пакеты", - }, - info: { - sys: "Информация о системе", - sbd: "Информация о Sing-Box", - host: "Хост", - cpu: "ЦП", - core: "Ядро", - uptime: "Время работы", - threads: "Потоки", - memory: "Память", - running: "Работает" - } - }, - objects: { - inbound: "Входящий", - client: "Клиент", - outbound: "Исходящий", - rule: "Правило", - user: "Пользователь", - tag: "Тег", - listen: "Прослушивание", - dial: "Звонок", - tls: "TLS", - multiplex: "Мультиплекс", - transport: "Транспорт", - method: "Метод", - headers: "Заголовки", - key: "Ключ", - value: "Значение", - }, - actions: { - action: "Действие", - add: "Добавить", - new: "Новый", - edit: "Редактировать", - del: "Удалить", - clone: "Клонировать", - save: "Сохранить", - update: "Обновить", - submit: "Отправить", - set: "Установить", - generate: "Генерировать", - disable: "Отключить", - close: "Закрыть", - restartApp: "Перезапустить приложение", - restartSb: "Перезапустить Singbox", - }, - login: { - title: "Вход", - username: "Имя пользователя", - unRules: "Имя пользователя не может быть пустым", - password: "Пароль", - pwRules: "Пароль не может быть пустым", - }, - menu: { - logout: "Выйти", - }, - admin: { - changeCred: "Изменить учетные данные", - oldPass: "Текущий пароль", - newUname: "Новое имя пользователя", - newPass: "Новый пароль", - lastLogin: "Последний вход", - date: "Дата", - time: "Время", - changes: "Изменения", - actor: "Исполнитель", - key: "Ключ", - action: "Действие", - }, - setting: { - interface: "Интерфейс", - sub: "Подписка", - addr: "Адрес", - port: "Порт", - webPath: "Базовый URI", - domain: "Домен", - sslKey: "Путь к SSL ключу", - sslCert: "Путь к SSL сертификату", - webUri: "URI панели", - sessionAge: "Максимальная длительность сессии", - trafficAge: "Максимальная длительность трафика", - timeLoc: "Часовой пояс", - subEncode: "Включить кодирование", - subInfo: "Включить информацию о клиенте", - path: "Путь по умолчанию", - update: "Время автоматического обновления", - subUri: "URI подписки", - jsonSub: "JSON подписка", - toDirect: "Маршрутизация на Direct", - toBlock: "Маршрутизация на Block", - timestamp: "Метка времени", - globalDns: "Глобальный DNS", - directDns: "Прямой DNS", - toDirectDns: "Маршрутизация на Direct DNS", - jsonSubOptions: "Другие параметры", - excludePkg: "Исключить пакеты", - }, - client: { - name: "Имя", - desc: "Описание", - group: "Группа", - inboundTags: "Теги входящих", - basics: "Основы", - config: "Конфигурация", - links: "Ссылки", - external: "Внешняя ссылка", - sub: "Внешняя подписка", - }, - bulk: { - add: "Добавить пакетно", - order: "Порядок", - random: "Случайный", - }, - types: { - un: "Имя пользователя", - pw: "Пароль", - direct: { - overrideAddr: "Переопределить адрес", - overridePort: "Переопределить порт", - }, - hy: { - obfs: "Обфусцированный пароль", - auth: "Пароль аутентификации", - hyOptions: "Параметры Hysteria", - hy2Options: "Параметры Hysteria2", - ignoreBw: "Игнорировать пропускную способность клиента", - }, - shdwTls: { - hs: "Сервер рукопожатий", - addHS: "Добавить сервер рукопожатий", - }, - ssh: { - passphrase: "Парольная фраза", - hostKey: "Ключи хоста", - algorithm: "Алгоритмы ключей", - clientVer: "Версия клиента", - options: "Параметры SSH", - }, - tor: { - execPath: "Путь к исполняемому файлу", - dataDir: "Каталог данных", - extArgs: "Дополнительные аргументы", - }, - tuic: { - congControl: "Контроль перегрузок", - authTimeout: "Таймаут аутентификации", - hb: "Сердцебиение", - }, - tun: { - addr: "Адреса", - ifName: "Имя интерфейса", - }, - vless: { - flow: "Поток", - udpEnc: "Кодирование UDP пакетов", - }, - vmess: { - security: "Безопасность", - globalPadding: "Глобальная подкладка", - authLen: "Длина шифрования", - }, - wg: { - privKey: "Приватный ключ", - pubKey: "Публичный ключ пира", - psk: "Предварительно разделенный ключ", - localIp: "Локальные IP", - worker: "Работники", - ifName: "Имя интерфейса", - sysIf: "Системный интерфейс", - gso: "Отключение сегментации", - options: "Параметры Wireguard", - multiPeer: "Множественный пир", - allowedIp: "Разрешенные IP", - peer: "Пир", - peers: "Пиры", - }, - lb: { - defaultOut: "Исходящий по умолчанию", - interruptConn: "Прервать существующие соединения", - testUrl: "Тестовый URL", - interval: "Интервал", - tolerance: "Толерантность", - urlTestOptions: "Параметры теста URL" - } - }, - in: { - addr: "Адрес", - port: "Порт", - clients: "Включить клиентов", - ssMethod: "Метод", - sSide: "Сторона сервера", - cSide: "Сторона клиента", - multiDomain: "Мультидомен", - remark: "Примечание", - mdOption: "Параметры мультидомена", - }, - listen: { - sniffing: "Обнаружение", - sniffingTimeout: "Таймаут обнаружения", - sniffingOverride: "Переопределение назначения", - options: "Параметры прослушивания", - tcpOptions: "Параметры TCP", - udpOptions: "Параметры UDP", - detour: "Обход", - detourText: "Переадресация на входящий", - domainStrategy: "Стратегия домена", - }, - dial: { - bindIf: "Привязка к сетевому интерфейсу", - bindIp4: "Привязка к IPv4", - bindIp6: "Привязка к IPv6", - reuseAddr: "Повторное использование адреса слушателя", - connTimeout: "Таймаут подключения", - fbTimeout: "Таймаут возврата", - options: "Параметры вызова", - detourText: "Переадресация на исходящий", - }, - transport: { - enable: "Включить транспорт", - host: "Хост", - hosts: "Хосты", - path: "Путь", - httpMethod: "Метод запроса", - idleTimeout: "Таймаут бездействия", - pingTimeout: "Таймаут пинга", - grpcServiceName: "Имя службы", - grpcPws: "Разрешить без потока", - }, - mux: { - enable: "Включить мультиплекс", - maxConn: "Максимальное количество соединений", - minStr: "Минимальное количество потоков", - maxStr: "Максимальное количество потоков", - padding: "Только подкладка", - enableBrutal: "Включить Brutal", - }, - out: { - addr: "Адрес сервера", - port: "Порт сервера", - }, - rule: { - add: "Добавить правило", - simple: "Простое", - logical: "Логическое", - mode: "Режим", - invert: "Инвертировать", - ipVer: "Версия IP", - domain: "Домены", - domainSufix: "Суффиксы доменов", - domainKw: "Ключевые слова домена", - domainRgx: "Регулярные выражения домена", - ip: "CIDR IP", - privateIp: "Недействительные диапазоны IP", - port: "Порты", - portRange: "Диапазоны портов", - srcCidr: "CIDR исходного IP", - srcPrivateIp: "Недействительные исходные IP", - srcPort: "Исходные порты", - srcPortRange: "Диапазоны исходных портов", - ruleset: "Наборы правил", - rulesetMatchSrc: "Набор правил для соответствия источника IPcidr", - options: "Параметры правила", - domainRules: "Домен/IP", - srcIpRules: "Источник IP", - srcPortRules: "Источник порта", - }, - ruleset: { - add: "Добавить набор правил", - format: "Формат данных", - interval: "Интервалы обновления", - remote: "Удаленный", - local: "Локальный", - }, - basic: { - log: { - title: "Журналы", - level: "Уровень", - output: "Вывод", - timestamp: "Включить метку времени", - }, - dns: { - final: "Итоговый", - server: "Сервер", - firstServer: "Первый сервер", - addrResolver: "Разрешение адреса", - }, - routing: { - title: "Маршрутизация", - defaultOut: "Исходящий по умолчанию", - defaultIf: "Сетевой интерфейс по умолчанию", - defaultRm: "Маршрут по умолчанию", - autoBind: "Автопривязка сетевого интерфейса", - }, - exp: { - storeFakeIp: "Хранить поддельный IP", - }, - }, - tls: { - enable: "Включить TLS", - usePath: "Использовать путь", - useText: "Использовать текст", - certPath: "Путь к файлу сертификата", - keyPath: "Путь к файлу ключа", - cert: "Сертификат", - key: "Ключ", - options: "Параметры TLS", - minVer: "Минимальная версия", - maxVer: "Максимальная версия", - cs: "Наборы шифров", - privKey: "Приватный ключ", - pubKey: "Публичный ключ", - disableSni: "Отключить SNI", - insecure: "Разрешить небезопасное", - acme: { - options: "Параметры ACME", - dataDir: "Каталог данных", - defaultDomain: "Домен по умолчанию", - disableChallenges: "Отключить вызовы", - httpChallenge: "Отключить HTTP вызов", - tlsChallenge: "Отключить TLS вызов", - altPorts: "Альтернативные порты", - altHport: "Альтернативный HTTP порт", - altTport: "Альтернативный TLS порт", - caProvider: "Поставщик CA", - customCa: "Пользовательский поставщик CA", - extAcc: "Внешний аккаунт", - dns01: "DNS01 вызов", - dns01Provider: "Поставщик DNS01 вызова", - }, - }, - stats: { - upload: "Загрузка", - download: "Скачивание", - volume: "Объем", - usage: "Использование", - enable: "Включить статистику", - graphTitle: "График трафика", - B: "Б", - KB: "КБ", - MB: "МБ", - GB: "ГБ", - TB: "ТБ", - PB: "ПБ", - p: "п", - Kp: "Кп", - Mp: "Мп", - Gb: "Гб", - bps: "б/с", - Kbps: "Кб/с", - Mbps: "Мб/с", - }, - date: { - expiry: "Срок действия", - expired: "Истек", - d: "д", - h: "ч", - m: "м", - s: "с", - ms: "мс", - }, -} diff --git a/frontend/src/locales/vi.ts b/frontend/src/locales/vi.ts deleted file mode 100644 index d4138d4..0000000 --- a/frontend/src/locales/vi.ts +++ /dev/null @@ -1,423 +0,0 @@ -export default { - message: "Chào mừng OHB", - success: "Thành công", - failed: "Thất bại", - enable: "Kích hoạt", - disable: "Vô hiệu hóa", - none: "Không", - all: "Tất cả", - filter: "Bộ lọc", - loading: "Đang tải...", - confirm: "Bạn chắc chắn chứ?", - yes: "có", - no: "không", - unlimited: "vô hạn", - remained: "Còn lại", - type: "Loại", - protocol: "Giao thức", - submit: "Gửi", - reset: "Đặt lại", - now: "Hiện tại", - network: "Mạng", - copyToClipboard: "Sao chép vào clipboard", - noData: "Không có dữ liệu!", - invalidLogin: "Đăng nhập không hợp lệ!", - online: "Trực tuyến", - version: "Phiên bản", - email: "Email", - commaSeparated: "(được phân tách bằng dấu phẩy)", - count: "Đếm", - template: "Mẫu", - error: { - dplData: "Dữ liệu trùng lặp", - core: "Lỗi Sing-Box", - }, - pages: { - login: "Đăng nhập", - home: "Trang chủ", - inbounds: "Đầu Vào", - outbounds: "Đầu ra", - clients: "Khách hàng", - rules: "Quy tắc", - tls: "Cài đặt TLS", - basics: "Cơ bản", - admins: "Quản trị viên", - settings: "Cài đặt", - }, - main: { - tiles: "OHB", - gauges: "Đồng hồ đo", - charts: "Biểu đồ", - infos: "Thông tin", - gauge: { - cpu: "Đồng hồ CPU", - mem: "Đồng hồ RAM", - }, - chart: { - cpu: "Máy theo dõi CPU", - mem: "Máy theo dõi RAM", - net: "Băng thông mạng", - pnet: "Gói mạng", - }, - info: { - sys: "Thông tin hệ thống", - sbd: "Thông tin Sing-Box", - host: "Máy chủ", - cpu: "CPU", - core: "Nhân", - uptime: "Thời gian hoạt động", - threads: "Luồng", - memory: "Bộ nhớ", - running: "Đang chạy" - } - }, - objects: { - inbound: "Đầu Vào", - client: "Máy Khách hàng", - outbound: "Đầu Ra", - rule: "Quy tắc", - user: "Người dùng", - tag: "Thẻ", - listen: "Nghe", - dial: "Quay số", - tls: "TLS", - multiplex: "Ghép đa truyền thông ", - transport: "Giao thông", - method: "Phương pháp", - headers: "Tiêu đề", - key: "Chìa khóa", - value: "Giá trị", - }, - actions: { - action: "Hành động", - add: "Thêm", - new: "Mới", - edit: "Chỉnh sửa", - del: "Xóa", - clone: "Nhân bản", - save: "Lưu", - update: "Cập nhật", - submit: "Gửi", - set: "Đặt", - generate: "Tạo ra", - disable: "Vô hiệu hóa", - close: "Đóng", - restartApp: "Khởi động lại ứng dụng", - restartSb: "Khởi động lại Singbox", - }, - login: { - title: "Đăng nhập", - username: "Tên người dùng", - unRules: "Tên người dùng không thể trống", - password: "Mật khẩu", - pwRules: "Mật khẩu không thể trống", - }, - menu: { - logout: "Đăng xuất", - }, - admin: { - changeCred: "Thay đổi thông tin đăng nhập", - oldPass: "Mật khẩu hiện tại", - newUname: "Tên người dùng mới", - newPass: "Mật khẩu mới", - lastLogin: "Lân đăng nhập cuôi", - date: "Ngày", - time: "Thời gian", - changes: "Thay đổi", - actor: "Diễn viên", - key: "Khóa", - action: "Hành động", - }, - setting: { - interface: "Giao diện", - sub: "Đăng ký", - addr: "Địa chỉ", - port: "Cổng", - webPath: "Đường dẫn gốc", - domain: "Miền", - sslKey: "Đường dẫn khóa SSL", - sslCert: "Đường dẫn chứng chỉ SSL", - webUri: "URI bảng điều khiển", - sessionAge: "Tuổi tối đa của phiên", - trafficAge: "Tuổi lưu thông tối đa", - timeLoc: "Vị trí múi giờ", - subEncode: "Kích hoạt mã hóa", - subInfo: "Kích hoạt thông tin khách hàng", - path: "Đường dẫn mặc định", - update: "Thời gian cập nhật tự động", - subUri: "URI đăng ký", - jsonSub: "Đăng ký JSON", - toDirect: "Chuyển hướng tới Trực tiếp", - toBlock: "Chuyển hướng tới Chặn", - timestamp: "Dấu thời gian", - globalDns: "DNS Toàn cầu", - directDns: "DNS Trực tiếp", - toDirectDns: "Chuyển hướng tới DNS Trực tiếp", - jsonSubOptions: "Tùy chọn Khác", - excludePkg: "Loại trừ Gói", - }, - client: { - name: "Tên", - desc: "Mô tả", - group: "Nhóm", - inboundTags: "Thẻ đầu vào", - basics: "Cơ bản", - config: "Cấu hình", - links: "Liên kết", - external: "Liên kết bên ngoài", - sub: "Đăng ký bên ngoài", - }, - bulk: { - add: "Thêm Hàng loạt", - order: "Sắp xếp", - random: "Ngẫu nhiên", - }, - types: { - un: "Tên người dùng", - pw: "Mật khẩu", - direct: { - overrideAddr: "Ghi đè Địa chỉ", - overridePort: "Ghi đè Cổng", - }, - hy: { - obfs: "Mật khẩu đã được Ẩn", - auth: "Mật khẩu Xác thực", - hyOptions: "Tùy chọn Hysteria", - hy2Options: "Tùy chọn Hysteria2", - ignoreBw: "Bỏ qua Băng thông của Client", - }, - shdwTls: { - hs: "Máy chủ Handshake", - addHS: "Thêm Máy chủ Handshake", - }, - ssh: { - passphrase: "Cụm từ mật khẩu", - hostKey: "Khóa Máy chủ", - algorithm: "Thuật toán Khóa", - clientVer: "Phiên bản Client", - options: "Tùy chọn SSH", - }, - tor: { - execPath: "Đường dẫn File thực thi", - dataDir: "Thư mục Dữ liệu", - extArgs: "Đối số Bổ sung", - }, - tuic: { - congControl: "Kiểm soát Tắc nghẽn", - authTimeout: "Thời gian chờ Xác thực", - hb: "Nhịp tim", - }, - tun: { - addr: "Địa chỉ", - ifName: "Tên Giao diện", - }, - vless: { - flow: "Luồng", - udpEnc: "Mã hóa Gói UDP", - }, - vmess: { - security: "Bảo mật", - globalPadding: "Đệm Toàn cầu", - authLen: "Chiều dài Mã hóa", - }, - wg: { - privKey: "Khóa Riêng tư", - pubKey: "Khóa Công khai của Đối tác", - psk: "Khóa được Chia sẻ trước", - localIp: "IPs Cục bộ", - worker: "Công nhân", - ifName: "Tên Giao diện", - sysIf: "Giao diện Hệ thống", - gso: "Giao Thức GSO", - options: "Tùy chọn Wireguard", - multiPeer: "Nhiều Đối tác", - allowedIp: "IPs được Phép", - peer: "Đối tác", - peers: "Đối tác", - }, - lb: { - defaultOut: "Đầu ra Mặc định", - interruptConn: "Ngắt kết nối hiện tại", - testUrl: "URL Kiểm tra", - interval: "Khoảng thời gian", - tolerance: "Sự dung hòa", - urlTestOptions: "Tùy chọn Kiểm tra URL", - } - }, - in: { - addr: "Địa chỉ", - port: "Cổng", - sniffing: "Đang Sniffing", - clients: "Kích hoạt khách hàng", - ssMethod: "Phương thức", - sSide: "Phía Máy chủ", - cSide: "Phía Khách hàng", - multiDomain: "Nhiều Tên miền", - remark: "Ghi chú", - mdOption: "Tùy chọn Nhiều Tên miền", - }, - listen: { - sniffing: "Đang Sniffing", - sniffingTimeout: "Thời gian Chờ Sniffing", - sniffingOverride: "Ghi đè Đích", - options: "Tùy chọn Nghe", - tcpOptions: "Tùy chọn TCP", - udpOptions: "Tùy chọn UDP", - detour: "Lạc đạo", - detourText: "Chuyển tiếp tới đầu vào", - domainStrategy: "Chiến lược Domain", - }, - dial: { - bindIf: "Ràng buộc tới Giao diện Mạng", - bindIp4: "Ràng buộc tới IPv4", - bindIp6: "Ràng buộc tới IPv6", - reuseAddr: "Sử dụng lại Địa chỉ Nghe", - connTimeout: "Thời gian Chờ Kết nối", - fbTimeout: "Thời gian Chờ Fallback", - options: "Tùy chọn Gọi", - detourText: "Chuyển tiếp tới thư đi", - }, - transport: { - enable: "Kích hoạt vận chuyển", - host: "Máy chủ", - hosts: "Máy chủ", - path: "Đường dẫn", - httpMethod: "Phương thức Yêu cầu", - idleTimeout: "Thời gian Chờ Chờ đợi", - pingTimeout: "Thời gian Chờ Ping", - grpcServiceName: "Tên Dịch vụ", - grpcPws: "Cho phép mà không Có Luồng", - }, - mux: { - enable: "Bật Multiplex", - maxConn: "Số kết nối Tối đa", - minStr: "Số Luồng Tối thiểu", - maxStr: "Số Luồng Tối đa", - padding: "Chỉ đệm", - enableBrutal: "Bật Brutal", - }, - out: { - addr: "Địa chỉ Máy chủ", - port: "Cổng Máy chủ", - }, - rule: { - add: "Thêm Quy tắc", - simple: "Đơn giản", - logical: "Logic", - mode: "Chế độ", - invert: "Nghịch đảo", - ipVer: "Phiên bản IP", - domain: "Tên miền", - domainSufix: "Hậu tố Miền", - domainKw: "Từ khóa Miền", - domainRgx: "Regex Miền", - ip: "CIDRs IP", - privateIp: "Dải IP Không hợp lệ", - port: "Cổng", - portRange: "Dải Cổng", - srcCidr: "CIDRs IP Nguồn", - srcPrivateIp: "IP Nguồn Không hợp lệ", - srcPort: "Cổng Nguồn", - srcPortRange: "Dải Cổng Nguồn", - ruleset: "Bộ quy tắc", - rulesetMatchSrc: "Bộ quy tắc IPcidr Phù hợp Nguồn", - options: "Tùy chọn Quy tắc", - domainRules: "Tên miền/IP", - srcIpRules: "IP Nguồn", - srcPortRules: "Cổng Nguồn", - }, - ruleset: { - add: "Thêm Bộ quy tắc", - format: "Định dạng Dữ liệu", - interval: "Khoảng cách Cập nhật", - remote: "Xa", - local: "Cục bộ", - }, - basic: { - log: { - title: "Nhật ký", - level: "Mức độ", - output: "Đầu ra", - timestamp: "Bật Dấu thời gian", - }, - dns: { - final: "Cuối cùng", - server: "Máy chủ", - firstServer: "Máy chủ Đầu tiên", - addrResolver: "Trình phân giải địa chỉ", - }, - routing: { - title: "Định tuyến", - defaultOut: "Ra ngoài Mặc định", - defaultIf: "NIC Mặc định", - defaultRm: "Đánh dấu Định tuyến Mặc định", - autoBind: "Tự động Ràng buộc NIC", - }, - exp: { - storeFakeIp: "Lưu IP Giả mạo", - }, - }, - tls : { - enable: "Kích hoạt TLS", - usePath: "Sử dụng đường dẫn", - useText: "Sử dụng văn bản", - certPath: "Đường dẫn tệp chứng chỉ", - keyPath: "Đường dẫn tệp khóa", - cert: "Chứng chỉ", - key: "Khóa", - options: "Tùy chọn TLS", - minVer: "Phiên bản Tối thiểu", - maxVer: "Phiên bản Tối đa", - cs: "Các bộ mã hóa", - privKey: "Khóa riêng", - pubKey: "Khóa Công khai", - disableSni: "Tắt SNI", - insecure: "Cho phép Không an toàn", - acme: { - options: "Tùy chọn ACME", - dataDir: "Thư mục Dữ liệu", - defaultDomain: "Tên miền Mặc định", - disableChallenges: "Vô hiệu hóa Thách thức", - httpChallenge: "Vô hiệu hóa Thách thức HTTP", - tlsChallenge: "Vô hiệu hóa Thách thức TLS", - altPorts: "Cổng Thay thế", - altHport: "Cổng HTTP Thay thế", - altTport: "Cổng TLS Thay thế", - caProvider: "Nhà cung cấp CA", - customCa: "Nhà cung cấp CA Tùy chỉnh", - extAcc: "Tài khoản Bên ngoài", - dns01: "Thách thức DNS01", - dns01Provider: "Nhà cung cấp Thách thức DNS01" - }, - }, - stats: { - upload: "Tải lên", - download: "Tải xuống", - volume: "Thể tích", - usage: "Sử dụng", - enable: "Kích hoạt thống kê", - graphTitle: "Biểu đồ lưu lượng", - B: "B", - KB: "KB", - MB: "MB", - GB: "GB", - TB: "TB", - PB: "PB", - p: "ph", - Kp: "Kph", - Mp: "Mph", - Gb: "Gb", - bps: "bps", - Kbps: "Kbps", - Mbps: "Mbps", - }, - date: { - expiry: "Hết hạn", - expired: "Đã hết hạn", - d: "ng", - h: "g", - m: "p", - s: "s", - ms: "ms", - }, -} diff --git a/frontend/src/locales/zhcn.ts b/frontend/src/locales/zhcn.ts deleted file mode 100644 index cae5af8..0000000 --- a/frontend/src/locales/zhcn.ts +++ /dev/null @@ -1,423 +0,0 @@ -export default { - message: "欢迎", - success: "成功", - failed: "失败", - enable: "启用", - disable: "禁用", - none: "无", - all: "全部", - filter: "过滤器", - loading: "加载中...", - confirm: "是否确定?", - yes: "确认", - no: "取消", - unlimited: "无限", - remained: "剩余", - type: "类型", - protocol: "协议", - submit: "提交", - reset: "重置", - now: "当前", - network: "网络", - copyToClipboard: "复制到剪贴板", - noData: "无数据!", - invalidLogin: "登录无效!", - online: "在线", - version: "版本", - email: "电子邮件", - commaSeparated: "(逗号分隔)", - count: "计数", - template: "模板", - error: { - dplData: "重复数据", - core: "Sing-Box 错误", - }, - pages: { - login: "登录", - home: "主页", - inbounds: "入站管理", - outbounds: "出站管理", - clients: "用户管理", - rules: "路由列表", - tls: "TLS 设置", - basics: "基础信息", - admins: "管理员", - settings: "设置", - }, - main: { - tiles: "信息卡", - gauges: "仪表板", - charts: "图表", - infos: "信息", - gauge: { - cpu: "CPU 仪表", - mem: "RAM 仪表", - }, - chart: { - cpu: "CPU 监视器", - mem: "RAM 监视器", - net: "网络带宽", - pnet: "网络数据包", - }, - info: { - sys: "系统信息", - sbd: "运行信息", - host: "主机", - cpu: "CPU", - core: "核心", - uptime: "运行时间", - threads: "线程", - memory: "内存", - running: "运行状态" - } - }, - objects: { - inbound: "入站", - client: "客户端", - outbound: "出站", - rule: "规则", - user: "用户", - tag: "标签", - listen: "监听", - dial: "拨号", - tls: "TLS", - multiplex: "多路复用", - transport: "传输", - method: "方法", - headers: "标头", - key: "键", - value: "值", - }, - actions: { - action: "操作", - add: "添加", - new: "新建", - edit: "编辑", - del: "删除", - clone: "克隆", - save: "保存", - update: "更新", - submit: "提交", - set: "设置", - generate: "生成", - disable: "禁用", - close: "关闭", - restartApp: "重启面板", - restartSb: "重启 Singbox", - }, - login: { - title: "登录", - username: "用户名", - unRules: "用户名不能为空", - password: "密码", - pwRules: "密码不能为空", - }, - menu: { - logout: "退出登录", - }, - admin: { - changeCred: "更改凭据", - oldPass: "当前密码", - newUname: "新用户名", - newPass: "新密码", - lastLogin: "上次登录", - date: "日期", - time: "时间", - changes: "更改", - actor: "执行者", - key: "键", - action: "操作", - }, - setting: { - interface: "界面", - sub: "订阅", - addr: "地址", - port: "端口", - webPath: "面板路径", - domain: "域名", - sslKey: "SSL 密钥 (Key) 路径", - sslCert: "SSL 证书 (cert) 路径", - webUri: "面板 URI", - sessionAge: "会话超时时限", - trafficAge: "流量过期时限", - timeLoc: "时区", - subEncode: "启用 Base64 编码", - subInfo: "启用用户信息", - path: "默认路径", - update: "自动更新时间", - subUri: "订阅 URI", - jsonSub: "JSON 订阅", - toDirect: "路由到直连", - toBlock: "路由到阻止", - timestamp: "时间戳", - globalDns: "全局 DNS", - directDns: "直连 DNS", - toDirectDns: "路由到直连 DNS", - jsonSubOptions: "其他选项", - excludePkg: "排除包", - }, - client: { - name: "名称", - desc: "描述", - group: "组", - inboundTags: "入站标签", - basics: "基础", - config: "配置", - links: "链接", - external: "外部链接", - sub: "外部订阅", - }, - bulk: { - add: "批量添加", - order: "排序", - random: "随机", - }, - types: { - un: "用户名", - pw: "密码", - direct: { - overrideAddr: "覆盖地址", - overridePort: "覆盖端口", - }, - hy: { - obfs: "混淆密码", - auth: "认证密码", - hyOptions: "Hysteria 选项", - hy2Options: "Hysteria2 选项", - ignoreBw: "忽略客户端带宽", - }, - shdwTls: { - hs: "握手服务器", - addHS: "添加握手服务器", - }, - ssh: { - passphrase: "密码短语", - hostKey: "主机密钥", - algorithm: "密钥算法", - clientVer: "客户端版本", - options: "SSH 选项", - }, - tor: { - execPath: "可执行文件路径", - dataDir: "数据目录", - extArgs: "额外参数", - }, - tuic: { - congControl: "拥塞控制", - authTimeout: "认证超时", - hb: "心跳包", - }, - tun: { - addr: "地址", - ifName: "接口名称", - }, - vless: { - flow: "流控", - udpEnc: "UDP 数据包编码", - }, - vmess: { - security: "安全性", - globalPadding: "全局填充", - authLen: "加密长度", - }, - wg: { - privKey: "私钥", - pubKey: "对等方公钥", - psk: "预共享密钥", - localIp: "本地 IP 地址", - worker: "工作线程", - ifName: "接口名称", - sysIf: "系统接口", - gso: "分段卸载", - options: "WireGuard 选项", - multiPeer: "多对等体", - allowedIp: "允许的 IP 地址", - peer: "对等体", - peers: "对等体", - }, - lb: { - defaultOut: "默认出站", - interruptConn: "中断现有连接", - testUrl: "测试 URL", - interval: "间隔", - tolerance: "容错", - urlTestOptions: "URL 测试选项", - } - }, - in: { - addr: "地址", - port: "端口", - sniffing: "嗅探", - clients: "启用客户端", - ssMethod: "方法", - sSide: "服务器端", - cSide: "客户端", - multiDomain: "多域名", - remark: "备注", - mdOption: "多域名选项", - }, - listen: { - sniffing: "嗅探", - sniffingTimeout: "嗅探超时", - sniffingOverride: "覆盖目标地址", - options: "监听选项", - tcpOptions: "TCP 选项", - udpOptions: "UDP 选项", - detour: "转发", - detourText: "转发到入站", - domainStrategy: "域名解析策略", - }, - dial: { - bindIf: "绑定到网络接口", - bindIp4: "绑定到 IPv4", - bindIp6: "绑定到 IPv6", - reuseAddr: "重用监听地址", - connTimeout: "连接超时", - fbTimeout: "回退超时", - options: "拨号选项", - detourText: "转发至出站", - }, - transport: { - enable: "启用传输", - host: "主机域名", - hosts: "主机域名列表", - path: "HTTP 请求路径", - httpMethod: "HTTP 请求方法", - idleTimeout: "空闲超时", - pingTimeout: "Ping 超时", - grpcServiceName: "gRPC 服务名称", - grpcPws: "允许无流时保持连接", - }, - mux: { - enable: "启用多路复用", - maxConn: "最大连接数", - minStr: "最小流数", - maxStr: "最大流数", - padding: "仅允许填充连接", - enableBrutal: "启用 TCP Brutal", - }, - out: { - addr: "服务器地址", - port: "服务器端口", - }, - rule: { - add: "添加规则", - simple: "简单", - logical: "逻辑", - mode: "模式", - invert: "反选结果", - ipVer: "IP 版本", - domain: "域名", - domainSufix: "域名后缀", - domainKw: "域名关键词", - domainRgx: "域名正则表达式", - ip: "IP CIDR", - privateIp: "匹配非公开 IP", - port: "端口", - portRange: "端口范围", - srcCidr: "源 IP CIDR", - srcPrivateIp: "匹配非公开源 IP", - srcPort: "源端口", - srcPortRange: "源端口范围", - ruleset: "规则集", - rulesetMatchSrc: "规则集 IP CIDR 匹配源 IP", - options: "规则选项", - domainRules: "域名/IP", - srcIpRules: "源 IP", - srcPortRules: "源端口", - }, - ruleset: { - add: "添加规则集", - format: "数据格式", - interval: "更新间隔", - remote: "远程", - local: "本地", - }, - basic: { - log: { - title: "日志", - level: "级别", - output: "输出", - timestamp: "启用时间戳", - }, - dns: { - final: "最终", - server: "服务器", - firstServer: "首选服务器", - addrResolver: "地址解析器", - }, - routing: { - title: "路由", - defaultOut: "默认出站", - defaultIf: "默认网卡", - defaultRm: "默认路由标记", - autoBind: "自动绑定网卡", - }, - exp: { - storeFakeIp: "持久化 Fake-IP", - }, - }, - tls : { - enable: "启用 TLS", - usePath: "使用外部路径", - useText: "使用文件内容", - certPath: "证书文件路径", - keyPath: "私钥文件路径", - cert: "证书文件内容", - key: "私钥文件内容", - options: "TLS 选项", - minVer: "最低版本", - maxVer: "最高版本", - cs: "密码套件", - privKey: "私钥", - pubKey: "公钥", - disableSni: "禁用 SNI", - insecure: "允许不安全", - acme: { - options: "ACME 选项", - dataDir: "数据目录", - defaultDomain: "默认域名", - disableChallenges: "禁用挑战", - httpChallenge: "禁用 HTTP 挑战", - tlsChallenge: "禁用 TLS 挑战", - altPorts: "替代端口", - altHport: "替代 HTTP 端口", - altTport: "替代 TLS 端口", - caProvider: "CA 提供商", - customCa: "自定义 CA 提供商", - extAcc: "外部账户", - dns01: "DNS01 挑战", - dns01Provider: "DNS01 挑战提供商" - }, - }, - stats: { - upload: "上传", - download: "下载", - volume: "流量", - usage: "已用", - enable: "启用统计", - graphTitle: "流量图表", - B: "B", - KB: "KB", - MB: "MB", - GB: "GB", - TB: "TB", - PB: "PB", - p: "p", - Kp: "Kp", - Mp: "Mp", - Gb: "Gb", - bps: "bps", - Kbps: "Kbps", - Mbps: "Mbps", - }, - date: { - expiry: "到期", - expired: "已到期", - d: "天", - h: "时", - m: "分", - s: "秒", - ms: "毫秒", - }, -} diff --git a/frontend/src/locales/zhtw.ts b/frontend/src/locales/zhtw.ts deleted file mode 100644 index 0fa8b7b..0000000 --- a/frontend/src/locales/zhtw.ts +++ /dev/null @@ -1,424 +0,0 @@ -export default { - open: "打開", - message: "歡迎", - success: "成功", - failed: "失敗", - enable: "啟用", - disable: "禁用", - none: "無", - all: "全部", - filter: "過濾器", - loading: "加載中...", - confirm: "是否確定?", - yes: "確認", - no: "取消", - unlimited: "無限", - remained: "剩余", - type: "類型", - protocol: "協定", - submit: "提交", - reset: "重置", - now: "當前", - network: "網絡", - copyToClipboard: "復製到剪貼板", - noData: "無數據!", - invalidLogin: "登錄無效!", - online: "在線", - version: "版本", - email: "電子郵件", - commaSeparated: "(逗號分隔)", - count: "計數", - template: "模板", - error: { - dplData: "重複數據", - core: "Sing-Box 錯誤", - }, - pages: { - login: "登錄", - home: "主頁", - inbounds: "入站管理", - outbounds: "出站管理", - clients: "用戶管理", - rules: "路由列表", - tls: "TLS 設置", - basics: "基礎信息", - admins: "管理員", - settings: "設置", - }, - main: { - tiles: "信息卡", - gauges: "儀表板", - charts: "圖表", - infos: "信息", - gauge: { - cpu: "CPU 儀表", - mem: "RAM 儀表", - }, - chart: { - cpu: "CPU 監視器", - mem: "RAM 監視器", - net: "網絡帶寬", - pnet: "網絡數據包", - }, - info: { - sys: "系統信息", - sbd: "運行信息", - host: "主機", - cpu: "CPU", - core: "核心", - uptime: "運行時間", - threads: "線程", - memory: "內存", - running: "運行狀態" - } - }, - objects: { - inbound: "入站", - client: "客戶端", - outbound: "出站", - rule: "規則", - user: "用戶", - tag: "標簽", - listen: "聽", - dial: "撥號", - tls: "TLS", - multiplex: "多路復用", - transport: "傳輸", - method: "方法", - headers: "方法", - key: "鑰匙", - value: "價值", - }, - actions: { - action: "操作", - add: "添加", - new: "新建", - edit: "編輯", - del: "刪除", - clone: "克隆", - save: "保存", - update: "更新", - submit: "提交", - set: "設置", - generate: "生成", - disable: "禁用", - close: "關閉", - restartApp: "重啟面板", - restartSb: "重啟 Singbox", - }, - login: { - title: "登錄", - username: "用戶名", - unRules: "用戶名不能為空", - password: "密碼", - pwRules: "密碼不能為空", - }, - menu: { - logout: "退出登錄", - }, - admin: { - changeCred: "更改憑據", - oldPass: "當前密碼", - newUname: "新用戶名", - newPass: "新密碼", - lastLogin: "上次登入", - date: "日期", - time: "時間", - changes: "更改", - actor: "執行者", - key: "鍵", - action: "操作", - }, - setting: { - interface: "界面", - sub: "訂閱", - addr: "地址", - port: "端口", - webPath: "基本 URI", - domain: "域名", - sslKey: "SSL 密鑰 (Key) 路徑", - sslCert: "SSL 證書 (cert) 路徑", - webUri: "面板 URI", - sessionAge: "會話最大連接數", - trafficAge: "流量最大年齡", - timeLoc: "時區", - subEncode: "啟用編碼", - subInfo: "啟用用戶信息", - path: "默認路徑", - update: "自動更新時間", - subUri: "訂閱 URL", - jsonSub: "JSON 訂閱", - toDirect: "路由到直連", - toBlock: "路由到阻止", - timestamp: "時間戳", - globalDns: "全局 DNS", - directDns: "直連 DNS", - toDirectDns: "路由到直連 DNS", - jsonSubOptions: "其他選項", - excludePkg: "排除包", - }, - client: { - name: "名稱", - desc: "描述", - group: "組", - inboundTags: "入站標簽", - basics: "基礎", - config: "配置", - links: "鏈接", - external: "外部鏈接", - sub: "外部訂閱", - }, - bulk: { - add: "批量添加", - order: "排序", - random: "隨機", - }, - types: { - un: "用戶名", - pw: "密碼", - direct: { - overrideAddr: "覆蓋地址", - overridePort: "覆蓋端口", - }, - hy: { - obfs: "混淆密碼", - auth: "驗證密碼", - hyOptions: "Hysteria 選項", - hy2Options: "Hysteria2 選項", - ignoreBw: "忽略客戶端帶寬", - }, - shdwTls: { - hs: "握手服務器", - addHS: "添加握手服務器", - }, - ssh: { - passphrase: "密語", - hostKey: "主機密鑰", - algorithm: "密鑰算法", - clientVer: "客戶端版本", - options: "SSH 選項", - }, - tor: { - execPath: "可執行文件路徑", - dataDir: "數據目錄", - extArgs: "額外參數", - }, - tuic: { - congControl: "擁塞控制", - authTimeout: "身份驗證超時", - hb: "心跳", - }, - tun: { - addr: "地址", - ifName: "介面名稱", - }, - vless: { - flow: "流量", - udpEnc: "UDP 封包編碼", - }, - vmess: { - security: "安全性", - globalPadding: "全局填充", - authLen: "加密長度", - }, - wg: { - privKey: "私鑰", - pubKey: "對等方公鑰", - psk: "預共享密鑰", - localIp: "本地 IP", - worker: "工作線程", - ifName: "介面名稱", - sysIf: "系統介面", - gso: "分段卸載", - options: "Wireguard 選項", - multiPeer: "多對等方", - allowedIp: "允許的 IP", - peer: "對等方", - peers: "對等方", - }, - lb: { - defaultOut: "默認外部", - interruptConn: "中斷現有連接", - testUrl: "測試 URL", - interval: "間隔", - tolerance: "容忍度", - urlTestOptions: "URL 測試選項" - } - }, - in: { - addr: "地址", - port: "端口", - sniffing: "嗅探", - clients: "啟用客戶端", - ssMethod: "方法", - sSide: "服務器端", - cSide: "客戶端", - multiDomain: "多域名", - remark: "備註", - mdOption: "多域名選項", - }, - listen: { - sniffing: "嗅探", - sniffingTimeout: "嗅探超時", - sniffingOverride: "覆蓋目的地", - options: "監聽選項", - tcpOptions: "TCP 選項", - udpOptions: "UDP 選項", - detour: "繞道", - detourText: "轉發到入站", - domainStrategy: "域名策略", - }, - dial: { - bindIf: "綁定到網路接口", - bindIp4: "綁定到 IPv4", - bindIp6: "綁定到 IPv6", - reuseAddr: "重用監聽地址", - connTimeout: "連接超時", - fbTimeout: "回退超時", - options: "撥號選項", - detourText: "轉寄至出站", - }, - transport: { - enable: "啟用傳輸", - host: "主機", - hosts: "主機列表", - path: "路徑", - httpMethod: "請求方法", - idleTimeout: "閒置超時", - pingTimeout: "Ping 超時", - grpcServiceName: "服務名稱", - grpcPws: "允許無流", - }, - mux: { - enable: "啟用多路徑", - maxConn: "最大連接數", - minStr: "最小串流數", - maxStr: "最大串流數", - padding: "僅填充", - enableBrutal: "啟用暴力", - }, - out: { - addr: "伺服器地址", - port: "伺服器端口", - }, - rule: { - add: "添加規則", - simple: "簡單", - logical: "邏輯", - mode: "模式", - invert: "反轉", - ipVer: "IP 版本", - domain: "域名", - domainSufix: "域名後綴", - domainKw: "域名關鍵詞", - domainRgx: "域名正則表達式", - ip: "IP CIDR", - privateIp: "無效 IP 範圍", - port: "端口", - portRange: "端口範圍", - srcCidr: "源 IP CIDR", - srcPrivateIp: "無效源 IP", - srcPort: "源端口", - srcPortRange: "源端口範圍", - ruleset: "規則集", - rulesetMatchSrc: "規則集 IP 範圍匹配源", - options: "規則選項", - domainRules: "域名/IP", - srcIpRules: "源 IP", - srcPortRules: "源端口", - }, - ruleset: { - add: "添加規則集", - format: "數據格式", - interval: "更新間隔", - remote: "遠端", - local: "本地", - }, - basic: { - log: { - title: "日誌", - level: "級別", - output: "輸出", - timestamp: "啟用時間戳記", - }, - dns: { - final: "最終", - server: "服務器", - firstServer: "首選服務器", - addrResolver: "地址解析器", - }, - routing: { - title: "路由", - defaultOut: "默認外部", - defaultIf: "默認網卡", - defaultRm: "默認路由標記", - autoBind: "自動綁定網卡", - }, - exp: { - storeFakeIp: "存儲假 IP", - }, - }, - tls : { - enable: "啟用 TLS", - usePath: "使用外部路徑", - useText: "使用文件內容", - certPath: "證書文件路徑", - keyPath: "私鑰文件路徑", - cert: "證書文件內容", - key: "私鑰文件內容", - options: "TLS 選項", - minVer: "最低版本", - maxVer: "最高版本", - cs: "加密套件", - privKey: "私鑰", - pubKey: "公鑰", - disableSni: "停用 SNI", - insecure: "允許不安全連線", - acme: { - options: "ACME 選項", - dataDir: "數據目錄", - defaultDomain: "默認域名", - disableChallenges: "禁用挑戰", - httpChallenge: "禁用 HTTP 挑戰", - tlsChallenge: "禁用 TLS 挑戰", - altPorts: "替代端口", - altHport: "替代 HTTP 端口", - altTport: "替代 TLS 端口", - caProvider: "CA 提供商", - customCa: "自定義 CA 提供商", - extAcc: "外部賬戶", - dns01: "DNS01 挑戰", - dns01Provider: "DNS01 挑戰提供商" - }, - }, - stats: { - upload: "上傳", - download: "下載", - volume: "流量", - usage: "已用", - enable: "啟用統計", - graphTitle: "流量圖表", - B: "B", - KB: "KB", - MB: "MB", - GB: "GB", - TB: "TB", - PB: "PB", - p: "p", - Kp: "Kp", - Mp: "Mp", - Gb: "Gb", - bps: "bps", - Kbps: "Kbps", - Mbps: "Mbps", - }, - date: { - expiry: "到期", - expired: "已到期", - d: "天", - h: "時", - m: "分", - s: "秒", - ms: "毫秒", - }, -} diff --git a/frontend/src/main.ts b/frontend/src/main.ts deleted file mode 100644 index 9af2edb..0000000 --- a/frontend/src/main.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * main.ts - * - * Bootstraps Vuetify and other plugins then mounts the App` - */ - -// Composables -import { createApp, ref } from 'vue' - -// Components -import App from './App.vue' - -// Use router -import router from './router' - -// Store -import store from './store' - -// Plugins -import { registerPlugins } from '@/plugins' - -// Locale -import { i18n } from '@/locales' -import Vue3PersianDatetimePicker from 'vue3-persian-datetime-picker' - -// Notivue -import { createNotivue } from 'notivue' -import 'notivue/notification.css' -import 'notivue/animations.css' -const notivue = createNotivue({ - position: 'bottom-center', - limit: 4, - enqueue: false, - avoidDuplicates: true, - notifications: { - global: { - duration: 3000 - } - }, -}) - -const loading = ref(false) - -const app = createApp(App) -app.provide('loading', loading) - -registerPlugins(app) - -app - .use(router) - .use(store) - .use(i18n) - .use(notivue) - .component('DatePicker', Vue3PersianDatetimePicker) - .mount('#app') diff --git a/frontend/src/plugins/api.ts b/frontend/src/plugins/api.ts deleted file mode 100644 index 8e562b3..0000000 --- a/frontend/src/plugins/api.ts +++ /dev/null @@ -1,57 +0,0 @@ -import axios from 'axios' - -axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' -axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' - -axios.defaults.baseURL = "./" -const pendingRequests = new Map() - -axios.interceptors.request.use( - (config) => { - // Generate a unique key for the request - const requestKey = `${config.method}:${config.url}` - - // Check if there is already a pending request with the same key - if (pendingRequests.has(requestKey)) { - const cancelSource = pendingRequests.get(requestKey) - cancelSource.cancel('Duplicate request cancelled') - } - - // Create a new cancel token for the request - const cancelSource = axios.CancelToken.source() - config.cancelToken = cancelSource.token - - // Store the cancel token in the pending requests map - pendingRequests.set(requestKey, cancelSource) - - if (config.data instanceof FormData) { - config.headers['Content-Type'] = 'multipart/form-data' - } - return config - }, - (error) => Promise.reject(error), -) - -axios.interceptors.response.use( - (response) => { - // Remove the request from the pending requests map - const requestKey = `${response.config.method}:${response.config.url}` - pendingRequests.delete(requestKey) - return response - }, - (error) => { - if (axios.isCancel(error)) { - // Handle duplicate request cancellation here if needed - console.warn(error.message) - } else { - // Remove the request from the pending requests map on error - const requestKey = `${error.config.method}:${error.config.url}` - pendingRequests.delete(requestKey) - } - return Promise.reject(error) - } -) - -const api = axios.create() - -export default api diff --git a/frontend/src/plugins/httputil.ts b/frontend/src/plugins/httputil.ts deleted file mode 100644 index a823c1c..0000000 --- a/frontend/src/plugins/httputil.ts +++ /dev/null @@ -1,88 +0,0 @@ -import api from './api' -import { i18n } from '@/locales' -import router from '@/router' -import { push } from 'notivue' - -export interface Msg { - success: boolean - msg: string - obj: any | null -} - -function _handleMsg(msg: any): void { - if (!isMsg(msg)) { - return - } - if(msg.msg){ - if (!msg.success && msg.msg == "Invalid login") { - push.error({ - title: i18n.global.t('invalidLogin'), - }) - logout() - return - } - if (msg.success) { - push.success({ - message: i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg), - }) - } else { - push.error({ - title: i18n.global.t('failed'), - message: msg.msg - }) - } - } -} - -export const logout = async () => { - const response = await HttpUtils.get('api/logout') - if(response.success){ - router.push('/login') - } -} - -function _respToMsg(resp: any): Msg { - const data = resp.data - if (data == null) { - return { success: true, msg: "", obj: null } - } else if (isMsg(data)) { - if (data.hasOwnProperty('success')) { - return { success: data.success, msg: data.msg, obj: data.obj || null } - } else { - return data - } - } else { - return { success: false, msg: `unknown data: ${data}`, obj: null } - } -} - -function isMsg(obj: any): obj is Msg { - return Object.hasOwn(obj,'success') && Object.hasOwn(obj,'msg') && Object.hasOwn(obj, 'obj') -} - -const HttpUtils = { - async get(url: string, data: object = {}, options: any[] = []): Promise { - let msg: Msg - try { - const resp = await api.get(url, { params: data, ...options }) - msg = _respToMsg(resp) - } catch (e: any) { - msg = { success: false, msg: e.toString(), obj: null } - } - _handleMsg(msg) - return msg - }, - async post(url: string, data: object | null, options: any = undefined): Promise { - let msg: Msg - try { - const resp = await api.post(url, data, options) - msg = _respToMsg(resp) - } catch (e: any) { - msg = { success: false, msg: e.toString(), obj: null } - } - _handleMsg(msg) - return msg - }, -} - -export default HttpUtils \ No newline at end of file diff --git a/frontend/src/plugins/inData.ts b/frontend/src/plugins/inData.ts deleted file mode 100644 index 6d2e584..0000000 --- a/frontend/src/plugins/inData.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Addr { - server: string - server_port: number - tls?: boolean - insecure?: boolean - server_name?: string - remark?: string -} - -export interface InData { - id: number - tag: string - addrs: Addr[] - outJson: any -} diff --git a/frontend/src/plugins/index.ts b/frontend/src/plugins/index.ts deleted file mode 100644 index 9d76810..0000000 --- a/frontend/src/plugins/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Plugins -import vuetify from './vuetify' - -// Types -import type { App } from 'vue' - -export function registerPlugins (app: App) { - app - .use(vuetify) -} diff --git a/frontend/src/plugins/link.ts b/frontend/src/plugins/link.ts deleted file mode 100644 index c5f9236..0000000 --- a/frontend/src/plugins/link.ts +++ /dev/null @@ -1,465 +0,0 @@ -import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds" -import { HTTP, WebSocket, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport" -import RandomUtil from "./randomUtil" -import { Client } from "@/types/clients" - -export interface Link { - type: "local" | "external" | "sub" - remark?: string - uri: string -} - -function utf8ToBase64(utf8String: string): string { - const encodedUtf8 = encodeURIComponent(utf8String).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16))) - return btoa(encodedUtf8) -} - -export namespace LinkUtil { - export function linkGenerator(user: Client, inbound: Inbound, tlsClient: any = {}, addrs: any[] = []): string[] { - switch(inbound.type){ - case InTypes.Shadowsocks: - return shadowsocksLink(user,inbound, addrs) - case InTypes.Naive: - return naiveLink(user,inbound, addrs, tlsClient) - case InTypes.Hysteria: - return hysteriaLink(user,inbound, addrs, tlsClient) - case InTypes.Hysteria2: - return hysteria2Link(user,inbound, addrs, tlsClient) - case InTypes.TUIC: - return tuicLink(user,inbound, addrs, tlsClient) - case InTypes.VLESS: - return vlessLink(user,inbound, addrs, tlsClient) - case InTypes.Trojan: - return trojanLink(user,inbound, addrs, tlsClient) - case InTypes.VMess: - return vmessLink(user,inbound, addrs, tlsClient) - } - return [] - } - - function shadowsocksLink(user: Client, inbound: Shadowsocks, addrs: any[]): string[] { - const userPass = inbound.method == "2022-blake3-aes-128-gcm" ? user.config.shadowsocks16?.password : user.config.shadowsocks?.password - const password = [userPass] - if (inbound.method.startsWith('2022')) password.push(inbound.password) - const params = { - tfo: inbound.tcp_fast_open? 1 : null, - network: inbound.network?? null - } - - let links = [] - if (addrs.length == 0) { - const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function hysteriaLink(user: Client, inbound: Hysteria, addrs: any[], tlsClient: any): string[] { - const auth = user.config.hysteria.auth_str - const params = { - upmbps: inbound.up_mbps?? null, - downmbps: inbound.down_mbps?? null, - auth: auth?? null, - peer: inbound.tls.server_name?? null, - alpn: inbound.tls.alpn?.join(',')?? null, - obfsParam: inbound.obfs?? null, - fastopen: inbound.tcp_fast_open? 1 : 0, - insecure: tlsClient?.insecure ? 1 : null - } - - let links = [] - if (addrs.length == 0) { - const uri = new URL(`hysteria://${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`hysteria://${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - if (a.server_name?.length>0) { - uri.searchParams.set('peer', a.server_name) - } else { - inbound.tls.server_name ? uri.searchParams.set('peer', inbound.tls.server_name) : uri.searchParams.delete('peer') - } - if (a.insecure) { - uri.searchParams.set('insecure', '1') - } else { - tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure') - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function hysteria2Link(user: Client, inbound: Hysteria2, addrs: any[], tlsClient: any): string[] { - const password = user.config.hysteria2.password - const params = { - upmbps: inbound.up_mbps?? null, - downmbps: inbound.down_mbps?? null, - sni: inbound.tls.server_name?? null, - alpn: inbound.tls.alpn?.join(',')?? null, - obfs: inbound.obfs?.type?? null, - 'obfs-password': inbound.obfs?.password?? null, - fastopen: inbound.tcp_fast_open? 1 : 0, - insecure: tlsClient?.insecure ? 1 : null - } - - let links = [] - if (addrs.length == 0) { - const uri = new URL(`hysteria2://${password}@${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`hysteria2://${password}@${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - if (a.server_name?.length>0) { - uri.searchParams.set('sni', a.server_name) - } else { - inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni') - } - if (a.insecure) { - uri.searchParams.set('insecure', '1') - } else { - tlsClient.insecure ? uri.searchParams.set('insecure', '1') : uri.searchParams.delete('insecure') - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function naiveLink(user: Client, inbound: Naive, addrs: any[], tlsClient: any): string[] { - const password = user.config.naive.password - - let links = [] - if (addrs.length == 0) { - const params = { - padding: 1, - peer: inbound.tls.server_name?? null, - alpn: inbound.tls.alpn?.join(',')?? null, - tfo: inbound.tcp_fast_open? 1 : 0, - allowInsecure: tlsClient?.insecure ? 1 : null - } - const uri = `http2://${utf8ToBase64(user.name + ":" + password + "@" + location.hostname + ":" + inbound.listen_port)}` - const paramsArray = [] - for (const [key, value] of Object.entries(params)){ - if (value) { - paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`) - } - } - links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag) - } else { - addrs.forEach(a => { - const params = { - padding: 1, - peer: a.server_name?.length>0 ? a.server_name : inbound.tls.server_name?? null, - alpn: inbound.tls.alpn?.join(',')?? null, - tfo: inbound.tcp_fast_open? 1 : 0, - allowInsecure: a.insecure ? 1 : tlsClient?.insecure ? 1 : null - } - const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + a.server + ":" + a.server_port)}` - const paramsArray = [] - for (const [key, value] of Object.entries(params)){ - if (value) { - paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`) - } - } - links.push(uri.toString() + "?" + paramsArray.join('&') + "#" + encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag)) - }) - } - return links - } - - function tuicLink(user: Client, inbound: TUIC, addrs: any[], tlsClient: any): string[] { - const u = user.config.tuic - const params = { - sni: inbound.tls.server_name?? null, - alpn: inbound.tls.alpn?.join(',')?? null, - congestion_control: inbound.congestion_control?? null, - allowInsecure: tlsClient?.insecure ? 1 : null, - disable_sni: tlsClient?.disable_sni ? 1 : null - } - - let links = [] - if (addrs.length == 0) { - const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries(params)){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - if (a.server_name?.length>0) { - uri.searchParams.set('sni', a.server_name) - } else { - inbound.tls.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni') - } - if (a.insecure) { - uri.searchParams.set('allowInsecure', '1') - } else { - tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure') - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function getTransportParams(t:Transport): any { - if (Object.keys(t).length == 0) return {} - - const params = { - host: '', - path: '', - serviceName: '', - } - switch (t.type){ - case TrspTypes.HTTP: - const th = t - params.host = th.host?.join(',')?? null - params.path = th.path?? null - break - case TrspTypes.WebSocket: - const tw = t - params.path = tw.path?? null - params.host = tw.headers?.Host?? null - break - case TrspTypes.gRPC: - const tg = t - params.serviceName = tg.service_name?? null - break - case TrspTypes.HTTPUpgrade: - const tu = t - params.host = tu.host?? null - params.path = tu.path?? null - break - } - - return params - } - - function vlessLink(user: Client, inbound: VLESS, addrs: any[], tlsClient: any): string[] { - const u = user.config.vless - const transport = inbound.transport - - const tParams = getTransportParams(transport) - - const params = { - type: transport?.type?? 'tcp', - security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null, - alpn: inbound.tls?.alpn?.join(',')?? null, - sni: inbound.tls?.server_name?? null, - flow: inbound.tls?.enabled ? u?.flow?? null : null, - allowInsecure: tlsClient?.insecure ? 1 : null, - fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null, - pbk: tlsClient?.reality?.public_key?? null, - sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null - } - let links = [] - if (addrs.length == 0) { - const uri = new URL(`vless://${u?.uuid}@${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries({...params, ...tParams})){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`vless://${u?.uuid}@${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries({...params, ...tParams})){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - if (a.tls != undefined){ - if (a.tls) { - uri.searchParams.set('security','tls') - } else { - uri.searchParams.delete('security') - uri.searchParams.delete('sni') - uri.searchParams.delete('alpn') - uri.searchParams.delete('allowInsecure') - } - } - if (a.server_name?.length>0) { - uri.searchParams.set('sni', a.server_name) - } else { - inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni') - } - if (a.insecure) { - uri.searchParams.set('allowInsecure', '1') - } else { - tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure') - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function trojanLink(user: Client, inbound: Trojan, addrs: any[], tlsClient: any): string[] { - const u = user.config.trojan - const transport = inbound.transport - - const tParams = getTransportParams(transport) - - const params = { - type: transport?.type?? 'tcp', - security: inbound.tls?.enabled? inbound.tls?.reality?.enabled ? 'reality' : 'tls' : null, - alpn: inbound.tls?.alpn?.join(',')?? null, - sni: inbound.tls?.server_name?? null, - allowInsecure: tlsClient?.insecure ? 1 : null, - fp: tlsClient?.utls?.enabled ? tlsClient.utls.fingerprint : null, - pbk: tlsClient?.reality?.public_key?? null, - sid: inbound.tls?.reality?.enabled ? (inbound.tls?.reality?.short_id?.length>0 ? inbound.tls.reality.short_id[RandomUtil.randomInt(inbound.tls.reality.short_id.length)] : null) : null - } - - let links = [] - if (addrs.length == 0) { - const uri = new URL(`trojan://${u?.password}@${location.hostname}:${inbound.listen_port}`) - for (const [key, value] of Object.entries({...params, ...tParams})){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - uri.hash = encodeURIComponent(inbound.tag) - links.push(uri.toString()) - } else { - addrs.forEach(a => { - const uri = new URL(`trojan://${u?.password}@${a.server}:${a.server_port}`) - for (const [key, value] of Object.entries({...params, ...tParams})){ - if (value) { - uri.searchParams.set(key, value.toString()) - } - } - if (a.tls != undefined){ - if (a.tls) { - uri.searchParams.set('security','tls') - } else { - uri.searchParams.delete('security') - uri.searchParams.delete('sni') - uri.searchParams.delete('alpn') - uri.searchParams.delete('allowInsecure') - } - } - if (a.server_name?.length>0) { - uri.searchParams.set('sni', a.server_name) - } else { - inbound.tls?.server_name ? uri.searchParams.set('sni', inbound.tls.server_name) : uri.searchParams.delete('sni') - } - if (a.insecure) { - uri.searchParams.set('allowInsecure', '1') - } else { - tlsClient.insecure ? uri.searchParams.set('allowInsecure', '1') : uri.searchParams.delete('allowInsecure') - } - uri.hash = encodeURIComponent(a.remark ? inbound.tag + a.remark : inbound.tag) - links.push(uri.toString()) - }) - } - return links - } - - function vmessLink(user: Client, inbound: VMess, addrs: any[], tlsClient: any): string[] { - const u = user.config.vmess - const transport = inbound.transport - - const tParams = getTransportParams(transport) - if (transport.type == TrspTypes.gRPC) tParams.path = tParams.serviceName - - const params = { - v: 2, - add: location.hostname, - aid: u?.alterId, - host: tParams.host?? undefined, - id: u?.uuid, - net: transport?.type == undefined || transport?.type == 'http' ? 'tcp' : transport.type, - type: transport?.type == 'http' ? 'http' : undefined, - path: tParams.path?? undefined, - port: inbound.listen_port, - ps: inbound.tag, - sni: inbound.tls.server_name?? undefined, - tls: Object.keys(inbound.tls).length>0? 'tls' : 'none', - allowInsecure: tlsClient?.insecure ? 1 : undefined - } - let links = [] - if (addrs.length == 0) { - links.push('vmess://' + utf8ToBase64(JSON.stringify(params, null, 2))) - } else { - addrs.forEach(a => { - let newParams = {...params} - newParams.add = a.server - newParams.port = a.server_port - if (a.tls != undefined){ - if (a.tls) { - newParams.tls = 'tls' - } else { - newParams.tls = 'none' - delete newParams.sni - delete newParams.allowInsecure - } - } - if (a.server_name?.length>0) { - newParams.sni = a.server_name - } - if (a.insecure) { - newParams.allowInsecure = 1 - } - newParams.ps = inbound.tag + (a.remark??'') - links.push('vmess://' + utf8ToBase64(JSON.stringify(newParams, null, 2))) - }) - } - return links - } -} \ No newline at end of file diff --git a/frontend/src/plugins/outJson.ts b/frontend/src/plugins/outJson.ts deleted file mode 100644 index 1bd29c9..0000000 --- a/frontend/src/plugins/outJson.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Hysteria, Hysteria2, Inbound, InTypes, Shadowsocks, Trojan, TUIC, VLESS, VMess, ShadowTLS } from "@/types/inbounds" -import { iTls } from "@/types/inTls" -import { oTls } from "@/types/outTls" -import RandomUtil from "./randomUtil" - -export function fillData(out: any, inbound: Inbound, tlsClient: any) { - if (Object.hasOwn(inbound, 'tls')) { - const inb = inbound - addTls(out,inb.tls,tlsClient) - } else { - delete out.tls - } - out.type = inbound.type - out.tag = inbound.tag - out.server = location.hostname - out.server_port = inbound.listen_port - switch(inbound.type){ - case InTypes.HTTP: case InTypes.SOCKS: case InTypes.Mixed: - return - case InTypes.Shadowsocks: - shadowsocksOut(out, inbound) - return - case InTypes.ShadowTLS: - shadowTlsOut(out, inbound) - return - case InTypes.Hysteria: - hysteriaOut(out, inbound) - return - case InTypes.Hysteria2: - hysteria2Out(out, inbound) - return - case InTypes.TUIC: - tuicOut(out, inbound) - return - case InTypes.VLESS: - vlessOut(out, inbound) - return - case InTypes.Trojan: - trojanOut(out, inbound) - return - case InTypes.VMess: - vmessOut(out, inbound) - return - } - Object.keys(out).forEach(key => delete out[key]) -} - -function addTls(out: any, tls: iTls, tlsClient: oTls){ - out.tls = tlsClient - if(tls.enabled) out.tls.enabled = tls.enabled - if(tls.server_name) out.tls.server_name = tls.server_name - if(tls.alpn) out.tls.alpn = tls.alpn - if(tls.min_version) out.tls.min_version = tls.min_version - if(tls.max_version) out.tls.max_version = tls.max_version - if(tls.cipher_suites) out.tls.cipher_suites = tls.cipher_suites - if(tls.reality?.enabled){ - out.tls.reality.enabled = true - out.tls.reality.short_id = tls.reality.short_id[RandomUtil.randomInt(tls.reality.short_id.length)] - } -} - -function shadowsocksOut(out: any, inbound: Shadowsocks) { - out.method = inbound.method -} - -function shadowTlsOut(out: any, inbound: ShadowTLS) { - if (inbound.version == 3) { - out.version = 3 - } else { - Object.keys(out).forEach(key => delete out[key]) - } - out.tls = { enabled: true } -} - -function hysteriaOut(out: any, inbound: Hysteria) { - out.up_mbps = inbound.down_mbps - out.down_mbps = inbound.up_mbps - out.obfs = inbound.obfs - out.recv_window_conn = inbound.recv_window_conn - out.disable_mtu_discovery = inbound.disable_mtu_discovery -} - -function hysteria2Out(out: any, inbound: Hysteria2) { - out.up_mbps = inbound.down_mbps - out.down_mbps = inbound.up_mbps - out.obfs = inbound.obfs -} - -function tuicOut(out: any, inbound: TUIC) { - out.congestion_control = inbound.congestion_control?? "cubic" - out.zero_rtt_handshake = inbound.zero_rtt_handshake - out.heartbeat = inbound.heartbeat -} - -function vlessOut(out: any, inbound: VLESS) { - out.transport = inbound.transport -} - -function trojanOut(out: any, inbound: Trojan) { - out.transport = inbound.transport -} - -function vmessOut(out: any, inbound: VMess) { - out.transport = inbound.transport -} diff --git a/frontend/src/plugins/randomUtil.ts b/frontend/src/plugins/randomUtil.ts deleted file mode 100644 index f9da01f..0000000 --- a/frontend/src/plugins/randomUtil.ts +++ /dev/null @@ -1,49 +0,0 @@ -const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') - -const RandomUtil = { - randomIntRange(min: number, max: number): number { - return parseInt((Math.random() * (max - min) + min).toString(), 10) - }, - randomInt(n: number) { - return this.randomIntRange(0, n) - }, - randomSeq(count: number): string { - let str = '' - for (let i = 0; i < count; ++i) { - str += seq[this.randomInt(62)] - } - return str - }, - randomLowerAndNum(count: number): string { - let str = '' - for (let i = 0; i < count; ++i) { - str += seq[this.randomInt(36)] - } - return str - }, - randomUUID(): string { - let d = new Date().getTime() - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - let r = (d + Math.random() * 16) % 16 | 0 - d = Math.floor(d / 16) - return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16) - }) - }, - randomShadowsocksPassword(n: number): string { - const array = new Uint8Array(n) - window.crypto.getRandomValues(array) - return btoa(String.fromCharCode(...array)) - }, - randomShortId(): string[] { - let shortIds = new Array(24).fill('') - for (var ii = 1; ii < 24; ii++) { - for (var jj = 0; jj <= this.randomInt(7); jj++){ - let randomNum = this.randomInt(256) - shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2) - } - } - return shortIds - } -} - -export default RandomUtil \ No newline at end of file diff --git a/frontend/src/plugins/utils.ts b/frontend/src/plugins/utils.ts deleted file mode 100644 index 63f79c1..0000000 --- a/frontend/src/plugins/utils.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { i18n } from "@/locales" - -type OBJ = { - [key: string]: any -} - -export const FindDiff = { - Config(obj1: OBJ, obj2: OBJ): any[] { - const differences: any[] = [] - - if(!obj2){ - return [ { key: "all", obj: obj1 } ] - } - - for (const key in obj1) { - if (obj2.hasOwnProperty(key)) { - const value1 = obj1[key] - const value2 = obj2[key] - - if (Array.isArray(value1)){ - value1.forEach((v1,index) => { - if(index >= value2.length){ - differences.push({key: key, action: "new", index: index, obj: v1}) - } else if(!this.deepCompare(v1,value2[index])) { - differences.push({key: key, action: "edit", index: index, obj: v1}) - } - }) - } else { - if (!this.deepCompare(value1,value2)) { - differences.push({ key: key, action: "set", obj: value1}) - } - } - } else { - differences.push({ key: key, action: "set", obj: obj1[key]}) - } - } - - return differences - }, - ArrObj(value1: any[], value2: any[], key: string): any { - const differences: any[] = [] - value1.forEach((v1,index) => { - if(index >= value2.length) differences.push({key: key, action: "new", obj: v1}) - else if(!this.deepCompare(v1,value2[index])) differences.push({key: key, action: "edit", obj: v1}) - }) - return differences - }, - Settings(obj1: OBJ, obj2: OBJ): any { - const differences: any[] = [] - for (const key in obj1) { - if (obj1[key] != obj2[key]) { - differences.push({ key: key, action: "set", obj: obj1[key]}) - } - } - return differences - }, - deepCompare(obj1: any, obj2: any): boolean { - // Check if the types of both objects are the same - if (typeof obj1 !== typeof obj2) { - return false - } - - // Check if both objects are arrays - if (Array.isArray(obj1) && Array.isArray(obj2)) { - if (obj1.length !== obj2.length) { - return false - } - - for (let i = 0; i < obj1.length; i++) { - if (!this.deepCompare(obj1[i], obj2[i])) { - return false - } - } - return true - } - - // Check if both objects are plain objects - if (typeof obj1 === 'object' && typeof obj2 === 'object' && obj1 !== null && obj2 !== null) { - const keys1 = Object.keys(obj1).filter(key => obj1[key] !== undefined) - const keys2 = Object.keys(obj2).filter(key => obj2[key] !== undefined) - - if (keys1.length !== keys2.length) { - return false - } - - for (const key of keys1) { - if (!keys2.includes(key) || !this.deepCompare(obj1[key], obj2[key])) { - return false - } - } - return true - } - - // Check primitive values - return obj1 === obj2 - } -} - -const ONE_KB = 1024 -const ONE_MB = ONE_KB * 1024 -const ONE_GB = ONE_MB * 1024 -const ONE_TB = ONE_GB * 1024 -const ONE_PB = ONE_TB * 1024 - -export const HumanReadable = { - sizeFormat(size:number, fix:number=2) { - if (!size || size<0) return "-" - if (size < ONE_KB) { - return size.toFixed(0) + " " + i18n.global.t('stats.B') - } else if (size < ONE_MB) { - return (size / ONE_KB).toFixed(fix) + " " + i18n.global.t('stats.KB') - } else if (size < ONE_GB) { - return (size / ONE_MB).toFixed(fix) + " " + i18n.global.t('stats.MB') - } else if (size < ONE_TB) { - return (size / ONE_GB).toFixed(fix) + " " + i18n.global.t('stats.GB') - } else if (size < ONE_PB) { - return (size / ONE_TB).toFixed(fix) + " " + i18n.global.t('stats.TB') - } else { - return (size / ONE_PB).toFixed(fix) + " " + i18n.global.t('stats.PB') - } - }, - packetFormat(size:number, fix:number=2) { - if (!size || size<0) return "-" - if (size < 1000) { - return size.toFixed(0) + " " + i18n.global.t('stats.p') - } else if (size < 1000000) { - return (size / 1000).toFixed(fix) + " " + i18n.global.t('stats.Kp') - } else if (size < 1000000000) { - return (size / 1000000).toFixed(fix) + " " + i18n.global.t('stats.Mp') - } else { - return (size / 1000000000).toFixed(fix) + " " + i18n.global.t('stats.Gp') - } - }, - formatSecond(second:number): string { - if (!second || second<0) return "-" - if (second < 60) { - return second.toFixed(0) + i18n.global.t('date.s') - } else if (second < 3600) { - return (second / 60).toFixed(0) + i18n.global.t('date.m') - } else if (second < 3600 * 24) { - return (second / 3600).toFixed(0) + i18n.global.t('date.h') - } - const day = Math.floor(second / 3600 / 24) - const remain = Math.floor((second/3600) - (day*24)) - return day + i18n.global.t('date.d') + (remain > 0 ? ' ' + remain + i18n.global.t('date.h') : '') - }, - remainedDays(exp:number): number|null { - if (exp == 0) return -1 - const now = Date.now()/1000 - if (exp < now) return null - return Math.floor((exp - now) / (3600*24)) - } -} \ No newline at end of file diff --git a/frontend/src/plugins/vuetify.ts b/frontend/src/plugins/vuetify.ts deleted file mode 100644 index 4bebe1d..0000000 --- a/frontend/src/plugins/vuetify.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * plugins/vuetify.ts - * - * Framework documentation: https://vuetifyjs.com` - */ - -// Styles -import '@mdi/font/css/materialdesignicons.css' -import 'vuetify/styles' - -import colors from 'vuetify/util/colors' -import { fa, en, vi, zhHans, zhHant, ru } from 'vuetify/locale' - -// Composables -import { createVuetify } from 'vuetify' - -// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides -export default createVuetify({ - defaults: { - VRow: { dense: true } // Apply dense to v-row as default - }, - theme: { - defaultTheme: localStorage.getItem('theme') ?? 'light', - themes: { - light: { - colors: { - primary: '#1867C0', - secondary: '#5CBBF6', - tertiary: '#E57373', - accent: '#005CAF', - error: colors.red.accent3, - warning: colors.amber.base, - info: colors.teal.darken1, - success: colors.green.base, - background: colors.grey.lighten4, - }, - }, - dark: { - colors: { - primary: colors.blue.darken4, - secondary: colors.grey.darken3, - accent: colors.pink.darken3, - error: colors.red.accent3, - warning: colors.amber.darken3, - info: colors.teal.lighten1, - success: colors.green.darken2, - surface: colors.grey.darken3, - background: colors.grey.darken4, - }, - }, - }, - }, - locale: { - locale: localStorage.getItem("locale") ?? 'en', - fallback: 'en', - messages: { en, fa, vi, zhHans, zhHant, ru }, - }, -}) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts deleted file mode 100644 index 1f74bab..0000000 --- a/frontend/src/router/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -// Composables -import { createRouter, createWebHistory } from 'vue-router' -import Login from '@/views/Login.vue' -import Data from '@/store/modules/data' - -const routes = [ - { - path: '/login', - name: 'pages.login', - component: Login, - }, - { - path: '/', - component: () => import('@/layouts/default/Default.vue'), - meta: { requiresAuth: true }, - children: [ - { - path: '/', - name: 'pages.home', - component: () => import('@/views/Home.vue'), - }, - { - path: '/inbounds', - name: 'pages.inbounds', - component: () => import('@/views/Inbounds.vue'), - }, - { - path: '/clients', - name: 'pages.clients', - component: () => import('@/views/Clients.vue'), - }, - { - path: '/outbounds', - name: 'pages.outbounds', - component: () => import('@/views/Outbounds.vue'), - }, - { - path: '/rules', - name: 'pages.rules', - component: () => import('@/views/Rules.vue'), - }, - { - path: '/tls', - name: 'pages.tls', - component: () => import('@/views/Tls.vue'), - }, - { - path: '/basics', - name: 'pages.basics', - component: () => import('@/views/Basics.vue'), - }, - { - path: '/admins', - name: 'pages.admins', - component: () => import('@/views/Admins.vue'), - }, - { - path: '/settings', - name: 'pages.settings', - component: () => import('@/views/Settings.vue'), - }, - ], - }, -] - -const router = createRouter({ - history: createWebHistory((window as any).BASE_URL), - routes, -}) - -const DEFAULT_TITLE = 'S-UI' -let intervalId:any - -// Navigation guard to check authentication state -router.beforeEach((to, from, next) => { - // Check the session cookie - const sessionCookie = document.cookie.split(';').find(cookie => cookie.trim().startsWith('s-ui=')) - const isAuthenticated = !!sessionCookie - - // If the route requires authentication and the user is not authenticated, redirect to /login - if (to.meta.requiresAuth && !isAuthenticated) { - next('/login') - } else if (to.path === '/login' && isAuthenticated) { - // If already authenticated and visiting /route, redirect to '/' - next('/') - } else { - // Load default data - if(to.path != '/login'){ - loadDataInterval() - } else { - if (intervalId) { - clearInterval(intervalId) - intervalId = undefined - } - } - next() - } -}) - -const loadDataInterval = () => { - if (intervalId) return - Data().loadData() - intervalId = setInterval(() => { - Data().loadData() - }, 10000) -} - -export default router diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts deleted file mode 100644 index e90ba41..0000000 --- a/frontend/src/store/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createPinia } from 'pinia' - -const pinia = createPinia() - -export default pinia \ No newline at end of file diff --git a/frontend/src/store/modules/data.ts b/frontend/src/store/modules/data.ts deleted file mode 100644 index 137ba6a..0000000 --- a/frontend/src/store/modules/data.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { FindDiff } from '@/plugins/utils' -import HttpUtils from '@/plugins/httputil' -import { defineStore } from 'pinia' -import { push } from 'notivue' -import { i18n } from '@/locales' - -const Data = defineStore('Data', { - state: () => ({ - lastLoad: 0, - reloadItems: localStorage.getItem("reloadItems")?.split(',')?? [], - subURI: "", - onlines: {inbound: [], outbound: [], user: []}, - oldData: <{config: any, clients: any[], tlsConfigs: any[], inData: any[]}>{}, - config: {}, - clients: [], - tlsConfigs: [], - inData: [], - }), - actions: { - async loadData() { - const msg = await HttpUtils.get('api/load', this.lastLoad >0 ? {lu: this.lastLoad} : {} ) - if(msg.success) { - this.lastLoad = Math.floor((new Date()).getTime()/1000) - - // Set new data - if (msg.obj.config) this.oldData.config = msg.obj.config - if (msg.obj.clients) this.oldData.clients = msg.obj.clients - if (msg.obj.tls) this.oldData.tlsConfigs = msg.obj.tls - if (msg.obj.inData) this.oldData.inData = msg.obj.inData - this.onlines = msg.obj.onlines - if (msg.obj.lastLog) { - push.error({ - title: i18n.global.t('error.core'), - duration: 5000, - message: msg.obj.lastLog - }) - } - - if (msg.obj.config) { - // To avoid ref copy - const data = JSON.parse(JSON.stringify(msg.obj)) - if (data.subURI) this.subURI = data.subURI - if (data.config) this.config = data.config - if (data.clients) this.clients = data.clients - if (data.tls) this.tlsConfigs = data.tls - if (data.inData) this.inData = data.inData - } - } - }, - async pushData() { - const diff = { - config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config), null, 2), - clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2), - tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2), - inData: JSON.stringify(FindDiff.ArrObj(this.inData,this.oldData.inData, "inData"), null, 2), - } - const msg = await HttpUtils.post('api/save',diff) - if(msg.success) { - this.lastLoad = 0 - this.loadData() - } - }, - async delInbound(index: number) { - const diff = { - config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]), - clients: JSON.stringify(FindDiff.ArrObj(this.clients,this.oldData.clients, "clients"), null, 2), - tls: JSON.stringify(FindDiff.ArrObj(this.tlsConfigs,this.oldData.tlsConfigs, "tls"), null, 2), - inData: undefined, - } - - // Validate inData - let invalidInData = [] - this.inData.forEach((d:any) => { - const inboundIndex = this.config.inbounds.findIndex((i:any) => i.tag == d.tag) - if (inboundIndex == -1) invalidInData.push({key: "inData", action: "del", index: d.id, obj: null}) - }) - if (invalidInData.length>0) { - diff.inData = JSON.stringify(invalidInData) - } - const msg = await HttpUtils.post('api/save',diff) - if(msg.success) { - this.loadData() - } - }, - async delInData(id: number) { - const diff = { - inData: JSON.stringify([{key: "inData", action: "del", index: id, obj: null}]) - } - await HttpUtils.post('api/save',diff) - }, - async delOutbound(index: number) { - const diff = { - config: JSON.stringify([{key: "outbounds", action: "del", index: index, obj: null}]), - } - const msg = await HttpUtils.post('api/save',diff) - if(msg.success) { - this.loadData() - } - }, - async delClient(id: number) { - const diff = { - config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)), - clients:JSON.stringify([{key: "clients", action: "del", index: id, obj: null}]), - } - const msg = await HttpUtils.post('api/save',diff) - if(msg.success) { - this.loadData() - } - }, - async delTls(id: number) { - const diff = { - tls:JSON.stringify([{key: "tls", action: "del", index: id, obj: null}]), - } - const msg = await HttpUtils.post('api/save',diff) - if(msg.success) { - this.loadData() - } - } - }, -}) - -export default Data \ No newline at end of file diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss deleted file mode 100644 index 3b4d288..0000000 --- a/frontend/src/styles/settings.scss +++ /dev/null @@ -1,39 +0,0 @@ -/** - * src/styles/settings.scss - * - * Configures SASS variables and Vuetify overwrites - */ - -// https://vuetifyjs.com/features/sass-variables/` -// @use 'vuetify/settings' with ( -// $color-pack: false -// ); -@font-face { - font-display: swap; - font-family: 'Vazirmatn'; - font-style: normal; - font-weight: 400; - src: url('@/assets/Vazirmatn-UI-NL-Regular.woff2') format('woff2'); - unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC, U+0030-0039; -} -$body-font-family: "Vazirmatn"; - -$typoOptions: text-h1, text-sm-h1, text-md-h1, text-lg-h1, text-h2, text-sm-h2, - text-md-h2, text-lg-h2, text-h3, text-sm-h3, text-md-h3, text-lg-h3, text-h4, - text-sm-h4, text-md-h4, text-lg-h4, text-h5, text-sm-h5, text-md-h5, - text-lg-h5, text-h6, text-sm-h6, text-md-h6, text-lg-h6, headline, title, - subtitle-1, subtitle-2, text-body-1, text-sm-body-1, text-md-body-1, - text-lg-body-1, text-body-2, text-sm-body-2, text-md-body-2, text-lg-body-2, - text-caption; -body { - font-family: -apple-system, BlinkMacSystemFont, $body-font-family, sans-serif !important; - @each $typoOption in $typoOptions { - .#{$typoOption} { - font-family: -apple-system, BlinkMacSystemFont, $body-font-family, sans-serif !important; - } - } -} - -.v-btn { - letter-spacing: 0; -} \ No newline at end of file diff --git a/frontend/src/types/brutal.ts b/frontend/src/types/brutal.ts deleted file mode 100644 index f05f562..0000000 --- a/frontend/src/types/brutal.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Brutal { - enabled: boolean - up_mbps: number - down_mbps: number -} \ No newline at end of file diff --git a/frontend/src/types/clients.ts b/frontend/src/types/clients.ts deleted file mode 100644 index 76a3d65..0000000 --- a/frontend/src/types/clients.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { Link } from "@/plugins/link" -import RandomUtil from "@/plugins/randomUtil" - -export interface Client { - id?: number - enable: boolean - name: string - config: Config - inbounds: string[] - links: Link[] - volume: number - expiry: number - up: number - down: number - desc: string - group: string -} - -const defaultClient: Client = { - enable: true, - name: "", - config: {}, - inbounds: [], - links: [], - volume: 0, - expiry: 0, - up: 0, - down: 0, - desc: "", - group: "", -} - -type Config = { - [key: string]: { - name?: string - username?: string - [key: string]: any - } -} - -export function updateConfigs(configs: Config, newUserName: string): Config { - - for (const key in configs) { - if (configs.hasOwnProperty(key)) { - const config = configs[key] - if (config.hasOwnProperty("name")) { - config.name = newUserName - } else if (config.hasOwnProperty("username")) { - config.username = newUserName - } - } - } - - return configs -} - -export function randomConfigs(user: string): Config { - const mixedPassword = RandomUtil.randomSeq(10) - const ssPassword16 = RandomUtil.randomShadowsocksPassword(16) - const ssPassword32 = RandomUtil.randomShadowsocksPassword(32) - const uuid = RandomUtil.randomUUID() - return { - mixed: { - username: user, - password: mixedPassword, - }, - socks: { - username: user, - password: mixedPassword, - }, - http: { - username: user, - password: mixedPassword, - }, - shadowsocks: { - name: user, - password: ssPassword32, - }, - shadowsocks16: { - name: user, - password: ssPassword16, - }, - shadowtls: { - name: user, - password: ssPassword32, - }, - vmess: { - name: user, - uuid: uuid, - alterId: 0, - }, - vless: { - name: user, - uuid: uuid, - flow: "xtls-rprx-vision", - }, - trojan: { - name: user, - password: mixedPassword, - }, - naive: { - username: user, - password: mixedPassword, - }, - hysteria: { - name: user, - auth_str: mixedPassword, - }, - tuic: { - name: user, - uuid: uuid, - password: mixedPassword, - }, - hysteria2: { - name: user, - password: mixedPassword, - }, - } -} - -export function createClient(json?: Partial): Client { - defaultClient.name = RandomUtil.randomSeq(8) - const defaultObject: Client = { ...defaultClient, ...(json || {}) } - - // Add missing config - defaultObject.config = { ...randomConfigs(defaultObject.name), ...defaultObject.config } - - return defaultObject -} diff --git a/frontend/src/types/config.ts b/frontend/src/types/config.ts deleted file mode 100644 index 7579982..0000000 --- a/frontend/src/types/config.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Inbound } from './inbounds' -import { Dial, Outbound } from './outbounds' - -interface Log { - disabled?: boolean - level?: string - output?: string - timestamp?: boolean -} - -interface Dns { - servers: DnsServer[] - final?: string - strategy?: string -} - -interface DnsServer{ - tag?: string, - address: string, - address_resolver?: string, - address_strategy?: string, - strategy?: string, - detour?: string -} - -export interface Ntp extends Dial{ - enabled?: boolean - server: string - server_port?: number - interval?: string -} - -interface Route { - rules: RouteRule[] | RouteRuleLogical[] - rule_set: RouteRuleSet[] - final?: string, - auto_detect_interface?: boolean - default_interface?: string - default_mark?: number -} - -interface RouteRule { - inbound?: string[] | string - ip_version?: 4 | 6, - network?: "tcp" | "udp" - auth_user?: string[] - protocol?: string[] | string - domain?: string[] | string - domain_suffix?: string[] | string - domain_keyword?: string[] | string - domain_regex?: string[] | string - source_ip_cidr?: string[] | string - source_ip_is_private?: boolean - ip_cidr?: string[] | string - ip_is_private?: boolean - source_port?: number[] | number - source_port_range?: string[] | string - port?: number[] | number - port_range?: string[] | string - clash_mode?: string - rule_set?: string[] | string - invert?: boolean - outbound: string -} - -interface RouteRuleLogical { - type: "logical" - mode: "and" | "or" - rules: RouteRule[] - invert?: boolean - outbound: string -} - -interface RouteRuleSet { - type: string - tag: string - format: string - path?: string - url?: string - download_detour?: string - update_interval?: string -} - -interface Experimental { - cache_file?: CacheFile - clash_api?: ClashApi - v2ray_api: V2rayApi -} - -interface CacheFile { - enabled?: boolean - path?: string - cache_id?: string - store_fakeip?: boolean -} - -interface V2rayApi { - listen: string - stats: V2rayApiStats -} - -export interface V2rayApiStats { - enabled: boolean - inbounds: string[] - outbounds: string[] - users: string[] -} - -interface ClashApi { - external_controller?: string - external_ui?: string - external_ui_download_url?: string - external_ui_download_detour?: string - secret?: string - default_mode?: string -} - -export interface Config { - log: Log - dns: Dns - ntp?: Ntp - inbounds: Inbound[] - outbounds: Outbound[] - route: Route - experimental: Experimental -} \ No newline at end of file diff --git a/frontend/src/types/dial.ts b/frontend/src/types/dial.ts deleted file mode 100644 index 06a2670..0000000 --- a/frontend/src/types/dial.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface Dial { - detour?: string - bind_interface?: string - inet4_bind_address?: string - inet6_bind_address?:string - routing_mark?: number - reuse_addr?: boolean - connect_timeout?: string - tcp_fast_open?: boolean - tcp_multi_path?: boolean - udp_fragment?: boolean - domain_strategy?: string - fallback_delay?: string -} \ No newline at end of file diff --git a/frontend/src/types/inTls.ts b/frontend/src/types/inTls.ts deleted file mode 100644 index a0114d5..0000000 --- a/frontend/src/types/inTls.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Dial } from "./dial" - -export interface iTls { - enabled?: boolean - server_name?: string - alpn?: string[] - min_version?: string - max_version?: string - cipher_suites?: string[] - certificate?: string[] - certificate_path?: string - key?: string[] - key_path?: string - acme?: acme - ech?: ech - reality?: reality -} - -export interface acme { - domain: string[] - data_directory?: string - default_server_name?: string - email?: string - provider?: string - disable_http_challenge?: boolean - disable_tls_alpn_challenge?: boolean - alternative_http_port?: number - alternative_tls_port?: number - external_account?: { - key_id: string - mac_key: string - } - dns01_challenge?: { - provider: string - [key: string]: string - } -} - -export interface ech { - enabled: boolean - pq_signature_schemes_enabled?: boolean - dynamic_record_sizing_disabled?: boolean - key?: string[] - key_path?: string -} - -interface realityHanshake extends Dial { - server: string - server_port: number -} - -export interface reality { - enabled: boolean - handshake: realityHanshake - private_key: string - short_id: string[] - max_time_difference?: string -} - -export const defaultInTls: iTls = { - alpn: ['h3', 'h2', 'http/1.1'], - min_version: "1.2", - max_version: "1.3", - cipher_suites: [], -} \ No newline at end of file diff --git a/frontend/src/types/inbounds.ts b/frontend/src/types/inbounds.ts deleted file mode 100644 index e2ec33c..0000000 --- a/frontend/src/types/inbounds.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { iMultiplex } from "./multiplex" -import { iTls } from "./inTls" -import { Dial } from "./outbounds" -import { Transport } from "./transport" - -export const InTypes = { - Direct: 'direct', - Mixed: 'mixed', - SOCKS: 'socks', - HTTP: 'http', - Shadowsocks: 'shadowsocks', - VMess: 'vmess', - Trojan: 'trojan', - Naive: 'naive', - Hysteria: 'hysteria', - ShadowTLS: 'shadowtls', - TUIC: 'tuic', - Hysteria2: 'hysteria2', - VLESS: 'vless', - Tun: 'tun', - Redirect: 'redirect', - TProxy: 'tproxy', -} - -type InType = typeof InTypes[keyof typeof InTypes] - -export interface Listen { - listen: string - listen_port: number - tcp_fast_open?: boolean - tcp_multi_path?: boolean - udp_fragment?: boolean - udp_timeout?: string - detour?: string - sniff?: boolean - sniff_override_destination?: boolean - sniff_timeout?: string - domain_strategy?: string -} - -interface InboundBasics extends Listen { - type: InType - tag: string -} - -interface UsernamePass { - username: string - password: string -} -interface NamePass { - name: string - password: string -} -interface NameUUID { - name: string - uuid: string -} -interface NameAuth { - name: string - auth_str: string -} -interface VmessUser extends NameUUID { - alterId: number -} -interface VlessUser extends NameUUID { - flow: string -} -interface TuicUser extends NameUUID { - password?: string -} - -interface ShadowTLSHandShake extends Dial { - server: string - server_port: number -} - -export interface Direct extends InboundBasics { - network?: "udp" | "tcp" - override_address?: string - override_port?: number -} -export interface Mixed extends InboundBasics { - users?: UsernamePass[] -} -export interface SOCKS extends InboundBasics { - users?: UsernamePass[] -} -export interface HTTP extends InboundBasics { - users?: UsernamePass[] - tls?: iTls, -} -export interface Shadowsocks extends InboundBasics { - method: string - password: string - network?: "udp" | "tcp" - users?: NamePass[] - multiplex?: iMultiplex -} -export interface VMess extends InboundBasics { - users: VmessUser[] - tls: iTls - multiplex?: iMultiplex - transport?: Transport -} -export interface Trojan extends InboundBasics { - users: NamePass[] - tls: iTls - fallback?: { - server: string - server_port: number - } - multiplex?: iMultiplex - transport?: Transport -} -export interface Naive extends InboundBasics { - users: UsernamePass[] - tls: iTls, -} -export interface Hysteria extends InboundBasics { - up_mbps: number - down_mbps: number - obfs?: string - users: NameAuth[] - recv_window_conn?: number - recv_window_client?: number - max_conn_client?: number - disable_mtu_discovery?: boolean - tls: iTls -} -export interface ShadowTLS extends InboundBasics { - version: 1|2|3 - password?: string - users?: NamePass[] - handshake: ShadowTLSHandShake - handshake_for_server_name?: { - [server_name: string]: ShadowTLSHandShake - } - strict_mode?: boolean -} -export interface VLESS extends InboundBasics { - users: VlessUser[] - tls?: iTls - multiplex?: iMultiplex - transport?: Transport -} -export interface TUIC extends InboundBasics { - users: TuicUser[] - congestion_control: ""|"cubic"|"new_reno"|"bbr" - auth_timeout?: string - zero_rtt_handshake?: boolean - heartbeat?: string - tls: iTls -} -export interface Hysteria2 extends InboundBasics { - up_mbps?: number - down_mbps?: number - obfs?: { - type?: "salamander" - password: string - } - users: NamePass[] - ignore_client_bandwidth?: boolean - tls: iTls - masquerade?: string - brutal_debug?: boolean -} -export interface Tun extends InboundBasics { - interface_name?: string - address?: string[] - mtu?: number - endpoint_independent_nat?: boolean - udp_timeout?: string - stack?: string - auto_route?: boolean - // gso?: boolean - // strict_route?: boolean - // iproute2_table_index?: number - // iproute2_rule_index?: number - // auto_redirect?: boolean - // auto_redirect_input_mark?: string - // auto_redirect_output_mark?: string - // route_address?: string[] - // route_exclude_address?: string[] - // include_interface?: string[] - // exclude_interface?: string[] - // include_uid?: string[] - // include_uid_range?: string[] - // exclude_uid?: number[] - // exclude_uid_range?: string[] - // include_android_user?: number[] - // include_package?: string[] - // exclude_package?: string[] -} -export interface Redirect extends InboundBasics {} -export interface TProxy extends InboundBasics { - network?: "udp" | "tcp" -} - -// Create interfaces dynamically based on InTypes keys -type InterfaceMap = { - direct: Direct - mixed: Mixed - socks: SOCKS - http: SOCKS - shadowsocks: Shadowsocks - vmess: VMess - trojan: Trojan - naive: Naive - hysteria: Hysteria - shadowtls: ShadowTLS - tuic: TUIC - hysteria2: Hysteria2 - vless: VLESS - tun: Tun - redirect: Redirect - tproxy: TProxy -} - -// Create union type from InterfaceMap -export type Inbound = InterfaceMap[keyof InterfaceMap] - -type userEnabledTypes = { - mixed: Mixed - socks: SOCKS - http: SOCKS - shadowsocks: Shadowsocks - vmess: VMess - trojan: Trojan - naive: Naive - hysteria: Hysteria - shadowtls: ShadowTLS - tuic: TUIC - hysteria2: Hysteria2 - vless: VLESS -} - -// Create union type from userEnabledTypes -export type InboundWithUser = userEnabledTypes[keyof userEnabledTypes] - -// Create defaultValues object dynamically -const defaultValues: Record = { - direct: { type: InTypes.Direct }, - mixed: { type: InTypes.Mixed }, - socks: { type: InTypes.SOCKS }, - http: { type: InTypes.HTTP, tls: {} }, - shadowsocks: { type: InTypes.Shadowsocks, method: 'none', multiplex: {} }, - vmess: { type: InTypes.VMess, users: [], tls: {}, multiplex: {}, transport: {} }, - trojan: { type: InTypes.Trojan, users: [], tls: {}, multiplex: {}, transport: {} }, - naive: { type: InTypes.Naive, users: [], tls: { enabled: true } }, - hysteria: { type: InTypes.Hysteria, users: [], up_mbps: 100, down_mbps: 100, tls: { enabled: true } }, - shadowtls: { type: InTypes.ShadowTLS, version: 3, users: [], handshake: {}, handshake_for_server_name: {} }, - tuic: { type: InTypes.TUIC, users: [], congestion_control: "cubic", tls: { enabled: true } }, - hysteria2: { type: InTypes.Hysteria2, users: [], tls: { enabled: true } }, - vless: { type: InTypes.VLESS, users: [], tls: {}, multiplex: {}, transport: {} }, - tun: { type: InTypes.Tun, mtu: 9000, stack: 'system', udp_timeout: '5m', auto_route: false }, - redirect: { type: InTypes.Redirect }, - tproxy: { type: InTypes.TProxy }, -} - -export function createInbound(type: InType,json?: Partial): Inbound { - const defaultObject: Inbound = { ...defaultValues[type] ?? {}, ...(json ?? {}) } - return defaultObject -} \ No newline at end of file diff --git a/frontend/src/types/multiplex.ts b/frontend/src/types/multiplex.ts deleted file mode 100644 index 720daf8..0000000 --- a/frontend/src/types/multiplex.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Brutal } from "./brutal" - -export interface iMultiplex{ - enabled: boolean - padding?: boolean - brutal?: Brutal -} - -export interface oMultiplex extends iMultiplex{ - protocol?: "smux" | "yamux" | "h2mux" - max_connections?: number - min_streams?: number - max_streams?: number -} \ No newline at end of file diff --git a/frontend/src/types/outTls.ts b/frontend/src/types/outTls.ts deleted file mode 100644 index a60e5d0..0000000 --- a/frontend/src/types/outTls.ts +++ /dev/null @@ -1,50 +0,0 @@ -export interface oTls { - enabled?: boolean - disable_sni?: boolean - server_name?: string - insecure?: boolean - alpn?: string[] - min_version?: string - max_version?: string - cipher_suites?: string[] - certificate?: string - certificate_path?: string - ech?: { - enabled: boolean - pq_signature_schemes_enabled?: boolean - dynamic_record_sizing_disabled?: boolean - config?: string[], - config_path?: string - }, - utls?: { - enabled: boolean - fingerprint: string - }, - reality?: { - enabled: boolean - public_key: string - short_id: string - } -} - -export const defaultOutTls: oTls = { - alpn: ['h3', 'h2', 'http/1.1'], - min_version: "1.2", - max_version: "1.3", - cipher_suites: [], - utls: { - enabled: true, - fingerprint: "chrome", - }, - reality: { - enabled: true, - public_key: "", - short_id: "", - }, - ech: { - enabled: true, - pq_signature_schemes_enabled: false, - dynamic_record_sizing_disabled: false, - config_path: "", - } -} \ No newline at end of file diff --git a/frontend/src/types/outbounds.ts b/frontend/src/types/outbounds.ts deleted file mode 100644 index 2ecfbf8..0000000 --- a/frontend/src/types/outbounds.ts +++ /dev/null @@ -1,282 +0,0 @@ -import { oTls } from "./outTls" -import { oMultiplex } from "./multiplex" -import { Transport } from "./transport" - -export const OutTypes = { - Direct: 'direct', - Block: 'block', - SOCKS: 'socks', - HTTP: 'http', - Shadowsocks: 'shadowsocks', - VMess: 'vmess', - Trojan: 'trojan', - Wireguard: 'wireguard', - Hysteria: 'hysteria', - VLESS: 'vless', - ShadowTLS: 'shadowtls', - TUIC: 'tuic', - Hysteria2: 'hysteria2', - Tor: 'tor', - SSH: 'ssh', - DNS: 'dns', - Selector: 'selector', - URLTest: 'urltest', -} - -type OutType = typeof OutTypes[keyof typeof OutTypes] - -export interface Dial { - detour?: string - bind_interface?: string - inet4_bind_address?: string - inet6_bind_address?: string - routing_mark?: number - reuse_addr?: boolean - connect_timeout?: string - tcp_fast_open?: boolean - tcp_multi_path?: boolean - udp_fragment?: boolean - domain_strategy?: string - fallback_delay?: string -} - -interface OutboundBasics { - type: OutType - tag: string -} - -export interface WgPeer { - server: string - server_port: number - public_key: string - pre_shared_key?: string - allowed_ips?: string[] - reserved?: number[] -} - -export interface Direct extends OutboundBasics, Dial { - override_address?: string - override_port?: number -} - -export interface Block extends OutboundBasics {} - -export interface SOCKS extends OutboundBasics, Dial { - server: string - server_port: number - version?: "4" | "4a" | "5" - username?: string - password?: string - network?: "udp" | "tcp" - udp_over_tcp?: false | { - enabled: true - version?: number - } -} - -export interface HTTP extends OutboundBasics, Dial { - server: string - server_port: number - username?: string - password?: string - path?: string - headers?: { - [key: string]: string - } - tls?: oTls -} - -export interface Shadowsocks extends OutboundBasics, Dial { - server: string - server_port: number - method: string - password: string - network?: "udp" | "tcp" - udp_over_tcp?: false | { - enabled: true - version?: number - } - multiplex?: oMultiplex -} - -export interface VMESS extends OutboundBasics, Dial { - server: string - server_port: number - uuid: string - security?: string - alter_id: 0 - global_padding?: boolean - authenticated_length?: boolean - network?: "udp" | "tcp" - packet_encoding?: string - tls?: oTls - multiplex?: oMultiplex - transport?: Transport -} - -export interface Trojan extends OutboundBasics, Dial { - server: string - server_port: number - password: string - network?: "udp" | "tcp" - tls?: oTls - multiplex?: oMultiplex - transport?: Transport -} - -export interface WireGuard extends OutboundBasics, Dial { - server?: string - server_port?: number - system_interface?: boolean - gso?: boolean - interface_name?: string - local_address: string[] - private_key: string - peers?: WgPeer[] - peer_public_key?: string - pre_shared_key?: string - reserved?: number[] - workers?: number - mtu?: number - network?: "udp" | "tcp" -} - -export interface Hysteria extends OutboundBasics, Dial { - server: string - server_port: number - up_mbps: number - down_mbps: number - obfs?: string - auth_str?: string - recv_window_conn?: number - recv_window?: number - disable_mtu_discovery?: boolean - network?: "udp" | "tcp" - tls: oTls -} - -export interface ShadowTLS extends OutboundBasics, Dial { - server: string - server_port: number - version: 1|2|3 - password?: string - tls: oTls -} - -export interface VLESS extends OutboundBasics, Dial { - server: string - server_port: number - uuid: string - flow?: string - network?: "udp" | "tcp" - packet_encoding?: string - tls?: oTls - multiplex?: oMultiplex - transport?: Transport -} - -export interface TUIC extends OutboundBasics, Dial { - server: string - server_port: number - uuid: string - password?: string - congestion_control?: "cubic"|"new_reno"|"bbr" - udp_relay_mode?: "native" | "quic" - udp_over_stream?: boolean - zero_rtt_handshake?: boolean - heartbeat?: string - network?: "udp" | "tcp" - tls: oTls -} - -export interface Hysteria2 extends OutboundBasics, Dial { - server: string - server_port: number - up_mbps?: number - down_mbps?: number - obfs?: { - type?: "salamander" - password: string - } - password?: string - network?: "udp" | "tcp" - tls: oTls - brutal_debug?: boolean -} - -export interface Tor extends OutboundBasics, Dial { - executable_path?: string - extra_args?: string[] - data_directory: string - torrc?: { - ClientOnly: 0 | 1 - } -} - -export interface SSH extends OutboundBasics, Dial { - server: string - server_port?: number - user?: string - password?: string - private_key?: string - private_key_path?: string - private_key_passphrase?: string - host_key?: string[] - host_key_algorithms?: string[] - client_version?: string -} - -export interface DNS extends OutboundBasics {} - -export interface Selector extends OutboundBasics { - outbounds: string[] - url?: string - interval?: string - tolerance?: number - idle_timeout?: string - interrupt_exist_connections?: boolean -} - -export interface URLTest extends OutboundBasics { - outbounds: string[] - default?: string - interrupt_exist_connections?: boolean -} - -// Create interfaces dynamically based on OutTypes keys -type InterfaceMap = { - [Key in keyof typeof OutTypes]: { - type: string - [otherProperties: string]: any; // You can add other properties as needed - } -} - -// Create union type from InterfaceMap -export type Outbound = InterfaceMap[keyof InterfaceMap] - -// Create defaultValues object dynamically -const defaultValues: Record = { - direct: { type: OutTypes.Direct }, - block: { type: OutTypes.Block }, - socks: { type: OutTypes.SOCKS, version: "5" }, - http: { type: OutTypes.HTTP, tls: {} }, - shadowsocks: { type: OutTypes.Shadowsocks, method: 'none', multiplex: {} }, - vmess: { type: OutTypes.VMess, tls: {}, multiplex: {}, transport: {}, security: 'auto', global_padding: false }, - trojan: { type: OutTypes.Trojan, tls: {}, multiplex: {}, transport: {} }, - wireguard: { type: OutTypes.Wireguard, local_address: ['10.0.0.2/32','fe80::2/128'], private_key: '' }, - hysteria: { type: OutTypes.Hysteria, up_mbps: 100, down_mbps: 100, tls: { enabled: true } }, - shadowtls: { type: OutTypes.ShadowTLS, version: 3, tls: { enabled: true } }, - vless: { type: OutTypes.VLESS, tls: {}, multiplex: {}, transport: {} }, - tuic: { type: OutTypes.TUIC, congestion_control: 'cubic', tls: { enabled: true } }, - hysteria2: { type: OutTypes.Hysteria2, tls: { enabled: true } }, - tor: { type: OutTypes.Tor, executable_path: './tor', data_directory: '$HOME/.cache/tor', torrc: { ClientOnly: 1 } }, - ssh: { type: OutTypes.SSH }, - dns: { type: OutTypes.DNS }, - selector: { type: OutTypes.Selector }, - urltest: { type: OutTypes.URLTest }, -} - -export function createOutbound(type: string,json?: Partial): Outbound { - const defaultObject: Outbound = { ...defaultValues[type], ...(json || {}) } - return defaultObject -} \ No newline at end of file diff --git a/frontend/src/types/rules.ts b/frontend/src/types/rules.ts deleted file mode 100644 index b8a0008..0000000 --- a/frontend/src/types/rules.ts +++ /dev/null @@ -1,47 +0,0 @@ -export interface logicalRule { - type: 'logical' | 'simple' - mode: 'and' | 'or' - rules: rule[] - invert: boolean - outbound: string -} - -export interface rule { - inbound?: string[] - ip_version?: 4 | 6 - network?: string[] - auth_user?: string[] - protocol?: string[] - domain?: string[] - domain_suffix?: string[] - domain_keyword?: string[] - domain_regex?: string[] - source_ip_cidr?: string[] - source_ip_is_private?: boolean - ip_cidr?: string[] - ip_is_private?: boolean - source_port?: number[] - source_port_range?: string[] - port?: number[] - port_range?: string[] - process_name?: string[] - process_path?: string[] - package_name?: string[] - user?: string[] - user_id?: number[] - clash_mode?: string - rule_set?: string[] - rule_set_ipcidr_match_source?: boolean - invert?: boolean - outbound?: string -} - -export interface ruleset { - type: 'local' | 'remote' - tag: string - format: 'source' | 'binary' - path?: string - url?: string - download_detour?: string - update_interval?: string -} \ No newline at end of file diff --git a/frontend/src/types/transport.ts b/frontend/src/types/transport.ts deleted file mode 100644 index 7805c66..0000000 --- a/frontend/src/types/transport.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const TrspTypes = { - HTTP: 'http', - WebSocket: 'ws', - QUIC: 'quic', - gRPC: 'grpc', - HTTPUpgrade: "httpupgrade" -} - -export type TrspType = typeof TrspTypes[keyof typeof TrspTypes] - -export type Transport = HTTP|WebSocket|QUIC|gRPC|HTTPUpgrade - -interface TransportBasics { - type: TrspType -} - -export interface HTTP extends TransportBasics { - host?: string[] - path?: string - method?: string - headers?: {} - idle_timeout?: string - ping_timeout?: string -} - -export interface WebSocket extends TransportBasics { - path: string - headers?: { - Host: string - } - max_early_data?: number - early_data_header_name?: string -} - -export interface QUIC extends TransportBasics {} - -export interface gRPC extends TransportBasics { - service_name?: string - idle_timeout?: string - ping_timeout?: string - permit_without_stream?: boolean -} - -export interface HTTPUpgrade extends TransportBasics { - host?: string - path?: string - headers?: {} -} diff --git a/frontend/src/views/Admins.vue b/frontend/src/views/Admins.vue deleted file mode 100644 index ff70da6..0000000 --- a/frontend/src/views/Admins.vue +++ /dev/null @@ -1,140 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/views/Basics.vue b/frontend/src/views/Basics.vue deleted file mode 100644 index f1fdd45..0000000 --- a/frontend/src/views/Basics.vue +++ /dev/null @@ -1,391 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue deleted file mode 100644 index f2542fc..0000000 --- a/frontend/src/views/Clients.vue +++ /dev/null @@ -1,649 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue deleted file mode 100644 index 365ad93..0000000 --- a/frontend/src/views/Home.vue +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue deleted file mode 100644 index 17c647d..0000000 --- a/frontend/src/views/Inbounds.vue +++ /dev/null @@ -1,377 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue deleted file mode 100644 index 87e0c39..0000000 --- a/frontend/src/views/Login.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/src/views/Outbounds.vue b/frontend/src/views/Outbounds.vue deleted file mode 100644 index 7ace1d9..0000000 --- a/frontend/src/views/Outbounds.vue +++ /dev/null @@ -1,205 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/views/Rules.vue b/frontend/src/views/Rules.vue deleted file mode 100644 index 1b9ef8f..0000000 --- a/frontend/src/views/Rules.vue +++ /dev/null @@ -1,282 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue deleted file mode 100644 index bafdf96..0000000 --- a/frontend/src/views/Settings.vue +++ /dev/null @@ -1,288 +0,0 @@ - - - diff --git a/frontend/src/views/Tls.vue b/frontend/src/views/Tls.vue deleted file mode 100644 index f857227..0000000 --- a/frontend/src/views/Tls.vue +++ /dev/null @@ -1,188 +0,0 @@ - - - diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts deleted file mode 100644 index 323c78a..0000000 --- a/frontend/src/vite-env.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -/// - -declare module '*.vue' { - import type { DefineComponent } from 'vue' - const component: DefineComponent<{}, {}, any> - export default component -} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json deleted file mode 100644 index e57bb25..0000000 --- a/frontend/tsconfig.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "target": "ESNext", - "useDefineForClassFields": true, - "module": "ESNext", - "moduleResolution": "Node", - "strict": true, - "jsx": "preserve", - "resolveJsonModule": true, - "isolatedModules": true, - "esModuleInterop": true, - "lib": ["ESNext", "DOM"], - "skipLibCheck": true, - "noEmit": true, - "paths": { - "@/*": [ - "src/*" - ] - } - }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], - "references": [{ "path": "./tsconfig.node.json" }], - "exclude": ["node_modules"], -} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json deleted file mode 100644 index 2634672..0000000 --- a/frontend/tsconfig.node.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.mts"], - "exclude": [] -} diff --git a/frontend/vite.config.mts b/frontend/vite.config.mts deleted file mode 100644 index 3165ca1..0000000 --- a/frontend/vite.config.mts +++ /dev/null @@ -1,63 +0,0 @@ -// Plugins -import vue from '@vitejs/plugin-vue' -import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify' - -// Utilities -import { defineConfig } from 'vite' -import { fileURLToPath, URL } from 'node:url' -import { randomBytes } from 'crypto'; - -function getUniqueFileName(template) { - if (template.includes('.js') || template.includes('.css')) { - const hash = randomBytes(8).toString('hex'); - return template.replace('[name]', hash); - } - return template; -} - -export default defineConfig({ - base: '', - plugins: [ - vue({ - template: { transformAssetUrls }, - }), - vuetify({ - autoImport: true, - styles: { - configFile: 'src/styles/settings.scss', - }, - }) - ], - build: { - manifest: false, - outDir: 'dist', - chunkSizeWarningLimit: 1600, - rollupOptions: { - output: { - inlineDynamicImports: true, - entryFileNames: getUniqueFileName('assets/[name].js'), - chunkFileNames: getUniqueFileName('assets/[name].js'), - assetFileNames: (assetInfo) => { - if (assetInfo.name == "index.css") return getUniqueFileName('assets/[name].css'); - return 'assets/' + assetInfo.name; - }, - }, - } - }, - define: { 'process.env': {} }, - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)), - }, - extensions: ['.js', '.json', '.jsx', '.mjs', '.ts', '.tsx', '.vue'], - }, - server: { - port: 3000, - proxy: { - '/app/api': { - target: 'http://localhost:2095', - changeOrigin: true, - }, - }, - } -}) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..955f05f --- /dev/null +++ b/go.mod @@ -0,0 +1,126 @@ +module s-ui + +go 1.23.2 + +require ( + github.com/gin-contrib/gzip v1.0.1 + 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.12 + github.com/sagernet/sing-box v1.11.0 + github.com/sagernet/sing-dns v0.4.0-beta.2 + 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/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/ebitengine/purego v0.8.1 // 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/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 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/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/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.4 // 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.8 // indirect + github.com/sagernet/sing-vmess v0.2.0-beta.2 // 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/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.31.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // 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/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.3.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1ebc5fd --- /dev/null +++ b/go.sum @@ -0,0 +1,301 @@ +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/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/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/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= +github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4= +github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI= +github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +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= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA= +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/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.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/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= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +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/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/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +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/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/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/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/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/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/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/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.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s= +github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-box v1.11.0-rc.1 h1:iJbMP+dcKOEbvjv0rBXkFndmJQCPAACrYmanYPPuIgk= +github.com/sagernet/sing-box v1.11.0-rc.1/go.mod h1:DmL1WKyrfaAEu5z88CtUeQBfELaEdUyQzLS5nzmRg8o= +github.com/sagernet/sing-box v1.11.0 h1:70DvEMhFMtDn4YQdj3aoOsRdvUznLGQREnORAt6UmYo= +github.com/sagernet/sing-box v1.11.0/go.mod h1:DmL1WKyrfaAEu5z88CtUeQBfELaEdUyQzLS5nzmRg8o= +github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY= +github.com/sagernet/sing-dns v0.4.0-beta.2/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-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g= +github.com/sagernet/sing-quic v0.4.0-beta.4/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.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw= +github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= +github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= +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.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/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4= +github.com/shirou/gopsutil/v4 v4.24.12/go.mod h1:DCtMPAad2XceTeIAbGyVfycbYQNBGk2P8cvDi7/VN9o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.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= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= +github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +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= +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.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +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.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.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.14.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.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +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.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= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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.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= +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/install.sh b/install.sh index d4d4b89..739ecd3 100755 --- a/install.sh +++ b/install.sh @@ -176,6 +176,21 @@ config_after_install() { fi } +prepare_services() { + if [[ -f "/etc/systemd/system/sing-box.service" ]]; then + echo -e "${yellow}Stopping sing-box service... ${plain}" + systemctl stop sing-box + rm -f /usr/local/s-ui/bin/sing-box /usr/local/s-ui/bin/runSingbox.sh /usr/local/s-ui/bin/signal + fi + if [[ -e "/usr/local/s-ui/bin" ]]; then + echo -e "###############################################################" + echo -e "${green}/usr/local/s-ui/bin${red} directory exists yet!" + echo -e "Please check the content and delete it manually after migration ${plain}" + echo -e "###############################################################" + fi + systemctl daemon-reload +} + install_s-ui() { cd /tmp/ @@ -204,26 +219,26 @@ install_s-ui() { if [[ -e /usr/local/s-ui/ ]]; then systemctl stop s-ui - systemctl stop sing-box fi tar zxvf s-ui-linux-$(arch).tar.gz rm s-ui-linux-$(arch).tar.gz -f - wget --no-check-certificate -O /usr/bin/s-ui https://raw.githubusercontent.com/alireza0/s-ui/main/s-ui.sh - - chmod +x s-ui/sui s-ui/bin/sing-box s-ui/bin/runSingbox.sh /usr/bin/s-ui + chmod +x s-ui/sui s-ui/s-ui.sh + cp s-ui/s-ui.sh /usr/bin/s-ui cp -rf s-ui /usr/local/ cp -f s-ui/*.service /etc/systemd/system/ rm -rf s-ui config_after_install + prepare_services - systemctl daemon-reload systemctl enable s-ui --now - systemctl enable sing-box --now echo -e "${green}s-ui v${last_version}${plain} installation finished, it is up and running now..." + echo -e "You may access the Panel with following URL(s):${green}" + /usr/local/s-ui/sui uri + echo -e "${plain}" echo -e "" s-ui help } diff --git a/backend/logger/logger.go b/logger/logger.go similarity index 95% rename from backend/logger/logger.go rename to logger/logger.go index a9d7a0c..83622a9 100644 --- a/backend/logger/logger.go +++ b/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/main.go b/main.go similarity index 100% rename from backend/main.go rename to main.go diff --git a/backend/middleware/domainValidator.go b/middleware/domainValidator.go similarity index 100% rename from backend/middleware/domainValidator.go rename to middleware/domainValidator.go diff --git a/backend/network/auto_https_conn.go b/network/auto_https_conn.go similarity index 100% rename from backend/network/auto_https_conn.go rename to network/auto_https_conn.go diff --git a/backend/network/auto_https_listener.go b/network/auto_https_listener.go similarity index 100% rename from backend/network/auto_https_listener.go rename to network/auto_https_listener.go diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0cfbd30 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "s-ui", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/s-ui.sh b/s-ui.sh index 7dfdab2..1e2cc18 100644 --- a/s-ui.sh +++ b/s-ui.sh @@ -172,7 +172,7 @@ custom_version() { } uninstall() { - confirm "Are you sure you want to uninstall the panel? sing-box will also uninstalled!" "n" + confirm "Are you sure you want to uninstall the panel?" "n" if [[ $? != 0 ]]; then if [[ $# == 0 ]]; then show_menu @@ -181,10 +181,7 @@ uninstall() { fi systemctl stop s-ui systemctl disable s-ui - systemctl stop sing-box - systemctl disable sing-box rm /etc/systemd/system/s-ui.service -f - rm /etc/systemd/system/sing-box.service -f systemctl daemon-reload systemctl reset-failed rm /etc/s-ui/ -rf @@ -254,9 +251,20 @@ set_setting() { view_setting() { /usr/local/s-ui/sui setting -show + view_uri before_show_menu } +view_uri() { + info=$(/usr/local/s-ui/sui uri) + if [[ $? != 0 ]]; then + LOGE "Get current uri error" + before_show_menu + fi + LOGI "You may access the Panel with following URL(s):" + echo -e "${green}${info}${plain}" +} + start() { check_status $1 if [[ $? == 0 ]]; then @@ -315,7 +323,6 @@ restart() { status() { systemctl status s-ui -l - systemctl status sing-box -l if [[ $# == 0 ]]; then before_show_menu fi @@ -763,10 +770,10 @@ show_usage() { echo -e "------------------------------------------" echo -e "SUBCOMMANDS:" echo -e "s-ui - Admin Management Script" - echo -e "s-ui start - Start s-ui and sing-box" - echo -e "s-ui stop - Stop s-ui and sing-box" - echo -e "s-ui restart - Restart s-ui and sing-box" - echo -e "s-ui status - Current Status of s-ui and sing-box" + echo -e "s-ui start - Start s-ui" + echo -e "s-ui stop - Stop s-ui" + echo -e "s-ui restart - Restart s-ui" + echo -e "s-ui status - Current Status of s-ui" echo -e "s-ui enable - Enable Autostart on OS Startup" echo -e "s-ui disable - Disable Autostart on OS Startup" echo -e "s-ui log - Check s-ui Logs" @@ -804,22 +811,13 @@ show_menu() { ${green}16.${plain} S-UI Enable Autostart ${green}17.${plain} S-UI Disable Autostart ———————————————————————————————— - ${green}18.${plain} Sing-Box Start - ${green}19.${plain} Sing-Box Stop - ${green}20.${plain} Sing-Box Restart - ${green}21.${plain} Sing-Box Check State - ${green}22.${plain} Sing-Box Check Logs - ${green}23.${plain} Sing-Box Enable Autostart - ${green}24.${plain} Sing-Box Disable Autostart -———————————————————————————————— - ${green}25.${plain} Enable or Disable BBR - ${green}26.${plain} SSL Certificate Management - ${green}27.${plain} Cloudflare SSL Certificate + ${green}18.${plain} Enable or Disable BBR + ${green}19.${plain} SSL Certificate Management + ${green}20.${plain} Cloudflare SSL Certificate ———————————————————————————————— " show_status s-ui - show_status sing-box - echo && read -p "Please enter your selection [0-27]: " num + echo && read -p "Please enter your selection [0-20]: " num case "${num}" in 0) @@ -877,37 +875,16 @@ show_menu() { check_install && disable s-ui ;; 18) - check_install && start sing-box - ;; - 19) - check_install && stop sing-box - ;; - 20) - check_install && restart sing-box - ;; - 21) - check_install && status sing-box - ;; - 22) - check_install && show_log sing-box - ;; - 23) - check_install && enable sing-box - ;; - 24) - check_install && disable sing-box - ;; - 25) bbr_menu ;; - 26) + 19) ssl_cert_issue_main ;; - 27) + 20) ssl_cert_issue_CF ;; *) - LOGE "Please enter the correct number [0-27]" + LOGE "Please enter the correct number [0-20]" ;; esac } @@ -915,22 +892,22 @@ show_menu() { if [[ $# > 0 ]]; then case $1 in "start") - check_install 0 && start s-ui 0 && start sing-box 0 + check_install 0 && start s-ui 0 ;; "stop") - check_install 0 && stop s-ui 0 && stop sing-box 0 + check_install 0 && stop s-ui 0 ;; "restart") - check_install 0 && restart s-ui 0 && restart sing-box 0 + check_install 0 && restart s-ui 0 ;; "status") check_install 0 && status 0 ;; "enable") - check_install 0 && enable s-ui 0 && enable sing-box 0 + check_install 0 && enable s-ui 0 ;; "disable") - check_install 0 && disable s-ui 0 && disable sing-box 0 + check_install 0 && disable s-ui 0 ;; "log") check_install 0 && show_log s-ui 0 diff --git a/service/client.go b/service/client.go new file mode 100644 index 0000000..82cdbe1 --- /dev/null +++ b/service/client.go @@ -0,0 +1,371 @@ +package service + +import ( + "encoding/json" + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util" + "s-ui/util/common" + "strings" + "time" + + "gorm.io/gorm" +) + +type ClientService struct { + InboundService +} + +func (s *ClientService) Get(id string) (*[]model.Client, error) { + if id == "" { + return s.GetAll() + } + return s.getById(id) +} + +func (s *ClientService) getById(id string) (*[]model.Client, error) { + db := database.GetDB() + var client []model.Client + err := db.Model(model.Client{}).Where("id in ?", strings.Split(id, ",")).Scan(&client).Error + if err != nil { + return nil, err + } + + return &client, nil +} + +func (s *ClientService) GetAll() (*[]model.Client, error) { + db := database.GetDB() + var clients []model.Client + err := db.Model(model.Client{}).Select("`id`, `enable`, `name`, `desc`, `group`, `inbounds`, `up`, `down`, `volume`, `expiry`").Scan(&clients).Error + if err != nil { + return nil, err + } + return &clients, nil +} + +func (s *ClientService) Save(tx *gorm.DB, act string, data json.RawMessage, hostname string) ([]uint, error) { + var err error + var inboundIds []uint + + switch act { + case "new", "edit": + var client model.Client + err = json.Unmarshal(data, &client) + if err != nil { + return nil, err + } + err = json.Unmarshal(client.Inbounds, &inboundIds) + if err != nil { + return nil, err + } + err = s.updateLinksWithFixedInbounds(tx, []*model.Client{&client}, inboundIds, hostname) + if err != nil { + return nil, err + } + err = tx.Save(&client).Error + if err != nil { + return nil, err + } + case "addbulk": + var clients []*model.Client + err = json.Unmarshal(data, &clients) + if err != nil { + return nil, err + } + err = json.Unmarshal(clients[0].Inbounds, &inboundIds) + if err != nil { + return nil, err + } + err = s.updateLinksWithFixedInbounds(tx, clients, inboundIds, hostname) + if err != nil { + return nil, err + } + err = tx.Save(clients).Error + if err != nil { + return nil, err + } + 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 + } + } + for index, client := range clients { + var clientLinks []map[string]string + err = json.Unmarshal(client.Links, &clientLinks) + if err != nil { + return err + } + + newClientLinks := []map[string]string{} + 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) UpdateClientsOnInboundAdd(tx *gorm.DB, initIds string, inboundId uint, hostname string) error { + clientIds := strings.Split(initIds, ",") + var clients []model.Client + err := tx.Model(model.Client{}).Where("id in ?", clientIds).Find(&clients).Error + if err != nil { + return err + } + var inbound model.Inbound + err = tx.Model(model.Inbound{}).Preload("Tls").Where("id = ?", inboundId).Find(&inbound).Error + if err != nil { + return err + } + for _, client := range clients { + // Add inbounds + var clientInbounds []uint + json.Unmarshal(client.Inbounds, &clientInbounds) + clientInbounds = append(clientInbounds, inboundId) + client.Inbounds, err = json.MarshalIndent(clientInbounds, "", " ") + if err != nil { + return err + } + // Add links + 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) 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 && database.IsNotFound(err) { + 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() + + tx := db.Begin() + defer func() { + if err == nil { + tx.Commit() + if len(inboundIds) > 0 && corePtr.IsRunning() { + err1 := s.InboundService.RestartInbounds(db, 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 err + } + + dt := time.Now().Unix() + for _, client := range clients { + logger.Debug("Client ", client.Name, " is going to be disabled") + users = append(users, client.Name) + var userInbounds []uint + json.Unmarshal(client.Inbounds, &userInbounds) + inboundIds = s.uniqueAppendInboundIds(inboundIds, userInbounds) + changes = append(changes, model.Changes{ + DateTime: dt, + Actor: "DepleteJob", + Key: "clients", + Action: "disable", + Obj: json.RawMessage("\"" + client.Name + "\""), + }) + } + + // Save changes + if len(changes) > 0 { + 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 err + } + err = tx.Model(model.Changes{}).Create(&changes).Error + if err != nil { + return err + } + LastUpdate = dt + } + + 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/service/config.go b/service/config.go new file mode 100644 index 0000000..4551bfa --- /dev/null +++ b/service/config.go @@ -0,0 +1,264 @@ +package service + +import ( + "encoding/json" + "s-ui/core" + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util/common" + "strconv" + "time" +) + +var ( + LastUpdate int64 + corePtr *core.Core +) + +type ConfigService struct { + ClientService + TlsService + SettingService + InboundService + OutboundService + EndpointService +} + +type SingBoxConfig struct { + Log json.RawMessage `json:"log"` + Dns json.RawMessage `json:"dns"` + 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(core *core.Core) *ConfigService { + corePtr = core + return &ConfigService{} +} + +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 + } + } + singboxConfig := SingBoxConfig{} + err = json.Unmarshal([]byte(data), &singboxConfig) + if err != nil { + return nil, err + } + + singboxConfig.Inbounds, err = s.InboundService.GetAllConfig(database.GetDB()) + if err != nil { + return nil, err + } + singboxConfig.Outbounds, err = s.OutboundService.GetAllConfig(database.GetDB()) + if err != nil { + return nil, err + } + singboxConfig.Endpoints, err = s.EndpointService.GetAllConfig(database.GetDB()) + if err != nil { + return nil, err + } + return &singboxConfig, nil +} + +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, initUsers string, loginUser string, hostname string) ([]string, error) { + var err error + var inboundIds []uint + var inboundId uint + var objs []string = []string{obj} + + db := database.GetDB() + tx := db.Begin() + defer func() { + if err == nil { + tx.Commit() + if len(inboundIds) > 0 && corePtr.IsRunning() { + err1 := s.InboundService.RestartInbounds(db, 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("") + } + } else { + tx.Rollback() + } + }() + + switch obj { + case "clients": + inboundIds, err = s.ClientService.Save(tx, act, data, hostname) + objs = append(objs, "inbounds") + case "tls": + inboundIds, err = s.TlsService.Save(tx, act, data) + case "inbounds": + inboundId, err = s.InboundService.Save(tx, act, data, initUsers, 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 nil, err + } + err = s.restartCoreWithConfig(data) + case "settings": + err = s.SettingService.Save(tx, data) + default: + return nil, common.NewError("unknown object: ", obj) + } + if err != nil { + return nil, err + } + + dt := time.Now().Unix() + 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() + tx = db.Begin() + + // Update side changes + + // Update client links + if obj == "tls" && len(inboundIds) > 0 { + err = s.ClientService.UpdateLinksByInboundChange(tx, inboundIds, hostname) + if err != nil { + return nil, err + } + objs = append(objs, "clients") + } + if obj == "inbounds" { + switch act { + case "new": + err = s.ClientService.UpdateClientsOnInboundAdd(tx, initUsers, inboundId, hostname) + 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") + } + + // 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 objs, nil +} + +func (s *ConfigService) CheckChanges(lu string) (bool, error) { + if lu == "" { + return true, nil + } + if LastUpdate == 0 { + db := database.GetDB() + var count int64 + err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error + if err == nil { + LastUpdate = time.Now().Unix() + } + return count > 0, err + } else { + intLu, err := strconv.ParseInt(lu, 10, 64) + return LastUpdate > intLu, err + } +} + +func (s *ConfigService) GetChanges(actor string, chngKey string, count string) []model.Changes { + c, _ := strconv.Atoi(count) + whereString := "`id`>0" + if len(actor) > 0 { + whereString += " and `actor`='" + actor + "'" + } + if len(chngKey) > 0 { + whereString += " and `key`='" + chngKey + "'" + } + db := database.GetDB() + var chngs []model.Changes + err := db.Model(model.Changes{}).Where(whereString).Order("`id` desc").Limit(c).Scan(&chngs).Error + if err != nil { + logger.Warning(err) + } + return chngs +} diff --git a/service/endpoints.go b/service/endpoints.go new file mode 100644 index 0000000..92df592 --- /dev/null +++ b/service/endpoints.go @@ -0,0 +1,139 @@ +package service + +import ( + "encoding/json" + "os" + "s-ui/database" + "s-ui/database/model" + "s-ui/util/common" + + "gorm.io/gorm" +) + +type EndpointService struct { + WarpService +} + +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, + "ext": endpoint.Ext, + } + 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 endpoint.Type == "warp" { + if act == "new" { + err = s.WarpService.RegisterWarp(&endpoint) + if err != nil { + return err + } + } else { + var old_license string + err = tx.Model(model.Endpoint{}).Select("json_extract(ext, '$.license_key')").Where("id = ?", endpoint.Id).Find(&old_license).Error + if err != nil { + return err + } + err = s.WarpService.SetWarpLicense(old_license, &endpoint) + 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/service/inbounds.go b/service/inbounds.go new file mode 100644 index 0000000..c3f1dfb --- /dev/null +++ b/service/inbounds.go @@ -0,0 +1,345 @@ +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 { + var shadowtls_version uint + 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"] + if inbound.Type == "shadowtls" { + json.Unmarshal(restFields["version"], &shadowtls_version) + } + } + switch inbound.Type { + case "mixed", "socks", "http", "shadowsocks", "vmess", "trojan", "naive", "hysteria", "shadowtls", "tuic", "hysteria2", "vless": + if inbound.Type == "shadowtls" && shadowtls_version < 3 { + break + } + users := []string{} + err = db.Raw("SELECT clients.name FROM clients, json_each(clients.inbounds) as je WHERE je.value = ?", inbound.Id).Scan(&users).Error + if err != nil { + return nil, err + } + if len(users) > 0 || inbound.Type != "shadowsocks" { + inbData["users"] = users + } + } + + 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, initUserIds string, 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 + } + if inbound.TlsId > 0 { + err = tx.Model(model.Tls{}).Where("id = ?", inbound.TlsId).Find(&inbound.Tls).Error + 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 + } + id = inbound.Id + + 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 + } + + if act == "edit" { + inboundConfig, err = s.addUsers(tx, inboundConfig, inbound.Id, inbound.Type) + } else { + inboundConfig, err = s.initUsers(tx, inboundConfig, initUserIds, inbound.Type) + } + if err != nil { + return 0, err + } + + err = corePtr.AddInbound(inboundConfig) + 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 == "shadowtls" { + version, _ := inbound["version"].(float64) + if int(version) < 3 { + return inboundJson, nil + } + } + 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)) + } + + if len(usersJson) > 0 || inboundType != "shadowsocks" { + inbound["users"] = usersJson + } + + return json.Marshal(inbound) +} + +func (s *InboundService) initUsers(db *gorm.DB, inboundJson []byte, clientIds string, inboundType string) ([]byte, error) { + ClientIds := strings.Split(clientIds, ",") + if len(ClientIds) == 0 { + return inboundJson, nil + } + 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 == "shadowtls" { + version, _ := inbound["version"].(float64) + if int(version) < 3 { + return inboundJson, nil + } + } + 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 + WHERE enable = true AND id in ?`, + "$."+inboundType, ClientIds).Scan(&users).Error + if err != nil { + return nil, err + } + var usersJson []json.RawMessage + for _, user := range users { + usersJson = append(usersJson, json.RawMessage(user)) + } + + if len(usersJson) > 0 || inboundType != "shadowsocks" { + 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/service/outbounds.go b/service/outbounds.go new file mode 100644 index 0000000..07b606c --- /dev/null +++ b/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/panel.go b/service/panel.go similarity index 100% rename from backend/service/panel.go rename to service/panel.go diff --git a/backend/service/server.go b/service/server.go similarity index 53% rename from backend/service/server.go rename to service/server.go index 33bc5a0..60cd1b9 100644 --- a/backend/service/server.go +++ b/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,70 @@ func (s *ServerService) GetSystemInfo() map[string]interface{} { return info } -func (s *ServerService) GetLogs(service string, 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() +func (s *ServerService) GetLogs(count string, level string) []string { + c, err := strconv.Atoi(count) if err != nil { - return []string{"Failed to get logs!", err.Error()} + c = 10 } - 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 s.generateWireGuardKey(options) } - // 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, ",") + if len(parts) != 2 { + return []string{"Failed to generate ECH keypair: ", "invalid 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 (s *ServerService) generateWireGuardKey(pk string) []string { + if len(pk) > 0 { + key, _ := wgtypes.ParseKey(pk) + return []string{key.PublicKey().String()} + } + wgKeys, err := wgtypes.GeneratePrivateKey() + if err != nil { + return []string{"Failed to generate wireguard keypair: ", err.Error()} + } + return []string{"PrivateKey: " + wgKeys.String(), "PublicKey: " + wgKeys.PublicKey().String()} } diff --git a/backend/service/setting.go b/service/setting.go similarity index 88% rename from backend/service/setting.go rename to service/setting.go index ea48a9f..7a58be7 100644 --- a/backend/service/setting.go +++ b/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,24 @@ import ( "gorm.io/gorm" ) +var defaultConfig = `{ + "log": { + "level": "info" + }, + "dns": {}, + "route": { + "rules": [ + { + "protocol": [ + "dns" + ], + "action": "hijack-dns" + } + ] + }, + "experimental": {} +}` + var defaultValueMap = map[string]string{ "webListen": "", "webDomain": "", @@ -37,6 +56,8 @@ var defaultValueMap = map[string]string{ "subShowInfo": "false", "subURI": "", "subJsonExt": "", + "config": defaultConfig, + "version": config.GetVersion(), } type SettingService struct { @@ -67,6 +88,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,13 +334,30 @@ func (s *SettingService) GetFinalSubURI(host string) (string, error) { return protocol + "://" + host + port + (*allSetting)["subPath"], nil } -func (s *SettingService) Save(tx *gorm.DB, changes []model.Changes) error { - var err error - for _, change := range changes { - key := change.Key - var obj string - json.Unmarshal(change.Obj, &obj) +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, data json.RawMessage) error { + var err error + var settings map[string]string + err = json.Unmarshal(data, &settings) + if err != nil { + return err + } + for key, obj := range settings { // Secure file existance check if obj != "" && (key == "webCertFile" || key == "webKeyFile" || diff --git a/backend/service/stats.go b/service/stats.go similarity index 77% rename from backend/service/stats.go rename to service/stats.go index fd7bf36..876b1e1 100644 --- a/backend/service/stats.go +++ b/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/service/tls.go b/service/tls.go new file mode 100644 index 0000000..11d23ab --- /dev/null +++ b/service/tls.go @@ -0,0 +1,68 @@ +package service + +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) { + db := database.GetDB() + tlsConfig := []model.Tls{} + err := db.Model(model.Tls{}).Scan(&tlsConfig).Error + if err != nil { + return nil, err + } + + return tlsConfig, nil +} + +func (s *TlsService) Save(tx *gorm.DB, action string, data json.RawMessage) ([]uint, error) { + var err error + var inboundIds []uint + + switch action { + case "new", "edit": + var tls model.Tls + err = json.Unmarshal(data, &tls) + if err != nil { + return nil, err + } + err = tx.Save(&tls).Error + if err != nil { + 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 nil, nil +} diff --git a/backend/service/user.go b/service/user.go similarity index 59% rename from backend/service/user.go rename to service/user.go index 6e2920d..c0f2e43 100644 --- a/backend/service/user.go +++ b/service/user.go @@ -1,13 +1,12 @@ package service import ( + "encoding/json" "s-ui/database" "s-ui/database/model" "s-ui/logger" "s-ui/util/common" "time" - - "gorm.io/gorm" ) type UserService struct { @@ -63,7 +62,7 @@ func (s *UserService) CheckUser(username string, password string, remoteIP strin Where("username = ? and password = ?", username, password). First(user). Error - if err == gorm.ErrRecordNotFound { + if database.IsNotFound(err) { return nil } else if err != nil { logger.Warning("check user err:", err, " IP: ", remoteIP) @@ -101,3 +100,61 @@ func (s *UserService) ChangePass(id string, oldPass string, newUser string, newP user.Password = newPass return db.Save(user).Error } + +func (s *UserService) LoadTokens() ([]byte, error) { + db := database.GetDB() + var tokens []model.Tokens + err := db.Model(model.Tokens{}).Preload("User").Where("expiry == 0 or expiry > ?", time.Now().Unix()).Find(&tokens).Error + if err != nil { + return nil, err + } + var result []map[string]interface{} + for _, t := range tokens { + result = append(result, map[string]interface{}{ + "token": t.Token, + "expiry": t.Expiry, + "username": t.User.Username, + }) + } + jsonResult, _ := json.MarshalIndent(result, "", " ") + return jsonResult, nil +} + +func (s *UserService) GetUserTokens(username string) (*[]model.Tokens, error) { + db := database.GetDB() + var token []model.Tokens + err := db.Model(model.Tokens{}).Select("id,desc,'****' as token,expiry,user_id").Where("user_id = (select id from users where username = ?)", username).Find(&token).Error + if err != nil && !database.IsNotFound(err) { + println(err.Error()) + return nil, err + } + return &token, nil +} + +func (s *UserService) AddToken(username string, expiry int64, desc string) (string, error) { + db := database.GetDB() + var userId uint + err := db.Model(model.User{}).Where("username = ?", username).Select("id").Scan(&userId).Error + if err != nil { + return "", err + } + if expiry > 0 { + expiry = expiry*86400 + time.Now().Unix() + } + token := &model.Tokens{ + Token: common.Random(32), + Desc: desc, + Expiry: expiry, + UserId: userId, + } + err = db.Create(token).Error + if err != nil { + return "", err + } + return token.Token, nil +} + +func (s *UserService) DeleteToken(id string) error { + db := database.GetDB() + return db.Model(model.Tokens{}).Where("id = ?", id).Delete(&model.Tokens{}).Error +} diff --git a/service/warp.go b/service/warp.go new file mode 100644 index 0000000..7733dd6 --- /dev/null +++ b/service/warp.go @@ -0,0 +1,229 @@ +package service + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util/common" + "strconv" + "time" + + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +type WarpService struct{} + +func (s *WarpService) getWarpInfo(ep *model.Endpoint) ([]byte, error) { + var warpData map[string]string + err := json.Unmarshal(ep.Ext, &warpData) + if err != nil { + return nil, err + } + + url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"]) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+warpData["access_token"]) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} + +func (s *WarpService) RegisterWarp(ep *model.Endpoint) error { + tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + privateKey, _ := wgtypes.GenerateKey() + publicKey := privateKey.PublicKey().String() + hostName, _ := os.Hostname() + + data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "s-ui", "name": "%s"}`, publicKey, tos, hostName) + url := "https://api.cloudflareclient.com/v0a2158/reg" + + req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data))) + if err != nil { + return err + } + + req.Header.Add("CF-Client-Version", "a-7.21-0721") + req.Header.Add("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return err + } + + var rspData map[string]interface{} + err = json.Unmarshal(buffer.Bytes(), &rspData) + if err != nil { + return err + } + + deviceId := rspData["id"].(string) + token := rspData["token"].(string) + license, ok := rspData["account"].(map[string]interface{})["license"].(string) + if !ok { + logger.Debug("Error accessing license value.") + return err + } + + warpData := map[string]string{ + "access_token": token, + "device_id": deviceId, + "license_key": license, + } + + ep.Ext, err = json.MarshalIndent(warpData, "", " ") + if err != nil { + return err + } + + warpInfo, err := s.getWarpInfo(ep) + if err != nil { + return err + } + + var warpDetails map[string]interface{} + err = json.Unmarshal(warpInfo, &warpDetails) + if err != nil { + return err + } + + warpConfig, _ := warpDetails["config"].(map[string]interface{}) + clientId, _ := warpConfig["client_id"].(string) + reserved := s.getReserved(clientId) + interfaceConfig, _ := warpConfig["interface"].(map[string]interface{}) + addresses, _ := interfaceConfig["addresses"].(map[string]interface{}) + v4, _ := addresses["v4"].(string) + v6, _ := addresses["v6"].(string) + peer, _ := warpConfig["peers"].([]interface{})[0].(map[string]interface{}) + peerEndpoint, _ := peer["endpoint"].(map[string]interface{})["host"].(string) + peerEpAddress, peerEpPort, err := net.SplitHostPort(peerEndpoint) + if err != nil { + return err + } + peerPublicKey, _ := peer["public_key"].(string) + peerPort, _ := strconv.Atoi(peerEpPort) + + peers := []map[string]interface{}{ + { + "address": peerEpAddress, + "port": peerPort, + "public_key": peerPublicKey, + "allowed_ips": []string{"0.0.0.0/0", "::/0"}, + "reserved": reserved, + }, + } + + var epOptions map[string]interface{} + err = json.Unmarshal(ep.Options, &epOptions) + if err != nil { + return err + } + epOptions["private_key"] = privateKey.String() + epOptions["address"] = []string{fmt.Sprintf("%s/32", v4), fmt.Sprintf("%s/128", v6)} + epOptions["listen_port"] = 0 + epOptions["peers"] = peers + + ep.Options, err = json.MarshalIndent(epOptions, "", " ") + return err +} + +func (s *WarpService) getReserved(clientID string) []int { + var reserved []int + decoded, err := base64.StdEncoding.DecodeString(clientID) + if err != nil { + return nil + } + + hexString := "" + for _, char := range decoded { + hex := fmt.Sprintf("%02x", char) + hexString += hex + } + + for i := 0; i < len(hexString); i += 2 { + hexByte := hexString[i : i+2] + decValue, err := strconv.ParseInt(hexByte, 16, 32) + if err != nil { + return nil + } + reserved = append(reserved, int(decValue)) + } + + return reserved +} + +func (s *WarpService) SetWarpLicense(old_license string, ep *model.Endpoint) error { + var warpData map[string]string + err := json.Unmarshal(ep.Ext, &warpData) + if err != nil { + return err + } + + if warpData["license_key"] == old_license { + return nil + } + + url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"]) + data := fmt.Sprintf(`{"license": "%s"}`, warpData["license_key"]) + + req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data))) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+warpData["access_token"]) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + buffer := bytes.NewBuffer(make([]byte, 8192)) + buffer.Reset() + _, err = buffer.ReadFrom(resp.Body) + if err != nil { + return err + } + var response map[string]interface{} + err = json.Unmarshal(buffer.Bytes(), &response) + if err != nil { + return err + } + + if success, ok := response["success"].(bool); ok && success == false { + errorArr, _ := response["errors"].([]interface{}) + errorObj := errorArr[0].(map[string]interface{}) + return common.NewError(errorObj["code"], errorObj["message"]) + } + + return nil +} diff --git a/sing-box.service b/sing-box.service deleted file mode 100644 index 8103f60..0000000 --- a/sing-box.service +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=sing-box service -Documentation=https://sing-box.sagernet.org -After=network.target nss-lookup.target network-online.target - -[Service] -CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH -WorkingDirectory=/usr/local/s-ui/bin/ -ExecStart=/usr/local/s-ui/bin/runSingbox.sh -ExecReload=/bin/kill -HUP $MAINPID -Restart=on-failure -RestartSec=10s -LimitNOFILE=infinity - -[Install] -WantedBy=multi-user.target diff --git a/backend/sub/jsonService.go b/sub/jsonService.go similarity index 86% rename from backend/sub/jsonService.go rename to sub/jsonService.go index 48c5c93..f2c4fbe 100644 --- a/backend/sub/jsonService.go +++ b/sub/jsonService.go @@ -21,7 +21,6 @@ const defaultJson = ` "mtu": 9000, "auto_route": true, "strict_route": false, - "sniff": true, "endpoint_independent_nat": false, "stack": "system", "platform": { @@ -36,7 +35,6 @@ const defaultJson = ` "type": "mixed", "listen": "127.0.0.1", "listen_port": 2080, - "sniff": true, "users": [] } ] @@ -87,27 +85,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 +114,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 +159,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 @@ -212,14 +202,6 @@ func (j *JsonService) addDefaultOutbounds(outbounds *[]map[string]interface{}, o "type": "direct", "tag": "direct", }, - { - "type": "dns", - "tag": "dns-out", - }, - { - "type": "block", - "tag": "block", - }, } *outbounds = append(outbound, *outbounds...) } diff --git a/backend/sub/linkService.go b/sub/linkService.go similarity index 100% rename from backend/sub/linkService.go rename to sub/linkService.go diff --git a/backend/sub/sub.go b/sub/sub.go similarity index 100% rename from backend/sub/sub.go rename to sub/sub.go diff --git a/backend/sub/subHandler.go b/sub/subHandler.go similarity index 100% rename from backend/sub/subHandler.go rename to sub/subHandler.go diff --git a/backend/sub/subService.go b/sub/subService.go similarity index 100% rename from backend/sub/subService.go rename to sub/subService.go diff --git a/backend/util/base64.go b/util/base64.go similarity index 100% rename from backend/util/base64.go rename to util/base64.go diff --git a/backend/util/common/err.go b/util/common/err.go similarity index 100% rename from backend/util/common/err.go rename to util/common/err.go diff --git a/backend/util/common/random.go b/util/common/random.go similarity index 89% rename from backend/util/common/random.go rename to util/common/random.go index b6cea34..5352b78 100644 --- a/backend/util/common/random.go +++ b/util/common/random.go @@ -24,3 +24,7 @@ func Random(n int) string { } return string(runes) } + +func RandomInt(n int) int { + return rnd.Intn(n) +} diff --git a/util/genLink.go b/util/genLink.go new file mode 100644 index 0000000..72200d0 --- /dev/null +++ b/util/genLink.go @@ -0,0 +1,552 @@ +package util + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "s-ui/database/model" + "s-ui/util/common" + "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 { + tls = prepareTls(i.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 i.TlsId > 0 { + newTls := map[string]interface{}{} + for k, v := range tls { + newTls[k] = v + } + + // Override tls + if addrTls, ok := addr["tls"].(map[string]interface{}); ok { + 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 prepareTls(t *model.Tls) map[string]interface{} { + var iTls, oTls map[string]interface{} + json.Unmarshal(t.Client, &oTls) + json.Unmarshal(t.Server, &iTls) + + for k, v := range iTls { + switch k { + case "enabled", "server_name", "alpn": + oTls[k] = v + case "reality": + reality := v.(map[string]interface{}) + clientReality := oTls["reality"].(map[string]interface{}) + clientReality["enabled"] = reality["enabled"] + if short_ids, hasSIds := reality["short_ids"].([]interface{}); hasSIds && len(short_ids) > 0 { + clientReality["short_id"] = short_ids[common.RandomInt(len(short_ids))] + } + oTls["reality"] = clientReality + } + } + return oTls +} + +func shadowsocksLink( + userConfig map[string]map[string]interface{}, + inbound map[string]interface{}, + addrs []map[string]interface{}) []string { + + var userPass []string + method, _ := inbound["method"].(string) + if strings.HasPrefix(method, "2022") { + inbPass, _ := inbound["password"].(string) + userPass = append(userPass, inbPass) + } + 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) + + 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["add"], _ = 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 +} + +func getTlsParams(t interface{}) map[string]string { + params := map[string]string{} + if tls, hasTls := t.(map[string]interface{}); hasTls { + 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["insecure"] = "1" + } + } + return params +} diff --git a/backend/util/linkToJson.go b/util/linkToJson.go similarity index 100% rename from backend/util/linkToJson.go rename to util/linkToJson.go diff --git a/util/outJson.go b/util/outJson.go new file mode 100644 index 0000000..cc5b291 --- /dev/null +++ b/util/outJson.go @@ -0,0 +1,190 @@ +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 + } + if ech, ok := tlsServer["ech"].(map[string]interface{}); ok && ech["enabled"].(bool) { + echConfig := tlsConfig["ech"].(map[string]interface{}) + echConfig["enabled"] = true + echConfig["pq_signature_schemes_enabled"] = ech["pq_signature_schemes_enabled"] + echConfig["dynamic_record_sizing_disabled"] = ech["dynamic_record_sizing_disabled"] + tlsConfig["ech"] = echConfig + } + + (*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/backend/web/web.go b/web/web.go similarity index 97% rename from backend/web/web.go rename to web/web.go index 2fbfd81..5ac72a7 100644 --- a/backend/web/web.go +++ b/web/web.go @@ -102,8 +102,11 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine.StaticFS(assetsBasePath, http.FS(assetsFS)) + group_apiv2 := engine.Group(base_url + "apiv2") + apiv2 := api.NewAPIv2Handler(group_apiv2) + group_api := engine.Group(base_url + "api") - api.NewAPIHandler(group_api) + api.NewAPIHandler(group_api, apiv2) // Serve index.html as the entry point // Handle all other routes by serving index.html