From f40b27fd8bf00f0721095ab3d28dee5060803245 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Tue, 13 Feb 2024 01:17:03 +0100 Subject: [PATCH] initial commit --- .github/ISSUE_TEMPLATE/bug_report.md | 38 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/ISSUE_TEMPLATE/question-template.md | 10 + .github/dependabot.yml | 14 + .github/workflows/docker.yml | 55 + .github/workflows/release.yml | 90 + .gitignore | 23 + Dockerfile | 23 + LICENSE | 674 +++ README.md | 139 + backend/.gitignore | 23 + backend/api/api.go | 164 + backend/api/session.go | 59 + backend/api/utils.go | 80 + backend/app/app.go | 102 + backend/config/config.go | 77 + backend/config/config.json | 38 + backend/config/name | 1 + backend/config/version | 1 + backend/cronjob/cronJob.go | 37 + backend/cronjob/delStatsJob.go | 22 + backend/cronjob/depleteJob.go | 22 + backend/cronjob/statsJob.go | 22 + backend/database/db.go | 79 + backend/database/model/model.go | 48 + backend/go.mod | 67 + backend/go.sum | 302 + backend/logger/logger.go | 116 + backend/main.go | 38 + backend/middleware/domainValidator.go | 21 + backend/network/auto_https_conn.go | 67 + backend/network/auto_https_listener.go | 21 + backend/service/client.go | 93 + backend/service/config.go | 322 ++ backend/service/panel.go | 26 + backend/service/server.go | 137 + backend/service/setting.go | 292 + backend/service/sinxbox.go | 42 + backend/service/stats.go | 100 + backend/service/user.go | 47 + backend/singbox/controller.go | 54 + backend/singbox/v2rayApi.go | 95 + backend/sub/sub.go | 154 + backend/sub/subHandler.go | 39 + backend/sub/subService.go | 181 + backend/util/common/err.go | 27 + backend/util/common/random.go | 13 + backend/web/web.go | 200 + build.sh | 13 + frontend/.browserslistrc | 4 + frontend/.editorconfig | 5 + frontend/.eslintrc.js | 14 + frontend/.gitignore | 23 + frontend/README.md | 69 + frontend/index.html | 16 + frontend/package-lock.json | 4964 +++++++++++++++++ frontend/package.json | 42 + frontend/public/assets/favicon.ico | Bin 0 -> 1562 bytes frontend/src/App.vue | 34 + .../src/assets/Vazirmatn-UI-NL-Regular.woff2 | Bin 0 -> 21840 bytes frontend/src/assets/logo.png | Bin 0 -> 763 bytes frontend/src/assets/logo.svg | 24 + frontend/src/components/DateTime.vue | 132 + frontend/src/components/Dial.vue | 216 + frontend/src/components/InMulitiplex.vue | 75 + frontend/src/components/InTLS.vue | 214 + frontend/src/components/Listen.vue | 154 + frontend/src/components/Main.vue | 218 + frontend/src/components/Network.vue | 29 + frontend/src/components/Transport.vue | 52 + frontend/src/components/Users.vue | 35 + frontend/src/components/message.vue | 18 + frontend/src/components/protocols/Direct.vue | 43 + .../src/components/protocols/Hysteria.vue | 63 + .../src/components/protocols/Hysteria2.vue | 91 + frontend/src/components/protocols/Naive.vue | 22 + .../src/components/protocols/ShadowTls.vue | 153 + .../src/components/protocols/Shadowsocks.vue | 44 + frontend/src/components/protocols/TProxy.vue | 22 + frontend/src/components/protocols/Tuic.vue | 66 + frontend/src/components/tiles/Gauge.vue | 111 + frontend/src/components/tiles/History.vue | 200 + frontend/src/components/transports/Http.vue | 75 + .../src/components/transports/HttpUpgrade.vue | 28 + .../src/components/transports/WebSocket.vue | 51 + frontend/src/components/transports/gRPC.vue | 65 + frontend/src/layouts/default/AppBar.vue | 44 + frontend/src/layouts/default/Default.vue | 27 + frontend/src/layouts/default/Drawer.vue | 67 + frontend/src/layouts/default/View.vue | 14 + frontend/src/layouts/modals/Client.vue | 230 + frontend/src/layouts/modals/Inbound.vue | 131 + frontend/src/layouts/modals/QrCode.vue | 84 + frontend/src/layouts/modals/Stats.vue | 186 + frontend/src/locales/en.ts | 160 + frontend/src/locales/fa.ts | 160 + frontend/src/locales/index.ts | 19 + frontend/src/main.ts | 38 + frontend/src/plugins/api.ts | 18 + frontend/src/plugins/httputil.ts | 66 + frontend/src/plugins/index.ts | 10 + frontend/src/plugins/link.ts | 235 + frontend/src/plugins/randomUtil.ts | 49 + frontend/src/plugins/utils.ts | 153 + frontend/src/plugins/vuetify.ts | 58 + frontend/src/router/index.ts | 98 + frontend/src/store/index.ts | 5 + frontend/src/store/modules/data.ts | 67 + frontend/src/store/modules/message.ts | 22 + frontend/src/styles/settings.scss | 39 + frontend/src/types/clients.ts | 115 + frontend/src/types/config.ts | 126 + frontend/src/types/dial.ts | 14 + frontend/src/types/inMultiplex.ts | 11 + frontend/src/types/inTls.ts | 19 + frontend/src/types/inbounds.ts | 242 + frontend/src/types/outTls.ts | 3 + frontend/src/types/outbounds.ts | 88 + frontend/src/types/transport.ts | 46 + frontend/src/views/Basics.vue | 346 ++ frontend/src/views/Clients.vue | 303 + frontend/src/views/Home.vue | 7 + frontend/src/views/Inbounds.vue | 289 + frontend/src/views/Login.vue | 86 + frontend/src/views/Outbounds.vue | 34 + frontend/src/views/Rules.vue | 62 + frontend/src/views/Settings.vue | 252 + frontend/src/vite-env.d.ts | 7 + frontend/tsconfig.json | 25 + frontend/tsconfig.node.json | 9 + frontend/vite.config.ts | 49 + install.sh | 132 + runSUI.sh | 2 + runSingbox.sh | 45 + s-ui.service | 14 + sing-box.service | 17 + 136 files changed, 16023 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/question-template.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/docker.yml create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 backend/.gitignore create mode 100644 backend/api/api.go create mode 100644 backend/api/session.go create mode 100644 backend/api/utils.go create mode 100644 backend/app/app.go create mode 100644 backend/config/config.go create mode 100644 backend/config/config.json create mode 100644 backend/config/name create mode 100644 backend/config/version create mode 100644 backend/cronjob/cronJob.go create mode 100644 backend/cronjob/delStatsJob.go create mode 100644 backend/cronjob/depleteJob.go create mode 100644 backend/cronjob/statsJob.go create mode 100644 backend/database/db.go create mode 100644 backend/database/model/model.go create mode 100644 backend/go.mod create mode 100644 backend/go.sum create mode 100644 backend/logger/logger.go create mode 100644 backend/main.go create mode 100644 backend/middleware/domainValidator.go create mode 100644 backend/network/auto_https_conn.go create mode 100644 backend/network/auto_https_listener.go create mode 100644 backend/service/client.go create mode 100644 backend/service/config.go create mode 100644 backend/service/panel.go create mode 100644 backend/service/server.go create mode 100644 backend/service/setting.go create mode 100644 backend/service/sinxbox.go create mode 100644 backend/service/stats.go create mode 100644 backend/service/user.go create mode 100644 backend/singbox/controller.go create mode 100644 backend/singbox/v2rayApi.go create mode 100644 backend/sub/sub.go create mode 100644 backend/sub/subHandler.go create mode 100644 backend/sub/subService.go create mode 100644 backend/util/common/err.go create mode 100644 backend/util/common/random.go create mode 100644 backend/web/web.go create mode 100755 build.sh create mode 100644 frontend/.browserslistrc create mode 100644 frontend/.editorconfig create mode 100644 frontend/.eslintrc.js create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/assets/favicon.ico create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/assets/Vazirmatn-UI-NL-Regular.woff2 create mode 100644 frontend/src/assets/logo.png create mode 100644 frontend/src/assets/logo.svg create mode 100644 frontend/src/components/DateTime.vue create mode 100644 frontend/src/components/Dial.vue create mode 100644 frontend/src/components/InMulitiplex.vue create mode 100644 frontend/src/components/InTLS.vue create mode 100644 frontend/src/components/Listen.vue create mode 100644 frontend/src/components/Main.vue create mode 100644 frontend/src/components/Network.vue create mode 100644 frontend/src/components/Transport.vue create mode 100644 frontend/src/components/Users.vue create mode 100644 frontend/src/components/message.vue create mode 100644 frontend/src/components/protocols/Direct.vue create mode 100644 frontend/src/components/protocols/Hysteria.vue create mode 100644 frontend/src/components/protocols/Hysteria2.vue create mode 100644 frontend/src/components/protocols/Naive.vue create mode 100644 frontend/src/components/protocols/ShadowTls.vue create mode 100644 frontend/src/components/protocols/Shadowsocks.vue create mode 100644 frontend/src/components/protocols/TProxy.vue create mode 100644 frontend/src/components/protocols/Tuic.vue create mode 100644 frontend/src/components/tiles/Gauge.vue create mode 100644 frontend/src/components/tiles/History.vue create mode 100644 frontend/src/components/transports/Http.vue create mode 100644 frontend/src/components/transports/HttpUpgrade.vue create mode 100644 frontend/src/components/transports/WebSocket.vue create mode 100644 frontend/src/components/transports/gRPC.vue create mode 100644 frontend/src/layouts/default/AppBar.vue create mode 100644 frontend/src/layouts/default/Default.vue create mode 100644 frontend/src/layouts/default/Drawer.vue create mode 100644 frontend/src/layouts/default/View.vue create mode 100644 frontend/src/layouts/modals/Client.vue create mode 100644 frontend/src/layouts/modals/Inbound.vue create mode 100644 frontend/src/layouts/modals/QrCode.vue create mode 100644 frontend/src/layouts/modals/Stats.vue create mode 100644 frontend/src/locales/en.ts create mode 100644 frontend/src/locales/fa.ts create mode 100644 frontend/src/locales/index.ts create mode 100644 frontend/src/main.ts create mode 100644 frontend/src/plugins/api.ts create mode 100644 frontend/src/plugins/httputil.ts create mode 100644 frontend/src/plugins/index.ts create mode 100644 frontend/src/plugins/link.ts create mode 100644 frontend/src/plugins/randomUtil.ts create mode 100644 frontend/src/plugins/utils.ts create mode 100644 frontend/src/plugins/vuetify.ts create mode 100644 frontend/src/router/index.ts create mode 100644 frontend/src/store/index.ts create mode 100644 frontend/src/store/modules/data.ts create mode 100644 frontend/src/store/modules/message.ts create mode 100644 frontend/src/styles/settings.scss create mode 100644 frontend/src/types/clients.ts create mode 100644 frontend/src/types/config.ts create mode 100644 frontend/src/types/dial.ts create mode 100644 frontend/src/types/inMultiplex.ts create mode 100644 frontend/src/types/inTls.ts create mode 100644 frontend/src/types/inbounds.ts create mode 100644 frontend/src/types/outTls.ts create mode 100644 frontend/src/types/outbounds.ts create mode 100644 frontend/src/types/transport.ts create mode 100644 frontend/src/views/Basics.vue create mode 100644 frontend/src/views/Clients.vue create mode 100644 frontend/src/views/Home.vue create mode 100644 frontend/src/views/Inbounds.vue create mode 100644 frontend/src/views/Login.vue create mode 100644 frontend/src/views/Outbounds.vue create mode 100644 frontend/src/views/Rules.vue create mode 100644 frontend/src/views/Settings.vue create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100755 install.sh create mode 100755 runSUI.sh create mode 100755 runSingbox.sh create mode 100644 s-ui.service create mode 100644 sing-box.service diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..f3d5c415 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question-template.md b/.github/ISSUE_TEMPLATE/question-template.md new file mode 100644 index 00000000..790bdf8d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question-template.md @@ -0,0 +1,10 @@ +--- +name: Question template +about: Ask if it is not clear that it is a bug +title: '' +labels: question +assignees: '' + +--- + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f25e6296 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..7365bf9c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,55 @@ +name: Docker Image CI +on: + push: + tags: + - "*" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + alireza7/s-ui + ghcr.io/alireza0/s-ui + tags: | + type=ref,event=branch + type=ref,event=tag + type=pep440,pattern={{version}} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64/v8 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..24cedb01 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,90 @@ +name: Release S-UI + +on: + push: + tags: + - "*" + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + platform: [amd64, arm64, arm] + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4.1.1 + + - name: Setup Go + uses: actions/setup-go@v5.0.0 + with: + go-version: '1.21' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies for arm64 and arm + if: matrix.platform == 'arm64' || matrix.platform == 'arm' + run: | + sudo apt-get update + sudo apt install gcc-aarch64-linux-gnu + if [ "${{ matrix.platform }}" == "arm" ]; then + sudo apt install gcc-arm-linux-gnueabihf + fi + + - name: Build frontend + run: | + cd frontend + npm install + npm run build + cd .. + mv frontend/dist backend/web/html + + - name: Set evironments + run: | + export CGO_ENABLED=1 + export GOOS=linux + export GOARCH=${{ matrix.platform }} + if [ "${{ matrix.platform }}" == "arm64" ]; then + export CC=aarch64-linux-gnu-gcc + elif [ "${{ matrix.platform }}" == "arm" ]; then + export CC=arm-linux-gnueabihf-gcc + fi + + - name: Build sing-box + run: | + git clone -b v1.8.5 https://github.com/SagerNet/sing-box + cd sing-box + go build -tags with_v2ray_api,with_clash_api,with_grpc,with_quic,with_ech -o sing-box ./cmd/sing-box + cd .. + + - name: Build s-ui + run: | + cd backend + go build -o ../sui main.go + cd .. + + mkdir s-ui + cp sui s-ui/ + cp s-ui.service s-ui/ + cp sing-box.service s-ui/ + mkdir s-ui/bin + cp sing-box/sing-box s-ui/bin/ + cp runSingbox.sh s-ui/bin/ + + - name: Package + run: tar -zcvf s-ui-linux-${{ matrix.platform }}.tar.gz s-ui + + - name: Upload + uses: svenstaro/upload-release-action@2.7.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + file: s-ui-linux-${{ matrix.platform }}.tar.gz + asset_name: s-ui-linux-${{ matrix.platform }}.tar.gz + prerelease: true + overwrite: true diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ff60407f --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +dist/ +release/ +backup/ +bin/ +db/ +sui +main +tmp +.sync* +*.tar.gz + +# local env files +.env.local +.env.*.local + +# Log files +*.log* +.cache + +# Editor directories and files +.idea +.vscode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7b899194 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM node:alpine as front-builder +WORKDIR /app +COPY frontend/ ./ +RUN npm install && npm run build + +FROM golang:1.21-alpine AS backend-builder +WORKDIR /app +ARG TARGETARCH +ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" +ENV CGO_ENABLED=1 +RUN apk --no-cache --update add build-base gcc wget unzip +COPY backend/ ./ +COPY --from=front-builder /app/dist/ /app/web/html/ +RUN go build -o sui main.go + +FROM alpine +LABEL org.opencontainers.image.authors="alireza7@gmail.com" +ENV TZ=Asia/Tehran +WORKDIR /app +RUN apk add --no-cache --update ca-certificates tzdata +COPY --from=backend-builder /app/sui /app/ +VOLUME [ "s-ui" ] +CMD [ "./sui" ] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e84afb9a --- /dev/null +++ b/README.md @@ -0,0 +1,139 @@ +# S-UI +**An Advanced Web Panel • Built on SagerNet/Sing-Box** + +![](https://img.shields.io/github/v/release/alireza0/s-ui.svg) +![](https://img.shields.io/docker/pulls/alireza7/s-ui.svg) +[![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) + +> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment + +**If you think this project is helpful to you, you may wish to give a**:star2: + +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/alireza7) + +- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz` + +## Quick Overview +| Features | Enable? | +| -------------------------------------- | :----------------: | +| Multi-Protocol | :heavy_check_mark: | +| Multi-Language | :heavy_check_mark: | +| Multi-Client/Inbound | :heavy_check_mark: | +| Advanced Traffic Routing Interface | :heavy_check_mark: | +| Client & Traffic & System Status | :heavy_check_mark: | +| Subscription Service (link + info) | :heavy_check_mark: | +| Dark/Light Theme | :heavy_check_mark: | + + +## Install & Upgrade to Latest Version + +```sh +bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) +``` + +## Install Custom Version + +**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `0.0.1`: + +```sh +bash <(curl -Ls https://raw.githubusercontent.com/alireza0/s-ui/master/install.sh) 0.0.1 +``` + +## Install using Docker + +
+ Click for details + +### Usage + +**Step 1:** Install Docker + +```shell +curl -fsSL https://get.docker.com | sh +``` + +**Step 2:** Install S-UI + +```shell +mkdir s-ui && cd s-ui +docker run -itd \ + -p 2095:2095 -p 443:443 -p 80:80 \ + -v $PWD/db/:/usr/local/s-ui/db/ \ + -v $PWD/cert/:/root/cert/ \ + --name s-ui --restart=unless-stopped \ + alireza7/s-ui:latest +``` + +> Build your own image + +```shell +docker build -t s-ui . +``` + +
+ +## Languages + +- English +- Farsi + +## Features + +- Supported protocols: + - General: Mixed, SOCKS, HTTP, HTTPS, Direct, Redirect, TProxy + - V2Ray based: VLESS, VMess, Trojan, Shadowsocks + - Other protocols: ShadowTLS, Hysteria, Hysteri2, 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 +- Clients’ traffic cap and expiration date +- Displays online clients, inbounds and outbounds with traffic statistics, and system status monitoring +- Subscription service with ability to add external links and subscription +- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate) +- Dark/Light theme + +## Recommended OS + +- CentOS 8+ +- Ubuntu 20+ +- Debian 10+ +- Fedora 36+ + +## Environment Variables + +
+ Click for details + +### Usage + +| Variable | Type | Default | +| -------------- | :--------------------------------------------: | :------------ | +| SUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` | +| SUI_DEBUG | `boolean` | `false` | +| SUI_BIN_FOLDER | `string` | `"bin"` | +| SUI_DB_FOLDER | `string` | `"db"` | +| SINGBOX_API | `string` | - | + +
+ +## SSL Certificate + +
+ Click for details + +### Certbot + +```bash +snap install core; snap refresh core +snap install --classic certbot +ln -s /snap/bin/certbot /usr/bin/certbot + +certbot certonly --standalone --register-unsafely-without-email --non-interactive --agree-tos -d +``` + +
+ +## Stargazers over Time + +[![Stargazers over time](https://starchart.cc/alireza0/s-ui.svg)](https://starchart.cc/alireza0/s-ui) diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..d9c57cc5 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +dist/ +release/ +backup/ +bin/ +sui +web/html +main +tmp +.sync* +*.tar.gz + +# local env files +.env.local +.env.*.local + +# Log files +*.log* +.cache + +# Editor directories and files +.idea +.vscode diff --git a/backend/api/api.go b/backend/api/api.go new file mode 100644 index 00000000..f0299a15 --- /dev/null +++ b/backend/api/api.go @@ -0,0 +1,164 @@ +package api + +import ( + "fmt" + "s-ui/logger" + "s-ui/service" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +type APIHandler struct { + service.SettingService + service.UserService + service.ConfigService + service.ClientService + service.PanelService + service.StatsService + service.ServerService +} + +func NewAPIHandler(g *gin.RouterGroup) { + a := &APIHandler{} + a.initRouter(g) +} + +func (a *APIHandler) initRouter(g *gin.RouterGroup) { + g.Use(func(c *gin.Context) { + if c.Request.URL.Path != "/api/login" && c.Request.URL.Path != "/api/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") + } + + if sessionMaxAge > 0 { + err = SetMaxAge(c, sessionMaxAge*60) + if err != nil { + logger.Infof("Unable to set session's max age") + } + } + + err = SetLoginUser(c, loginUser) + logger.Info("user ", loginUser, " login success") + + jsonMsg(c, "", nil) + 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) + 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 "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) + default: + jsonMsg(c, "API call", nil) + } +} + +func (a *APIHandler) loadData(c *gin.Context) (string, error) { + var data string + lu := c.Query("lu") + isUpdated, err := a.ConfigService.CheckChnages(lu) + if err != nil { + return "", err + } + onlines, err := a.StatsService.GetOnlines() + 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 + } + subURI, err := a.SettingService.GetFinalSubURI(strings.Split(c.Request.Host, ":")[0]) + if err != nil { + return "", err + } + data = fmt.Sprintf(`{"config": %s,"clients": %s,"subURI": "%s", "onlines": %s}`, string(*config), clients, subURI, onlines) + } else { + data = fmt.Sprintf(`{"onlines": %s}`, onlines) + } + + return data, nil +} diff --git a/backend/api/session.go b/backend/api/session.go new file mode 100644 index 00000000..d02e4606 --- /dev/null +++ b/backend/api/session.go @@ -0,0 +1,59 @@ +package api + +import ( + "encoding/gob" + "s-ui/database/model" + + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" +) + +const ( + loginUser = "LOGIN_USER" +) + +func init() { + gob.Register(model.User{}) +} + +func SetLoginUser(c *gin.Context, userName string) error { + s := sessions.Default(c) + s.Set(loginUser, userName) + return s.Save() +} + +func SetMaxAge(c *gin.Context, maxAge int) error { + s := sessions.Default(c) + s.Options(sessions.Options{ + Path: "/", + MaxAge: maxAge, + }) + return s.Save() +} + +func GetLoginUser(c *gin.Context) string { + s := sessions.Default(c) + obj := s.Get(loginUser) + if obj == nil { + return "" + } + objStr, ok := obj.(string) + if !ok { + return "" + } + return objStr +} + +func IsLogin(c *gin.Context) bool { + return GetLoginUser(c) != "" +} + +func ClearSession(c *gin.Context) { + s := sessions.Default(c) + s.Clear() + s.Options(sessions.Options{ + Path: "/", + MaxAge: -1, + }) + s.Save() +} diff --git a/backend/api/utils.go b/backend/api/utils.go new file mode 100644 index 00000000..93b062fa --- /dev/null +++ b/backend/api/utils.go @@ -0,0 +1,80 @@ +package api + +import ( + "net" + "net/http" + "s-ui/logger" + "strings" + + "github.com/gin-gonic/gin" +) + +type Msg struct { + Success bool `json:"success"` + Msg string `json:"msg"` + Obj interface{} `json:"obj"` +} + +func getRemoteIp(c *gin.Context) string { + value := c.GetHeader("X-Forwarded-For") + if value != "" { + ips := strings.Split(value, ",") + return ips[0] + } else { + addr := c.Request.RemoteAddr + ip, _, _ := net.SplitHostPort(addr) + return ip + } +} + +func jsonMsg(c *gin.Context, msg string, err error) { + jsonMsgObj(c, msg, nil, err) +} + +func jsonObj(c *gin.Context, obj interface{}, err error) { + jsonMsgObj(c, "", obj, err) +} + +func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) { + m := Msg{ + Obj: obj, + } + if err == nil { + m.Success = true + if msg != "" { + m.Msg = msg + } + } else { + m.Success = false + m.Msg = msg + err.Error() + logger.Warning("failed :", err) + } + c.JSON(http.StatusOK, m) +} + +func pureJsonMsg(c *gin.Context, success bool, msg string) { + if success { + c.JSON(http.StatusOK, Msg{ + Success: true, + Msg: msg, + }) + } else { + c.JSON(http.StatusOK, Msg{ + Success: false, + Msg: msg, + }) + } +} + +func checkLogin(c *gin.Context) { + if !IsLogin(c) { + if c.GetHeader("X-Requested-With") == "XMLHttpRequest" { + pureJsonMsg(c, false, "Not authorized") + } else { + c.Redirect(http.StatusTemporaryRedirect, "/login") + } + c.Abort() + } else { + c.Next() + } +} diff --git a/backend/app/app.go b/backend/app/app.go new file mode 100644 index 00000000..d9587b86 --- /dev/null +++ b/backend/app/app.go @@ -0,0 +1,102 @@ +package app + +import ( + "log" + "s-ui/config" + "s-ui/cronjob" + "s-ui/database" + "s-ui/logger" + "s-ui/service" + "s-ui/sub" + "s-ui/web" + + "github.com/op/go-logging" +) + +type APP struct { + service.SettingService + webServer *web.Server + subServer *sub.Server + cronJob *cronjob.CronJob +} + +func NewApp() *APP { + return &APP{} +} + +func (a *APP) Init() error { + log.Printf("%v %v", config.GetName(), config.GetVersion()) + + a.initLog() + + err := database.InitDB(config.GetDBPath()) + if err != nil { + return err + } + + a.cronJob = cronjob.NewCronJob() + a.webServer = web.NewServer() + a.subServer = sub.NewServer() + + configService := service.NewConfigService() + err = configService.InitConfig() + if err != nil { + return err + } + return nil +} + +func (a *APP) Start() error { + loc, err := a.SettingService.GetTimeLocation() + if err != nil { + return err + } + err = a.cronJob.Start(loc) + if err != nil { + return err + } + + err = a.webServer.Start() + if err != nil { + return err + } + + err = a.subServer.Start() + if err != nil { + return err + } + + return nil +} + +func (a *APP) Stop() { + a.cronJob.Stop() + err := a.subServer.Stop() + if err != nil { + logger.Warning("stop Sub Server err:", err) + } + err = a.webServer.Stop() + if err != nil { + logger.Warning("stop Web Server err:", err) + } +} + +func (a *APP) initLog() { + switch config.GetLogLevel() { + case config.Debug: + logger.InitLogger(logging.DEBUG) + case config.Info: + logger.InitLogger(logging.INFO) + case config.Warn: + logger.InitLogger(logging.WARNING) + case config.Error: + logger.InitLogger(logging.ERROR) + default: + log.Fatal("unknown log level:", config.GetLogLevel()) + } +} + +func (a *APP) RestartApp() { + a.Stop() + a.Start() +} diff --git a/backend/config/config.go b/backend/config/config.go new file mode 100644 index 00000000..f5f175a5 --- /dev/null +++ b/backend/config/config.go @@ -0,0 +1,77 @@ +package config + +import ( + _ "embed" + "fmt" + "os" + "strings" +) + +//go:embed version +var version string + +//go:embed name +var name string + +//go:embed config.json +var defaultConfig string + +type LogLevel string + +const ( + Debug LogLevel = "debug" + Info LogLevel = "info" + Warn LogLevel = "warn" + Error LogLevel = "error" +) + +func GetVersion() string { + return strings.TrimSpace(version) +} + +func GetName() string { + return strings.TrimSpace(name) +} + +func GetLogLevel() LogLevel { + if IsDebug() { + return Debug + } + logLevel := os.Getenv("SUI_LOG_LEVEL") + if logLevel == "" { + return Info + } + return LogLevel(logLevel) +} + +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 = "db" + } + return dbFolderPath +} + +func GetDBPath() string { + return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName()) +} + +func GetDefaultConfig() string { + return defaultConfig +} + +func GetEnvApi() string { + return os.Getenv("SINGBOX_API") +} diff --git a/backend/config/config.json b/backend/config/config.json new file mode 100644 index 00000000..f5441f9a --- /dev/null +++ b/backend/config/config.json @@ -0,0 +1,38 @@ +{ + "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/name b/backend/config/name new file mode 100644 index 00000000..037c3b93 --- /dev/null +++ b/backend/config/name @@ -0,0 +1 @@ +s-ui \ No newline at end of file diff --git a/backend/config/version b/backend/config/version new file mode 100644 index 00000000..bd52db81 --- /dev/null +++ b/backend/config/version @@ -0,0 +1 @@ +0.0.0 \ No newline at end of file diff --git a/backend/cronjob/cronJob.go b/backend/cronjob/cronJob.go new file mode 100644 index 00000000..9115f52e --- /dev/null +++ b/backend/cronjob/cronJob.go @@ -0,0 +1,37 @@ +package cronjob + +import ( + "time" + + "github.com/robfig/cron/v3" +) + +type CronJob struct { + cron *cron.Cron +} + +func NewCronJob() *CronJob { + return &CronJob{} +} + +func (c *CronJob) Start(loc *time.Location) error { + c.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) + c.cron.Start() + + go func() { + // Start stats job + c.cron.AddJob("@every 10s", NewStatsJob()) + // Start expiry job + c.cron.AddJob("@every 1m", NewDepleteJob()) + // Start deleting old stats + c.cron.AddJob("@daily", NewDelStatsJob()) + }() + + return nil +} + +func (c *CronJob) Stop() { + if c.cron != nil { + c.cron.Stop() + } +} diff --git a/backend/cronjob/delStatsJob.go b/backend/cronjob/delStatsJob.go new file mode 100644 index 00000000..f4451569 --- /dev/null +++ b/backend/cronjob/delStatsJob.go @@ -0,0 +1,22 @@ +package cronjob + +import ( + "s-ui/logger" + "s-ui/service" +) + +type DelStatsJob struct { + service.StatsService +} + +func NewDelStatsJob() *DelStatsJob { + return &DelStatsJob{} +} + +func (s *DelStatsJob) Run() { + err := s.StatsService.DelOldStats(30) + if err != nil { + logger.Warning("Deleting old statistics failed: ", err) + return + } +} diff --git a/backend/cronjob/depleteJob.go b/backend/cronjob/depleteJob.go new file mode 100644 index 00000000..0b0eb9b1 --- /dev/null +++ b/backend/cronjob/depleteJob.go @@ -0,0 +1,22 @@ +package cronjob + +import ( + "s-ui/logger" + "s-ui/service" +) + +type DepleteJob struct { + service.ConfigService +} + +func NewDepleteJob() *DepleteJob { + return new(DepleteJob) +} + +func (s *DepleteJob) Run() { + err := s.ConfigService.DepleteClients() + if err != nil { + logger.Warning("Disable depleted users failed: ", err) + return + } +} diff --git a/backend/cronjob/statsJob.go b/backend/cronjob/statsJob.go new file mode 100644 index 00000000..84a41249 --- /dev/null +++ b/backend/cronjob/statsJob.go @@ -0,0 +1,22 @@ +package cronjob + +import ( + "s-ui/logger" + "s-ui/service" +) + +type StatsJob struct { + service.SingBoxService +} + +func NewStatsJob() *StatsJob { + return new(StatsJob) +} + +func (s *StatsJob) Run() { + err := s.SingBoxService.GetStats() + if err != nil { + logger.Warning("Get stats failed: ", err) + return + } +} diff --git a/backend/database/db.go b/backend/database/db.go new file mode 100644 index 00000000..a6b1d965 --- /dev/null +++ b/backend/database/db.go @@ -0,0 +1,79 @@ +package database + +import ( + "os" + "path" + "s-ui/config" + "s-ui/database/model" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var db *gorm.DB + +func initUser() error { + var count int64 + err := db.Model(&model.User{}).Count(&count).Error + if err != nil { + return err + } + if count == 0 { + user := &model.User{ + Username: "admin", + Password: "admin", + } + return db.Create(user).Error + } + return nil +} + +func InitDB(dbPath string) error { + dir := path.Dir(dbPath) + err := os.MkdirAll(dir, 01740) + if err != nil { + return err + } + + var gormLogger logger.Interface + + if config.IsDebug() { + gormLogger = logger.Default + } else { + gormLogger = logger.Discard + } + + c := &gorm.Config{ + Logger: gormLogger, + } + db, err = gorm.Open(sqlite.Open(dbPath), c) + if err != nil { + return err + } + + err = db.AutoMigrate( + &model.Setting{}, + &model.User{}, + &model.Stats{}, + &model.Client{}, + &model.Changes{}, + ) + if err != nil { + return err + } + err = initUser() + if err != nil { + return err + } + + return nil +} + +func GetDB() *gorm.DB { + return db +} + +func IsNotFound(err error) bool { + return err == gorm.ErrRecordNotFound +} diff --git a/backend/database/model/model.go b/backend/database/model/model.go new file mode 100644 index 00000000..7a4b70c1 --- /dev/null +++ b/backend/database/model/model.go @@ -0,0 +1,48 @@ +package model + +import "encoding/json" + +type Setting struct { + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Key string `json:"key" form:"key"` + Value string `json:"value" form:"value"` +} + +type User struct { + Id uint `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` + Username string `json:"username" form:"username"` + Password string `json:"password" form:"password"` + LastLogins string `json:"lastLogin"` +} + +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 string `json:"config" form:"config"` + Inbounds string `json:"inbounds" form:"inbounds"` + Links string `json:"links" form:"links"` + Volume int64 `json:"volume" form:"volume"` + Expiry int64 `json:"expiry" form:"expiry"` + Down int64 `json:"down" form:"down"` + Up int64 `json:"up" form:"up"` +} + +type Stats struct { + Id uint64 `json:"id" gorm:"primaryKey;autoIncrement"` + DateTime int64 `json:"dateTime"` + Resource string `json:"resource"` + Tag string `json:"tag"` + Direction bool `json:"direction"` + Traffic int64 `json:"traffic"` +} + +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"` +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 00000000..91375ab8 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,67 @@ +module s-ui + +go 1.21.5 + +require ( + github.com/gin-contrib/gzip v0.0.6 + github.com/gin-contrib/sessions v0.0.5 + github.com/gin-gonic/gin v1.9.1 + github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 + gorm.io/driver/sqlite v1.5.5 + gorm.io/gorm v1.25.7 +) + +require ( + github.com/adrg/xdg v0.4.0 // indirect + github.com/bytedance/sonic v1.10.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.2.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pires/go-proxyproto v0.7.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // 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.3 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/validator/v10 v10.17.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/robfig/cron/v3 v3.0.1 + github.com/shirou/gopsutil/v3 v3.24.1 + github.com/v2fly/v2ray-core/v5 v5.13.0 + google.golang.org/grpc v1.61.0 +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 00000000..b5e2466f --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,302 @@ +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +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.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/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.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= +github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE= +github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY= +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.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/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 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +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.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= +github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= +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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/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/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +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-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +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/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.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +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.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= +github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +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/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw= +github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= +github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= +github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +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.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= +github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= +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/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.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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +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 v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.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.13.0 h1:BDJfi3Ftx1NpQlZZPpeWJe3RDqRNyIVBs+YGG4RRMDU= +github.com/v2fly/v2ray-core/v5 v5.13.0/go.mod h1:Bc3gmQWLr8UR7xBSCYI9FbfKuVvqA9lbkeBTWNRRAS4= +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.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/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.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo= +go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= +gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +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.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= +lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/backend/logger/logger.go b/backend/logger/logger.go new file mode 100644 index 00000000..11948aaa --- /dev/null +++ b/backend/logger/logger.go @@ -0,0 +1,116 @@ +package logger + +import ( + "fmt" + "os" + "time" + + "github.com/op/go-logging" +) + +var logger *logging.Logger +var logBuffer []struct { + time string + level logging.Level + log string +} + +func init() { + InitLogger(logging.INFO) +} + +func InitLogger(level logging.Level) { + newLogger := logging.MustGetLogger("s-ui") + var err error + var backend logging.Backend + var format logging.Formatter + ppid := os.Getppid() + + backend, err = logging.NewSyslogBackend("") + if err != nil { + println(err) + backend = logging.NewLogBackend(os.Stderr, "", 0) + } + if ppid > 0 && err != nil { + format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`) + } else { + format = logging.MustStringFormatter(`%{level} - %{message}`) + } + + backendFormatter := logging.NewBackendFormatter(backend, format) + backendLeveled := logging.AddModuleLevel(backendFormatter) + backendLeveled.SetLevel(level, "s-ui") + newLogger.SetBackend(backendLeveled) + + logger = newLogger +} + +func Debug(args ...interface{}) { + logger.Debug(args...) + addToBuffer("DEBUG", fmt.Sprint(args...)) +} + +func Debugf(format string, args ...interface{}) { + logger.Debugf(format, args...) + addToBuffer("DEBUG", fmt.Sprintf(format, args...)) +} + +func Info(args ...interface{}) { + logger.Info(args...) + addToBuffer("INFO", fmt.Sprint(args...)) +} + +func Infof(format string, args ...interface{}) { + logger.Infof(format, args...) + addToBuffer("INFO", fmt.Sprintf(format, args...)) +} + +func Warning(args ...interface{}) { + logger.Warning(args...) + addToBuffer("WARNING", fmt.Sprint(args...)) +} + +func Warningf(format string, args ...interface{}) { + logger.Warningf(format, args...) + addToBuffer("WARNING", fmt.Sprintf(format, args...)) +} + +func Error(args ...interface{}) { + logger.Error(args...) + addToBuffer("ERROR", fmt.Sprint(args...)) +} + +func Errorf(format string, args ...interface{}) { + logger.Errorf(format, args...) + addToBuffer("ERROR", fmt.Sprintf(format, args...)) +} + +func addToBuffer(level string, newLog string) { + t := time.Now() + if len(logBuffer) >= 10240 { + logBuffer = logBuffer[1:] + } + + logLevel, _ := logging.LogLevel(level) + logBuffer = append(logBuffer, struct { + time string + level logging.Level + log string + }{ + time: t.Format("2006/01/02 15:04:05"), + level: logLevel, + log: newLog, + }) +} + +func GetLogs(c int, level string) []string { + var output []string + logLevel, _ := logging.LogLevel(level) + + for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- { + if logBuffer[i].level <= logLevel { + output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log)) + } + } + return output +} diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 00000000..092a6fbd --- /dev/null +++ b/backend/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "log" + "os" + "os/signal" + "s-ui/app" + "syscall" +) + +func main() { + app := app.NewApp() + + err := app.Init() + if err != nil { + log.Fatal(err) + } + + err = app.Start() + if err != nil { + log.Fatal(err) + } + + sigCh := make(chan os.Signal, 1) + // Trap shutdown signals + signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM) + for { + sig := <-sigCh + + switch sig { + case syscall.SIGHUP: + app.RestartApp() + default: + app.Stop() + return + } + } +} diff --git a/backend/middleware/domainValidator.go b/backend/middleware/domainValidator.go new file mode 100644 index 00000000..e99d35f7 --- /dev/null +++ b/backend/middleware/domainValidator.go @@ -0,0 +1,21 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func DomainValidator(domain string) gin.HandlerFunc { + return func(c *gin.Context) { + host := strings.Split(c.Request.Host, ":")[0] + + if host != domain { + c.AbortWithStatus(http.StatusForbidden) + return + } + + c.Next() + } +} diff --git a/backend/network/auto_https_conn.go b/backend/network/auto_https_conn.go new file mode 100644 index 00000000..d1a9d521 --- /dev/null +++ b/backend/network/auto_https_conn.go @@ -0,0 +1,67 @@ +package network + +import ( + "bufio" + "bytes" + "fmt" + "net" + "net/http" + "sync" +) + +type AutoHttpsConn struct { + net.Conn + + firstBuf []byte + bufStart int + + readRequestOnce sync.Once +} + +func NewAutoHttpsConn(conn net.Conn) net.Conn { + return &AutoHttpsConn{ + Conn: conn, + } +} + +func (c *AutoHttpsConn) readRequest() bool { + c.firstBuf = make([]byte, 2048) + n, err := c.Conn.Read(c.firstBuf) + c.firstBuf = c.firstBuf[:n] + if err != nil { + return false + } + reader := bytes.NewReader(c.firstBuf) + bufReader := bufio.NewReader(reader) + request, err := http.ReadRequest(bufReader) + if err != nil { + return false + } + resp := http.Response{ + Header: http.Header{}, + } + resp.StatusCode = http.StatusTemporaryRedirect + location := fmt.Sprintf("https://%v%v", request.Host, request.RequestURI) + resp.Header.Set("Location", location) + resp.Write(c.Conn) + c.Close() + c.firstBuf = nil + return true +} + +func (c *AutoHttpsConn) Read(buf []byte) (int, error) { + c.readRequestOnce.Do(func() { + c.readRequest() + }) + + if c.firstBuf != nil { + n := copy(buf, c.firstBuf[c.bufStart:]) + c.bufStart += n + if c.bufStart >= len(c.firstBuf) { + c.firstBuf = nil + } + return n, nil + } + + return c.Conn.Read(buf) +} diff --git a/backend/network/auto_https_listener.go b/backend/network/auto_https_listener.go new file mode 100644 index 00000000..26614696 --- /dev/null +++ b/backend/network/auto_https_listener.go @@ -0,0 +1,21 @@ +package network + +import "net" + +type AutoHttpsListener struct { + net.Listener +} + +func NewAutoHttpsListener(listener net.Listener) net.Listener { + return &AutoHttpsListener{ + Listener: listener, + } +} + +func (l *AutoHttpsListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + return NewAutoHttpsConn(conn), nil +} diff --git a/backend/service/client.go b/backend/service/client.go new file mode 100644 index 00000000..44558ccc --- /dev/null +++ b/backend/service/client.go @@ -0,0 +1,93 @@ +package service + +import ( + "encoding/json" + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "strings" + "time" + + "gorm.io/gorm" +) + +type ClientService struct { +} + +func (s *ClientService) GetAll() (string, error) { + db := database.GetDB() + clients := []model.Client{} + err := db.Model(model.Client{}).Scan(&clients).Error + if err != nil { + return "", err + } + data, err := json.Marshal(clients) + if err != nil { + return "", err + } + return string(data), 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 + } + + var users, inbounds []string + for _, client := range clients { + logger.Debug("Client ", client.Name, " is going to be disabled") + users = append(users, client.Name) + userInbounds := strings.Split(client.Inbounds, ",") + inbounds = append(inbounds, userInbounds...) + changes = append(changes, model.Changes{ + DateTime: time.Now().Unix(), + 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 + } + } + + return users, inbounds, nil +} diff --git a/backend/service/config.go b/backend/service/config.go new file mode 100644 index 00000000..4e70ed62 --- /dev/null +++ b/backend/service/config.go @@ -0,0 +1,322 @@ +package service + +import ( + "encoding/json" + "os" + "s-ui/config" + "s-ui/database" + "s-ui/database/model" + "s-ui/singbox" + "time" +) + +var ApiAddr string + +type ConfigService struct { + ClientService + 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 { + 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 + } + } + return s.RefreshApiAddr(&data) +} + +func (s *ConfigService) GetConfig() (*[]byte, error) { + configPath := config.GetBinFolderPath() + data, err := os.ReadFile(configPath + "/config.json") + if err != nil { + return nil, err + } + return &data, nil +} + +func (s *ConfigService) SaveChanges(changes map[string]string, loginUser string) error { + var err error + var clientChanges, settingChanges, configChanges []model.Changes + if _, ok := changes["clients"]; ok { + err = json.Unmarshal([]byte(changes["clients"]), &clientChanges) + 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(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{} + err = json.Unmarshal(*singboxConfig, &newConfig) + if err != nil { + return err + } + 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 { + newConfig.Outbounds = append(newConfig.Outbounds, rawObject) + } + } + } + + // Save to config.json + data, err := json.MarshalIndent(newConfig, "", " ") + if err != nil { + return err + } + err = s.Save(&data) + if err != nil { + return err + } + } + + // Log changes + dt := time.Now().Unix() + allChanges := append(append(clientChanges, settingChanges...), configChanges...) + 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 + } + + return nil +} + +func (s *ConfigService) CheckChnages(lu string) (bool, error) { + if lu == "" { + return true, nil + } + db := database.GetDB() + var count int64 + err := db.Model(model.Changes{}).Where("date_time > " + lu).Count(&count).Error + return count > 0, err +} + +func (s *ConfigService) Save(data *[]byte) 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 + } + + err = os.WriteFile(configPath+"/config.json", *data, 0764) + if err != nil { + return err + } + + s.RefreshApiAddr(data) + s.Controller.Restart() + + return nil +} + +func (s *ConfigService) RefreshApiAddr(data *[]byte) error { + Env_API := config.GetEnvApi() + if len(Env_API) > 0 { + ApiAddr = Env_API + } else { + var err error + if data == nil { + data, err = s.GetConfig() + if err != nil { + return err + } + } + + singboxConfig := SingBoxConfig{} + err = json.Unmarshal(*data, &singboxConfig) + 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 + } + newConfig := SingBoxConfig{} + err = json.Unmarshal(*singboxConfig, &newConfig) + if err != nil { + return err + } + for inbound_index, inbound := range newConfig.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 + } + newConfig.Inbounds[inbound_index] = modifiedInbound + } + modifiedConfig, err := json.MarshalIndent(newConfig, "", " ") + if err != nil { + return err + } + err = s.Save(&modifiedConfig) + 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 +} diff --git a/backend/service/panel.go b/backend/service/panel.go new file mode 100644 index 00000000..4d85dc5d --- /dev/null +++ b/backend/service/panel.go @@ -0,0 +1,26 @@ +package service + +import ( + "os" + "s-ui/logger" + "syscall" + "time" +) + +type PanelService struct { +} + +func (s *PanelService) RestartPanel(delay time.Duration) error { + p, err := os.FindProcess(syscall.Getpid()) + if err != nil { + return err + } + go func() { + time.Sleep(delay) + err := p.Signal(syscall.SIGHUP) + if err != nil { + logger.Error("send signal SIGHUP failed:", err) + } + }() + return nil +} diff --git a/backend/service/server.go b/backend/service/server.go new file mode 100644 index 00000000..123fbadf --- /dev/null +++ b/backend/service/server.go @@ -0,0 +1,137 @@ +package service + +import ( + "os" + "runtime" + "s-ui/config" + "s-ui/logger" + "strings" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/mem" + "github.com/shirou/gopsutil/v3/net" +) + +type ServerService struct { + SingBoxService +} + +func (s *ServerService) GetStatus(request string) *map[string]interface{} { + status := make(map[string]interface{}, 0) + requests := strings.Split(request, ",") + for _, req := range requests { + switch req { + case "cpu": + status["cpu"] = s.GetCpuPercent() + case "mem": + status["mem"] = s.GetMemInfo() + case "net": + status["net"] = s.GetNetInfo() + case "sys": + status["uptime"] = s.GetUptime() + status["sys"] = s.GetSystemInfo() + case "sbd": + status["sbd"] = s.GetSingboxInfo() + } + } + return &status +} + +func (s *ServerService) GetCpuPercent() float64 { + percents, err := cpu.Percent(0, false) + if err != nil { + logger.Warning("get cpu percent failed:", err) + return 0 + } else { + return percents[0] + } +} + +func (s *ServerService) GetUptime() uint64 { + upTime, err := host.Uptime() + if err != nil { + logger.Warning("get uptime failed:", err) + return 0 + } else { + return upTime + } +} + +func (s *ServerService) GetMemInfo() map[string]interface{} { + info := make(map[string]interface{}, 0) + memInfo, err := mem.VirtualMemory() + if err != nil { + logger.Warning("get virtual memory failed:", err) + } else { + info["current"] = memInfo.Used + info["total"] = memInfo.Total + } + return info +} + +func (s *ServerService) GetNetInfo() map[string]interface{} { + info := make(map[string]interface{}, 0) + ioStats, err := net.IOCounters(false) + if err != nil { + logger.Warning("get io counters failed:", err) + } else if len(ioStats) > 0 { + ioStat := ioStats[0] + info["sent"] = ioStat.BytesSent + info["recv"] = ioStat.BytesRecv + info["psent"] = ioStat.PacketsSent + info["precv"] = ioStat.PacketsRecv + } else { + logger.Warning("can not find io counters") + } + return info +} + +func (s *ServerService) GetSingboxInfo() map[string]interface{} { + info := make(map[string]interface{}, 0) + if s.SingBoxService.IsRunning() { + info["running"] = true + sysStats, _ := s.SingBoxService.GetSysStats() + info["stats"] = sysStats + } else { + info["running"] = false + } + return info +} + +func (s *ServerService) GetSystemInfo() map[string]interface{} { + info := make(map[string]interface{}, 0) + var rtm runtime.MemStats + runtime.ReadMemStats(&rtm) + + info["appMem"] = rtm.Sys + info["appThreads"] = uint32(runtime.NumGoroutine()) + cpuInfo, err := cpu.Info() + if err == nil { + info["cpuType"] = cpuInfo[0].ModelName + } + info["cpuCount"] = runtime.NumCPU() + info["hostName"], _ = os.Hostname() + info["appVersion"] = config.GetVersion() + ipv4 := make([]string, 0) + ipv6 := make([]string, 0) + // 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 { + if strings.Contains(address.Addr, ".") { + ipv4 = append(ipv4, address.Addr) + } else if address.Addr[0:6] != "fe80::" { + ipv6 = append(ipv6, address.Addr) + } + } + } + } + info["ipv4"] = ipv4 + info["ipv6"] = ipv6 + + return info +} diff --git a/backend/service/setting.go b/backend/service/setting.go new file mode 100644 index 00000000..108984af --- /dev/null +++ b/backend/service/setting.go @@ -0,0 +1,292 @@ +package service + +import ( + "encoding/json" + "os" + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util/common" + "strconv" + "strings" + "time" + + "gorm.io/gorm" +) + +var defaultValueMap = map[string]string{ + "webListen": "", + "webDomain": "", + "webPort": "2095", + "webSecret": common.Random(32), + "webCertFile": "", + "webKeyFile": "", + "sessionMaxAge": "0", + "timeLocation": "Asia/Tehran", + "subListen": "", + "subPort": "2096", + "subPath": "/sub/", + "subDomain": "", + "subCertFile": "", + "subKeyFile": "", + "subUpdates": "12", + "subEncode": "true", + "subShowInfo": "false", + "subURI": "", +} + +type SettingService struct { +} + +func (s *SettingService) GetAllSetting() (*map[string]string, error) { + db := database.GetDB() + settings := make([]*model.Setting, 0) + err := db.Model(model.Setting{}).Find(&settings).Error + if err != nil { + return nil, err + } + allSetting := map[string]string{} + + for _, setting := range settings { + allSetting[setting.Key] = setting.Value + } + + for key, defaultValue := range defaultValueMap { + if _, exists := allSetting[key]; !exists { + err = s.saveSetting(key, defaultValue) + if err != nil { + return nil, err + } + allSetting[key] = defaultValue + } + } + + // Due to security principles + delete(allSetting, "webSecret") + + return &allSetting, nil +} + +func (s *SettingService) getSetting(key string) (*model.Setting, error) { + db := database.GetDB() + setting := &model.Setting{} + err := db.Model(model.Setting{}).Where("key = ?", key).First(setting).Error + if err != nil { + return nil, err + } + return setting, nil +} + +func (s *SettingService) getString(key string) (string, error) { + setting, err := s.getSetting(key) + if database.IsNotFound(err) { + value, ok := defaultValueMap[key] + if !ok { + return "", common.NewErrorf("key <%v> not in defaultValueMap", key) + } + return value, nil + } else if err != nil { + return "", err + } + return setting.Value, nil +} + +func (s *SettingService) saveSetting(key string, value string) error { + setting, err := s.getSetting(key) + db := database.GetDB() + if database.IsNotFound(err) { + return db.Create(&model.Setting{ + Key: key, + Value: value, + }).Error + } else if err != nil { + return err + } + setting.Key = key + setting.Value = value + return db.Save(setting).Error +} + +func (s *SettingService) setString(key string, value string) error { + return s.saveSetting(key, value) +} + +func (s *SettingService) getBool(key string) (bool, error) { + str, err := s.getString(key) + if err != nil { + return false, err + } + return strconv.ParseBool(str) +} + +func (s *SettingService) setBool(key string, value bool) error { + return s.setString(key, strconv.FormatBool(value)) +} + +func (s *SettingService) getInt(key string) (int, error) { + str, err := s.getString(key) + if err != nil { + return 0, err + } + return strconv.Atoi(str) +} + +func (s *SettingService) setInt(key string, value int) error { + return s.setString(key, strconv.Itoa(value)) +} +func (s *SettingService) GetListen() (string, error) { + return s.getString("webListen") +} + +func (s *SettingService) GetWebDomain() (string, error) { + return s.getString("webDomain") +} + +func (s *SettingService) GetPort() (int, error) { + return s.getInt("webPort") +} + +func (s *SettingService) SetPort(port int) error { + return s.setInt("webPort", port) +} + +func (s *SettingService) GetCertFile() (string, error) { + return s.getString("webCertFile") +} + +func (s *SettingService) GetKeyFile() (string, error) { + return s.getString("webKeyFile") +} + +func (s *SettingService) GetSecret() ([]byte, error) { + secret, err := s.getString("webSecret") + if secret == defaultValueMap["webSecret"] { + err := s.saveSetting("webSecret", secret) + if err != nil { + logger.Warning("save webSecret failed:", err) + } + } + return []byte(secret), err +} + +func (s *SettingService) GetSessionMaxAge() (int, error) { + return s.getInt("sessionMaxAge") +} + +func (s *SettingService) GetTimeLocation() (*time.Location, error) { + l, err := s.getString("timeLocation") + if err != nil { + return nil, err + } + location, err := time.LoadLocation(l) + if err != nil { + defaultLocation := defaultValueMap["timeLocation"] + logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation) + return time.LoadLocation(defaultLocation) + } + return location, nil +} + +func (s *SettingService) GetSubListen() (string, error) { + return s.getString("subListen") +} + +func (s *SettingService) GetSubPort() (int, error) { + return s.getInt("subPort") +} + +func (s *SettingService) GetSubPath() (string, error) { + subPath, err := s.getString("subPath") + if err != nil { + return "", err + } + if !strings.HasPrefix(subPath, "/") { + subPath = "/" + subPath + } + if !strings.HasSuffix(subPath, "/") { + subPath += "/" + } + return subPath, nil +} + +func (s *SettingService) GetSubDomain() (string, error) { + return s.getString("subDomain") +} + +func (s *SettingService) GetSubCertFile() (string, error) { + return s.getString("subCertFile") +} + +func (s *SettingService) GetSubKeyFile() (string, error) { + return s.getString("subKeyFile") +} + +func (s *SettingService) GetSubUpdates() (int, error) { + return s.getInt("subUpdates") +} + +func (s *SettingService) GetSubEncode() (bool, error) { + return s.getBool("subEncode") +} + +func (s *SettingService) GetSubShowInfo() (bool, error) { + return s.getBool("subShowInfo") +} + +func (s *SettingService) GetSubURI() (string, error) { + return s.getString("subURI") +} + +func (s *SettingService) GetFinalSubURI(host string) (string, error) { + allSetting, err := s.GetAllSetting() + if err != nil { + return "", err + } + SubURI := (*allSetting)["subURI"] + if SubURI != "" { + return SubURI, nil + } + protocol := "http" + if (*allSetting)["subKeyFile"] != "" && (*allSetting)["subCertFile"] != "" { + protocol = "https" + } + if (*allSetting)["subDomain"] != "" { + host = (*allSetting)["subDomain"] + } + port := ":" + (*allSetting)["subPort"] + if (port == "80" && protocol == "http") || (port == "443" && protocol == "https") { + port = "" + } + 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) + + // Secure file existance check + if key == "webCertFile" || + key == "webKeyFile" || + key == "subCertFile" || + key == "subKeyFile" { + err = s.fileExists(obj) + if err != nil { + return common.NewError(" -> ", obj, " is not exists") + } + } + + err = tx.Model(model.Setting{}).Where("key = ?", key).Update("value", obj).Error + if err != nil { + return err + } + } + return err +} + +func (s *SettingService) fileExists(path string) error { + _, err := os.Stat(path) + return err +} diff --git a/backend/service/sinxbox.go b/backend/service/sinxbox.go new file mode 100644 index 00000000..e7c1f5b3 --- /dev/null +++ b/backend/service/sinxbox.go @@ -0,0 +1,42 @@ +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) { + s.V2rayAPI.Init(ApiAddr) + defer s.V2rayAPI.Close() + resp, err := s.V2rayAPI.GetSysStats() + if err != nil { + return nil, err + } + + result := make(map[string]interface{}) + result["NumGoroutine"] = resp.NumGoroutine + result["Alloc"] = resp.Alloc + result["Uptime"] = resp.Uptime + + return &result, nil +} diff --git a/backend/service/stats.go b/backend/service/stats.go new file mode 100644 index 00000000..7394321b --- /dev/null +++ b/backend/service/stats.go @@ -0,0 +1,100 @@ +package service + +import ( + "encoding/json" + "s-ui/database" + "s-ui/database/model" + "time" + + "gorm.io/gorm" +) + +type onlines struct { + Inbound []string `json:"inbound,omitempty"` + User []string `json:"user,omitempty"` + Outbound []string `json:"outbound,omitempty"` +} + +var onlineResources = &onlines{} + +type StatsService struct { +} + +func (s *StatsService) SaveStats(stats []*model.Stats) error { + var err error + + // Reset onlines + onlineResources.Inbound = nil + onlineResources.Outbound = nil + onlineResources.User = nil + + if len(stats) == 0 { + return nil + } + + db := database.GetDB() + tx := db.Begin() + defer func() { + if err == nil { + tx.Commit() + } else { + tx.Rollback() + } + }() + + for _, stat := range stats { + if stat.Resource == "user" { + if stat.Direction { + err = tx.Model(model.Client{}).Where("name = ?", stat.Tag). + UpdateColumn("up", gorm.Expr("up + ?", stat.Traffic)).Error + } else { + err = tx.Model(model.Client{}).Where("name = ?", stat.Tag). + UpdateColumn("down", gorm.Expr("down + ?", stat.Traffic)).Error + } + if err != nil { + return err + } + } + if stat.Direction { + switch stat.Resource { + case "inbound": + onlineResources.Inbound = append(onlineResources.Inbound, stat.Tag) + case "outbound": + onlineResources.Outbound = append(onlineResources.Outbound, stat.Tag) + case "user": + onlineResources.User = append(onlineResources.User, stat.Tag) + } + } + } + + err = tx.Create(&stats).Error + return err +} + +func (s *StatsService) GetStats(resorce string, tag string, limit int) ([]model.Stats, error) { + var err error + var result []model.Stats + + currentTime := time.Now().Unix() + 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 + if err != nil { + return nil, err + } + return result, nil +} + +func (s *StatsService) GetOnlines() (string, error) { + onlines, err := json.Marshal(onlineResources) + if err != nil { + return "", err + } + return string(onlines), nil +} +func (s *StatsService) DelOldStats(days int) error { + oldTime := time.Now().AddDate(0, 0, -(days)).Unix() + db := database.GetDB() + return db.Where("date_time < ?", oldTime).Delete(model.Stats{}).Error +} diff --git a/backend/service/user.go b/backend/service/user.go new file mode 100644 index 00000000..094d8834 --- /dev/null +++ b/backend/service/user.go @@ -0,0 +1,47 @@ +package service + +import ( + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "s-ui/util/common" + "time" + + "gorm.io/gorm" +) + +type UserService struct { +} + +func (s *UserService) Login(username string, password string, remoteIP string) (string, error) { + user := s.CheckUser(username, password, remoteIP) + if user == nil { + return "", common.NewError("wrong user or password! IP: ", remoteIP) + } + return user.Username, nil +} + +func (s *UserService) CheckUser(username string, password string, remoteIP string) *model.User { + db := database.GetDB() + + user := &model.User{} + err := db.Model(model.User{}). + Where("username = ? and password = ?", username, password). + First(user). + Error + if err == gorm.ErrRecordNotFound { + return nil + } else if err != nil { + logger.Warning("check user err:", err, " IP: ", remoteIP) + return nil + } + + lastLoginTxt := time.Now().Format("2006-01-02 15:04:05") + "-" + remoteIP + err = db.Model(model.User{}). + Where("username = ?", username). + Update("last_logins", &lastLoginTxt).Error + if err != nil { + logger.Warning("unable to log login data", err) + } + return user +} diff --git a/backend/singbox/controller.go b/backend/singbox/controller.go new file mode 100644 index 00000000..d5ca5b3e --- /dev/null +++ b/backend/singbox/controller.go @@ -0,0 +1,54 @@ +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 new file mode 100644 index 00000000..b6ff138b --- /dev/null +++ b/backend/singbox/v2rayApi.go @@ -0,0 +1,95 @@ +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" +) + +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.Dial(ApiAddr, grpc.WithInsecure()) + if err != nil { + return err + } + v.isConnected = true + + ssClient := statsService.NewStatsServiceClient(v.grpcClient) + + v.StatsServiceClient = &ssClient + + return +} + +func (v *V2rayAPI) Close() { + v.grpcClient.Close() + v.StatsServiceClient = nil + v.isConnected = false +} + +func (v *V2rayAPI) GetStats(reset bool) ([]*model.Stats, error) { + if v.grpcClient == nil { + return nil, common.NewError("v2ray api is not initialized") + } + var trafficRegex = regexp.MustCompile("(inbound|outbound|user)>>>([^>]+)>>>traffic>>>(downlink|uplink)") + + client := *v.StatsServiceClient + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + request := &statsService.QueryStatsRequest{ + Reset_: reset, + } + resp, err := client.QueryStats(ctx, request) + if err != nil { + return nil, err + } + + dt := time.Now().Unix() + stats := make([]*model.Stats, 0) + for _, stat := range resp.GetStat() { + if stat.Value > 0 { + matchs := trafficRegex.FindStringSubmatch(stat.Name) + if len(matchs) > 3 { + stat := model.Stats{ + DateTime: dt, + Resource: matchs[1], + Tag: matchs[2], + Direction: matchs[3] == "uplink", + Traffic: stat.Value, + } + stats = append(stats, &stat) + } + } + } + + return stats, nil +} + +func (v *V2rayAPI) GetSysStats() (*statsService.SysStatsResponse, error) { + if v.grpcClient == nil { + return nil, common.NewError("v2ray api is not initialized") + } + client := *v.StatsServiceClient + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + request := &statsService.SysStatsRequest{} + resp, err := client.GetSysStats(ctx, request) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/backend/sub/sub.go b/backend/sub/sub.go new file mode 100644 index 00000000..f3169dce --- /dev/null +++ b/backend/sub/sub.go @@ -0,0 +1,154 @@ +package sub + +import ( + "context" + "crypto/tls" + "io" + "net" + "net/http" + "s-ui/config" + "s-ui/logger" + "s-ui/middleware" + "s-ui/network" + "s-ui/service" + "strconv" + + "github.com/gin-gonic/gin" +) + +type Server struct { + httpServer *http.Server + listener net.Listener + ctx context.Context + cancel context.CancelFunc + + service.SettingService +} + +func NewServer() *Server { + ctx, cancel := context.WithCancel(context.Background()) + return &Server{ + ctx: ctx, + cancel: cancel, + } +} + +func (s *Server) initRouter() (*gin.Engine, error) { + if config.IsDebug() { + gin.SetMode(gin.DebugMode) + } else { + gin.DefaultWriter = io.Discard + gin.DefaultErrorWriter = io.Discard + gin.SetMode(gin.ReleaseMode) + } + + engine := gin.Default() + + subPath, err := s.SettingService.GetSubPath() + if err != nil { + return nil, err + } + + subDomain, err := s.SettingService.GetSubDomain() + if err != nil { + return nil, err + } + + if subDomain != "" { + engine.Use(middleware.DomainValidator(subDomain)) + } + + g := engine.Group(subPath) + NewSubHandler(g) + + return engine, nil +} + +func (s *Server) Start() (err error) { + //This is an anonymous function, no function name + defer func() { + if err != nil { + s.Stop() + } + }() + + engine, err := s.initRouter() + if err != nil { + return err + } + + certFile, err := s.SettingService.GetSubCertFile() + if err != nil { + return err + } + keyFile, err := s.SettingService.GetSubKeyFile() + if err != nil { + return err + } + listen, err := s.SettingService.GetSubListen() + if err != nil { + return err + } + port, err := s.SettingService.GetSubPort() + if err != nil { + return err + } + + listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + return err + } + + if certFile != "" || keyFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + listener.Close() + return err + } + c := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + listener = network.NewAutoHttpsListener(listener) + listener = tls.NewListener(listener, c) + } + + if certFile != "" || keyFile != "" { + logger.Info("Sub server run https on", listener.Addr()) + } else { + logger.Info("Sub server run http on", listener.Addr()) + } + s.listener = listener + + s.httpServer = &http.Server{ + Handler: engine, + } + + go func() { + s.httpServer.Serve(listener) + }() + + return nil +} + +func (s *Server) Stop() error { + s.cancel() + var err error + if s.httpServer != nil { + err = s.httpServer.Shutdown(s.ctx) + if err != nil { + return err + } + } + if s.listener != nil { + err = s.listener.Close() + if err != nil { + return err + } + } + return nil +} + +func (s *Server) GetCtx() context.Context { + return s.ctx +} diff --git a/backend/sub/subHandler.go b/backend/sub/subHandler.go new file mode 100644 index 00000000..5a97817f --- /dev/null +++ b/backend/sub/subHandler.go @@ -0,0 +1,39 @@ +package sub + +import ( + "s-ui/logger" + "s-ui/service" + + "github.com/gin-gonic/gin" +) + +type SubHandler struct { + service.SettingService + SubService +} + +func NewSubHandler(g *gin.RouterGroup) { + a := &SubHandler{} + a.initRouter(g) +} + +func (s *SubHandler) initRouter(g *gin.RouterGroup) { + g.GET("/:subid", s.subs) +} + +func (s *SubHandler) subs(c *gin.Context) { + subId := c.Param("subid") + result, headers, err := s.SubService.GetSubs(subId) + if err != nil || result == nil { + logger.Error(err) + c.String(400, "Error!") + } else { + + // Add headers + c.Writer.Header().Set("Subscription-Userinfo", headers[0]) + c.Writer.Header().Set("Profile-Update-Interval", headers[1]) + c.Writer.Header().Set("Profile-Title", headers[2]) + + c.String(200, *result) + } +} diff --git a/backend/sub/subService.go b/backend/sub/subService.go new file mode 100644 index 00000000..0dcf18d3 --- /dev/null +++ b/backend/sub/subService.go @@ -0,0 +1,181 @@ +package sub + +import ( + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "s-ui/database" + "s-ui/database/model" + "s-ui/logger" + "s-ui/service" + "strings" + "time" +) + +type SubService struct { + service.SettingService +} + +type Link struct { + Type string `json:"type"` + Remark string `json:"remark"` + Uri string `json:"uri"` +} + +func (s *SubService) GetSubs(subId string) (*string, []string, error) { + var err 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 + } + + links := []Link{} + err = json.Unmarshal([]byte(client.Links), &links) + if err != nil { + return nil, nil, err + } + + clientInfo := "" + subShowInfo, _ := s.SettingService.GetSubShowInfo() + if subShowInfo { + clientInfo = s.getClientInfo(client) + } + + var result string + for _, link := range links { + switch link.Type { + case "external": + result += fmt.Sprintln(link.Uri) + case "sub": + result += s.getExternalSub(link.Uri) + case "local": + result += fmt.Sprintln(s.addClientInfo(link.Uri, clientInfo)) + } + } + + var headers []string + updateInterval, _ := s.SettingService.GetSubUpdates() + headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", client.Up, client.Down, client.Volume, client.Expiry)) + headers = append(headers, fmt.Sprintf("%d", updateInterval)) + headers = append(headers, subId) + + subEncode, _ := s.SettingService.GetSubEncode() + if subEncode { + result = base64.StdEncoding.EncodeToString([]byte(result)) + } + + return &result, headers, nil +} + +func (s *SubService) getClientInfo(c *model.Client) string { + now := time.Now().Unix() + + var result []string + if vol := c.Volume - (c.Up + c.Down); vol > 0 { + result = append(result, fmt.Sprintf("%s%s", s.formatTraffic(vol), "📊")) + } + if c.Expiry > 0 { + result = append(result, fmt.Sprintf("%d%s⏳", (c.Expiry-now)/86400, "Days")) + } + if len(result) > 0 { + return " " + strings.Join(result, " ") + } else { + return " ♾" + } +} + +func (s *SubService) addClientInfo(uri string, clientInfo string) string { + protocol := strings.Split(uri, "://") + if len(protocol) < 2 { + return uri + } + switch protocol[0] { + case "vmess": + var vmessJson map[string]interface{} + config, err := base64.StdEncoding.DecodeString(protocol[1]) + if err != nil { + logger.Warning("sub: Error decoding vmess content:", err) + return uri + } + err = json.Unmarshal(config, &vmessJson) + if err != nil { + logger.Warning("sub: Error decoding vmess content:", err) + return uri + } + vmessJson["ps"] = vmessJson["ps"].(string) + clientInfo + result, err := json.MarshalIndent(vmessJson, "", " ") + if err != nil { + logger.Warning("sub: Error decoding vmess + clientInfo content:", err) + return uri + } + return "vmess://" + base64.StdEncoding.EncodeToString(result) + default: + return uri + clientInfo + } +} + +func (s *SubService) getExternalSub(url string) string { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + client := &http.Client{Transport: tr} + + // Make the HTTP request + response, err := client.Get(url) + if err != nil { + logger.Warning("sub: Error making HTTP request:", err) + return "" + } + defer response.Body.Close() + + // Read the response body + body, err := io.ReadAll(response.Body) + if err != nil { + logger.Warning("sub: Error reading response body:", err) + return "" + } + + // Check if the content is Base64 encoded + isBase64 := s.isBase64Encoded(string(body)) + if isBase64 { + // Decode Base64 content + decodedText, err := base64.StdEncoding.DecodeString(string(body)) + if err != nil { + logger.Warning("sub: Error decoding Base64 content:", err) + return "" + } + + return string(decodedText) + } else { + return string(body) + } +} + +// Function to check if a string is Base64 encoded +func (s *SubService) isBase64Encoded(str string) bool { + _, err := base64.StdEncoding.DecodeString(str) + return err == nil +} + +func (s *SubService) formatTraffic(trafficBytes int64) string { + if trafficBytes < 1024 { + return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1)) + } else if trafficBytes < (1024 * 1024) { + return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024)) + } else if trafficBytes < (1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024)) + } else if trafficBytes < (1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024)) + } else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) { + return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024)) + } else { + return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024)) + } +} diff --git a/backend/util/common/err.go b/backend/util/common/err.go new file mode 100644 index 00000000..bd13cdf6 --- /dev/null +++ b/backend/util/common/err.go @@ -0,0 +1,27 @@ +package common + +import ( + "errors" + "fmt" + "s-ui/logger" +) + +func NewErrorf(format string, a ...interface{}) error { + msg := fmt.Sprintf(format, a...) + return errors.New(msg) +} + +func NewError(a ...interface{}) error { + msg := fmt.Sprintln(a...) + return errors.New(msg) +} + +func Recover(msg string) interface{} { + panicErr := recover() + if panicErr != nil { + if msg != "" { + logger.Error(msg, "panic:", panicErr) + } + } + return panicErr +} diff --git a/backend/util/common/random.go b/backend/util/common/random.go new file mode 100644 index 00000000..b475716d --- /dev/null +++ b/backend/util/common/random.go @@ -0,0 +1,13 @@ +package common + +import "math/rand" + +var allSeq [62]rune + +func Random(n int) string { + runes := make([]rune, n) + for i := 0; i < n; i++ { + runes[i] = allSeq[rand.Intn(len(allSeq))] + } + return string(runes) +} diff --git a/backend/web/web.go b/backend/web/web.go new file mode 100644 index 00000000..24a046d9 --- /dev/null +++ b/backend/web/web.go @@ -0,0 +1,200 @@ +package web + +import ( + "context" + "crypto/tls" + "embed" + "io" + "io/fs" + "net" + "net/http" + "s-ui/api" + "s-ui/config" + "s-ui/logger" + "s-ui/middleware" + "s-ui/network" + "s-ui/service" + "strconv" + "strings" + + "github.com/gin-contrib/gzip" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/sessions/cookie" + "github.com/gin-gonic/gin" +) + +//go:embed html/* +var content embed.FS + +type Server struct { + httpServer *http.Server + listener net.Listener + ctx context.Context + cancel context.CancelFunc + settingService service.SettingService +} + +func NewServer() *Server { + ctx, cancel := context.WithCancel(context.Background()) + return &Server{ + ctx: ctx, + cancel: cancel, + } +} + +func (s *Server) initRouter() (*gin.Engine, error) { + if config.IsDebug() { + gin.SetMode(gin.DebugMode) + } else { + gin.DefaultWriter = io.Discard + gin.DefaultErrorWriter = io.Discard + gin.SetMode(gin.ReleaseMode) + } + + engine := gin.Default() + + webDomain, err := s.settingService.GetWebDomain() + if err != nil { + return nil, err + } + + if webDomain != "" { + engine.Use(middleware.DomainValidator(webDomain)) + } + + secret, err := s.settingService.GetSecret() + if err != nil { + return nil, err + } + + engine.Use(gzip.Gzip(gzip.DefaultCompression)) + assetsBasePath := "/assets/" + + store := cookie.NewStore(secret) + engine.Use(sessions.Sessions("session", store)) + engine.Use(func(c *gin.Context) { + uri := c.Request.RequestURI + if strings.HasPrefix(uri, assetsBasePath) { + c.Header("Cache-Control", "max-age=31536000") + } + }) + + // Serve the assets folder + assetsFS, err := fs.Sub(content, "html/assets") + if err != nil { + panic(err) + } + + engine.StaticFS(assetsBasePath, http.FS(assetsFS)) + + group_api := engine.Group("/api") + api.NewAPIHandler(group_api) + + // Serve index.html as the entry point + // Handle all other routes by serving index.html + engine.NoRoute(func(c *gin.Context) { + if c.Request.URL.Path != "/login" && !api.IsLogin(c) { + c.Redirect(http.StatusTemporaryRedirect, "/login") + return + } + if c.Request.URL.Path == "/login" && api.IsLogin(c) { + c.Redirect(http.StatusTemporaryRedirect, "/") + return + } + data, err := content.ReadFile("html/index.html") + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + c.Data(http.StatusOK, "text/html", data) + }) + + return engine, nil +} + +func (s *Server) Start() (err error) { + //This is an anonymous function, no function name + defer func() { + if err != nil { + s.Stop() + } + }() + + engine, err := s.initRouter() + if err != nil { + return err + } + + certFile, err := s.settingService.GetCertFile() + if err != nil { + return err + } + keyFile, err := s.settingService.GetKeyFile() + if err != nil { + return err + } + listen, err := s.settingService.GetListen() + if err != nil { + return err + } + port, err := s.settingService.GetPort() + if err != nil { + return err + } + listenAddr := net.JoinHostPort(listen, strconv.Itoa(port)) + listener, err := net.Listen("tcp", listenAddr) + if err != nil { + return err + } + if certFile != "" || keyFile != "" { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + listener.Close() + return err + } + c := &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + listener = network.NewAutoHttpsListener(listener) + listener = tls.NewListener(listener, c) + } + + if certFile != "" || keyFile != "" { + logger.Info("web server run https on", listener.Addr()) + } else { + logger.Info("web server run http on", listener.Addr()) + } + s.listener = listener + + s.httpServer = &http.Server{ + Handler: engine, + } + + go func() { + s.httpServer.Serve(listener) + }() + + return nil +} + +func (s *Server) Stop() error { + s.cancel() + var err error + if s.httpServer != nil { + err = s.httpServer.Shutdown(s.ctx) + if err != nil { + return err + } + } + if s.listener != nil { + err = s.listener.Close() + if err != nil { + return err + } + } + return nil +} + +func (s *Server) GetCtx() context.Context { + return s.ctx +} diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..7bca0fde --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +cd frontend +npm run build + +cd .. +cd backend +echo "Backend" + +rm -fr web/html/* +cp -R ../frontend/dist/ web/html/ + +go build -o ../sui main.go \ No newline at end of file diff --git a/frontend/.browserslistrc b/frontend/.browserslistrc new file mode 100644 index 00000000..dc3bc09a --- /dev/null +++ b/frontend/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 00000000..7053c49a --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,5 @@ +[*.{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 new file mode 100644 index 00000000..5f3949bd --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000..1b57351f --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,23 @@ +.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 new file mode 100644 index 00000000..cea000b0 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,69 @@ +# 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 new file mode 100644 index 00000000..971aff79 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,16 @@ + + + + + + + + S-UI + + + +
+ + + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..84f021c5 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4964 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@mdi/font": "7.0.96", + "axios": "^1.6.5", + "chart.js": "^4.4.1", + "clipboard": "^2.0.11", + "core-js": "^3.29.0", + "pinia": "^2.1.7", + "qrcode.vue": "^3.4.1", + "roboto-fontface": "*", + "vue": "^3.2.0", + "vue-chartjs": "^5.3.0", + "vue-i18n": "^9.8.0", + "vue-router": "^4.0.0", + "vue3-persian-datetime-picker": "^1.2.2", + "vuetify": "^3.0.0" + }, + "devDependencies": { + "@babel/types": "^7.21.4", + "@types/node": "^18.15.0", + "@vitejs/plugin-vue": "^4.0.0", + "@vue/eslint-config-typescript": "^11.0.0", + "eslint": "^8.22.0", + "eslint-plugin-vue": "^9.3.0", + "material-design-icons-iconfont": "^6.7.0", + "sass": "^1.60.0", + "typescript": "^5.0.0", + "unplugin-fonts": "^1.0.3", + "vite": "^4.2.0", + "vite-plugin-vuetify": "^1.0.0", + "vue-tsc": "^1.2.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/standalone": { + "version": "7.23.10", + "resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.23.10.tgz", + "integrity": "sha512-xqWviI/pt1Zb/d+6ilWa5IDL2mkDzsBnlHbreqnfyP3/QB/ofQ1bNVcHj8YQX154Rf/xZKR6y0s1ydVF3nAS8g==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.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, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@intlify/core-base": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.9.1.tgz", + "integrity": "sha512-qsV15dg7jNX2faBRyKMgZS8UcFJViWEUPLdzZ9UR0kQZpFVeIpc0AG7ZOfeP7pX2T9SQ5jSiorq/tii9nkkafA==", + "dependencies": { + "@intlify/message-compiler": "9.9.1", + "@intlify/shared": "9.9.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.9.1.tgz", + "integrity": "sha512-zTvP6X6HeumHOXuAE1CMMsV6tTX+opKMOxO1OHTCg5N5Sm/F7d8o2jdT6W6L5oHUsJ/vvkGefHIs7Q3hfowmsA==", + "dependencies": { + "@intlify/shared": "9.9.1", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.9.1.tgz", + "integrity": "sha512-b3Pta1nwkz5rGq434v0psHwEwHGy1pYCttfcM22IE//K9owbpkEvFptx9VcuRAxjQdrO2If249cmDDjBu5wMDA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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==" + }, + "node_modules/@mdi/font": { + "version": "7.0.96", + "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.0.96.tgz", + "integrity": "sha512-rzlxTfR64hqY8yiBzDjmANfcd8rv+T5C0Yedv/TWk2QyAQYdc66e0kaN1ipmnYU3RukHRTRcBARHzzm+tIhL7w==" + }, + "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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxt/kit": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.10.0.tgz", + "integrity": "sha512-SNyZqk57kyEvTdFSYhQCYFCwT/EQO13O9SWtY2ULscdhXd2C5D0SQW9E7OM7O/31Fa+KQ9RnSTA8DnwaOFne2g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@nuxt/schema": "3.10.0", + "c12": "^1.6.1", + "consola": "^3.2.3", + "defu": "^6.1.4", + "globby": "^14.0.0", + "hash-sum": "^2.0.0", + "ignore": "^5.3.0", + "jiti": "^1.21.0", + "knitwork": "^1.0.0", + "mlly": "^1.5.0", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "scule": "^1.2.0", + "semver": "^7.5.4", + "ufo": "^1.3.2", + "unctx": "^2.3.1", + "unimport": "^3.7.1", + "untyped": "^1.4.2" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/kit/node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/kit/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/kit/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nuxt/schema": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.10.0.tgz", + "integrity": "sha512-XwxyoW1DFMpHsoF3LHvwd2e2JFy9bTBfTo2/gH2RH9tU2W3I56A9uPRBftFXTNEDBrO2whYOFsRgjVOmM0ZZHg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@nuxt/ui-templates": "^1.3.1", + "consola": "^3.2.3", + "defu": "^6.1.4", + "hookable": "^5.5.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "scule": "^1.2.0", + "std-env": "^3.7.0", + "ufo": "^1.3.2", + "unimport": "^3.7.1", + "untyped": "^1.4.2" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/@nuxt/ui-templates": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@nuxt/ui-templates/-/ui-templates-1.3.1.tgz", + "integrity": "sha512-5gc02Pu1HycOVUWJ8aYsWeeXcSTPe8iX8+KIrhyEtEoOSkY0eMBuo0ssljB8wALuEmepv31DlYe5gpiRwkjESA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "optional": true, + "peer": true + }, + "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 + }, + "node_modules/@types/node": { + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", + "devOptional": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", + "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/shared": "3.4.15", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", + "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "dependencies": { + "@vue/compiler-core": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", + "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "dependencies": { + "@babel/parser": "^7.23.6", + "@vue/compiler-core": "3.4.15", + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.5", + "postcss": "^8.4.33", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", + "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", + "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==" + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.3.tgz", + "integrity": "sha512-dkt6W0PX6H/4Xuxg/BlFj5xHvksjpSlVjtkQCpaYJBIEuKj2hOVU7r+TIe+ysCwRYFz/lGqvklntRkCAibsbPw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", + "vue-eslint-parser": "^9.1.1" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", + "eslint-plugin-vue": "^9.0.0", + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "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.4.15", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", + "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "dependencies": { + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", + "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "dependencies": { + "@vue/reactivity": "3.4.15", + "@vue/shared": "3.4.15" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", + "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "dependencies": { + "@vue/runtime-core": "3.4.15", + "@vue/shared": "3.4.15", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", + "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "dependencies": { + "@vue/compiler-ssr": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "vue": "3.4.15" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", + "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==" + }, + "node_modules/@vuetify/loader-shared": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-1.7.1.tgz", + "integrity": "sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==", + "devOptional": true, + "dependencies": { + "find-cache-dir": "^3.3.2", + "upath": "^2.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0", + "vuetify": "^3.0.0-beta.4" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "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, + "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, + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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 + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", + "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "dependencies": { + "follow-redirects": "^1.15.4", + "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==" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "devOptional": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/c12": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.6.1.tgz", + "integrity": "sha512-fAZOi3INDvIbmjuwAVVggusyRTxwNdTAnwLay8IsXwhFzDwPPGzFxzrx6L55CPFGPulUSZI0eyFUvRDXveoE3g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chokidar": "^3.5.3", + "defu": "^6.1.3", + "dotenv": "^16.3.1", + "giget": "^1.2.1", + "jiti": "^1.21.0", + "mlly": "^1.4.2", + "ohash": "^1.1.3", + "pathe": "^1.1.1", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.0.3", + "rc9": "^2.1.1" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001584", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz", + "integrity": "sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "peer": true + }, + "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, + "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.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.5.tgz", + "integrity": "sha512-AS7n5NSc0OQVMV9v6wt3ByujNIrne0/cTjiC2MYqhvao57VNfiuVksTSr2p17nVOhEr2KtqiAkGwHcgMC/qUuQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "consola": "^3.2.3" + } + }, + "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==", + "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, + "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 + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "devOptional": true + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/core-js": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.1.tgz", + "integrity": "sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==", + "hasInstallScript": true, + "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, + "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, + "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==" + }, + "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 + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "devOptional": true, + "dependencies": { + "ms": "2.1.2" + }, + "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 + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "optional": true, + "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==", + "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==" + }, + "node_modules/destr": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.2.tgz", + "integrity": "sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", + "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.656", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", + "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.21.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.21.1.tgz", + "integrity": "sha512-XVtI7z39yOVBFJyi8Ljbn7kY9yHzznKXL02qQYn+ta63Iy4A9JFBw6o4OSB9hyD2++tVT+su9kQqetUyCCwhjw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.4.2", + "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" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "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/eslint/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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "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/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "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==" + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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 + }, + "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, + "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, + "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 + }, + "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 + }, + "node_modules/fastq": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "devOptional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "devOptional": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==" + }, + "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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/giget": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", + "integrity": "sha512-4VG22mopWtIeHwogGSy1FViXVo0YT+m6BrqZfz0JJFwbSsePsCdOzdLIIli5BtMp7Xe8f/o2OmBpQX2NBOC24g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "citty": "^0.1.5", + "consola": "^3.2.3", + "defu": "^6.1.3", + "node-fetch-native": "^1.6.1", + "nypm": "^0.3.3", + "ohash": "^1.1.3", + "pathe": "^1.1.1", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "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==", + "dependencies": { + "delegate": "^3.1.2" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "devOptional": true + }, + "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, + "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, + "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==", + "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==" + }, + "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, + "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, + "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, + "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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jalaali-js": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.6.tgz", + "integrity": "sha512-io974va+Qyu+UfuVX3UIAgJlxLhAMx9Y8VMfh+IG00Js7hXQo1qNQuwSiSa0xxco0SVgx5HWNkaiCcV+aZ8WPw==" + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "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 + }, + "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 + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "optional": true, + "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, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knitwork": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.0.0.tgz", + "integrity": "sha512-dWl0Dbjm6Xm+kDxhPQJsCBTxrJzuGl0aP9rhr+TG8D3l+GL90N8O8lYUi7dTSAN2uuDqCtNgb6aEuQH5wsiV8Q==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", + "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mlly": "^1.4.2", + "pkg-types": "^1.0.3" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "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 + }, + "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 + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.6", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", + "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "devOptional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "devOptional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "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 + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", + "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.0.3", + "ufo": "^1.3.2" + } + }, + "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==", + "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==", + "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.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "devOptional": true + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, + "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" + } + ], + "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 + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/node-fetch-native": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.1.tgz", + "integrity": "sha512-bW9T/uJDPAJB2YNYEpWzE54U5O3MQidXsOyTfnbKYtTtFexRvGzb1waphBN4ZwP6EcIvYYEOwW0b72BpAqydTw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true, + "optional": true, + "peer": true + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", + "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nypm": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.6.tgz", + "integrity": "sha512-2CATJh3pd6CyNfU5VZM7qSwFu0ieyabkEdnogE30Obn1czrmOYiZ8DOZLe1yBdLKWoyD3Mcy2maUs+0MR3yVjQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "citty": "^0.1.5", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "ufo": "^1.3.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true, + "optional": true, + "peer": true + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "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, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "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, + "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 + }, + "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==", + "devOptional": 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==", + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz", + "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "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.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "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/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "devOptional": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "devOptional": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "devOptional": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "devOptional": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "devOptional": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "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, + "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==" + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode.vue": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.4.1.tgz", + "integrity": "sha512-wq/zHsifH4FJ1GXQi8/wNxD1KfQkckIpjK1KPTc/qwYU5/Bkd4me0w4xZSg6EXk6xLBkVDE0zxVagewv5EMAVA==", + "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" + } + ] + }, + "node_modules/rc9": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.1.tgz", + "integrity": "sha512-lNeOl38Ws0eNxpO3+wD1I9rkHGQyj1NU1jlzv4go2CtEnEQEUfqnIvZG7W+bC/aXdJ27n5x/yUjb6RoT9tko+Q==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "defu": "^6.1.2", + "destr": "^2.0.0", + "flat": "^5.0.2" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "devOptional": true, + "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, + "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, + "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==", + "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==" + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "devOptional": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.70.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.70.0.tgz", + "integrity": "sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==", + "devOptional": true, + "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/scule": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.2.0.tgz", + "integrity": "sha512-CRCmi5zHQnSoeCik9565PONMg0kfkvYmcSqrbOJY4txFfy1wvVULV4FDaiXhUblUgahdqz3F2NwHZ8i4eBTwUw==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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 + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "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, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/unctx": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.3.1.tgz", + "integrity": "sha512-PhKke8ZYauiqh3FEMVNm7ljvzQiph0Mt3GBRve03IJm7ukfaON2OBK795tLwhbyfzknuRRkW0+Ze+CQUmzOZ+A==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "acorn": "^8.8.2", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.0", + "unplugin": "^1.3.1" + } + }, + "node_modules/unctx/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "devOptional": true + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.7.1.tgz", + "integrity": "sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "acorn": "^8.11.2", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "mlly": "^1.4.2", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "scule": "^1.1.1", + "strip-literal": "^1.3.0", + "unplugin": "^1.5.1" + } + }, + "node_modules/unimport/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unplugin": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz", + "integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.2", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.6.1" + } + }, + "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, + "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/untyped": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/untyped/-/untyped-1.4.2.tgz", + "integrity": "sha512-nC5q0DnPEPVURPhfPQLahhSTnemVtPzdx7ofiRxXpOB2SYnb3MfdU3DVGyJdS8Lx+tBWeAePO8BfU/3EgksM7Q==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/standalone": "^7.23.8", + "@babel/types": "^7.23.6", + "defu": "^6.1.4", + "jiti": "^1.21.0", + "mri": "^1.2.0", + "scule": "^1.2.0" + }, + "bin": { + "untyped": "dist/cli.mjs" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "devOptional": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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, + "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 + }, + "node_modules/vite": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz", + "integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==", + "devOptional": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vuetify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-1.0.2.tgz", + "integrity": "sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==", + "devOptional": true, + "dependencies": { + "@vuetify/loader-shared": "^1.7.1", + "debug": "^4.3.3", + "upath": "^2.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "vite": "^2.7.0 || ^3.0.0 || ^4.0.0", + "vuetify": "^3.0.0-beta.4" + } + }, + "node_modules/vue": { + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", + "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", + "dependencies": { + "@vue/compiler-dom": "3.4.15", + "@vue/compiler-sfc": "3.4.15", + "@vue/runtime-dom": "3.4.15", + "@vue/server-renderer": "3.4.15", + "@vue/shared": "3.4.15" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-chartjs": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.0.tgz", + "integrity": "sha512-8XqX0JU8vFZ+WA2/knz4z3ThClduni2Nm0BMe2u0mXgTfd9pXrmJ07QBI+WAij5P/aPmPMX54HCE1seWL37ZdQ==", + "peerDependencies": { + "chart.js": "^4.1.1", + "vue": "^3.0.0-0 || ^2.7.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz", + "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==", + "dev": true, + "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, + "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/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vue-i18n": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.9.1.tgz", + "integrity": "sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==", + "dependencies": { + "@intlify/core-base": "9.9.1", + "@intlify/shared": "9.9.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.2.5", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.5.tgz", + "integrity": "sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==", + "dependencies": { + "@vue/devtools-api": "^6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "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==", + "dependencies": { + "moment-jalaali": "^0.9.4" + } + }, + "node_modules/vuetify": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.5.2.tgz", + "integrity": "sha512-H/g9+8Loykt50qe9mruHkwWsAIgfQ8gO+SPTAV2uaCFmfYPSFRfu0aGcZ42iWu4bZ9ZkgswIIDaVsfnKINd8yg==", + "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-alpha.12", + "vue": "^3.3.0", + "vue-i18n": "^9.0.0", + "webpack-plugin-vuetify": ">=2.0.0-alpha.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vite-plugin-vuetify": { + "optional": true + }, + "vue-i18n": { + "optional": true + }, + "webpack-plugin-vuetify": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz", + "integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg==", + "dev": true + }, + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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==" + }, + "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, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..1f41d48e --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "frontend", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview", + "lint": "eslint . --fix --ignore-path .gitignore" + }, + "dependencies": { + "@mdi/font": "7.0.96", + "axios": "^1.6.5", + "chart.js": "^4.4.1", + "clipboard": "^2.0.11", + "core-js": "^3.29.0", + "pinia": "^2.1.7", + "qrcode.vue": "^3.4.1", + "roboto-fontface": "*", + "vue": "^3.2.0", + "vue-chartjs": "^5.3.0", + "vue-i18n": "^9.8.0", + "vue-router": "^4.0.0", + "vue3-persian-datetime-picker": "^1.2.2", + "vuetify": "^3.0.0" + }, + "devDependencies": { + "@babel/types": "^7.21.4", + "@types/node": "^18.15.0", + "@vitejs/plugin-vue": "^4.0.0", + "@vue/eslint-config-typescript": "^11.0.0", + "eslint": "^8.22.0", + "eslint-plugin-vue": "^9.3.0", + "material-design-icons-iconfont": "^6.7.0", + "sass": "^1.60.0", + "typescript": "^5.0.0", + "unplugin-fonts": "^1.0.3", + "vite": "^4.2.0", + "vite-plugin-vuetify": "^1.0.0", + "vue-tsc": "^1.2.0" + } +} diff --git a/frontend/public/assets/favicon.ico b/frontend/public/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2e250185d002727a6f100d6d9a2366bc46d53473 GIT binary patch literal 1562 zcmV+#2Ictx0096204^^80096X00agA02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|E&u=k zF9-$z005lj_>uqs1j?jS0rtI@n^wP&LCq;)CZtz#yR}On z2P;@Q9h?G(K(cr|-Y0;szyMg!nfGzqx3We-Qnba7NTjLbt%AtoEJe~AoN{azN1Zy^ zmN`Hd6fNm@WgV=uv$Gs0{{Z|Aa;X{Uye+PW%Hk6A#&saL1PAulFjx$3dL93fjOuF0v1*vi*MlF0bN0 zslsz}b1nHGcepJPBsSCR7>I1bWug_nX3aICAVYs}#U=L-u9w{!TEZ~-*fd;;Ra$bYNI?Gs$mz%M)s z%ZmV)sh!{qAfXrvar$QH^VZhZ$FOfb7!>4*v*3MjmqI)VT(;%Wv?0~zH6LVxbJpl9 zewfaq8V%3!LinLD;z2H#TX5V8aDY3kcmcnH7ZmcbR6#&Kcn>J66nU}V3G%i6c!R6M zB3p2!hPVZNd!=YTn~>83CI$Ix;2ZF$N=#?N@e4sCxPGhS;-8EBGf;5_ov(9!p_ft-#{x$)3cEH*R8yT&*z)K5Pvf}JKK_{=6M*b zRR~j?ZUVjblBg8t52-g(r#n?!TWfk1w+I9RlT}q!!JM2N>jEwrp*c@HpZ9K%WrPN` zt@K{vrWO}=ilcT3dD&f$Cy*(Qzyn+$=o@YptD7rvVdn(=)cu&wdTur%$O{%17h9Ew zn$H>y7kzPSD=I45+|trA9_?=*^Al^(3Ohe>CC&%NR#a338XFr&qbZO1&y^V!7Xp@} zjeb#6Q!|LtC%xft(R7}had9Dt%4aXWv9EFB&ppZyg2l5U&SBGK48MYbU*JtsZ>}R_ z`>crzp-Z@&(Eyf-d(%F2=93YMAB>7Pf>xUmu6ZEyNic4eHyob|BDsJ?4%yD)ICvcx z62i`dj0oiK$TdUL*!`N~>s*QBz(j}8C)|h&^1T}3nQq0ubO_CLE6&*)G{m>L6_>3Q zTGV?H=lp-dh`*>KPV?o>w%`ee(3heh9coRZ>s4+ zyxT##Uy_x=e&o4S_2F*Jn>!_eG&fnLBdsC+mxFkgkg<$>#4lBKGP;4t!0pyKNnp9I zw39e;);dVjW0n|d!{8mBG198meBF~SZfk2h?WUS&i2EGGY2HH%LC3<~P$)Muw4yrQ z`|0M_t2lDrP)WD9x9`9)pBlo=y6X*r&x`U1|D&Oy;bAx84$>o-S+*OY<%f-iV)e_7 z{8M6G_xt^mb#-+g>5Ai+2SBS#8d*Jgd3kidNl!NxMn%{rPeXNqcv7FwH{Q|FL5~RE z25F}7*bo0t#9b0qp?$J30nKg*cl*?i>G_fC2LzE^Ks?xLhMu_TOqN8(AiN(JMD3Us zh0| zRz)ywFYP6BrBJVP&-EsZzO;OHnP2I>-jjv7+<5e*(e6@2qh5sDe=h=tB^^g}N&o-= M07*qoM6N<$f;H>)5&!@I literal 0 HcmV?d00001 diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 00000000..0ec30e4c --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,34 @@ + + + + + \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..3ce577ae8add0f5496932a4953764c669a0fc679 GIT binary patch literal 21840 zcmV)5K*_&%Pew8T0RR91098-`4*&oF0NPXl095k;Appey00000000000000000000 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⪼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 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/DateTime.vue b/frontend/src/components/DateTime.vue new file mode 100644 index 00000000..b90995f2 --- /dev/null +++ b/frontend/src/components/DateTime.vue @@ -0,0 +1,132 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/Dial.vue b/frontend/src/components/Dial.vue new file mode 100644 index 00000000..8bc617c8 --- /dev/null +++ b/frontend/src/components/Dial.vue @@ -0,0 +1,216 @@ + + + diff --git a/frontend/src/components/InMulitiplex.vue b/frontend/src/components/InMulitiplex.vue new file mode 100644 index 00000000..e734de86 --- /dev/null +++ b/frontend/src/components/InMulitiplex.vue @@ -0,0 +1,75 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/InTLS.vue b/frontend/src/components/InTLS.vue new file mode 100644 index 00000000..e201d4ef --- /dev/null +++ b/frontend/src/components/InTLS.vue @@ -0,0 +1,214 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Listen.vue b/frontend/src/components/Listen.vue new file mode 100644 index 00000000..fec65f57 --- /dev/null +++ b/frontend/src/components/Listen.vue @@ -0,0 +1,154 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Main.vue b/frontend/src/components/Main.vue new file mode 100644 index 00000000..ab2c733f --- /dev/null +++ b/frontend/src/components/Main.vue @@ -0,0 +1,218 @@ + + + diff --git a/frontend/src/components/Network.vue b/frontend/src/components/Network.vue new file mode 100644 index 00000000..97aec76a --- /dev/null +++ b/frontend/src/components/Network.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Transport.vue b/frontend/src/components/Transport.vue new file mode 100644 index 00000000..955d624d --- /dev/null +++ b/frontend/src/components/Transport.vue @@ -0,0 +1,52 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/Users.vue b/frontend/src/components/Users.vue new file mode 100644 index 00000000..71e99ae9 --- /dev/null +++ b/frontend/src/components/Users.vue @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/message.vue b/frontend/src/components/message.vue new file mode 100644 index 00000000..d464072c --- /dev/null +++ b/frontend/src/components/message.vue @@ -0,0 +1,18 @@ + + + diff --git a/frontend/src/components/protocols/Direct.vue b/frontend/src/components/protocols/Direct.vue new file mode 100644 index 00000000..9d98fd90 --- /dev/null +++ b/frontend/src/components/protocols/Direct.vue @@ -0,0 +1,43 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/Hysteria.vue b/frontend/src/components/protocols/Hysteria.vue new file mode 100644 index 00000000..2f3c0e55 --- /dev/null +++ b/frontend/src/components/protocols/Hysteria.vue @@ -0,0 +1,63 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/Hysteria2.vue b/frontend/src/components/protocols/Hysteria2.vue new file mode 100644 index 00000000..00e07d1c --- /dev/null +++ b/frontend/src/components/protocols/Hysteria2.vue @@ -0,0 +1,91 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/Naive.vue b/frontend/src/components/protocols/Naive.vue new file mode 100644 index 00000000..aec55f48 --- /dev/null +++ b/frontend/src/components/protocols/Naive.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/ShadowTls.vue b/frontend/src/components/protocols/ShadowTls.vue new file mode 100644 index 00000000..3f03ba00 --- /dev/null +++ b/frontend/src/components/protocols/ShadowTls.vue @@ -0,0 +1,153 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/Shadowsocks.vue b/frontend/src/components/protocols/Shadowsocks.vue new file mode 100644 index 00000000..0177257c --- /dev/null +++ b/frontend/src/components/protocols/Shadowsocks.vue @@ -0,0 +1,44 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/TProxy.vue b/frontend/src/components/protocols/TProxy.vue new file mode 100644 index 00000000..61c5c34f --- /dev/null +++ b/frontend/src/components/protocols/TProxy.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/protocols/Tuic.vue b/frontend/src/components/protocols/Tuic.vue new file mode 100644 index 00000000..71a2695a --- /dev/null +++ b/frontend/src/components/protocols/Tuic.vue @@ -0,0 +1,66 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/tiles/Gauge.vue b/frontend/src/components/tiles/Gauge.vue new file mode 100644 index 00000000..33525c7c --- /dev/null +++ b/frontend/src/components/tiles/Gauge.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/frontend/src/components/tiles/History.vue b/frontend/src/components/tiles/History.vue new file mode 100644 index 00000000..4069e5be --- /dev/null +++ b/frontend/src/components/tiles/History.vue @@ -0,0 +1,200 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/transports/Http.vue b/frontend/src/components/transports/Http.vue new file mode 100644 index 00000000..f544ce87 --- /dev/null +++ b/frontend/src/components/transports/Http.vue @@ -0,0 +1,75 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/transports/HttpUpgrade.vue b/frontend/src/components/transports/HttpUpgrade.vue new file mode 100644 index 00000000..d62bed72 --- /dev/null +++ b/frontend/src/components/transports/HttpUpgrade.vue @@ -0,0 +1,28 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/transports/WebSocket.vue b/frontend/src/components/transports/WebSocket.vue new file mode 100644 index 00000000..fa899ee8 --- /dev/null +++ b/frontend/src/components/transports/WebSocket.vue @@ -0,0 +1,51 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/transports/gRPC.vue b/frontend/src/components/transports/gRPC.vue new file mode 100644 index 00000000..218db64f --- /dev/null +++ b/frontend/src/components/transports/gRPC.vue @@ -0,0 +1,65 @@ + + + \ No newline at end of file diff --git a/frontend/src/layouts/default/AppBar.vue b/frontend/src/layouts/default/AppBar.vue new file mode 100644 index 00000000..c82f003f --- /dev/null +++ b/frontend/src/layouts/default/AppBar.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/layouts/default/Default.vue b/frontend/src/layouts/default/Default.vue new file mode 100644 index 00000000..1ad7ba85 --- /dev/null +++ b/frontend/src/layouts/default/Default.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/layouts/default/Drawer.vue b/frontend/src/layouts/default/Drawer.vue new file mode 100644 index 00000000..35e9b2ff --- /dev/null +++ b/frontend/src/layouts/default/Drawer.vue @@ -0,0 +1,67 @@ + + + \ No newline at end of file diff --git a/frontend/src/layouts/default/View.vue b/frontend/src/layouts/default/View.vue new file mode 100644 index 00000000..f17623b7 --- /dev/null +++ b/frontend/src/layouts/default/View.vue @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/layouts/modals/Client.vue b/frontend/src/layouts/modals/Client.vue new file mode 100644 index 00000000..f0cd14ac --- /dev/null +++ b/frontend/src/layouts/modals/Client.vue @@ -0,0 +1,230 @@ + + + \ No newline at end of file diff --git a/frontend/src/layouts/modals/Inbound.vue b/frontend/src/layouts/modals/Inbound.vue new file mode 100644 index 00000000..3ab0536e --- /dev/null +++ b/frontend/src/layouts/modals/Inbound.vue @@ -0,0 +1,131 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/layouts/modals/QrCode.vue b/frontend/src/layouts/modals/QrCode.vue new file mode 100644 index 00000000..7b7b4370 --- /dev/null +++ b/frontend/src/layouts/modals/QrCode.vue @@ -0,0 +1,84 @@ + + + \ No newline at end of file diff --git a/frontend/src/layouts/modals/Stats.vue b/frontend/src/layouts/modals/Stats.vue new file mode 100644 index 00000000..be2787e8 --- /dev/null +++ b/frontend/src/layouts/modals/Stats.vue @@ -0,0 +1,186 @@ + + + \ No newline at end of file diff --git a/frontend/src/locales/en.ts b/frontend/src/locales/en.ts new file mode 100644 index 00000000..23dc07fe --- /dev/null +++ b/frontend/src/locales/en.ts @@ -0,0 +1,160 @@ +export default { + message: "Welcome", + success: "success", + failed: "failed", + enable: "Enable", + disable: "Disable", + loading: "Loading...", + confirm: "Are you sure ?", + yes: "yes", + no: "no", + unlimited: "infinite", + remained: "Remained", + type: "Type", + submit: "Submit", + reset: "Reset", + now: "Now", + network: "Network", + copyToClipboard: "Copy to clipboard", + noData: "No data!", + online: "Online", + pages: { + login: "Login", + home: "Home", + inbounds: "Inbounds", + outbounds: "Outbounds", + clients: "Clients", + rules: "Rules", + basics: "Basics", + 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", + }, + actions: { + action: "Action", + add: "Add", + edit: "Edit", + del: "Delete", + save: "Save", + update: "Update", + close: "Close", + restartApp: "Restart App", + }, + login: { + username: "Username", + unRules: "Username can not be empty", + password: "Password", + pwRules: "Password can not be empty", + }, + menu: { + logout: "Logout", + }, + setting: { + interface: "Interface", + sub: "Subscription", + addr: "Address", + port: "Port", + domain: "Domain", + sslKey: "SSL Key Path", + sslCert: "SSL Certificate Path", + sessionAge: "Session Maximum Age", + timeLoc: "Timezone Location", + subEncode: "Enable Encoding", + subInfo: "Enable Client Info", + path: "Default Path", + update: "Automatic Update Time", + subUri: "Subscription URI", + }, + client: { + name: "Name", + inboundTags: "Inbound Tags", + basics: "Basics", + config: "Config", + links: "Links", + external: "External Link", + sub: "External Subscription", + }, + in: { + tag: "Tag", + addr: "Address", + port: "Port", + sniffing: "Sniffing", + tls: "TLS", + clients: "Enable Clients", + multiplex: "Multiplex", + transport: "Transport", + }, + transport: { + enable: "Enable Transport", + host: "Host", + hosts: "Hosts", + path: "Path", + }, + tls : { + enable: "Enable TLS", + usePath: "Use Path", + useText: "Use Text", + certPath: "Certificate File Path", + keyPath: "Key File Path", + cert: "Certificate", + key: "Key", + }, + 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", + }, + date: { + expiry: "Expiry", + expired: "Expired", + d: "d", + h: "h", + m: "m", + s: "s", + } +} \ No newline at end of file diff --git a/frontend/src/locales/fa.ts b/frontend/src/locales/fa.ts new file mode 100644 index 00000000..ce7111db --- /dev/null +++ b/frontend/src/locales/fa.ts @@ -0,0 +1,160 @@ +export default { + message: "خوش آمدید", + success: "موفق", + failed: "خطا", + enable: "فعال", + disable: "غیرفعال", + loading: "در حال بارگذاری...", + confirm: "آیا مطمئن هستید ؟", + yes: "بله", + no: "خیر", + unlimited: "نامحدود", + remained: "باقیمانده", + type: "مدل", + submit: "تایید", + reset: "ریست", + now: "اکنون", + network: "شبکه", + copyToClipboard: "کپی در حافظه", + noData: "بدون داده!", + online: "آنلاین", + pages: { + login: "ورود", + home: "خانه", + inbounds: "ورودی‌ها", + outbounds: "خروجی‌ها", + clients: "کاربران", + rules: "قوانین", + basics: "ترازها", + 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: "کاربر", + }, + actions: { + action: "فرمان", + add: "ایجاد", + edit: "ویرایش", + del: "حذف", + save: "ذخیره", + update: "بروزرسانی", + close: "بستن", + restartApp: "ریستارت پنل", + }, + login: { + username: "نام کاربری", + unRules: "نام کاربری نمی‌تواند خالی باشد", + password: "کلمه عبور", + pwRules: "کلمه عبور نمی‌تواند خالی باشد", + }, + menu: { + logout: "خروج", + }, + setting: { + interface: "نما", + sub: "سابسکریپشن", + addr: "آدرس", + port: "پورت", + domain: "دامنه", + sslKey: "مسیر فایل کلید", + sslCert: "مسیر فایل گواهی", + sessionAge: "بیشینه زمان لاگین ماندن", + timeLoc: "منطقه زمانی", + subEncode: "رمزگذاری", + subInfo: "نمایش اطلاعات کاربر", + path: "مسیر پیشفرض", + update: "زمان بروزرسانی خودکار", + subUri: "آدرس نهایی سابسکریپشن", + }, + client: { + name: "نام", + inboundTags: "برچسب‌های ورودی", + basics: "پایه", + config: "تنظیم", + links: "لینک‌ها", + external: "لینک‌ خارجی", + sub: "سابسکریپشن خارجی", + }, + in: { + tag: "برچسب", + addr: "آدرس", + port: "پورت", + sniffing: "مبدل آدرس", + tls: "رمزنگاری", + clients: "فعال‌سازی کاربران", + multiplex: "تسهیم", + transport: "انتقال", + }, + transport: { + enable: "فعال‌سازی انتقال", + host: "دامنه", + hosts: "دامنه‌ها", + path: "مسیر", + }, + tls : { + enable: "فعالسازی رمزنگاری", + usePath: "مسیر فایل", + useText: "متن گواهی", + certPath: "مسیر فایل گواهی", + keyPath: "مسیر فایل کلید", + cert: "گواهی", + key: "کلید", + }, + stats: { + upload: "آپلود", + download: "دانلود", + volume: "حجم", + usage: "استفاده", + enable: "فعال سازی کنترل ترافیک", + graphTitle: "نمودار ترافیک", + B: "ب", + KB: "ک‌ب", + MB: "م‌ب", + GB: "گ‌ب", + TB: "ت‌ب", + PB: "پ‌ب", + p: "پ", + Kp: "ک‌پ", + Mp: "م‌پ", + Gp: "گ‌پ", + }, + date: { + expiry: "انقضا", + expired: "منقضی", + d: "ر", + h: "س", + m: "د", + s: "ث", + } +} \ No newline at end of file diff --git a/frontend/src/locales/index.ts b/frontend/src/locales/index.ts new file mode 100644 index 00000000..66bb9a31 --- /dev/null +++ b/frontend/src/locales/index.ts @@ -0,0 +1,19 @@ +import { createI18n } from 'vue-i18n' +import en from './en' +import fa from './fa' + + +export const i18n = createI18n({ + legacy: false, + locale: localStorage.getItem("locale") ?? 'en', + fallbackLocale: 'en', + messages: { + en, + fa, + }, +}) + +export const languages = [ + { title: 'English', value: 'en' }, + { title: 'فارسی', value: 'fa' }, +] diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 00000000..72c28fc3 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,38 @@ +/** + * 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' + +const loading = ref(false) + +const app = createApp(App) +app.provide('loading', loading) + +registerPlugins(app) + +app + .use(router) + .use(store) + .use(i18n) + .component('DatePicker', Vue3PersianDatetimePicker) + .mount('#app') diff --git a/frontend/src/plugins/api.ts b/frontend/src/plugins/api.ts new file mode 100644 index 00000000..138b2758 --- /dev/null +++ b/frontend/src/plugins/api.ts @@ -0,0 +1,18 @@ +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.interceptors.request.use( + (config) => { + if (config.data instanceof FormData) { + config.headers['Content-Type'] = 'multipart/form-data' + } + return config + }, + (error) => Promise.reject(error), +) + +const api = axios.create() + +export default api \ No newline at end of file diff --git a/frontend/src/plugins/httputil.ts b/frontend/src/plugins/httputil.ts new file mode 100644 index 00000000..b9da3866 --- /dev/null +++ b/frontend/src/plugins/httputil.ts @@ -0,0 +1,66 @@ +import api from './api' +import { i18n } from '@/locales' +import Message from "@/store/modules/message" + +export interface Msg { + success: boolean + msg: string + obj: any | null +} + +function _handleMsg(msg: any): void { + const sb = Message() + if (!isMsg(msg)) { + return + } + if(msg.msg){ + const message = msg.success ? i18n.global.t('success') + ": " + i18n.global.t('actions.' + msg.msg) : i18n.global.t('failed') + ": " + msg.msg + sb.showMessage(message, msg.success ? 'success' : 'error', 5000) + } +} + +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 'success' in obj && 'msg' in obj && 'obj' in 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/index.ts b/frontend/src/plugins/index.ts new file mode 100644 index 00000000..9d76810a --- /dev/null +++ b/frontend/src/plugins/index.ts @@ -0,0 +1,10 @@ +// 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 new file mode 100644 index 00000000..ff882442 --- /dev/null +++ b/frontend/src/plugins/link.ts @@ -0,0 +1,235 @@ +import { Hysteria, Hysteria2, InTypes, Inbound, Naive, Shadowsocks, TUIC, Trojan, VLESS, VMess } from "@/types/inbounds" +import { HTTP, WebSocket, QUIC, gRPC, HTTPUpgrade, Transport, TrspTypes } from "@/types/transport"; + +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: string, inbound: Inbound): string { + const addr = location.hostname + switch(inbound.type){ + case InTypes.Shadowsocks: + return shadowsocksLink(user,inbound,addr) + case InTypes.Naive: + return naiveLink(user,inbound,addr) + case InTypes.Hysteria: + return hysteriaLink(user,inbound,addr) + case InTypes.Hysteria2: + return hysteria2Link(user,inbound,addr) + case InTypes.TUIC: + return tuicLink(user,inbound,addr) + case InTypes.VLESS: + return vlessLink(user,inbound,addr) + case InTypes.Trojan: + return trojanLink(user,inbound,addr) + case InTypes.VMess: + return vmessLink(user,inbound,addr) + } + return '' + } + + function shadowsocksLink(user: string, inbound: Shadowsocks, addr: string): string { + const userPass = inbound.users?.find(i => i.name == user)?.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 + } + + const uri = new URL(`ss://${utf8ToBase64(inbound.method + ':' + password.join(':'))}@${addr}:${inbound.listen_port}`) + for (const [key, value] of Object.entries(params)){ + if (value) { + uri.searchParams.set(key, value.toString()) + } + } + uri.hash = encodeURIComponent(inbound.tag) + return uri.toString() + } + + function hysteriaLink(user: string, inbound: Hysteria, addr: string): string { + const auth = inbound.users.find(i => i.name == user)?.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 + } + const uri = new URL(`hysteria://${addr}:${inbound.listen_port}`) + for (const [key, value] of Object.entries(params)){ + if (value) { + uri.searchParams.set(key, value.toString()) + } + } + uri.hash = encodeURIComponent(inbound.tag) + return uri.toString() + } + + function hysteria2Link(user: string, inbound: Hysteria2, addr: string): string { + const password = inbound.users.find(i => i.name == user)?.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 + } + const uri = new URL(`hysteria2://${password}@${addr}:${inbound.listen_port}`) + for (const [key, value] of Object.entries(params)){ + if (value) { + uri.searchParams.set(key, value.toString()) + } + } + uri.hash = encodeURIComponent(inbound.tag) + return uri.toString() + } + + function naiveLink(user: string, inbound: Naive, addr: string): string { + const password = inbound.users.find(i => i.username == user)?.password + const params = { + padding: 1, + peer: inbound.tls.server_name?? null, + alpn: inbound.tls.alpn?.join(',')?? null, + tfo: inbound.tcp_fast_open? 1 : 0 + } + const uri = `http2://${utf8ToBase64(user + ":" + password + "@" + addr + ":" + inbound.listen_port)}` + const paramsArray = [] + for (const [key, value] of Object.entries(params)){ + if (value) { + paramsArray.push(`${key}=${encodeURIComponent(value.toString())}`) + } + } + return uri.toString() + "?" + paramsArray.join('&') + "#" + inbound.tag + } + + function tuicLink(user: string, inbound: TUIC, addr: string): string { + const u = inbound.users.find(i => i.name == user) + const params = { + sni: inbound.tls.server_name?? null, + alpn: inbound.tls.alpn?.join(',')?? null, + congestion_control: inbound.congestion_control?? null + } + const uri = new URL(`tuic://${u?.uuid}:${u?.password}@${addr}:${inbound.listen_port}`) + for (const [key, value] of Object.entries(params)){ + if (value) { + uri.searchParams.set(key, value.toString()) + } + } + uri.hash = encodeURIComponent(inbound.tag) + return uri.toString() + } + + 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 + 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: string, inbound: VLESS, addr: string): string { + const u = inbound.users.find(i => i.name == user) + const transport = inbound.transport + + const tParams = getTransportParams(transport) + + const params = { + type: transport?.type?? 'none', + security: inbound.tls?.enabled? 'tls' : null, + alpn: inbound.tls?.alpn?.join(',')?? null, + sni: inbound.tls?.server_name?? null, + flow: inbound.tls?.enabled ? u?.flow?? null : null + } + const uri = new URL(`vless://${u?.uuid}@${addr}:${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) + return uri.toString() + } + + function trojanLink(user: string, inbound: Trojan, addr: string): string { + const u = inbound.users.find(i => i.name == user) + const transport = inbound.transport + + const tParams = getTransportParams(transport) + + const params = { + type: transport?.type?? 'none', + security: inbound.tls?.enabled? 'tls' : null, + alpn: inbound.tls?.alpn?.join(',')?? null, + sni: inbound.tls?.server_name?? null, + } + const uri = new URL(`trojan://${u?.password}@${addr}:${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) + return uri.toString() + } + + function vmessLink(user: string, inbound: VMess, addr: string): string { + const u = inbound.users.find(i => i.name == user) + const transport = inbound.transport + + const tParams = getTransportParams(transport) + if (transport.type == TrspTypes.gRPC) tParams.path = tParams.serviceName + + const params = { + v: 2, + add: addr, + aid: u?.alterId, + host: tParams.host, + id: u?.uuid, + net: transport.type, + path: tParams.path, + port: inbound.listen_port, + ps: inbound.tag, + sni: inbound.tls.server_name?? '', + tls: Object.keys(inbound.tls).length>0? 'tls' : 'none' + } + return 'vmess://' + utf8ToBase64(JSON.stringify(params)) + } +} \ No newline at end of file diff --git a/frontend/src/plugins/randomUtil.ts b/frontend/src/plugins/randomUtil.ts new file mode 100644 index 00000000..53009fcc --- /dev/null +++ b/frontend/src/plugins/randomUtil.ts @@ -0,0 +1,49 @@ +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 = ['','','',''] + for (var ii = 0; ii < 4; ii++) { + for (var jj = 0; jj < this.randomInt(8); 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 new file mode 100644 index 00000000..49f344db --- /dev/null +++ b/frontend/src/plugins/utils.ts @@ -0,0 +1,153 @@ +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 + }, + Clients(value1: any[], value2: any[]): any { + const differences: any[] = [] + value1.forEach((v1,index) => { + if(index >= value2.length) differences.push({key: "clients", action: "new", obj: v1}) + else if(!this.deepCompare(v1,value2[index])) differences.push({key: "clients", 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) + const keys2 = Object.keys(obj2) + + 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 new file mode 100644 index 00000000..457cc921 --- /dev/null +++ b/frontend/src/plugins/vuetify.ts @@ -0,0 +1,58 @@ +/** + * 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 } 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 }, + }, +}) diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 00000000..2ca4327e --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,98 @@ +// 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: '/basics', + name: 'pages.basics', + component: () => import('@/views/Basics.vue'), + }, + { + path: '/settings', + name: 'pages.settings', + component: () => import('@/views/Settings.vue'), + }, + ], + }, +] + +const router = createRouter({ + history: createWebHistory(process.env.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('session=')) + 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 new file mode 100644 index 00000000..e90ba419 --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,5 @@ +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 new file mode 100644 index 00000000..3deaaa49 --- /dev/null +++ b/frontend/src/store/modules/data.ts @@ -0,0 +1,67 @@ +import { FindDiff } from '@/plugins/utils' +import HttpUtils from '@/plugins/httputil' +import { defineStore } from 'pinia' +import { onMounted } from 'vue' + +const Data = defineStore('Data', { + state: () => ({ + lastLoad: 0, + reloadItems: localStorage.getItem("reloadItems")?.split(',')?? [], + subURI: "", + onlines: {inbound: [], outbound: [], user: []}, + oldData: <{config: any, clients: any[]}>{}, + config: {}, + clients: [], + }), + 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 + const data = JSON.parse(msg.obj) + if (data.config) this.config = data.config + if (data.clients) this.clients = data.clients + if (data.subURI) this.subURI = data.subURI + this.onlines = data.onlines + + // To avoid ref copy + if (data.config) this.oldData.config = { ...JSON.parse(msg.obj).config } + if (data.clients) this.oldData.clients = [ ...JSON.parse(msg.obj).clients ] + } + }, + async pushData() { + const diff = { + config: JSON.stringify(FindDiff.Config(this.config,this.oldData.config)), + clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)), + } + const msg = await HttpUtils.post('/api/save',diff) + if(msg.success) { + this.loadData() + } + }, + async delInbound(index: number) { + const diff = { + config: JSON.stringify([{key: "inbounds", action: "del", index: index, obj: null}]), + clients: JSON.stringify(FindDiff.Clients(this.clients,this.oldData.clients)), + } + 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() + } + } + }, +}) + +export default Data \ No newline at end of file diff --git a/frontend/src/store/modules/message.ts b/frontend/src/store/modules/message.ts new file mode 100644 index 00000000..22dc9572 --- /dev/null +++ b/frontend/src/store/modules/message.ts @@ -0,0 +1,22 @@ +import { defineStore } from 'pinia' + +const Message = defineStore('msg', { + state: () => ({ + showMsg: false, + snackbar: { + message: '', + timeout: 5000, + color: '', + } + }), + actions: { + showMessage(message:string, color='success',timeout=5000) { + this.snackbar.message = message + this.snackbar.color = color + this.snackbar.timeout = timeout + this.showMsg = true + } + }, +}) + +export default Message \ No newline at end of file diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss new file mode 100644 index 00000000..3b4d2889 --- /dev/null +++ b/frontend/src/styles/settings.scss @@ -0,0 +1,39 @@ +/** + * 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/clients.ts b/frontend/src/types/clients.ts new file mode 100644 index 00000000..6706e217 --- /dev/null +++ b/frontend/src/types/clients.ts @@ -0,0 +1,115 @@ +import RandomUtil from "@/plugins/randomUtil" + +export interface Client { + id?: number + enable: boolean + name: string + config: string + inbounds: string + links: string + volume: number + expiry: number + up: number + down: number +} + +const defaultClient: Client = { + enable: true, + name: "", + config: "[]", + inbounds: "", + links: "[]", + volume: 0, + expiry: 0, + up: 0, + down: 0, +} + +type Config = { + [key: string]: { + name?: string + username?: string + [key: string]: any + } +} + +export function updateConfigs(configs: string, newUserName: string): string { + const updatedConfigs: Config = JSON.parse(configs) + + for (const key in updatedConfigs) { + if (updatedConfigs.hasOwnProperty(key)) { + const config = updatedConfigs[key] + if (config.hasOwnProperty("name")) { + config.name = newUserName + } else if (config.hasOwnProperty("username")) { + config.username = newUserName + } + } + } + + return JSON.stringify(updatedConfigs) +} + +export function randomConfigs(user: string): Config { + const mixedPassword = RandomUtil.randomSeq(10) + const ssPassword = 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: ssPassword, + }, + shadowtls: { + name: user, + password: ssPassword, + }, + 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 { + const defaultObject: Client = { ...defaultClient, ...(json || {}) } + return defaultObject +} diff --git a/frontend/src/types/config.ts b/frontend/src/types/config.ts new file mode 100644 index 00000000..75799828 --- /dev/null +++ b/frontend/src/types/config.ts @@ -0,0 +1,126 @@ +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 new file mode 100644 index 00000000..06a26705 --- /dev/null +++ b/frontend/src/types/dial.ts @@ -0,0 +1,14 @@ +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/inMultiplex.ts b/frontend/src/types/inMultiplex.ts new file mode 100644 index 00000000..34f7f8e0 --- /dev/null +++ b/frontend/src/types/inMultiplex.ts @@ -0,0 +1,11 @@ +interface Brutal { + enabled: boolean + up_mbps: number + down_mbps: number +} + +export interface iMultiplex{ + enabled: boolean + padding?: boolean + brutal?: Brutal +} \ No newline at end of file diff --git a/frontend/src/types/inTls.ts b/frontend/src/types/inTls.ts new file mode 100644 index 00000000..9b4f88f1 --- /dev/null +++ b/frontend/src/types/inTls.ts @@ -0,0 +1,19 @@ +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 +} + +export const defaultInTls: iTls = { + alpn: ['HTTP/3', 'HTTP/2', '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 new file mode 100644 index 00000000..02b38955 --- /dev/null +++ b/frontend/src/types/inbounds.ts @@ -0,0 +1,242 @@ +import { iMultiplex } from "./inMultiplex" +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?: { + type?: "salamander" + password?: 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 { + [otherProperties: string]: any +} +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 }, + 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/outTls.ts b/frontend/src/types/outTls.ts new file mode 100644 index 00000000..701b5f3a --- /dev/null +++ b/frontend/src/types/outTls.ts @@ -0,0 +1,3 @@ +export interface oTls { + enabled?: boolean +} \ No newline at end of file diff --git a/frontend/src/types/outbounds.ts b/frontend/src/types/outbounds.ts new file mode 100644 index 00000000..b1471048 --- /dev/null +++ b/frontend/src/types/outbounds.ts @@ -0,0 +1,88 @@ +import { oTls } from "./outTls" + +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', + Tur: 'tur', + 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 Direct extends OutboundBasics, Dial { + override_address?: string + override_port?: number + proxy_protocol?: 0 | 1 | 2 +} + +// 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 }, + http: { type: OutTypes.HTTP }, + shadowsocks: { type: OutTypes.Shadowsocks }, + vmess: { type: OutTypes.VMess, tls: { enabled: true } }, + trojan: { type: OutTypes.Trojan }, + wireguard: { type: OutTypes.Wireguard }, + hysteria: { type: OutTypes.Hysteria }, + vless: { type: OutTypes.VLESS }, + shadowtls: { type: OutTypes.ShadowTLS }, + tuic: { type: OutTypes.TUIC }, + hysteria2: { type: OutTypes.Hysteria2, users: [], tls: {} }, + tur: { type: OutTypes.Tur }, + 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/transport.ts b/frontend/src/types/transport.ts new file mode 100644 index 00000000..769dfa68 --- /dev/null +++ b/frontend/src/types/transport.ts @@ -0,0 +1,46 @@ +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?: {} + 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/Basics.vue b/frontend/src/views/Basics.vue new file mode 100644 index 00000000..db3e8f8d --- /dev/null +++ b/frontend/src/views/Basics.vue @@ -0,0 +1,346 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Clients.vue b/frontend/src/views/Clients.vue new file mode 100644 index 00000000..fdf7a439 --- /dev/null +++ b/frontend/src/views/Clients.vue @@ -0,0 +1,303 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 00000000..365ad93c --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,7 @@ + + + diff --git a/frontend/src/views/Inbounds.vue b/frontend/src/views/Inbounds.vue new file mode 100644 index 00000000..3bfbd4eb --- /dev/null +++ b/frontend/src/views/Inbounds.vue @@ -0,0 +1,289 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue new file mode 100644 index 00000000..4af7b78a --- /dev/null +++ b/frontend/src/views/Login.vue @@ -0,0 +1,86 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/Outbounds.vue b/frontend/src/views/Outbounds.vue new file mode 100644 index 00000000..5669f7cc --- /dev/null +++ b/frontend/src/views/Outbounds.vue @@ -0,0 +1,34 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Rules.vue b/frontend/src/views/Rules.vue new file mode 100644 index 00000000..b51c2fcf --- /dev/null +++ b/frontend/src/views/Rules.vue @@ -0,0 +1,62 @@ + + + \ No newline at end of file diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue new file mode 100644 index 00000000..2a9a05c1 --- /dev/null +++ b/frontend/src/views/Settings.vue @@ -0,0 +1,252 @@ + + + diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..323c78a6 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1,7 @@ +/// + +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 new file mode 100644 index 00000000..e57bb255 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "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 new file mode 100644 index 00000000..9d31e2ae --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..c42e3592 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,49 @@ +// 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' + +export default defineConfig({ + 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: 'assets/[name].js', + assetFileNames: 'assets/[name].[ext]', + }, + } + }, + define: { 'process.env': {} }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + extensions: ['.js', '.json', '.jsx', '.mjs', '.ts', '.tsx', '.vue'], + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:2095', + changeOrigin: true, + }, + }, + } +}) diff --git a/install.sh b/install.sh new file mode 100755 index 00000000..4c97ce13 --- /dev/null +++ b/install.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +red='\033[0;31m' +green='\033[0;32m' +yellow='\033[0;33m' +plain='\033[0m' + +cur_dir=$(pwd) + +# check root +[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1 + +# Check OS and set release variable +if [[ -f /etc/os-release ]]; then + source /etc/os-release + release=$ID +elif [[ -f /usr/lib/os-release ]]; then + source /usr/lib/os-release + release=$ID +else + echo "Failed to check the system OS, please contact the author!" >&2 + exit 1 +fi +echo "The OS release is: $release" + +arch=$(arch) + +if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then + arch="amd64" +elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then + arch="arm64" +elif [[ $arch == "s390x" ]]; then + arch="s390x" +else + arch="amd64" + echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}" +fi + +echo "arch: ${arch}" + +if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then + echo "s-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!" + exit -1 +fi + +os_version="" +os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1) + +if [[ "${release}" == "centos" ]]; then + if [[ ${os_version} -lt 8 ]]; then + echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1 + fi +elif [[ "${release}" == "ubuntu" ]]; then + if [[ ${os_version} -lt 20 ]]; then + echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1 + fi + +elif [[ "${release}" == "fedora" ]]; then + if [[ ${os_version} -lt 36 ]]; then + echo -e "${red}please use Fedora 36 or higher version! ${plain}\n" && exit 1 + fi + +elif [[ "${release}" == "debian" ]]; then + if [[ ${os_version} -lt 10 ]]; then + echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1 + fi +else + echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1 +fi + + +install_base() { + if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then + yum install wget curl tar unzip jq -y + else + apt install wget curl tar unzip jq -y + fi +} + +install_s-ui() { + cd /tmp/ + + if [ $# == 0 ]; then + last_version=$(curl -Ls "https://api.github.com/repos/alireza0/s-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + if [[ ! -n "$last_version" ]]; then + echo -e "${red}Failed to fetch s-ui version, it maybe due to Github API restrictions, please try it later${plain}" + exit 1 + fi + echo -e "Got s-ui latest version: ${last_version}, beginning the installation..." + wget -N --no-check-certificate -O /tmp/s-ui-linux-${arch}.tar.gz https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-${arch}.tar.gz + if [[ $? -ne 0 ]]; then + echo -e "${red}Dowanloading s-ui failed, please be sure that your server can access Github ${plain}" + exit 1 + fi + else + last_version=$1 + url="https://github.com/alireza0/s-ui/releases/download/${last_version}/s-ui-linux-${arch}.tar.gz" + echo -e "Begining to install s-ui v$1" + wget -N --no-check-certificate -O /tmp/s-ui-linux-${arch}.tar.gz ${url} + if [[ $? -ne 0 ]]; then + echo -e "${red}dowanload s-ui v$1 failed,please check the verison exists${plain}" + exit 1 + fi + fi + + 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 + chmod +x s-ui/sui s-ui/bin/sing-box s-ui/bin/runSingbox.sh + cp -rf s-ui /usr/local/ + cp -f s-ui/*.service /etc/systemd/system/ + rm -rf s-ui + + 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 "" + echo -e "s-ui usages: " + echo -e "----------------------------------------------" + echo -e "s-ui - Enter Run S-UI" + echo -e "----------------------------------------------" +} + +echo -e "${green}Excuting...${plain}" +install_base +install_s-ui $1 \ No newline at end of file diff --git a/runSUI.sh b/runSUI.sh new file mode 100755 index 00000000..64dc2f24 --- /dev/null +++ b/runSUI.sh @@ -0,0 +1,2 @@ +./build.sh +SUI_DB_FOLDER="db" SUI_DEBUG=true ./sui \ No newline at end of file diff --git a/runSingbox.sh b/runSingbox.sh new file mode 100755 index 00000000..9b8cafb9 --- /dev/null +++ b/runSingbox.sh @@ -0,0 +1,45 @@ +#!/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 +} + +trap terminateSingbox SIGINT SIGTERM SIGKILL + +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") + terminateSingbox + runSingbox + ;; + esac + fi +done \ No newline at end of file diff --git a/s-ui.service b/s-ui.service new file mode 100644 index 00000000..31b5ecbe --- /dev/null +++ b/s-ui.service @@ -0,0 +1,14 @@ +[Unit] +Description=s-ui Service +After=network.target +Wants=network.target + +[Service] +Type=simple +WorkingDirectory=/usr/local/s-ui/ +ExecStart=/usr/local/s-ui/sui +Restart=on-failure +RestartSec=10s + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/sing-box.service b/sing-box.service new file mode 100644 index 00000000..8103f606 --- /dev/null +++ b/sing-box.service @@ -0,0 +1,17 @@ +[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