diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b2333fb6..9a4f9110 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,6 @@ on: - "**.mjs" - "**.cjs" - "**.ts" - - "**.vue" - "**.html" - "**.css" - "frontend/package.json" @@ -27,7 +26,6 @@ on: - "**.mjs" - "**.cjs" - "**.ts" - - "**.vue" - "**.html" - "**.css" - "frontend/package.json" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 966c581b..31f7d215 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -14,7 +14,6 @@ on: - "**.mjs" - "**.cjs" - "**.ts" - - "**.vue" - "frontend/package-lock.json" pull_request: paths: @@ -25,7 +24,6 @@ on: - "**.mjs" - "**.cjs" - "**.ts" - - "**.vue" - "frontend/package-lock.json" schedule: - cron: "18 2 * * 2" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b638eab..df86a474 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,7 +116,7 @@ jobs: cd x-ui/bin # Download dependencies - Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.4.25/" + Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.5.9/" if [ "${{ matrix.platform }}" == "amd64" ]; then wget -q ${Xray_URL}Xray-linux-64.zip unzip Xray-linux-64.zip @@ -250,7 +250,7 @@ jobs: cd x-ui\bin # Download Xray for Windows - $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.4.25/" + $Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.5.9/" Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip" Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath . Remove-Item "Xray-windows-64.zip" diff --git a/.gitignore b/.gitignore index 81a1f0c4..d343f43b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ tmp/ backup/ bin/ dist/ +!web/dist/ +web/dist/* +!web/dist/.gitkeep release/ node_modules/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ea49667..a09ae517 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,33 +1,33 @@ # Contributing -Thanks for taking the time to contribute to 3x-ui. This guide gets a development panel running on your machine in a few minutes. +Thanks for taking the time to contribute to 3x-ui. This guide gets a development panel running locally and explains the conventions the project follows so changes land cleanly. ## Prerequisites -- **Go 1.26+** (the version in `go.mod`) -- **Node.js 22+** and npm (for the Vue frontend) +- **Go 1.26+** (the version pinned in `go.mod`) +- **Node.js 22+** and npm 10+ (for the React frontend) - **Git** -- **A C compiler** — required by the CGo SQLite driver (`github.com/mattn/go-sqlite3`). Linux/macOS already ship one; on Windows see below. +- **A C compiler** — required by the CGo SQLite driver (`github.com/mattn/go-sqlite3`). Linux and macOS already ship one; for Windows see below. ### Windows: MinGW-w64 -`go build` on Windows will fail with `cgo: C compiler "gcc" not found` until you install a GCC toolchain. Two options — pick whichever fits. +`go build` on Windows fails with `cgo: C compiler "gcc" not found` until a GCC toolchain is installed. Two options — pick whichever fits. **Option A — standalone zip (fastest, no package manager)** -1. Grab the latest build from ****. For most setups you want a release named like: +1. Download the latest build from . For most setups, pick a release named: ``` x86_64--release-posix-seh-ucrt-rt_-rev.7z ``` - (64-bit, POSIX threads, SEH exceptions, UCRT runtime — matches the modern Windows defaults.) + (64-bit, POSIX threads, SEH exceptions, UCRT runtime — matches modern Windows defaults.) 2. Extract it somewhere stable, e.g. `C:\mingw64\`. -3. Add `C:\mingw64\bin` to your **Windows** `PATH` (System Properties → Environment Variables → Path → New). +3. Add `C:\mingw64\bin` to the **Windows** `PATH` (System Properties → Environment Variables → Path → New). 4. Open a fresh terminal and confirm: ```powershell gcc --version ``` -**Option B — MSYS2 (if you also want a Unix-y shell)** +**Option B — MSYS2 (when a Unix shell is also useful)** 1. Install MSYS2 from . 2. Open the **MSYS2 UCRT64** shell from the Start menu and update once: @@ -38,14 +38,14 @@ Thanks for taking the time to contribute to 3x-ui. This guide gets a development ```bash pacman -S --needed mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-pkg-config ``` -4. Add `C:\msys64\ucrt64\bin` to your Windows `PATH`. +4. Add `C:\msys64\ucrt64\bin` to the Windows `PATH`. 5. Verify with `gcc --version` in a fresh terminal. -After either, `go build ./...` and `go run .` work normally. +After either path, `go build ./...` and `go run .` work normally. -> Why MinGW-w64 over MSVC: `mattn/go-sqlite3` officially supports GCC, builds are faster on Windows, and the toolchain doesn't lock you into a Visual Studio install. If you already have Visual Studio Build Tools installed it works too — just make sure `CC=cl` is **not** set in your environment. +> **Why MinGW-w64 over MSVC:** `mattn/go-sqlite3` officially supports GCC, builds are faster on Windows, and the toolchain does not require a Visual Studio install. If Visual Studio Build Tools are already present that works too — just make sure `CC=cl` is **not** set in the environment. -The Linux SQLite cross-build from Windows (or vice versa) needs an extra cross-compiler — out of scope here; build natively on the target OS. +Cross-building the Linux SQLite target from Windows (or vice versa) requires a separate cross-compiler and is out of scope here; build natively on the target OS. ## First-time setup @@ -65,7 +65,7 @@ npm run build cd .. ``` -`.env.example` ships with sane defaults that point the database, logs, and xray binary at the local `x-ui/` folder so nothing escapes the project directory: +`.env.example` ships with defaults that keep the database, logs, and xray binary inside the local `x-ui/` folder so nothing escapes the project directory: ``` XUI_DEBUG=true @@ -74,7 +74,7 @@ XUI_LOG_FOLDER=x-ui XUI_BIN_FOLDER=x-ui ``` -You need to drop the xray binary (`xray-windows-amd64.exe` on Windows, `xray-linux-amd64` on Linux, etc.) plus the matching `geoip.dat` / `geosite.dat` files into `x-ui/`. The easiest source is a [released Xray-core build](https://github.com/XTLS/Xray-core/releases). On Windows you also want `wintun.dll` if you plan to test TUN inbounds. +Drop the xray binary (`xray-windows-amd64.exe` on Windows, `xray-linux-amd64` on Linux, etc.) plus the matching `geoip.dat` and `geosite.dat` files into `x-ui/`. The easiest source is a [released Xray-core build](https://github.com/XTLS/Xray-core/releases). On Windows, `wintun.dll` is also required for testing TUN inbounds. ## Running @@ -82,11 +82,11 @@ You need to drop the xray binary (`xray-windows-amd64.exe` on Windows, `xray-lin go run . ``` -Open [http://localhost:2053](http://localhost:2053) and log in with `admin` / `admin`. You will be prompted to change the credentials on first login. +Open [http://localhost:2053](http://localhost:2053) and log in with `admin` / `admin`. Credentials must be changed on first login. ### Inside VS Code -The repo ships a launch profile in `.vscode/launch.json` (gitignored — copy from the snippet below if it is missing): +The repo ships a launch profile in `.vscode/launch.json` (gitignored — copy from the snippet below if absent): ```jsonc { @@ -113,93 +113,97 @@ The repo ships a launch profile in `.vscode/launch.json` (gitignored — copy fr ## Working on the frontend -The panel UI is a Vue 3 + Ant Design Vue 4 app under `frontend/`. A few things worth knowing before you dive in. +The panel UI is a **React 19 + Ant Design 6 + TypeScript** app under `frontend/`, built with Vite 8. The sections below cover the architecture, the conventions, and the two dev workflows. -### Architecture in one paragraph +### Architecture -It's a **multi-page app**, not a SPA. Every panel route (`/panel`, `/panel/inbounds`, `/panel/clients`, `/panel/xray`, `/panel/settings`, `/panel/sub`, `/panel/api-docs`, plus `login`) has its own HTML entry under `frontend/*.html` and its own bootstrap in `src/entries/.js`. Vite builds them into `web/dist/`, and the Go binary embeds that directory at compile time with `embed.FS`. Each navigation triggers a real document load — but each page's bundle is small, so it stays snappy. There's no Vue Router and no central store; Vuex/Pinia were rejected as overkill for the panel's surface area. +The frontend is a **multi-page application**, not a SPA. Every panel route (`/panel`, `/panel/inbounds`, `/panel/clients`, `/panel/xray`, `/panel/settings`, `/panel/nodes`, `/panel/api-docs`, `/panel/sub`, plus `login`) has its own HTML entry in `frontend/*.html` and its own bootstrap in `src/entries/.tsx`. Vite emits each entry into `web/dist/`, and the Go binary embeds that directory at compile time via `embed.FS`. Each panel navigation is a real document load, but every per-page bundle is small enough to keep the experience responsive. There is no React Router and no global store; the surface area does not justify either. ### State and data flow -- **No global store.** State lives where it's used. Cross-page data (settings, current user, theme) is re-fetched on each page load — the backend is on the same box and responses are cheap. -- **Composables** in `src/composables/` carry reactive logic worth sharing inside a page (theme switching, status polling, node lists). Reach for one before adding a new global. -- **Domain classes** in `src/models/` (`Inbound`, `DBInbound`, `Outbound`, `Status`, …) own the protocol-specific logic — link generation, settings JSON shape, TLS/Reality stream handling. The Vue components stay dumb; they ask the model "what's my link?" and render the answer. -- **HTTP** goes through `src/utils/index.js`'s `HttpUtil`, which is a thin Axios wrapper with CSRF, response toast handling, and a `silent: true` opt-out for bulk operations that would otherwise spam toasts. +- **No global store.** State lives in the page that owns it. Cross-page data (settings, current user, theme) is re-fetched on each page load — the backend is local and responses are inexpensive. +- **Hooks** in `src/hooks/` encapsulate reactive logic worth sharing inside a page (`useTheme`, `useStatus`, `useNodes`, `useWebSocket`, `useDatepicker`, …). Prefer extending an existing hook over introducing a new global. +- **Domain models** in `src/models/` (`Inbound`, `DBInbound`, `Outbound`, `Status`, …) own the protocol-specific logic — link generation, settings JSON shape, TLS/Reality stream handling. React components stay declarative; they ask the model "what is my link?" and render the answer. +- **HTTP** goes through `src/utils/index.js`'s `HttpUtil`, a thin Axios wrapper that handles CSRF, response toasts, and a `silent: true` opt-out for bulk operations that would otherwise spam toasts. The Axios setup itself lives in `src/api/axios-init.js`. ### i18n -Locale strings live in `web/translation/.json`, not under `frontend/`. The Go side embeds the same JSON and serves it to both backend templates and `vue-i18n`. When you add a new English key, add it to **every** non-English locale too — missing keys don't fail the build, they just render the raw key in the UI. +Locale strings live in `web/translation/.json`, **not** under `frontend/`. The Go binary embeds the same JSON and serves it to both backend templates and `react-i18next` (initialized in `src/i18n/react.ts`). When a new English key is added it must also land in **every** non-English locale — missing keys do not break the build, they just render the raw key in the UI. ### Two dev workflows -| When you want… | Use | -|----------------|-----| -| To iterate on UI tweaks fast | `cd frontend && npm run dev` (Vite on `:5173`, proxies `/panel/*` and `/api/*` to the Go panel on `:2053`). Start the Go panel first. | -| To test what users actually see | `cd frontend && npm run build`, then `go run .`. The Go binary serves the built bundle either embedded (release mode) or from disk (debug mode). | +| Goal | Command | +|------|---------| +| Iterate on UI changes with HMR | `cd frontend && npm run dev` (Vite on `:5173`, proxies `/panel/*` and `/api/*` to the Go panel on `:2053`). Start the Go panel first. | +| Verify what end users actually see | `cd frontend && npm run build`, then `go run .`. The Go binary serves the built bundle — embedded in release mode, off disk in debug mode. | -The Vite dev proxy auto-rewrites the sidebar's production-style links (`/panel`, `/panel/inbounds`, `/panel/clients`, etc.) to the matching Vite-served HTML, so the navigation feels identical to prod without round-tripping through Go. The route allowlist lives in `MIGRATED_ROUTES` in `vite.config.js` — if you add a new page, register it there too. +The Vite dev proxy rewrites the sidebar's production-style links (`/panel`, `/panel/inbounds`, `/panel/clients`, …) to the matching Vite-served HTML, so navigation behaves identically to production without round-tripping through Go. The allowlist lives in `MIGRATED_ROUTES` in `vite.config.js` — register every new page there. -> **`XUI_DEBUG=true` gotcha** — in debug mode the panel serves HTML out of the embedded FS (frozen at the last `go build` / `go run`) but JS/CSS off disk. Re-running `npm run build` without restarting Go leaves the embedded HTML pointing at the *old* hashed asset names → blank page with 404s in the browser console. Always restart `go run .` after a frontend rebuild. +> **`XUI_DEBUG=true` gotcha** — in debug mode the panel serves HTML from the embedded FS (frozen at the last `go build` / `go run`) but JS/CSS off disk. Re-running `npm run build` without restarting Go leaves the embedded HTML pointing at the *old* hashed asset names, producing a blank page with 404s in the console. Always restart `go run .` after a frontend rebuild. ### Adding a new page -1. Create `frontend/.html` (copy an existing one and adjust the title + the imported entry). -2. Create `src/entries/.js` — `createApp(Page).use(antd).use(i18n).mount('#app')`. -3. Create the page component under `src/pages//.vue` (kebab-case folder, PascalCase component). +1. Create `frontend/.html` (copy an existing entry and adjust the title and the imported ` + diff --git a/frontend/clients.html b/frontend/clients.html index a2c03040..67c76866 100644 --- a/frontend/clients.html +++ b/frontend/clients.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 95c46dbb..1b66c511 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,21 +1,16 @@ import js from '@eslint/js'; -import vue from 'eslint-plugin-vue'; -import vueParser from 'vue-eslint-parser'; +import tseslint from 'typescript-eslint'; +import reactHooks from 'eslint-plugin-react-hooks'; import globals from 'globals'; export default [ { ignores: ['node_modules/**', '../web/dist/**'] }, js.configs.recommended, - ...vue.configs['flat/recommended'], { - files: ['**/*.{js,vue}'], + files: ['**/*.js'], languageOptions: { ecmaVersion: 2022, sourceType: 'module', - parser: vueParser, - parserOptions: { - ecmaFeatures: { jsx: false }, - }, globals: { ...globals.browser, ...globals.node, @@ -29,30 +24,48 @@ export default [ }], 'no-empty': ['error', { allowEmptyCatch: true }], 'no-case-declarations': 'off', + }, + }, + ...tseslint.configs.recommended.map((config) => ({ + ...config, + files: ['**/*.{ts,tsx}'], + })), + { + files: ['**/*.{ts,tsx}'], + plugins: { + 'react-hooks': reactHooks, + }, + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + ...globals.browser, + }, + }, + rules: { + ...reactHooks.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': ['warn', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }], + 'no-empty': ['error', { allowEmptyCatch: true }], - // Stylistic rules from vue/recommended that don't match the - // existing codebase formatting. Disable rather than churn the - // whole tree to satisfy them. - 'vue/multi-word-component-names': 'off', - 'vue/no-v-html': 'off', - 'vue/html-self-closing': 'off', - 'vue/max-attributes-per-line': 'off', - 'vue/singleline-html-element-content-newline': 'off', - 'vue/multiline-html-element-content-newline': 'off', - 'vue/html-indent': 'off', - 'vue/html-closing-bracket-newline': 'off', - 'vue/attributes-order': 'off', - 'vue/first-attribute-linebreak': 'off', - 'vue/one-component-per-file': 'off', - 'vue/order-in-components': 'off', - 'vue/attribute-hyphenation': 'off', - 'vue/v-on-event-hyphenation': 'off', - - // Pervasive in form components ported from the Vue 2 codebase - // (parent passes a reactive object; child mutates it in place). - // Properly fixing this means rewiring those components to emit - // updates — a meaningful architectural change, separate task. - 'vue/no-mutating-props': 'off', + // react-hooks v7 introduces several new rules driven by the React + // Compiler. The migration uses several legitimate patterns those + // rules flag (initial-fetch in useEffect, dirty-check derived + // state, `Date.now()` inside derive helpers, inline arrow event + // handlers, in-place mutation of imported Outbound class + // instances in the OutboundFormModal). We're not running the + // compiler, so the memoization-preservation warnings have no + // effect on runtime — turning them off until the codebase + // stabilises. + 'react-hooks/set-state-in-effect': 'off', + 'react-hooks/purity': 'off', + 'react-hooks/react-compiler': 'off', + 'react-hooks/preserve-manual-memoization': 'off', + 'react-hooks/immutability': 'off', + 'react-hooks/refs': 'off', }, }, ]; diff --git a/frontend/inbounds.html b/frontend/inbounds.html index 9e8861fc..52bb49c0 100644 --- a/frontend/inbounds.html +++ b/frontend/inbounds.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/index.html b/frontend/index.html index d13b400f..196cea68 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/login.html b/frontend/login.html index 658f8d10..44543fd0 100644 --- a/frontend/login.html +++ b/frontend/login.html @@ -9,6 +9,6 @@
- + diff --git a/frontend/nodes.html b/frontend/nodes.html index fec96dbc..908ae240 100644 --- a/frontend/nodes.html +++ b/frontend/nodes.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 460f6758..ab11d1d5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,34 +1,39 @@ { "name": "3x-ui-frontend", - "version": "0.0.3", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "3x-ui-frontend", - "version": "0.0.3", + "version": "0.1.0", "dependencies": { - "@ant-design/icons-vue": "^7.0.1", + "@ant-design/icons": "^6.2.3", "@codemirror/lang-json": "^6.0.2", "@codemirror/theme-one-dark": "^6.1.3", - "ant-design-vue": "^4.2.6", - "axios": "^1.7.9", + "antd": "^6.4.3", + "axios": "^1.16.1", "codemirror": "^6.0.2", "dayjs": "^1.11.20", + "i18next": "^26.2.0", "otpauth": "^9.5.1", - "qs": "^6.13.1", - "vue": "^3.5.34", - "vue-i18n": "^11.1.4", - "vue3-persian-datetime-picker": "^1.2.2" + "persian-calendar-suite": "^1.5.5", + "qs": "^6.15.2", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-i18next": "^17.0.8" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@vitejs/plugin-vue": "^6.0.6", - "eslint": "^10.3.0", - "eslint-plugin-vue": "^10.9.1", + "@types/react": "^19.2.15", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "eslint": "^10.4.0", + "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.6.0", - "vite": "8.0.13", - "vue-eslint-parser": "^10.4.0" + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.4", + "vite": "8.0.13" }, "engines": { "node": ">=22.0.0", @@ -36,12 +41,74 @@ } }, "node_modules/@ant-design/colors": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", - "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", "license": "MIT", "dependencies": { - "@ctrl/tinycolor": "^3.4.0" + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.3.tgz", + "integrity": "sha512-Pl3aoAtxQeKryYnt6VvDJtOxMOtA8wrRSACe/pTjOAIG3fdHrWm6Ivb4ku9tsFjYroSXBKirvuxG4QkwBXD9gg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.4.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" } }, "node_modules/@ant-design/icons-svg": { @@ -50,23 +117,159 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", "license": "MIT" }, - "node_modules/@ant-design/icons-vue": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz", - "integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==", + "node_modules/@ant-design/react-slick": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-2.0.0.tgz", + "integrity": "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==", "license": "MIT", "dependencies": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons-svg": "^4.2.1" + "@babel/runtime": "^7.28.4", + "clsx": "^2.1.1", + "json2mq": "^0.2.0", + "throttle-debounce": "^5.0.0" }, "peerDependencies": { - "vue": ">=3.0.3" + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "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/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -76,15 +279,41 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.29.3", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -105,10 +334,45 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -221,15 +485,6 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@ctrl/tinycolor": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", - "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -265,15 +520,15 @@ } }, "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", "license": "MIT" }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { @@ -470,73 +725,56 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@intlify/core-base": { - "version": "11.4.4", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.4.tgz", - "integrity": "sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { - "@intlify/devtools-types": "11.4.4", - "@intlify/message-compiler": "11.4.4", - "@intlify/shared": "11.4.4" - }, - "engines": { - "node": ">= 22" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@intlify/devtools-types": { - "version": "11.4.4", - "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.4.tgz", - "integrity": "sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { - "@intlify/core-base": "11.4.4", - "@intlify/shared": "11.4.4" - }, - "engines": { - "node": ">= 22" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@intlify/message-compiler": { - "version": "11.4.4", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.4.tgz", - "integrity": "sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==", - "license": "MIT", - "dependencies": { - "@intlify/shared": "11.4.4", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 22" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/shared": { - "version": "11.4.4", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.4.tgz", - "integrity": "sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 22" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" + "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@lezer/common": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", @@ -619,6 +857,703 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@rc-component/async-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/cascader": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@rc-component/cascader/-/cascader-1.15.0.tgz", + "integrity": "sha512-ZzpMtwFCRo3fbXHuDnncARJMZQjdqA2w7aDuPofNQt+aDx39st1hgfIpEwTBLhe2Hqsvs/zOr8RTtgxTkCPySw==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.3.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/checkbox": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/checkbox/-/checkbox-2.0.0.tgz", + "integrity": "sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/collapse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/collapse/-/collapse-1.2.0.tgz", + "integrity": "sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-3.1.1.tgz", + "integrity": "sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-2.0.1.tgz", + "integrity": "sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/dialog": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/dialog/-/dialog-1.9.0.tgz", + "integrity": "sha512-zbAAogkg4kkKum79sLE6M+vq1jSAW25zdkafrahgcTP9t9S//SD634Znd1A4c8F2Gc12ZKnehGLsVaaOvZzD2A==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.3", + "@rc-component/portal": "^2.1.0", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/drawer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@rc-component/drawer/-/drawer-1.4.2.tgz", + "integrity": "sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.1.3", + "@rc-component/util": "^1.9.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/dropdown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/dropdown/-/dropdown-1.0.2.tgz", + "integrity": "sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/@rc-component/form": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@rc-component/form/-/form-1.8.1.tgz", + "integrity": "sha512-8O7TB55Fi2mWIGvSnwZjk8jFqVNYyKDAswglwGShcbndxqzKz4cHwNtNaLjZlAeRge9wcB0LL8IWsC/Bl18raQ==", + "license": "MIT", + "dependencies": { + "@rc-component/async-validator": "^5.1.0", + "@rc-component/util": "^1.6.2", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/image/-/image-1.9.0.tgz", + "integrity": "sha512-khF7w7xkBH5B1bsBcI1FSUZdkyd1aqpl2eYyILCqCzzQH3XdfehGUaZTnptyaJJfs09/R5hv9jXWyazOMFIClQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/portal": "^2.1.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/input": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/input/-/input-1.3.0.tgz", + "integrity": "sha512-IUUNOdAuWuEvDEFFgfmwQl818tiDbvXwLgon4HL1q2hJeYkqrRrYwYhJN0zfPHGTDxs3gvyVC/C02D4hWFoIcA==", + "license": "MIT", + "dependencies": { + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/input-number": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@rc-component/input-number/-/input-number-1.6.2.tgz", + "integrity": "sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==", + "license": "MIT", + "dependencies": { + "@rc-component/mini-decimal": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mentions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/mentions/-/mentions-1.9.0.tgz", + "integrity": "sha512-WUwfFKDSOF5S9UPsNsXcLYtzjTxBGsftTXWRbZuxX6BYrsySISTnujfJNgaaQ6qVzaCDJ35QUkZKvsYxip1C5g==", + "license": "MIT", + "dependencies": { + "@rc-component/input": "~1.3.0", + "@rc-component/menu": "~1.3.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/menu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/menu/-/menu-1.3.0.tgz", + "integrity": "sha512-u3NfiwpiEgT177qa5Yxm5QsI8i/93EBGpWj8HYZQDnh2pCZ2xtQCe/+w3pSR2NlwKOZDTCKzEhEyD09mGphssA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.3.tgz", + "integrity": "sha512-bk/FJ09fLf+NLODMAFll6CfYrHPBioTedhW6lxDBuuWucJEqFUd4l/D/5JgIi3dina6sYahB8iuPAZTNz2pMxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/motion": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.2.tgz", + "integrity": "sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz", + "integrity": "sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/notification": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@rc-component/notification/-/notification-2.0.7.tgz", + "integrity": "sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/overflow": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/overflow/-/overflow-1.0.1.tgz", + "integrity": "sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/pagination": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/pagination/-/pagination-1.2.0.tgz", + "integrity": "sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/picker": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/picker/-/picker-1.10.0.tgz", + "integrity": "sha512-vVOXP2RVWozwpERGUFAehVH1Jz6o/uRrAb9qSZm1LC+iJs8rvEwFo1bzz2jlOYV+uWwu0dIuG86tnDui14Ea0w==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/trigger": "^3.6.15", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/@rc-component/portal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-2.2.0.tgz", + "integrity": "sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=12.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rc-component/progress/-/progress-1.0.2.tgz", + "integrity": "sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/rate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/rate/-/rate-1.0.1.tgz", + "integrity": "sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/segmented": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@rc-component/segmented/-/segmented-1.3.0.tgz", + "integrity": "sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@rc-component/motion": "^1.1.4", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@rc-component/select": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/@rc-component/select/-/select-1.6.15.tgz", + "integrity": "sha512-SyVCWnqxCQZZcQvQJ/CxSjx2bGma6ds/HtnpkIfZVnt6RoEgbqUmHgD6vrzNarNXwbLXerwVzWwq8F3d1sst7g==", + "license": "MIT", + "dependencies": { + "@rc-component/overflow": "^1.0.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.3.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/slider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/slider/-/slider-1.0.1.tgz", + "integrity": "sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/steps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@rc-component/steps/-/steps-1.2.2.tgz", + "integrity": "sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/switch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rc-component/switch/-/switch-1.0.3.tgz", + "integrity": "sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/table": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@rc-component/table/-/table-1.10.0.tgz", + "integrity": "sha512-SjtpcCf+rL7dDc62GKT3rXTdERjVuJvRiqjpU7g0Jc/ewCifXynHc7Nm3Em1XsD+WhGrgQtxNDScI/0+Lpfr0w==", + "license": "MIT", + "dependencies": { + "@rc-component/context": "^2.0.1", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.1.0", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tabs": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/tabs/-/tabs-1.9.0.tgz", + "integrity": "sha512-tn1slmbbaTyt8mgwyWJcT8jo/qNiYUs6u1H7OgGQt9faYO06BJIkU5cTmMqORzIrNmSEeeUY6pD5i+JlqSHYhg==", + "license": "MIT", + "dependencies": { + "@rc-component/dropdown": "~1.0.0", + "@rc-component/menu": "~1.3.0", + "@rc-component/motion": "^1.1.3", + "@rc-component/resize-observer": "^1.0.0", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tooltip": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tooltip/-/tooltip-1.4.0.tgz", + "integrity": "sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==", + "license": "MIT", + "dependencies": { + "@rc-component/trigger": "^3.7.1", + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-2.4.0.tgz", + "integrity": "sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==", + "license": "MIT", + "dependencies": { + "@rc-component/portal": "^2.2.0", + "@rc-component/trigger": "^3.0.0", + "@rc-component/util": "^1.7.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tree": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/tree/-/tree-1.3.1.tgz", + "integrity": "sha512-zlL0PW0bTFlveTtLcA01VD/yMWKK73EywItFMgIZUY5sb6tMOAw7zV6qGzqldufqrV93ZWQB4H3NBNoTMCueJA==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.0.0", + "@rc-component/util": "^1.8.1", + "@rc-component/virtual-list": "^1.0.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/tree-select": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/tree-select/-/tree-select-1.9.0.tgz", + "integrity": "sha512-GXcFe15a+trUl1/J3OHWQhsVWFpwFpGFK2cqYWZ1sK22Zs3KZTvMwDpzr75PIo1s6QVioVxpE/pRwRopkeDQ6w==", + "license": "MIT", + "dependencies": { + "@rc-component/select": "~1.6.0", + "@rc-component/tree": "~1.3.0", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@rc-component/trigger": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-3.9.0.tgz", + "integrity": "sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==", + "license": "MIT", + "dependencies": { + "@rc-component/motion": "^1.1.4", + "@rc-component/portal": "^2.2.0", + "@rc-component/resize-observer": "^1.1.1", + "@rc-component/util": "^1.2.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/upload/-/upload-1.1.0.tgz", + "integrity": "sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.3.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/util": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", + "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/virtual-list": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rc-component/virtual-list/-/virtual-list-1.2.0.tgz", + "integrity": "sha512-iavRm1Jo4GDbASQwdGa7jFyk93RvSOo9xHyBT4QL1pgFJj/Fdf1G+3RErH7/7BmAMvx2AkF62mjGYxDbXsK9TQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", @@ -901,16 +1836,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@simonwep/pickr": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", - "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", - "license": "MIT", - "dependencies": { - "core-js": "^3.15.1", - "nanopop": "^2.1.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", @@ -943,129 +1868,295 @@ "dev": true, "license": "MIT" }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "node_modules/@types/react": { + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz", + "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "dev": true, "license": "MIT", "dependencies": { - "@rolldown/pluginutils": "^1.0.1" + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", + "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/type-utils": "8.59.4", + "@typescript-eslint/utils": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.4", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz", + "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", + "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.4", + "@typescript-eslint/types": "^8.59.4", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz", + "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", + "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz", + "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/utils": "8.59.4", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", + "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", + "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.4", + "@typescript-eslint/tsconfig-utils": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/visitor-keys": "8.59.4", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", + "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.4", + "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", + "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.4", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", - "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/shared": "3.5.34", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", - "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", - "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.3", - "@vue/compiler-core": "3.5.34", - "@vue/compiler-dom": "3.5.34", - "@vue/compiler-ssr": "3.5.34", - "@vue/shared": "3.5.34", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.14", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", - "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/@vue/reactivity": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", - "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", - "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.34", - "@vue/shared": "3.5.34" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", - "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.34", - "@vue/runtime-core": "3.5.34", - "@vue/shared": "3.5.34", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", - "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.34", - "@vue/shared": "3.5.34" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" }, - "peerDependencies": { - "vue": "3.5.34" + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, - "node_modules/@vue/shared": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", - "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", - "license": "MIT" - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -1118,58 +2209,69 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ant-design-vue": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-4.2.6.tgz", - "integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==", + "node_modules/antd": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-6.4.3.tgz", + "integrity": "sha512-6H2avkxCGfxcF67r3J2mwm9Ck50el1pks/73vfM1wDsPL/tPtj5vHuauMgJFnrqmq7CH3g8aoZ0VBQbt+jpAsw==", "license": "MIT", "dependencies": { - "@ant-design/colors": "^6.0.0", - "@ant-design/icons-vue": "^7.0.0", - "@babel/runtime": "^7.10.5", - "@ctrl/tinycolor": "^3.5.0", - "@emotion/hash": "^0.9.0", - "@emotion/unitless": "^0.8.0", - "@simonwep/pickr": "~1.8.0", - "array-tree-filter": "^2.1.0", - "async-validator": "^4.0.0", - "csstype": "^3.1.1", - "dayjs": "^1.10.5", - "dom-align": "^1.12.1", - "dom-scroll-into-view": "^2.0.0", - "lodash": "^4.17.21", - "lodash-es": "^4.17.15", - "resize-observer-polyfill": "^1.5.1", - "scroll-into-view-if-needed": "^2.2.25", - "shallow-equal": "^1.0.0", - "stylis": "^4.1.3", - "throttle-debounce": "^5.0.0", - "vue-types": "^3.0.0", - "warning": "^4.0.0" - }, - "engines": { - "node": ">=12.22.0" + "@ant-design/colors": "^8.0.1", + "@ant-design/cssinjs": "^2.1.2", + "@ant-design/cssinjs-utils": "^2.1.2", + "@ant-design/fast-color": "^3.0.1", + "@ant-design/icons": "^6.2.3", + "@ant-design/react-slick": "~2.0.0", + "@babel/runtime": "^7.29.2", + "@rc-component/cascader": "~1.15.0", + "@rc-component/checkbox": "~2.0.0", + "@rc-component/collapse": "~1.2.0", + "@rc-component/color-picker": "~3.1.1", + "@rc-component/dialog": "~1.9.0", + "@rc-component/drawer": "~1.4.2", + "@rc-component/dropdown": "~1.0.2", + "@rc-component/form": "~1.8.1", + "@rc-component/image": "~1.9.0", + "@rc-component/input": "~1.3.0", + "@rc-component/input-number": "~1.6.2", + "@rc-component/mentions": "~1.9.0", + "@rc-component/menu": "~1.3.0", + "@rc-component/motion": "^1.3.2", + "@rc-component/mutate-observer": "^2.0.1", + "@rc-component/notification": "~2.0.7", + "@rc-component/pagination": "~1.2.0", + "@rc-component/picker": "~1.10.0", + "@rc-component/progress": "~1.0.2", + "@rc-component/qrcode": "~1.1.1", + "@rc-component/rate": "~1.0.1", + "@rc-component/resize-observer": "^1.1.2", + "@rc-component/segmented": "~1.3.0", + "@rc-component/select": "~1.6.15", + "@rc-component/slider": "~1.0.1", + "@rc-component/steps": "~1.2.2", + "@rc-component/switch": "~1.0.3", + "@rc-component/table": "~1.10.0", + "@rc-component/tabs": "~1.9.0", + "@rc-component/tooltip": "~1.4.0", + "@rc-component/tour": "~2.4.0", + "@rc-component/tree": "~1.3.1", + "@rc-component/tree-select": "~1.9.0", + "@rc-component/trigger": "^3.9.0", + "@rc-component/upload": "~1.1.0", + "@rc-component/util": "^1.11.0", + "clsx": "^2.1.1", + "dayjs": "^1.11.11", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/ant-design-vue" + "url": "https://opencollective.com/ant-design" }, "peerDependencies": { - "vue": ">=3.2.0" + "react": ">=18.0.0", + "react-dom": ">=18.0.0" } }, - "node_modules/array-tree-filter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", - "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==", - "license": "MIT" - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1198,12 +2300,18 @@ "node": "18 || 20 || >=22" } }, - "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==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz", + "integrity": "sha512-MujYO3eP72uvmSE0i4wltsodRfIpZATP3jvzRNRGGxgzId7aVocVJJV3nf01qnzzKFGxQVC9bpWxl5cjxTr/7Q==", "dev": true, - "license": "ISC" + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/brace-expansion": { "version": "5.0.6", @@ -1218,6 +2326,40 @@ "node": "18 || 20 || >=22" } }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1247,6 +2389,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/codemirror": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", @@ -1275,21 +2447,17 @@ } }, "node_modules/compute-scroll-into-view": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", "license": "MIT" }, - "node_modules/core-js": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", - "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } + "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, + "license": "MIT" }, "node_modules/crelt": { "version": "1.0.6", @@ -1312,19 +2480,6 @@ "node": ">= 8" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1380,18 +2535,6 @@ "node": ">=8" } }, - "node_modules/dom-align": { - "version": "1.12.4", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", - "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==", - "license": "MIT" - }, - "node_modules/dom-scroll-into-view": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", - "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==", - "license": "MIT" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1406,17 +2549,12 @@ "node": ">= 0.4" } }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "node_modules/electron-to-chromium": { + "version": "1.5.361", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", + "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "dev": true, + "license": "ISC" }, "node_modules/es-define-property": { "version": "1.0.1", @@ -1463,6 +2601,16 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "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", @@ -1532,36 +2680,24 @@ } } }, - "node_modules/eslint-plugin-vue": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.9.1.tgz", - "integrity": "sha512-cHB0Tf4Duvzwecwd/AqWzZvF/QszE13BhjVUpVXWCy9AeMR5GjkAjP3i85vqgLgOuTmkHR1OJ5oMeqLHtuw8zg==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "natural-compare": "^1.4.0", - "nth-check": "^2.1.1", - "postcss-selector-parser": "^7.1.0", - "semver": "^7.6.3", - "xml-name-validator": "^4.0.0" + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "peerDependencies": { - "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", - "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "vue-eslint-parser": "^10.3.0" - }, - "peerDependenciesMeta": { - "@stylistic/eslint-plugin": { - "optional": true - }, - "@typescript-eslint/parser": { - "optional": true - } + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-scope": { @@ -1650,12 +2786,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1816,6 +2946,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1930,6 +3070,32 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1943,6 +3109,34 @@ "node": ">= 6" } }, + "node_modules/i18next": { + "version": "26.2.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.2.0.tgz", + "integrity": "sha512-zwBHldHdTmwN7r6UNc7lC6GWNN+YYg3DrRSeHR5PRRBf5QnJZcYHrQc0uaU26qZeYxR7iFZD+Y315dPnKP47wA==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1986,14 +3180,11 @@ "node": ">=0.10.0" } }, - "node_modules/is-plain-object": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", - "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", @@ -2002,18 +3193,26 @@ "dev": true, "license": "ISC" }, - "node_modules/jalaali-js": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/jalaali-js/-/jalaali-js-1.2.8.tgz", - "integrity": "sha512-Jl/EwY84JwjW2wsWqeU4pNd22VNQ7EkjI36bDuLw31wH98WQW4fPjD0+mG7cdCK+Y8D6s9R3zLiQ3LaKu6bD8A==", - "license": "MIT" - }, "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, "license": "MIT" }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2035,6 +3234,28 @@ "dev": true, "license": "MIT" }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "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, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2348,37 +3569,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", + "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, + "license": "ISC", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "yallist": "^3.0.2" } }, "node_modules/math-intrinsics": { @@ -2427,38 +3625,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/moment-jalaali": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz", - "integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==", - "license": "MIT", - "dependencies": { - "jalaali-js": "^1.2.7", - "moment": "^2.29.4", - "moment-timezone": "^0.5.46" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.48", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", - "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", - "license": "MIT", - "dependencies": { - "moment": "^2.29.4" - }, - "engines": { - "node": "*" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2469,6 +3635,7 @@ "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, "funding": [ { "type": "github", @@ -2483,12 +3650,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/nanopop": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", - "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2496,17 +3657,14 @@ "dev": true, "license": "MIT" }, - "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==", + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/object-inspect": { @@ -2603,10 +3761,20 @@ "node": ">=8" } }, + "node_modules/persian-calendar-suite": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/persian-calendar-suite/-/persian-calendar-suite-1.5.5.tgz", + "integrity": "sha512-KJSzN9q7MZKhfkm97X/j+nD6L0AQ5coUq/B7PpIklXAvRjkALwiV+KmYG0pfr546EQxO9l4fBwE7R1HPI3yT7w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2626,6 +3794,7 @@ "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2650,20 +3819,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2708,10 +3863,58 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/react-i18next": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.8.tgz", + "integrity": "sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 26.2.0", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, "node_modules/rolldown": { @@ -2748,34 +3951,31 @@ "@rolldown/binding-win32-x64-msvc": "1.0.1" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/scroll-into-view-if-needed": { - "version": "2.2.31", - "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", - "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", "license": "MIT", "dependencies": { - "compute-scroll-into-view": "^1.0.20" + "compute-scroll-into-view": "^3.0.2" } }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/shallow-equal": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", - "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2875,11 +4075,18 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT" + }, "node_modules/style-mod": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", @@ -2918,6 +4125,19 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2939,6 +4159,75 @@ "node": ">= 0.8.0" } }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.4", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz", + "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.4", + "@typescript-eslint/parser": "8.59.4", + "@typescript-eslint/typescript-estree": "8.59.4", + "@typescript-eslint/utils": "8.59.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "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", @@ -2949,12 +4238,14 @@ "punycode": "^2.1.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } }, "node_modules/vite": { "version": "8.0.13", @@ -3034,94 +4325,13 @@ } } }, - "node_modules/vue": { - "version": "3.5.34", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", - "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.34", - "@vue/compiler-sfc": "3.5.34", - "@vue/runtime-dom": "3.5.34", - "@vue/server-renderer": "3.5.34", - "@vue/shared": "3.5.34" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-eslint-parser": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz", - "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "eslint-scope": "^8.2.0 || ^9.0.0", - "eslint-visitor-keys": "^4.2.0 || ^5.0.0", - "espree": "^10.3.0 || ^11.0.0", - "esquery": "^1.6.0", - "semver": "^7.6.3" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" - } - }, - "node_modules/vue-i18n": { - "version": "11.4.4", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.4.tgz", - "integrity": "sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==", - "license": "MIT", - "dependencies": { - "@intlify/core-base": "11.4.4", - "@intlify/devtools-types": "11.4.4", - "@intlify/shared": "11.4.4", - "@vue/devtools-api": "^6.5.0" - }, - "engines": { - "node": ">= 22" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/vue-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", - "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", - "license": "MIT", - "dependencies": { - "is-plain-object": "3.0.1" - }, - "engines": { - "node": ">=10.15.0" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/vue3-persian-datetime-picker": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/vue3-persian-datetime-picker/-/vue3-persian-datetime-picker-1.2.2.tgz", - "integrity": "sha512-d7nkj5vgtUvEXZboSdRmP1uwBfXvXgXqdvsOOMQb34jiMZU/aBDrTYWTEe1N+XKF9pvTTJn8Rws9ttJmyhK/hw==", - "license": "MIT", - "dependencies": { - "moment-jalaali": "^0.9.4" + "node": ">=0.10.0" } }, "node_modules/w3c-keyname": { @@ -3130,15 +4340,6 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3165,15 +4366,12 @@ "node": ">=0.10.0" } }, - "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==", + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } + "license": "ISC" }, "node_modules/yocto-queue": { "version": "0.1.0", @@ -3187,6 +4385,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/frontend/package.json b/frontend/package.json index aefe82c1..c36aa065 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,9 +1,9 @@ { "name": "3x-ui-frontend", "private": true, - "version": "0.0.3", + "version": "0.1.0", "type": "module", - "description": "3x-ui panel frontend (Vue 3 + Ant Design Vue 4 + Vite 8).", + "description": "3x-ui panel frontend (React 19 + Ant Design 6 + Vite 8).", "engines": { "node": ">=22.0.0", "npm": ">=10.0.0" @@ -12,32 +12,35 @@ "dev": "vite", "build": "vite build", "preview": "vite preview", - "lint": "eslint src" + "lint": "eslint src", + "typecheck": "tsc --noEmit" }, "dependencies": { - "@ant-design/icons-vue": "^7.0.1", + "@ant-design/icons": "^6.2.3", "@codemirror/lang-json": "^6.0.2", "@codemirror/theme-one-dark": "^6.1.3", - "ant-design-vue": "^4.2.6", - "axios": "^1.7.9", + "antd": "^6.4.3", + "axios": "^1.16.1", "codemirror": "^6.0.2", "dayjs": "^1.11.20", + "i18next": "^26.2.0", "otpauth": "^9.5.1", - "qs": "^6.13.1", - "vue": "^3.5.34", - "vue-i18n": "^11.1.4", - "vue3-persian-datetime-picker": "^1.2.2" + "persian-calendar-suite": "^1.5.5", + "qs": "^6.15.2", + "react": "^19.2.6", + "react-dom": "^19.2.6", + "react-i18next": "^17.0.8" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@vitejs/plugin-vue": "^6.0.6", - "eslint": "^10.3.0", - "eslint-plugin-vue": "^10.9.1", + "@types/react": "^19.2.15", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.2", + "eslint": "^10.4.0", + "eslint-plugin-react-hooks": "^7.1.1", "globals": "^17.6.0", - "vite": "8.0.13", - "vue-eslint-parser": "^10.4.0" - }, - "overrides": { - "moment-jalaali": "^0.10.4" + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.4", + "vite": "8.0.13" } -} \ No newline at end of file +} diff --git a/frontend/settings.html b/frontend/settings.html index 0ef6413b..e753ffe1 100644 --- a/frontend/settings.html +++ b/frontend/settings.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/src/components/AppSidebar.css b/frontend/src/components/AppSidebar.css new file mode 100644 index 00000000..85f66d6f --- /dev/null +++ b/frontend/src/components/AppSidebar.css @@ -0,0 +1,287 @@ +.ant-sidebar > .ant-layout-sider { + position: sticky; + top: 0; + height: 100vh; + align-self: flex-start; +} + +.sider-brand, +.drawer-brand { + font-weight: 600; + font-size: 18px; + letter-spacing: 0.5px; + color: rgba(0, 0, 0, 0.88); +} + +.sider-brand { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 14px 14px; + border-bottom: 1px solid rgba(128, 128, 128, 0.15); + user-select: none; +} + +.sider-brand-collapsed { + justify-content: center; + font-size: 16px; + padding: 14px 4px; + letter-spacing: 0; +} + +.brand-block { + display: inline-flex; + flex-direction: column; + align-items: center; + min-width: 0; + line-height: 1.1; +} + +.brand-text { + display: block; +} + +.brand-version { + display: block; + width: 100%; + text-align: center; + font-size: 10px; + font-weight: 500; + letter-spacing: 0; + opacity: 0.6; + margin-top: 2px; +} + +.sider-brand-collapsed .brand-block { + align-items: center; + flex: 0 0 auto; +} + +.brand-actions { + display: inline-flex; + align-items: center; + gap: 2px; + flex-shrink: 0; +} + +.sidebar-donate { + background: transparent; + border: none; + width: 30px; + height: 30px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + color: rgba(0, 0, 0, 0.75); + text-decoration: none; + flex-shrink: 0; + transition: background-color 0.2s, transform 0.15s, color 0.2s; +} + +.sidebar-donate:hover, +.sidebar-donate:focus-visible { + background-color: rgba(236, 72, 153, 0.12); + color: #ec4899; + transform: scale(1.08); + outline: none; +} + +.sidebar-donate .anticon { + font-size: 16px; +} + +.sidebar-theme-cycle { + background: transparent; + border: none; + width: 30px; + height: 30px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: rgba(0, 0, 0, 0.75); + padding: 0; + flex-shrink: 0; + transition: background-color 0.2s, transform 0.15s, color 0.2s; +} + +.sidebar-theme-cycle:hover, +.sidebar-theme-cycle:focus-visible { + background-color: rgba(64, 150, 255, 0.1); + color: #4096ff; + transform: scale(1.08); + outline: none; +} + +.sidebar-theme-cycle svg { + width: 16px; + height: 16px; +} + +.drawer-header-actions { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.drawer-handle { + position: fixed; + top: 12px; + left: 12px; + z-index: 1100; + background: rgba(0, 0, 0, 0.55); + color: #fff; + border: none; + width: 40px; + height: 40px; + border-radius: 50%; + cursor: pointer; + display: none; + align-items: center; + justify-content: center; + font-size: 18px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); +} + +.drawer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 16px; + border-bottom: 1px solid rgba(128, 128, 128, 0.15); +} + +.drawer-close { + background: transparent; + border: none; + width: 32px; + height: 32px; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 16px; + color: rgba(0, 0, 0, 0.65); +} + +.drawer-close:hover, +.drawer-close:focus-visible { + background: rgba(128, 128, 128, 0.18); +} + +.drawer-menu .ant-menu-item { + height: 48px; + line-height: 48px; + margin: 0; + border-radius: 0; +} + +.drawer-menu .ant-menu-item .anticon { + font-size: 16px; +} + +.drawer-utility { + margin-top: auto; + border-top: 1px solid rgba(128, 128, 128, 0.15); +} + +.ant-sidebar > .ant-layout-sider .ant-layout-sider-children { + display: flex; + flex-direction: column; + height: 100%; +} + +.sider-nav { + flex: 1 1 auto; + overflow-y: auto; + overflow-x: hidden; + min-height: 0; +} + +.sider-utility { + flex: 0 0 auto; + border-top: 1px solid rgba(128, 128, 128, 0.15); +} + +@media (max-width: 768px) { + .drawer-handle { + display: inline-flex; + } + + .ant-sidebar > .ant-layout-sider .ant-layout-sider-children, + .ant-sidebar > .ant-layout-sider .ant-layout-sider-trigger { + display: none; + } + + .ant-sidebar > .ant-layout-sider { + flex: 0 0 0 !important; + max-width: 0 !important; + min-width: 0 !important; + width: 0 !important; + } +} + +body.dark .drawer-brand, +body.dark .sider-brand { + color: rgba(255, 255, 255, 0.92); +} + +html[data-theme='ultra-dark'] .drawer-brand, +html[data-theme='ultra-dark'] .sider-brand { + color: #ffffff; +} + +body.dark .drawer-close { + color: rgba(255, 255, 255, 0.75); +} + +html[data-theme='ultra-dark'] .drawer-close { + color: rgba(255, 255, 255, 0.85); +} + +body.dark .sidebar-theme-cycle { + color: rgba(255, 255, 255, 0.85); +} + +html[data-theme='ultra-dark'] .sidebar-theme-cycle { + color: rgba(255, 255, 255, 0.92); +} + +body.dark .sidebar-donate { + color: rgba(255, 255, 255, 0.85); +} + +html[data-theme='ultra-dark'] .sidebar-donate { + color: rgba(255, 255, 255, 0.92); +} + +body.dark .ant-drawer .ant-drawer-content, +body.dark .ant-drawer .ant-drawer-body { + background: #252526 !important; +} + +html[data-theme='ultra-dark'] .ant-drawer .ant-drawer-content, +html[data-theme='ultra-dark'] .ant-drawer .ant-drawer-body { + background: #0a0a0a !important; +} + +.sider-nav .ant-menu-item-selected, +.sider-utility .ant-menu-item-selected, +.drawer-menu .ant-menu-item-selected { + background-color: rgba(64, 150, 255, 0.2) !important; + color: #4096ff !important; +} + +.sider-nav .ant-menu-item-active:not(.ant-menu-item-selected), +.sider-utility .ant-menu-item-active:not(.ant-menu-item-selected), +.drawer-menu .ant-menu-item-active:not(.ant-menu-item-selected), +.sider-nav .ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled):hover, +.sider-utility .ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled):hover, +.drawer-menu .ant-menu-item:not(.ant-menu-item-selected):not(.ant-menu-item-disabled):hover { + background-color: rgba(64, 150, 255, 0.1) !important; + color: #4096ff !important; +} diff --git a/frontend/src/components/AppSidebar.tsx b/frontend/src/components/AppSidebar.tsx new file mode 100644 index 00000000..27dc0571 --- /dev/null +++ b/frontend/src/components/AppSidebar.tsx @@ -0,0 +1,290 @@ +import { useCallback, useMemo, useState } from 'react'; +import type { ComponentType } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Drawer, Layout, Menu } from 'antd'; +import type { MenuProps } from 'antd'; +import { + ApiOutlined, + ClusterOutlined, + CloseOutlined, + DashboardOutlined, + HeartOutlined, + LogoutOutlined, + MenuOutlined, + SettingOutlined, + TeamOutlined, + ToolOutlined, + UserOutlined, +} from '@ant-design/icons'; + +import { HttpUtil } from '@/utils'; +import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; +import './AppSidebar.css'; + +const SIDEBAR_COLLAPSED_KEY = 'isSidebarCollapsed'; +const DONATE_URL = 'https://donate.sanaei.dev/'; + +interface AppSidebarProps { + basePath?: string; + requestUri?: string; +} + +type IconName = 'dashboard' | 'user' | 'team' | 'setting' | 'tool' | 'cluster' | 'logout' | 'apidocs'; + +const iconByName: Record = { + dashboard: DashboardOutlined, + user: UserOutlined, + team: TeamOutlined, + setting: SettingOutlined, + tool: ToolOutlined, + cluster: ClusterOutlined, + logout: LogoutOutlined, + apidocs: ApiOutlined, +}; + +function readCollapsed(): boolean { + try { + return JSON.parse(localStorage.getItem(SIDEBAR_COLLAPSED_KEY) || 'false'); + } catch { + return false; + } +} + +function DonateButton({ ariaLabel }: { ariaLabel: string }) { + return ( + + + + ); +} + +function ThemeCycleButton({ id, isDark, isUltra, onCycle, ariaLabel }: { + id: string; + isDark: boolean; + isUltra: boolean; + onCycle: () => void; + ariaLabel: string; +}) { + return ( + + ); +} + +export default function AppSidebar({ basePath = '', requestUri = '' }: AppSidebarProps) { + const { t } = useTranslation(); + const { isDark, isUltra, toggleTheme, toggleUltra } = useTheme(); + + const [collapsed, setCollapsed] = useState(() => readCollapsed()); + const [drawerOpen, setDrawerOpen] = useState(false); + + const prefix = basePath.startsWith('/') ? basePath : `/${basePath || ''}`; + const currentTheme: 'light' | 'dark' = isDark ? 'dark' : 'light'; + const panelVersion = window.X_UI_CUR_VER || ''; + + const tabs = useMemo<{ key: string; icon: IconName; title: string }[]>(() => [ + { key: `${prefix}panel/`, icon: 'dashboard', title: t('menu.dashboard') }, + { key: `${prefix}panel/inbounds`, icon: 'user', title: t('menu.inbounds') }, + { key: `${prefix}panel/clients`, icon: 'team', title: t('menu.clients') }, + { key: `${prefix}panel/nodes`, icon: 'cluster', title: t('menu.nodes') }, + { key: `${prefix}panel/settings`, icon: 'setting', title: t('menu.settings') }, + { key: `${prefix}panel/xray`, icon: 'tool', title: t('menu.xray') }, + { key: `${prefix}panel/api-docs`, icon: 'apidocs', title: t('menu.apiDocs') }, + { key: 'logout', icon: 'logout', title: t('logout') }, + ], [prefix, t]); + + const navItems = useMemo(() => tabs.filter((tab) => tab.icon !== 'logout'), [tabs]); + const utilItems = useMemo(() => tabs.filter((tab) => tab.icon === 'logout'), [tabs]); + + const toMenuItems = useCallback((items: typeof tabs): MenuProps['items'] => + items.map((tab) => { + const Icon = iconByName[tab.icon]; + return { + key: tab.key, + icon: , + label: tab.title, + }; + }), + []); + + const openLink = useCallback(async (key: string) => { + if (key === 'logout') { + await HttpUtil.post('/logout'); + window.location.href = basePath || '/'; + return; + } + if (key.startsWith('http')) { + window.open(key); + } else { + window.location.href = key; + } + }, [basePath]); + + const onMenuClick = useCallback>(({ key }) => { + openLink(String(key)); + }, [openLink]); + + const onSiderCollapse = useCallback((isCollapsed: boolean, type: 'clickTrigger' | 'responsive') => { + if (type === 'clickTrigger') { + localStorage.setItem(SIDEBAR_COLLAPSED_KEY, String(isCollapsed)); + setCollapsed(isCollapsed); + } + }, []); + + const cycleTheme = useCallback((id: string) => { + pauseAnimationsUntilLeave(id); + if (!isDark) { + toggleTheme(); + if (isUltra) toggleUltra(); + } else if (!isUltra) { + toggleUltra(); + } else { + toggleUltra(); + toggleTheme(); + } + }, [isDark, isUltra, toggleTheme, toggleUltra]); + + return ( +
+ +
+
+ {collapsed ? '3X' : '3X-UI'} + {!collapsed && panelVersion && ( + v{panelVersion} + )} +
+ {!collapsed && ( +
+ + cycleTheme('theme-cycle')} + ariaLabel={t('menu.theme')} + /> +
+ )} +
+ + + + + setDrawerOpen(false)} + > +
+
+ 3X-UI + {panelVersion && v{panelVersion}} +
+
+ + cycleTheme('theme-cycle-drawer')} + ariaLabel={t('menu.theme')} + /> + +
+
+ { onMenuClick(info); setDrawerOpen(false); }} + /> + { onMenuClick(info); setDrawerOpen(false); }} + /> + + + {!drawerOpen && ( + + )} +
+ ); +} diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue deleted file mode 100644 index 1fd4dfc9..00000000 --- a/frontend/src/components/AppSidebar.vue +++ /dev/null @@ -1,432 +0,0 @@ - - - - - - - diff --git a/frontend/src/components/CustomStatistic.css b/frontend/src/components/CustomStatistic.css new file mode 100644 index 00000000..9cb3e065 --- /dev/null +++ b/frontend/src/components/CustomStatistic.css @@ -0,0 +1,52 @@ +.ant-statistic-content { + font-size: 17px !important; + line-height: 1.4 !important; + font-weight: 600; +} + +.ant-statistic-content-value, +.ant-statistic-content-prefix, +.ant-statistic-content-suffix { + font-size: 17px !important; +} + +.ant-statistic-content-prefix { + margin-inline-end: 8px !important; + opacity: 0.7; +} + +.ant-statistic-content-prefix .anticon { + font-size: 17px !important; +} + +.ant-statistic-content-suffix { + font-size: 12px !important; + opacity: 0.55; + margin-inline-start: 4px; + font-weight: 500; +} + +.ant-statistic-title { + font-size: 11px !important; + margin-bottom: 6px !important; + letter-spacing: 0.6px; + text-transform: uppercase; + color: rgba(0, 0, 0, 0.55); + font-weight: 500; +} + +body.dark .ant-statistic-content { + color: rgba(255, 255, 255, 0.92); +} + +body.dark .ant-statistic-title { + color: rgba(255, 255, 255, 0.72); +} + +html[data-theme='ultra-dark'] .ant-statistic-content { + color: rgba(255, 255, 255, 0.95); +} + +html[data-theme='ultra-dark'] .ant-statistic-title { + color: rgba(255, 255, 255, 0.70); +} diff --git a/frontend/src/components/CustomStatistic.tsx b/frontend/src/components/CustomStatistic.tsx new file mode 100644 index 00000000..6089637f --- /dev/null +++ b/frontend/src/components/CustomStatistic.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from 'react'; +import { Statistic } from 'antd'; +import './CustomStatistic.css'; + +interface CustomStatisticProps { + title?: string; + value?: string | number; + prefix?: ReactNode; + suffix?: ReactNode; +} + +export default function CustomStatistic({ title = '', value = '', prefix, suffix }: CustomStatisticProps) { + return ; +} diff --git a/frontend/src/components/CustomStatistic.vue b/frontend/src/components/CustomStatistic.vue deleted file mode 100644 index 9d59e37c..00000000 --- a/frontend/src/components/CustomStatistic.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/frontend/src/components/DateTimePicker.css b/frontend/src/components/DateTimePicker.css new file mode 100644 index 00000000..b745afb4 --- /dev/null +++ b/frontend/src/components/DateTimePicker.css @@ -0,0 +1,35 @@ +.jdp-wrap { + width: 100%; +} + +.jdp-wrap > * { + width: 100%; +} + +.jdp-wrap input { + direction: ltr; + text-align: left; + unicode-bidi: plaintext; +} + +.jdp-dark .jdp-wrap input, +.jdp-dark input { + color: rgba(255, 255, 255, 0.88) !important; + background-color: #23252b !important; +} + +.jdp-ultra .jdp-wrap input, +.jdp-ultra input { + color: rgba(255, 255, 255, 0.88) !important; + background-color: #101013 !important; +} + +.jdp-dark input::placeholder, +.jdp-ultra input::placeholder { + color: rgba(255, 255, 255, 0.30) !important; +} + +.jdp-disabled { + pointer-events: none; + opacity: 0.6; +} diff --git a/frontend/src/components/DateTimePicker.tsx b/frontend/src/components/DateTimePicker.tsx new file mode 100644 index 00000000..bdd521b6 --- /dev/null +++ b/frontend/src/components/DateTimePicker.tsx @@ -0,0 +1,98 @@ +import { useMemo } from 'react'; +import { DatePicker } from 'antd'; +import dayjs from 'dayjs'; +import type { Dayjs } from 'dayjs'; +import { PersianDateTimePicker } from 'persian-calendar-suite'; + +import { useDatepicker } from '@/hooks/useDatepicker'; +import { useTheme } from '@/hooks/useTheme'; +import './DateTimePicker.css'; + +interface DateTimePickerProps { + value: Dayjs | null; + onChange: (next: Dayjs | null) => void; + showTime?: boolean; + format?: string; + placeholder?: string; + disabled?: boolean; +} + +const LIGHT_THEME = { + primaryColor: '#1677ff', + backgroundColor: '#ffffff', + borderColor: '#d9d9d9', + hoverColor: 'rgba(22, 119, 255, 0.10)', + selectedTextColor: '#ffffff', + textColor: 'rgba(0, 0, 0, 0.88)', +}; + +const DARK_THEME = { + primaryColor: '#1677ff', + backgroundColor: '#23252b', + borderColor: 'rgba(255, 255, 255, 0.12)', + hoverColor: 'rgba(22, 119, 255, 0.18)', + selectedTextColor: '#ffffff', + textColor: 'rgba(255, 255, 255, 0.88)', +}; + +const ULTRA_DARK_THEME = { + primaryColor: '#1677ff', + backgroundColor: '#101013', + borderColor: 'rgba(255, 255, 255, 0.08)', + hoverColor: 'rgba(22, 119, 255, 0.16)', + selectedTextColor: '#ffffff', + textColor: 'rgba(255, 255, 255, 0.88)', +}; + +export default function DateTimePicker({ + value, + onChange, + showTime = true, + format = 'YYYY-MM-DD HH:mm:ss', + placeholder = '', + disabled = false, +}: DateTimePickerProps) { + const { datepicker } = useDatepicker(); + const { isDark, isUltra } = useTheme(); + + const persianTheme = useMemo(() => { + if (isUltra) return ULTRA_DARK_THEME; + if (isDark) return DARK_THEME; + return LIGHT_THEME; + }, [isDark, isUltra]); + + if (datepicker === 'jalalian') { + return ( +
+ { + if (next == null || next === '') { + onChange(null); + return; + } + const ms = typeof next === 'number' ? next : Number(next); + if (Number.isFinite(ms)) onChange(dayjs(ms)); + }} + showTime={showTime} + outputFormat="timestamp" + persianNumbers + rtlCalendar + theme={persianTheme} + /> +
+ ); + } + + return ( + onChange(next || null)} + showTime={showTime ? { format: 'HH:mm:ss' } : false} + format={format} + placeholder={placeholder} + disabled={disabled} + style={{ width: '100%' }} + /> + ); +} diff --git a/frontend/src/components/DateTimePicker.vue b/frontend/src/components/DateTimePicker.vue deleted file mode 100644 index 750d2594..00000000 --- a/frontend/src/components/DateTimePicker.vue +++ /dev/null @@ -1,366 +0,0 @@ - - - - - - - - diff --git a/frontend/src/components/FinalMaskForm.tsx b/frontend/src/components/FinalMaskForm.tsx new file mode 100644 index 00000000..07039cca --- /dev/null +++ b/frontend/src/components/FinalMaskForm.tsx @@ -0,0 +1,738 @@ +import { useMemo } from 'react'; +import { Button, Divider, Form, Input, InputNumber, Select, Switch } from 'antd'; +import { DeleteOutlined, PlusOutlined, ReloadOutlined } from '@ant-design/icons'; + +import { RandomUtil } from '@/utils'; +import { Protocols } from '@/models/outbound.js'; + +interface StreamShape { + network?: string; + kcp?: { mtu?: number }; + finalmask: { + tcp?: MaskRow[]; + udp?: MaskRow[]; + enableQuicParams?: boolean; + quicParams?: QuicParams; + }; + addTcpMask: (type?: string) => void; + delTcpMask: (index: number) => void; + addUdpMask: (type?: string) => void; + delUdpMask: (index: number) => void; +} + +interface MaskRow { + type: string; + settings: Record; + _getDefaultSettings: (type: string, settings: Record) => Record; +} + +interface ItemRow { + type: string; + packet: string | unknown[]; + delay?: number | string; + rand?: number | string; + randRange?: string; +} + +interface QuicParams { + congestion: string; + debug?: boolean; + brutalUp?: number | string; + brutalDown?: number | string; + hasUdpHop?: boolean; + udpHop?: { ports: string; interval: string | number }; + maxIdleTimeout?: number; + keepAlivePeriod?: number; + disablePathMTUDiscovery?: boolean; + maxIncomingStreams?: number; + initStreamReceiveWindow?: number; + maxStreamReceiveWindow?: number; + initConnectionReceiveWindow?: number; + maxConnectionReceiveWindow?: number; +} + +interface FinalMaskFormProps { + stream: StreamShape; + protocol: string; + onChange: () => void; +} + +function changeMaskType(mask: MaskRow, type: string) { + mask.type = type; + mask.settings = mask._getDefaultSettings(type, {}); +} + +function changeItemType(item: ItemRow, type: string) { + item.type = type; + if (type === 'base64') item.packet = RandomUtil.randomBase64(); + else if (type === 'array') { + item.rand = 0; + item.packet = []; + } else item.packet = ''; +} + +function newClientServerItem(): ItemRow { + return { delay: 0, rand: 0, randRange: '0-255', type: 'array', packet: [] }; +} + +function newUdpClientServerItem(): ItemRow { + return { rand: 0, randRange: '0-255', type: 'array', packet: [] }; +} + +function newNoiseItem(): ItemRow { + return { rand: '1-8192', randRange: '0-255', type: 'array', packet: [], delay: '10-20' }; +} + +export default function FinalMaskForm({ stream, protocol, onChange }: FinalMaskFormProps) { + const isHysteria = protocol === Protocols.Hysteria || protocol === 'hysteria'; + const network = stream?.network || ''; + + const showTcp = useMemo( + () => ['raw', 'tcp', 'httpupgrade', 'ws', 'grpc', 'xhttp'].includes(network), + [network], + ); + const showUdp = isHysteria || network === 'kcp'; + const showQuic = isHysteria || network === 'xhttp'; + + function notify() { + onChange(); + } + + function changeUdpMaskType(mask: MaskRow, type: string) { + changeMaskType(mask, type); + if (network === 'kcp' && stream.kcp) { + stream.kcp.mtu = type === 'xdns' ? 900 : 1350; + } + notify(); + } + + function addUdpMaskWithDefault() { + const def = isHysteria ? 'salamander' : 'mkcp-aes128gcm'; + stream.addUdpMask(def); + notify(); + } + + const tcpMasks = stream.finalmask.tcp || []; + const udpMasks = stream.finalmask.udp || []; + + if (!showTcp && !showUdp && !showQuic) return null; + + return ( +
+ {showTcp && ( + <> + + + )} + + + )} + > + + + + ); +} diff --git a/frontend/src/components/TextModal.vue b/frontend/src/components/TextModal.vue deleted file mode 100644 index 2e9bfe6d..00000000 --- a/frontend/src/components/TextModal.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - - - diff --git a/frontend/src/composables/useDatepicker.js b/frontend/src/composables/useDatepicker.js deleted file mode 100644 index 03eba91c..00000000 --- a/frontend/src/composables/useDatepicker.js +++ /dev/null @@ -1,45 +0,0 @@ -// Module-scoped reactive ref for the panel's "Calendar Type" setting. -// Loaded from /panel/setting/defaultSettings on first use, so any -// component (modals, inbound forms, future pages) can read the same -// value without prop-drilling and without re-fetching. -// -// useInbounds (which already reads defaultSettings for its own state) -// calls setDatepicker() after its fetch so we don't issue a second -// HTTP round-trip on the inbounds page. - -import { readonly, ref } from 'vue'; -import { HttpUtil } from '@/utils'; - -const datepicker = ref('gregorian'); -let fetched = false; -let pending = null; - -async function loadOnce() { - if (fetched) return; - if (pending) { - await pending; - return; - } - pending = (async () => { - try { - const msg = await HttpUtil.post('/panel/setting/defaultSettings'); - if (msg?.success) { - datepicker.value = msg.obj?.datepicker || 'gregorian'; - } - } finally { - fetched = true; - pending = null; - } - })(); - await pending; -} - -export function setDatepicker(value) { - fetched = true; - datepicker.value = value || 'gregorian'; -} - -export function useDatepicker() { - loadOnce(); - return { datepicker: readonly(datepicker) }; -} diff --git a/frontend/src/composables/useMediaQuery.js b/frontend/src/composables/useMediaQuery.js deleted file mode 100644 index a1861c93..00000000 --- a/frontend/src/composables/useMediaQuery.js +++ /dev/null @@ -1,26 +0,0 @@ -import { ref, onBeforeUnmount, onMounted } from 'vue'; - -const MOBILE_BREAKPOINT_PX = 768; - -// Vue 3 replacement for the legacy MediaQueryMixin. Returns a reactive -// `isMobile` ref that updates on window resize. Use inside - - - - - - diff --git a/frontend/src/pages/api-docs/CodeBlock.vue b/frontend/src/pages/api-docs/CodeBlock.css similarity index 54% rename from frontend/src/pages/api-docs/CodeBlock.vue rename to frontend/src/pages/api-docs/CodeBlock.css index 446016c7..5cf64ceb 100644 --- a/frontend/src/pages/api-docs/CodeBlock.vue +++ b/frontend/src/pages/api-docs/CodeBlock.css @@ -1,67 +1,3 @@ - - - - - - diff --git a/frontend/src/pages/api-docs/CodeBlock.tsx b/frontend/src/pages/api-docs/CodeBlock.tsx new file mode 100644 index 00000000..805d1378 --- /dev/null +++ b/frontend/src/pages/api-docs/CodeBlock.tsx @@ -0,0 +1,69 @@ +import { useMemo, useState } from 'react'; +import { message } from 'antd'; +import { CheckOutlined, CopyOutlined } from '@ant-design/icons'; +import { ClipboardManager } from '@/utils'; +import './CodeBlock.css'; + +interface CodeBlockProps { + code?: string; + lang?: string; +} + +function escapeHtml(str: string): string { + return str.replace(/&/g, '&').replace(//g, '>'); +} + +function highlightJson(str: string): string { + const escaped = escapeHtml(str); + return escaped.replace( + /("(?:[^"\\]|\\.)*")\s*(:)|("(?:[^"\\]|\\.)*")|(-?\d+\.?\d*(?:[eE][+-]?\d+)?)\b|(true|false)|(null)|([{}[\]])/g, + (_m, key, colon, string, number, bool, nil) => { + if (colon) return `${key}${colon}`; + if (string) return `${string}`; + if (number) return `${number}`; + if (bool) return `${bool}`; + if (nil) return `${nil}`; + return _m; + }, + ); +} + +export default function CodeBlock({ code = '', lang = 'json' }: CodeBlockProps) { + const [copied, setCopied] = useState(false); + const [messageApi, messageContextHolder] = message.useMessage(); + + const highlighted = useMemo( + () => (lang === 'json' ? highlightJson(code) : escapeHtml(code)), + [code, lang], + ); + + async function copyCode() { + const ok = await ClipboardManager.copyText(code); + if (ok) { + setCopied(true); + messageApi.success('Copied'); + window.setTimeout(() => setCopied(false), 2000); + } else { + messageApi.error('Copy failed'); + } + } + + return ( +
+ {messageContextHolder} +
+ {lang.toUpperCase()} + +
+
+        
+      
+
+ ); +} diff --git a/frontend/src/pages/api-docs/EndpointRow.css b/frontend/src/pages/api-docs/EndpointRow.css new file mode 100644 index 00000000..43cd8c3f --- /dev/null +++ b/frontend/src/pages/api-docs/EndpointRow.css @@ -0,0 +1,93 @@ +.endpoint-row { + padding: 14px 8px; + margin: 0 -8px; + transition: background 0.15s; + border-radius: 6px; +} + +.endpoint-row:hover { + background: rgba(128, 128, 128, 0.03); +} + +.endpoint-row + .endpoint-row { + border-top: 1px solid rgba(128, 128, 128, 0.1); +} + +.endpoint-header { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.method-tag { + font-weight: 700; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 11px; + letter-spacing: 0.5px; + min-width: 56px; + text-align: center; + text-transform: uppercase; + border-radius: 4px; + padding: 2px 8px; + line-height: 1.6; +} + +.endpoint-path { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13.5px; + word-break: break-all; + color: rgba(0, 0, 0, 0.8); + background: rgba(128, 128, 128, 0.06); + padding: 2px 8px; + border-radius: 4px; +} + +.endpoint-summary { + margin: 8px 0 0; + color: rgba(0, 0, 0, 0.6); + line-height: 1.6; + font-size: 13.5px; +} + +.endpoint-block { + margin-top: 14px; +} + +.block-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.6px; + color: rgba(0, 0, 0, 0.45); + margin-bottom: 6px; +} + +.error-label { + color: #cf222e; +} + +body.dark .endpoint-row:hover { + background: rgba(255, 255, 255, 0.02); +} + +body.dark .endpoint-row + .endpoint-row { + border-top-color: rgba(255, 255, 255, 0.08); +} + +body.dark .endpoint-path { + color: rgba(255, 255, 255, 0.82); + background: rgba(255, 255, 255, 0.05); +} + +body.dark .endpoint-summary { + color: rgba(255, 255, 255, 0.65); +} + +body.dark .block-label { + color: rgba(255, 255, 255, 0.45); +} + +body.dark .error-label { + color: #ff7b72; +} diff --git a/frontend/src/pages/api-docs/EndpointRow.tsx b/frontend/src/pages/api-docs/EndpointRow.tsx new file mode 100644 index 00000000..d7fd7ad2 --- /dev/null +++ b/frontend/src/pages/api-docs/EndpointRow.tsx @@ -0,0 +1,84 @@ +import { Table, Tag } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { methodColors, safeInlineHtml } from './endpoints.js'; +import CodeBlock from './CodeBlock'; +import './EndpointRow.css'; + +interface Param { + name: string; + in?: string; + type?: string; + desc?: string; +} + +export interface Endpoint { + method: string; + path: string; + summary?: string; + params?: Param[]; + body?: string; + response?: string; + errorResponse?: string; +} + +const paramColumns: ColumnsType = [ + { title: 'Name', dataIndex: 'name', key: 'name', width: 180 }, + { title: 'In', dataIndex: 'in', key: 'in', width: 100 }, + { title: 'Type', dataIndex: 'type', key: 'type', width: 120 }, + { title: 'Description', dataIndex: 'desc', key: 'desc' }, +]; + +export default function EndpointRow({ endpoint }: { endpoint: Endpoint }) { + const tagColor = (methodColors as Record)[endpoint.method] || 'default'; + const hasParams = Array.isArray(endpoint.params) && endpoint.params.length > 0; + + return ( +
+
+ {endpoint.method} + {endpoint.path} +
+ + {endpoint.summary && ( +

+ )} + + {hasParams && ( +

+
Parameters
+ + + )} + + {endpoint.body && ( +
+
Request body
+ +
+ )} + + {endpoint.response && ( +
+
Response
+ +
+ )} + + {endpoint.errorResponse && ( +
+
Error response
+ +
+ )} + + ); +} diff --git a/frontend/src/pages/api-docs/EndpointRow.vue b/frontend/src/pages/api-docs/EndpointRow.vue deleted file mode 100644 index 5a811427..00000000 --- a/frontend/src/pages/api-docs/EndpointRow.vue +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - diff --git a/frontend/src/pages/api-docs/EndpointSection.vue b/frontend/src/pages/api-docs/EndpointSection.css similarity index 51% rename from frontend/src/pages/api-docs/EndpointSection.vue rename to frontend/src/pages/api-docs/EndpointSection.css index 795e9bb6..f6a54568 100644 --- a/frontend/src/pages/api-docs/EndpointSection.vue +++ b/frontend/src/pages/api-docs/EndpointSection.css @@ -1,63 +1,3 @@ - - - - - - diff --git a/frontend/src/pages/api-docs/EndpointSection.tsx b/frontend/src/pages/api-docs/EndpointSection.tsx new file mode 100644 index 00000000..8b254d19 --- /dev/null +++ b/frontend/src/pages/api-docs/EndpointSection.tsx @@ -0,0 +1,90 @@ +import type { ComponentType } from 'react'; +import { Table } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { DownOutlined, RightOutlined } from '@ant-design/icons'; +import EndpointRow from './EndpointRow'; +import type { Endpoint } from './EndpointRow'; +import { safeInlineHtml } from './endpoints.js'; +import './EndpointSection.css'; + +interface SubHeader { + name: string; + desc?: string; +} + +export interface Section { + id: string; + title: string; + description?: string; + endpoints: Endpoint[]; + subHeader?: SubHeader[]; +} + +interface EndpointSectionProps { + section: Section; + icon?: ComponentType<{ className?: string }> | null; + collapsed?: boolean; + onToggle?: () => void; +} + +const subHeaderColumns: ColumnsType = [ + { title: 'Header', dataIndex: 'name', key: 'name', width: 240 }, + { + title: 'Description', + dataIndex: 'desc', + key: 'desc', + render: (value: string) => ( + + ), + }, +]; + +export default function EndpointSection({ + section, + icon: Icon = null, + collapsed = false, + onToggle, +}: EndpointSectionProps) { + const endpointLabel = section.endpoints.length === 1 + ? '1 endpoint' + : `${section.endpoints.length} endpoints`; + + return ( +
+
+
+ {collapsed ? : } + {Icon && } +

{section.title}

+
+ {endpointLabel} +
+ + {section.description && !collapsed && ( +

+ )} + + {section.subHeader && !collapsed && ( +

+
Response headers
+
+ + )} + +
+ {section.endpoints.map((endpoint, idx) => ( + + ))} +
+ + ); +} diff --git a/frontend/src/pages/clients/ClientBulkAddModal.css b/frontend/src/pages/clients/ClientBulkAddModal.css new file mode 100644 index 00000000..e49ef577 --- /dev/null +++ b/frontend/src/pages/clients/ClientBulkAddModal.css @@ -0,0 +1,5 @@ +.random-icon { + margin-left: 4px; + cursor: pointer; + color: var(--ant-color-primary, #1677ff); +} diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx new file mode 100644 index 00000000..43358b78 --- /dev/null +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -0,0 +1,341 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Form, Input, InputNumber, Modal, Select, Switch, message } from 'antd'; +import { SyncOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; +import type { Dayjs } from 'dayjs'; + +import { HttpUtil, RandomUtil, SizeFormatter } from '@/utils'; +import { TLS_FLOW_CONTROL } from '@/models/inbound'; +import DateTimePicker from '@/components/DateTimePicker'; +import type { InboundOption } from '@/hooks/useClients'; +import './ClientBulkAddModal.css'; + +const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL); +const JSON_HEADERS = { headers: { 'Content-Type': 'application/json' } } as const; + +const MULTI_CLIENT_PROTOCOLS = new Set([ + 'shadowsocks', 'vless', 'vmess', 'trojan', 'hysteria', 'hysteria2', +]); + +interface ApiMsg { + success?: boolean; + msg?: string; +} + +interface ClientBulkAddModalProps { + open: boolean; + inbounds: InboundOption[]; + ipLimitEnable?: boolean; + onOpenChange: (open: boolean) => void; + onSaved?: () => void; +} + +interface FormState { + emailMethod: number; + firstNum: number; + lastNum: number; + emailPrefix: string; + emailPostfix: string; + quantity: number; + subId: string; + comment: string; + flow: string; + limitIp: number; + totalGB: number; + expiryTime: number; + inboundIds: number[]; +} + +function emptyForm(): FormState { + return { + emailMethod: 0, + firstNum: 1, + lastNum: 1, + emailPrefix: '', + emailPostfix: '', + quantity: 1, + subId: '', + comment: '', + flow: '', + limitIp: 0, + totalGB: 0, + expiryTime: 0, + inboundIds: [], + }; +} + +export default function ClientBulkAddModal({ + open, + inbounds, + ipLimitEnable = false, + onOpenChange, + onSaved, +}: ClientBulkAddModalProps) { + const { t } = useTranslation(); + const [messageApi, messageContextHolder] = message.useMessage(); + + const [form, setForm] = useState(emptyForm); + const [delayedStart, setDelayedStart] = useState(false); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (!open) return; + + setForm(emptyForm()); + setDelayedStart(false); + + }, [open]); + + function update(key: K, value: FormState[K]) { + setForm((prev) => ({ ...prev, [key]: value })); + } + + const flowCapableIds = useMemo(() => { + const ids = new Set(); + for (const row of inbounds || []) { + if (row?.tlsFlowCapable) ids.add(row.id); + } + return ids; + }, [inbounds]); + + const showFlow = useMemo( + () => (form.inboundIds || []).some((id) => flowCapableIds.has(id)), + [form.inboundIds, flowCapableIds], + ); + + useEffect(() => { + if (!showFlow && form.flow) { + + update('flow', ''); + } + }, [showFlow, form.flow]); + + const inboundOptions = useMemo( + () => (inbounds || []) + .filter((ib) => MULTI_CLIENT_PROTOCOLS.has(ib.protocol || '')) + .map((ib) => ({ + label: `${ib.remark || `#${ib.id}`} · ${ib.protocol}:${ib.port}`, + value: ib.id, + })), + [inbounds], + ); + + const expiryDate = useMemo( + () => (form.expiryTime > 0 ? dayjs(form.expiryTime) : null), + [form.expiryTime], + ); + + const delayedExpireDays = form.expiryTime < 0 ? form.expiryTime / -86400000 : 0; + + function buildEmails(): string[] { + const method = form.emailMethod; + const out: string[] = []; + let start: number; + let end: number; + if (method > 1) { + start = form.firstNum; + end = form.lastNum + 1; + } else { + start = 0; + end = form.quantity; + } + const prefix = method > 0 && form.emailPrefix.length > 0 ? form.emailPrefix : ''; + const useNum = method > 1; + const postfix = method > 2 && form.emailPostfix.length > 0 ? form.emailPostfix : ''; + for (let i = start; i < end; i++) { + let email = ''; + if (method !== 4) email = RandomUtil.randomLowerAndNum(6); + email += useNum ? prefix + String(i) + postfix : prefix + postfix; + out.push(email); + } + return out; + } + + async function submit() { + if (!Array.isArray(form.inboundIds) || form.inboundIds.length === 0) { + messageApi.error(t('pages.clients.selectInbound')); + return; + } + const emails = buildEmails(); + if (emails.length === 0) return; + + setSaving(true); + const silentJsonOpts = { ...JSON_HEADERS, silent: true }; + try { + const results = await Promise.all(emails.map((email) => { + const client = { + email, + subId: form.subId || RandomUtil.randomLowerAndNum(16), + id: RandomUtil.randomUUID(), + password: RandomUtil.randomLowerAndNum(16), + auth: RandomUtil.randomLowerAndNum(16), + flow: showFlow ? (form.flow || '') : '', + totalGB: Math.round((form.totalGB || 0) * SizeFormatter.ONE_GB), + expiryTime: form.expiryTime, + limitIp: Number(form.limitIp) || 0, + comment: form.comment, + enable: true, + }; + const payload = { client, inboundIds: form.inboundIds }; + return HttpUtil.post('/panel/api/clients/add', payload, silentJsonOpts) as Promise; + })); + let ok = 0; + let failed = 0; + let firstError = ''; + for (const msg of results) { + if (msg?.success) ok++; + else { + failed++; + if (!firstError && msg?.msg) firstError = msg.msg; + } + } + if (failed === 0) { + messageApi.success(t('pages.clients.toasts.bulkCreated', { count: ok })); + } else { + messageApi.warning(firstError + ? `${t('pages.clients.toasts.bulkCreatedMixed', { ok, failed })} — ${firstError}` + : t('pages.clients.toasts.bulkCreatedMixed', { ok, failed })); + } + onSaved?.(); + onOpenChange(false); + } finally { + setSaving(false); + } + } + + return ( + <> + {messageContextHolder} + onOpenChange(false)} + > +
+ + update('emailMethod', v)} + options={[ + { value: 0, label: 'Random' }, + { value: 1, label: 'Random + Prefix' }, + { value: 2, label: 'Random + Prefix + Num' }, + { value: 3, label: 'Random + Prefix + Num + Postfix' }, + { value: 4, label: 'Prefix + Num + Postfix' }, + ]} + /> + + + {form.emailMethod > 1 && ( + <> + + update('firstNum', Number(v) || 1)} /> + + + update('lastNum', Number(v) || 1)} /> + + + )} + {form.emailMethod > 0 && ( + + update('emailPrefix', e.target.value)} /> + + )} + {form.emailMethod > 2 && ( + + update('emailPostfix', e.target.value)} /> + + )} + {form.emailMethod < 2 && ( + + update('quantity', Number(v) || 1)} /> + + )} + + + {t('subscription.title')} + update('subId', RandomUtil.randomLowerAndNum(16))} + /> + + }> + update('subId', e.target.value)} /> + + + + update('comment', e.target.value)} /> + + + {showFlow && ( + + update('email', e.target.value)} + /> + + + + +
+ + + update('subId', e.target.value)} /> + + + + + + + + + + + update('auth', e.target.value)} /> + + + + + + + + update('password', e.target.value)} /> + + + + + + + + + + + update('uuid', e.target.value)} /> + + + + + + + update('totalGB', Number(v) || 0)} /> + + + {ipLimitEnable && ( + + + update('limitIp', Number(v) || 0)} /> + + + )} + + + + + {form.delayedStart ? ( + + update('delayedDays', Number(v) || 0)} /> + + ) : ( + + update('expiryDate', d || null)} + /> + + )} + + + + { + update('delayedStart', v); + if (v) update('expiryDate', null); + else update('delayedDays', 0); + }} + /> + + + + + {(showFlow || showReverseTag) && ( + + {showFlow && ( + + + update('reverseTag', e.target.value)} /> + + + )} + + )} + + + {tgBotEnable && ( + + + update('tgId', Number(v) || 0)} /> + + + )} + + + update('comment', e.target.value)} /> + + + + + +
+ + + + + + + + + + + + + + + + + + {client.uuid && ( + + + + + )} + {client.password && ( + + + + + )} + {client.auth && ( + + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {client.comment && ( + + + + + )} + + + + + +
{t('pages.clients.online')} + {client.enable && isOnline + ? {t('pages.clients.online')} + : {t('pages.clients.offline')}} + {t('lastOnline')}: {dateLabel(traffic?.lastOnline)} +
{t('status')} + + {client.enable ? t('enabled') : t('disabled')} + +
{t('pages.clients.email')} + {client.email + ? {client.email} + : {t('none')}} +
{t('pages.clients.subId')} + {client.subId || '-'} + {client.subId && ( +
{t('pages.clients.uuid')} + {client.uuid} +
{t('password')} + {client.password} +
{t('pages.clients.auth')} + {client.auth} +
{t('pages.clients.flow')} + {client.flow ? {client.flow} : {t('none')}} +
{t('pages.inbounds.traffic')} + + ↑ {SizeFormatter.sizeFormat(traffic?.up || 0)} + {' '}/ ↓ {SizeFormatter.sizeFormat(traffic?.down || 0)} + + + {SizeFormatter.sizeFormat(used)} / {totalBytes > 0 ? SizeFormatter.sizeFormat(totalBytes) : '∞'} + +
{t('remained')} + {remaining < 0 + ? + : 0 ? '' : 'red'}>{SizeFormatter.sizeFormat(remaining)}} +
{t('pages.inbounds.expireDate')} + {!client.expiryTime || client.expiryTime <= 0 + ? + : {expiryLabel(client.expiryTime)}} + {(client.expiryTime ?? 0) > 0 && ( + {IntlUtil.formatRelativeTime(client.expiryTime)} + )} +
{t('pages.clients.ipLimit')}{!client.limitIp ? : {client.limitIp}}
{t('pages.inbounds.createdAt')}{dateLabel(client.createdAt)}
{t('pages.inbounds.updatedAt')}{dateLabel(client.updatedAt)}
{t('pages.clients.comment')}{client.comment}
{t('pages.clients.attachedInbounds')} +
+ {(client.inboundIds || []).map((id) => { + const ib = inboundsById[id]; + return ( + + {ib ? `${ib.remark || `#${id}`} (${ib.protocol}:${ib.port})` : `#${id}`} + + ); + })} + {(!client.inboundIds || client.inboundIds.length === 0) && ( + + )} +
+
+ + {links.length > 0 && ( + <> + {t('pages.inbounds.copyLink')} + {links.map((link, idx) => ( +
+
+ {`${t('pages.clients.link')} ${idx + 1}`} + +
+ {link} +
+ ))} + + )} + + {showSubscription && subLink && ( + <> + {t('subscription.title')} +
+
+ {t('subscription.title')} + +
+ {subLink} +
+ {subJsonLink && ( +
+
+ JSON + +
+ {subJsonLink} +
+ )} + + )} + + )} + + + ); +} diff --git a/frontend/src/pages/clients/ClientInfoModal.vue b/frontend/src/pages/clients/ClientInfoModal.vue deleted file mode 100644 index 8694e8c2..00000000 --- a/frontend/src/pages/clients/ClientInfoModal.vue +++ /dev/null @@ -1,411 +0,0 @@ - - - - - diff --git a/frontend/src/pages/clients/ClientQrModal.tsx b/frontend/src/pages/clients/ClientQrModal.tsx new file mode 100644 index 00000000..6263414a --- /dev/null +++ b/frontend/src/pages/clients/ClientQrModal.tsx @@ -0,0 +1,136 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Collapse, Modal, Spin } from 'antd'; +import { HttpUtil } from '@/utils'; +import QrPanel from '@/pages/inbounds/QrPanel'; +import type { ClientRecord } from '@/hooks/useClients'; + +interface SubSettings { + enable: boolean; + subURI: string; + subJsonURI: string; + subJsonEnable: boolean; +} + +interface ClientQrModalProps { + open: boolean; + client: ClientRecord | null; + subSettings?: SubSettings; + onOpenChange: (open: boolean) => void; +} + +interface ApiMsg { + success?: boolean; + obj?: T; +} + +const DEFAULT_SUB: SubSettings = { enable: false, subURI: '', subJsonURI: '', subJsonEnable: false }; + +export default function ClientQrModal({ + open, + client, + subSettings = DEFAULT_SUB, + onOpenChange, +}: ClientQrModalProps) { + const { t } = useTranslation(); + const [links, setLinks] = useState([]); + const [loading, setLoading] = useState(false); + + const subLink = useMemo(() => { + if (!client?.subId || !subSettings?.enable || !subSettings?.subURI) return ''; + return subSettings.subURI + client.subId; + }, [client?.subId, subSettings?.enable, subSettings?.subURI]); + + const subJsonLink = useMemo(() => { + if (!client?.subId || !subSettings?.enable) return ''; + if (!subSettings?.subJsonEnable || !subSettings?.subJsonURI) return ''; + return subSettings.subJsonURI + client.subId; + }, [client?.subId, subSettings?.enable, subSettings?.subJsonEnable, subSettings?.subJsonURI]); + + const hasAnything = !!subLink || !!subJsonLink || links.length > 0; + + useEffect(() => { + if (!open || !client?.subId) { + setLinks([]); + return; + } + let cancelled = false; + setLoading(true); + (async () => { + try { + const msg = await HttpUtil.get( + `/panel/api/clients/subLinks/${encodeURIComponent(client.subId!)}`, + ) as ApiMsg; + if (!cancelled) { + setLinks(msg?.success && Array.isArray(msg.obj) ? msg.obj : []); + } + } finally { + if (!cancelled) setLoading(false); + } + })(); + return () => { cancelled = true; }; + }, [open, client?.subId]); + + const [activeKey, setActiveKey] = useState([]); + + const items = useMemo(() => { + const out: { key: string; label: string; children: React.ReactNode }[] = []; + if (subLink) { + out.push({ + key: 'sub', + label: t('subscription.title'), + children: , + }); + } + if (subJsonLink) { + out.push({ + key: 'subJson', + label: `${t('subscription.title')} (JSON)`, + children: , + }); + } + links.forEach((link, idx) => { + out.push({ + key: `l${idx}`, + label: `${t('pages.clients.link')} ${idx + 1}`, + children: , + }); + }); + return out; + }, [subLink, subJsonLink, links, client?.email, t]); + + useEffect(() => { + if (!open) { + setActiveKey([]); + return; + } + setActiveKey(items.length > 0 ? [items[0].key] : []); + }, [open, items]); + + return ( + onOpenChange(false)} + > + + {!client?.subId && !loading && ( +
{t('pages.clients.noSubId')}
+ )} + {client?.subId && !hasAnything && !loading && ( +
{t('pages.clients.noLinks')}
+ )} + {hasAnything && ( + setActiveKey(typeof keys === 'string' ? [keys] : (keys as string[]))} + items={items} + /> + )} +
+
+ ); +} diff --git a/frontend/src/pages/clients/ClientQrModal.vue b/frontend/src/pages/clients/ClientQrModal.vue deleted file mode 100644 index 3ae69c3f..00000000 --- a/frontend/src/pages/clients/ClientQrModal.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/frontend/src/pages/clients/ClientsPage.css b/frontend/src/pages/clients/ClientsPage.css new file mode 100644 index 00000000..575d68c3 --- /dev/null +++ b/frontend/src/pages/clients/ClientsPage.css @@ -0,0 +1,221 @@ +.clients-page { + --bg-page: #e6e8ec; + --bg-card: #ffffff; + min-height: 100vh; + background: var(--bg-page); +} + +.clients-page.is-dark { + --bg-page: #1a1b1f; + --bg-card: #23252b; +} + +.clients-page.is-dark.is-ultra { + --bg-page: #000; + --bg-card: #101013; +} + +.clients-page .ant-layout, +.clients-page .ant-layout-content { + background: transparent; +} + +.clients-page .content-shell { + background: transparent; +} + +.clients-page .content-area { + padding: 24px; +} + +@media (max-width: 768px) { + .clients-page .content-area { + padding: 8px; + } +} + +.clients-page .ant-pagination-options-size-changer, +.clients-page .ant-pagination-options-size-changer .ant-select-selector { + min-width: 100px !important; +} + +.clients-page .loading-spacer { + min-height: calc(100vh - 120px); +} + +.clients-page .summary-card { + padding: 16px; +} + +@media (max-width: 768px) { + .clients-page .summary-card { + padding: 8px; + } +} + +.client-email-list { + max-height: 280px; + min-width: 160px; + overflow-y: auto; + padding-right: 4px; +} + +.client-email-list > div { + padding: 2px 0; + font-size: 12px; + white-space: nowrap; +} + +.filter-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 12px; +} + +.filter-bar.mobile { + gap: 6px; + margin-bottom: 8px; +} + +.filter-bar.mobile > * { + flex: 0 0 auto; +} + +.dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-right: 4px; + vertical-align: middle; +} + +.dot-green { background: #52c41a; } +.dot-blue { background: #1677ff; } +.dot-red { background: #ff4d4f; } +.dot-orange { background: #fa8c16; } +.dot-gray { background: rgba(128, 128, 128, 0.6); } + +.status-tag { + margin: 0 0 0 4px; + font-size: 11px; + padding: 0 6px; + line-height: 18px; +} + +.card-toolbar { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.email-cell { + display: flex; + flex-direction: column; +} + +.email-cell .email { + font-weight: 500; +} + +.email-cell .sub { + font-size: 11px; + opacity: 0.55; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 220px; +} + +.client-cards { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 4px; +} + +.card-bulk-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 4px 8px; +} + +.bulk-count { + font-size: 12px; + background: rgba(22, 119, 255, 0.12); + color: var(--ant-color-primary, #1677ff); + padding: 1px 8px; + border-radius: 10px; +} + +.client-card { + border: 1px solid rgba(128, 128, 128, 0.2); + border-radius: 10px; + padding: 10px 12px; + background: rgba(255, 255, 255, 0.02); +} + +.client-card.is-selected { + border-color: var(--ant-color-primary, #1677ff); + background: rgba(22, 119, 255, 0.06); +} + +body.dark .client-card { + background: rgba(255, 255, 255, 0.03); + border-color: rgba(255, 255, 255, 0.1); +} + +.card-head { + display: flex; + align-items: center; + gap: 8px; + user-select: none; +} + +.card-head .tag-name { + font-weight: 600; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.card-actions { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; +} + +.row-action-trigger { + font-size: 18px; + cursor: pointer; + opacity: 0.75; + transition: opacity 120ms ease; +} + +.row-action-trigger:hover { + opacity: 1; +} + +.card-empty { + text-align: center; + padding: 40px 16px; + opacity: 0.55; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.clients-empty { + padding: 32px 0; + text-align: center; + opacity: 0.55; +} diff --git a/frontend/src/pages/clients/ClientsPage.tsx b/frontend/src/pages/clients/ClientsPage.tsx new file mode 100644 index 00000000..3018fadb --- /dev/null +++ b/frontend/src/pages/clients/ClientsPage.tsx @@ -0,0 +1,908 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Badge, + Button, + Card, + Checkbox, + Col, + ConfigProvider, + Dropdown, + Input, + Layout, + Modal, + Popover, + Radio, + Row, + Select, + Space, + Spin, + Switch, + Table, + Tag, + Tooltip, + message, +} from 'antd'; +import type { ColumnsType, TableProps } from 'antd/es/table'; +import { + DeleteOutlined, + EditOutlined, + FilterOutlined, + InfoCircleOutlined, + MoreOutlined, + PlusOutlined, + QrcodeOutlined, + RestOutlined, + RetweetOutlined, + SearchOutlined, + TeamOutlined, + UserOutlined, + UsergroupAddOutlined, +} from '@ant-design/icons'; + +import { useTheme } from '@/hooks/useTheme'; +import { useMediaQuery } from '@/hooks/useMediaQuery'; +import { useWebSocket } from '@/hooks/useWebSocket'; +import { useClients } from '@/hooks/useClients'; +import { useDatepicker } from '@/hooks/useDatepicker'; +import type { ClientRecord, InboundOption } from '@/hooks/useClients'; +import AppSidebar from '@/components/AppSidebar'; +import CustomStatistic from '@/components/CustomStatistic'; +import { IntlUtil, ObjectUtil, SizeFormatter } from '@/utils'; +import { setMessageInstance } from '@/utils/messageBus'; +import ClientFormModal from './ClientFormModal'; +import ClientInfoModal from './ClientInfoModal'; +import ClientQrModal from './ClientQrModal'; +import ClientBulkAddModal from './ClientBulkAddModal'; +import '@/styles/page-cards.css'; +import './ClientsPage.css'; + +const basePath = window.X_UI_BASE_PATH || ''; +const requestUri = window.location.pathname; +const FILTER_STATE_KEY = 'clientsFilterState'; + +type Bucket = 'active' | 'deactive' | 'depleted' | 'expiring'; + +interface FilterState { + enableFilter: boolean; + searchKey: string; + filterBy: string; + protocolFilter?: string; +} + +function readFilterState(): FilterState { + try { + const raw = JSON.parse(localStorage.getItem(FILTER_STATE_KEY) || '{}'); + return { + enableFilter: !!raw.enableFilter, + searchKey: raw.searchKey || '', + filterBy: raw.filterBy || '', + protocolFilter: raw.protocolFilter, + }; + } catch { + return { enableFilter: false, searchKey: '', filterBy: '', protocolFilter: undefined }; + } +} + +export default function ClientsPage() { + const { t } = useTranslation(); + const { isDark, isUltra, antdThemeConfig } = useTheme(); + const { datepicker } = useDatepicker(); + const { isMobile } = useMediaQuery(); + const [modal, modalContextHolder] = Modal.useModal(); + const [messageApi, messageContextHolder] = message.useMessage(); + useEffect(() => { setMessageInstance(messageApi); }, [messageApi]); + + const { + clients, inbounds, onlines, loading, fetched, subSettings, + ipLimitEnable, tgBotEnable, expireDiff, trafficDiff, pageSize, + create, update, remove, removeMany, attach, detach, + resetTraffic, resetAllTraffics, delDepleted, setEnable, + applyTrafficEvent, applyClientStatsEvent, applyInvalidate, + } = useClients(); + + useWebSocket({ + traffic: applyTrafficEvent, + client_stats: applyClientStatsEvent, + invalidate: applyInvalidate, + }); + + const [togglingEmail, setTogglingEmail] = useState(null); + const [formOpen, setFormOpen] = useState(false); + const [formMode, setFormMode] = useState<'add' | 'edit'>('add'); + const [editingClient, setEditingClient] = useState(null); + const [editingAttachedIds, setEditingAttachedIds] = useState([]); + const [infoOpen, setInfoOpen] = useState(false); + const [infoClient, setInfoClient] = useState(null); + const [qrOpen, setQrOpen] = useState(false); + const [qrClient, setQrClient] = useState(null); + const [bulkAddOpen, setBulkAddOpen] = useState(false); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const initial = readFilterState(); + const [enableFilter, setEnableFilter] = useState(initial.enableFilter); + const [searchKey, setSearchKey] = useState(initial.searchKey); + const [filterBy, setFilterBy] = useState(initial.filterBy); + const [protocolFilter, setProtocolFilter] = useState(initial.protocolFilter); + + const [sortColumn, setSortColumn] = useState(null); + const [sortOrder, setSortOrder] = useState<'ascend' | 'descend' | null>(null); + const [currentPage, setCurrentPage] = useState(1); + const [tablePageSize, setTablePageSize] = useState(20); + + useEffect(() => { + localStorage.setItem(FILTER_STATE_KEY, JSON.stringify({ + enableFilter, searchKey, filterBy, protocolFilter, + })); + }, [enableFilter, searchKey, filterBy, protocolFilter]); + + useEffect(() => { + if (pageSize > 0) { + + setTablePageSize(pageSize); + } + }, [pageSize]); + + const onlineSet = useMemo(() => new Set(onlines || []), [onlines]); + const inboundsById = useMemo(() => { + const out: Record = {}; + for (const ib of inbounds) out[ib.id] = ib; + return out; + }, [inbounds]); + + const protocolOptions = useMemo(() => { + const values = new Set((inbounds || []).map((i) => i.protocol).filter((x): x is string => !!x)); + return [...values].sort(); + }, [inbounds]); + + const isOnline = useCallback((email: string) => !!email && onlineSet.has(email), [onlineSet]); + + function inboundLabel(id: number) { + const ib = inboundsById[id]; + if (!ib) return `#${id}`; + return ib.remark ? `${ib.remark} (${ib.protocol}:${ib.port})` : `${ib.protocol}:${ib.port}`; + } + + const clientBucket = useCallback((row: ClientRecord | null | undefined): Bucket | null => { + if (!row) return null; + const traffic = row.traffic || {}; + const used = (traffic.up || 0) + (traffic.down || 0); + const total = row.totalGB || 0; + const now = Date.now(); + const expired = (row.expiryTime ?? 0) > 0 && (row.expiryTime ?? 0) <= now; + const exhausted = total > 0 && used >= total; + if (expired || exhausted) return 'depleted'; + if (!row.enable) return 'deactive'; + const nearExpiry = (row.expiryTime ?? 0) > 0 && (row.expiryTime ?? 0) - now < (expireDiff || 0); + const nearLimit = total > 0 && total - used < (trafficDiff || 0); + if (nearExpiry || nearLimit) return 'expiring'; + return 'active'; + }, [expireDiff, trafficDiff]); + + function bucketBadgeColor(bucket: Bucket | null): string { + switch (bucket) { + case 'depleted': return '#ff4d4f'; + case 'expiring': return '#fa8c16'; + case 'deactive': return 'rgba(128,128,128,0.6)'; + case 'active': return '#52c41a'; + default: return 'rgba(128,128,128,0.6)'; + } + } + + function clientMatchesProtocol(row: ClientRecord, protocol?: string) { + if (!protocol) return true; + const ids = Array.isArray(row.inboundIds) ? row.inboundIds : []; + for (const id of ids) { + const ib = inboundsById[id]; + if (ib && ib.protocol === protocol) return true; + } + return false; + } + + const filteredClients = useMemo(() => { + let rows = clients || []; + if (enableFilter) { + if (filterBy === 'online') { + rows = rows.filter((r) => r.enable && isOnline(r.email)); + } else if (filterBy) { + rows = rows.filter((r) => clientBucket(r) === filterBy); + } + } else if (!ObjectUtil.isEmpty(searchKey)) { + rows = rows.filter((r) => ObjectUtil.deepSearch(r, searchKey)); + } + if (protocolFilter) { + rows = rows.filter((r) => clientMatchesProtocol(r, protocolFilter)); + } + return rows; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clients, enableFilter, filterBy, searchKey, protocolFilter, clientBucket]); + + const summary = useMemo(() => { + const rows = clients || []; + const deactive: string[] = []; + const depleted: string[] = []; + const expiring: string[] = []; + const online: string[] = []; + let active = 0; + for (const row of rows) { + const bucket = clientBucket(row); + if (bucket === 'deactive') deactive.push(row.email); + else if (bucket === 'depleted') depleted.push(row.email); + else if (bucket === 'expiring') expiring.push(row.email); + else if (bucket === 'active') active++; + if (row.enable && isOnline(row.email)) online.push(row.email); + } + return { total: rows.length, active, deactive, depleted, expiring, online }; + }, [clients, clientBucket, isOnline]); + + const sortFns: Record number> = { + enable: (a, b) => Number(a.enable) - Number(b.enable), + email: (a, b) => (a.email || '').localeCompare(b.email || ''), + inboundIds: (a, b) => (a.inboundIds?.length || 0) - (b.inboundIds?.length || 0), + traffic: (a, b) => { + const ua = (a.traffic?.up || 0) + (a.traffic?.down || 0); + const ub = (b.traffic?.up || 0) + (b.traffic?.down || 0); + return ua - ub; + }, + remaining: (a, b) => { + const ra = (a.totalGB || 0) > 0 ? (a.totalGB || 0) - ((a.traffic?.up || 0) + (a.traffic?.down || 0)) : Infinity; + const rb = (b.totalGB || 0) > 0 ? (b.totalGB || 0) - ((b.traffic?.up || 0) + (b.traffic?.down || 0)) : Infinity; + return ra - rb; + }, + expiryTime: (a, b) => { + const ea = (a.expiryTime ?? 0) > 0 ? (a.expiryTime ?? 0) : Infinity; + const eb = (b.expiryTime ?? 0) > 0 ? (b.expiryTime ?? 0) : Infinity; + return ea - eb; + }, + }; + + const sortedClients = useMemo(() => { + if (!sortColumn || !sortOrder) return filteredClients; + const fn = sortFns[sortColumn]; + if (!fn) return filteredClients; + const sorted = [...filteredClients].sort(fn); + return sortOrder === 'descend' ? sorted.reverse() : sorted; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [filteredClients, sortColumn, sortOrder]); + + function trafficLabel(row: ClientRecord) { + const t0 = row.traffic; + if (!t0) return '-'; + const used = (t0.up || 0) + (t0.down || 0); + const total = row.totalGB || 0; + if (total <= 0) return `${SizeFormatter.sizeFormat(used)} / ∞`; + return `${SizeFormatter.sizeFormat(used)} / ${SizeFormatter.sizeFormat(total)}`; + } + + function remainingLabel(row: ClientRecord) { + const total = row.totalGB || 0; + if (total <= 0) return '∞'; + const used = (row.traffic?.up || 0) + (row.traffic?.down || 0); + const r = total - used; + return r > 0 ? SizeFormatter.sizeFormat(r) : '0'; + } + + function remainingColor(row: ClientRecord): string { + const total = row.totalGB || 0; + if (total <= 0) return 'purple'; + const used = (row.traffic?.up || 0) + (row.traffic?.down || 0); + const ratio = used / total; + if (ratio >= 1) return 'red'; + if (ratio >= 0.85) return 'orange'; + return 'green'; + } + + function expiryLabel(row: ClientRecord) { + if (!row.expiryTime) return '∞'; + if (row.expiryTime < 0) { + const days = Math.round(row.expiryTime / -86400000); + return `${t('pages.clients.delayedStart')}: ${days}d`; + } + return IntlUtil.formatDate(row.expiryTime, datepicker); + } + + function expiryRelative(row: ClientRecord) { + if (!row.expiryTime) return ''; + if (row.expiryTime < 0) { + const days = Math.round(row.expiryTime / -86400000); + return `${days}d`; + } + return IntlUtil.formatRelativeTime(row.expiryTime); + } + + function expiryColor(row: ClientRecord): string { + if (!row.expiryTime) return 'purple'; + if (row.expiryTime < 0) return 'blue'; + const now = Date.now(); + if (row.expiryTime <= now) return 'red'; + if (row.expiryTime - now < 86400 * 1000 * 3) return 'orange'; + return 'green'; + } + + async function onToggleEnable(row: ClientRecord, next: boolean) { + setTogglingEmail(row.email); + try { + const msg = await setEnable(row, next); + if (!msg?.success) { + messageApi.error(msg?.msg || t('somethingWentWrong')); + } + } finally { + setTogglingEmail(null); + } + } + + function onAdd() { + setFormMode('add'); + setEditingClient(null); + setEditingAttachedIds([]); + setFormOpen(true); + } + + function onEdit(row: ClientRecord) { + setFormMode('edit'); + setEditingClient({ ...row }); + setEditingAttachedIds(Array.isArray(row.inboundIds) ? [...row.inboundIds] : []); + setFormOpen(true); + } + + function onDelete(row: ClientRecord) { + modal.confirm({ + title: t('pages.clients.deleteConfirmTitle', { email: row.email }), + content: t('pages.clients.deleteConfirmContent'), + okText: t('delete'), + okType: 'danger', + cancelText: t('cancel'), + onOk: async () => { + const msg = await remove(row.email); + if (msg?.success) messageApi.success(t('pages.clients.toasts.deleted')); + }, + }); + } + + function onResetTraffic(row: ClientRecord) { + if (!row?.email || !Array.isArray(row.inboundIds) || row.inboundIds.length === 0) { + messageApi.warning(t('pages.clients.resetNotPossible')); + return; + } + modal.confirm({ + title: `${t('pages.inbounds.resetTraffic')} — ${row.email}`, + content: t('pages.inbounds.resetTrafficContent'), + okText: t('reset'), + cancelText: t('cancel'), + onOk: async () => { + const msg = await resetTraffic(row); + if (msg?.success) messageApi.success(t('pages.clients.toasts.trafficReset')); + }, + }); + } + + function onShowInfo(row: ClientRecord) { + setInfoClient(row); + setInfoOpen(true); + } + + function onShowQr(row: ClientRecord) { + setQrClient(row); + setQrOpen(true); + } + + function onResetAllTraffics() { + modal.confirm({ + title: t('pages.clients.resetAllTrafficsTitle'), + content: t('pages.clients.resetAllTrafficsContent'), + okText: t('reset'), + okType: 'danger', + cancelText: t('cancel'), + onOk: async () => { + const msg = await resetAllTraffics(); + if (msg?.success) messageApi.success(t('pages.clients.toasts.allTrafficsReset')); + }, + }); + } + + function onDelDepleted() { + modal.confirm({ + title: t('pages.clients.delDepletedConfirmTitle'), + content: t('pages.clients.delDepletedConfirmContent'), + okText: t('delete'), + okType: 'danger', + cancelText: t('cancel'), + onOk: async () => { + const msg = await delDepleted(); + if (msg?.success) { + const deleted = msg.obj?.deleted ?? 0; + messageApi.success(t('pages.clients.toasts.delDepleted', { count: deleted })); + } + }, + }); + } + + function onBulkDelete() { + const emails = [...selectedRowKeys]; + if (emails.length === 0) return; + modal.confirm({ + title: t('pages.clients.bulkDeleteConfirmTitle', { count: emails.length }), + content: t('pages.clients.bulkDeleteConfirmContent'), + okText: t('delete'), + okType: 'danger', + cancelText: t('cancel'), + onOk: async () => { + const results = await removeMany(emails); + setSelectedRowKeys([]); + let ok = 0; + let failed = 0; + let firstError = ''; + for (const msg of results) { + if (msg?.success) ok++; + else { + failed++; + if (!firstError && msg?.msg) firstError = msg.msg; + } + } + if (failed === 0) { + messageApi.success(t('pages.clients.toasts.bulkDeleted', { count: ok })); + } else { + messageApi.warning(firstError + ? `${t('pages.clients.toasts.bulkDeletedMixed', { ok, failed })} — ${firstError}` + : t('pages.clients.toasts.bulkDeletedMixed', { ok, failed })); + } + }, + }); + } + + const onSave = useCallback(async ( + payload: Record | { client: Record; inboundIds: number[] }, + meta: { isEdit: false } | { isEdit: true; email: string; attach: number[]; detach: number[] }, + ) => { + if (!meta.isEdit) { + return create(payload); + } + const updateMsg = await update(meta.email, payload); + if (!updateMsg?.success) return updateMsg; + if (Array.isArray(meta.attach) && meta.attach.length > 0) { + const r = await attach(meta.email, meta.attach); + if (!r?.success) return r; + } + if (Array.isArray(meta.detach) && meta.detach.length > 0) { + const r = await detach(meta.email, meta.detach); + if (!r?.success) return r; + } + return updateMsg; + }, [create, update, attach, detach]); + + const pageClass = useMemo(() => { + const classes = ['clients-page']; + if (isDark) classes.push('is-dark'); + if (isUltra) classes.push('is-ultra'); + return classes.join(' '); + }, [isDark, isUltra]); + + const onTableChange: NonNullable['onChange']> = (pag, _filters, sorter) => { + if (pag?.current) setCurrentPage(pag.current); + if (pag?.pageSize) setTablePageSize(pag.pageSize); + const s = Array.isArray(sorter) ? sorter[0] : sorter; + setSortColumn((s?.columnKey as string) || (s?.field as string) || null); + setSortOrder((s?.order as 'ascend' | 'descend' | null) || null); + }; + + const columns = useMemo>(() => { + function sortableCol[number]>(col: T, key: string): T { + return { + ...col, + sorter: true, + showSorterTooltip: false, + sortOrder: sortColumn === key ? sortOrder : null, + sortDirections: ['ascend', 'descend'], + }; + } + return [ + { + title: t('pages.clients.actions'), + key: 'actions', + width: 200, + render: (_v, record) => ( + + +
} + > + } /> + + + + {summary.expiring.map((e) =>
{e}
)}
} + > + } /> + + + + {summary.deactive.map((e) =>
{e}
)}} + > + } /> +
+ + + } /> + + + + + + + + + + {selectedRowKeys.length > 0 && ( + + )} + + + + } + > +
+ } + unCheckedChildren={} + /> + {!enableFilter && ( + setSearchKey(e.target.value)} + placeholder={t('search')} + autoFocus + size={isMobile ? 'small' : 'middle'} + style={{ maxWidth: 300 }} + /> + )} + {enableFilter && ( + setFilterBy(e.target.value)} + optionType="button" + buttonStyle="solid" + size={isMobile ? 'small' : 'middle'} + > + {t('none')} + {t('subscription.active')} + {t('disabled')} + {t('depleted')} + {t('depletingSoon')} + {t('online')} + + )} + { form.remark = e.target.value; refresh(); }} /> + + {selectableNodes.length > 0 && isNodeEligible && ( + + + + )} + + + + + { ib.listen = e.target.value; refresh(); }} + /> + + + { ib.port = Number(v) || 0; refresh(); }} + /> + + {t('pages.inbounds.totalFlow')}}> + { + form.total = NumberFormatter.toFixed((Number(v) || 0) * SizeFormatter.ONE_GB, 0); + refresh(); + }} + /> + + + + + {t('pages.inbounds.expireDate')}}> + { form.expiryTime = d ? d.valueOf() : 0; refresh(); }} + /> + + + ); + + const renderFallbacksCard = () => ( + + + {t('pages.inbounds.fallbacks.help') || 'When a connection on this inbound does not match any client, route it to another inbound. Pick a child below and the routing fields (SNI / ALPN / path / xver) auto-fill from its transport — most setups need no further tweaking. Each child should listen on 127.0.0.1 with security=none.'} + + {fallbacks.length === 0 && ( + + )} + {fallbacks.map((record, index) => ( +
+ + + + + + + + + updateFallback(record.rowKey, { name: e.target.value })} /> + + + + + ALPN + updateFallback(record.rowKey, { alpn: e.target.value })} /> + + + + + Path + updateFallback(record.rowKey, { path: e.target.value })} /> + + + + + xver + updateFallback(record.rowKey, { xver: Number(v) || 0 })} /> + + + + )} +
+ ))} + + + + +
+ ); + + const renderProtocolTab = () => ( + <> + {isVlessLike && ( +
+ + { ib.settings.decryption = e.target.value; refresh(); }} /> + + + { ib.settings.encryption = e.target.value; refresh(); }} /> + + + + + + + + + {t('pages.inbounds.vlessAuthSelected', { auth: selectedVlessAuth })} + + +
+ )} + + {isFallbackHost && renderFallbacksCard()} + + {ib.protocol === Protocols.SHADOWSOCKS && ( +
+ + + + {ib.isSS2022 && ( + Password randomSSPassword(ib.settings)} />}> + { ib.settings.password = e.target.value; refresh(); }} /> + + )} + + + + + { ib.settings.ivCheck = v; refresh(); }} /> + +
+ )} + + {(ib.protocol === Protocols.HTTP || ib.protocol === Protocols.MIXED) && ( +
+ + + + + {(ib.settings.accounts || []).map((account: any, idx: number) => ( + + {String(idx + 1)} + { account.user = e.target.value; refresh(); }} /> + { account.pass = e.target.value; refresh(); }} /> + + + ))} + + {ib.protocol === Protocols.HTTP && ( + + { ib.settings.allowTransparent = v; refresh(); }} /> + + )} + {ib.protocol === Protocols.MIXED && ( + <> + + + + + { ib.settings.udp = v; refresh(); }} /> + + {ib.settings.udp && ( + + { ib.settings.ip = e.target.value; refresh(); }} /> + + )} + + )} +
+ )} + + {ib.protocol === Protocols.TUNNEL && ( +
+ + { ib.settings.rewriteAddress = e.target.value; refresh(); }} /> + + + { ib.settings.rewritePort = Number(v) || 0; refresh(); }} /> + + + + + + + + {(ib.settings.portMap || []).length > 0 && ( + + {(ib.settings.portMap as { name: string; value: string }[]).map((pm, idx) => ( + + {String(idx + 1)} + { pm.name = e.target.value; refresh(); }} /> + { pm.value = e.target.value; refresh(); }} /> + + + ))} + + )} + + { ib.settings.followRedirect = v; refresh(); }} /> + +
+ )} + + {ib.protocol === Protocols.TUN && ( +
+ + { ib.settings.name = e.target.value; refresh(); }} /> + + + { ib.settings.mtu = Number(v) || 0; refresh(); }} /> + + + + {(ib.settings.gateway || []).map((_ip: string, j: number) => ( + + { ib.settings.gateway[j] = e.target.value; refresh(); }} /> + + + ))} + + + + {(ib.settings.dns || []).map((_ip: string, j: number) => ( + + { ib.settings.dns[j] = e.target.value; refresh(); }} /> + + + ))} + + + { ib.settings.userLevel = Number(v) || 0; refresh(); }} /> + + Auto system routes}> + + {(ib.settings.autoSystemRoutingTable || []).map((_ip: string, j: number) => ( + + { ib.settings.autoSystemRoutingTable[j] = e.target.value; refresh(); }} /> + + + ))} + + Auto outbounds interface}> + { ib.settings.autoOutboundsInterface = e.target.value; refresh(); }} /> + +
+ )} + + {ib.protocol === Protocols.WIREGUARD && ( +
+ Secret key }> + { ib.settings.secretKey = e.target.value; refresh(); }} /> + + + + + + { ib.settings.mtu = Number(v) || 0; refresh(); }} /> + + + { ib.settings.noKernelTun = v; refresh(); }} /> + + + + + {(ib.settings.peers || []).map((peer: any, idx: number) => ( +
+ + Peer {idx + 1} + {ib.settings.peers.length > 1 && ( + { ib.settings.delPeer(idx); refresh(); }} /> + )} + + Secret key regenWgKeypair(peer)} />}> + { peer.privateKey = e.target.value; refresh(); }} /> + + + { peer.publicKey = e.target.value; refresh(); }} /> + + + { peer.psk = e.target.value; refresh(); }} /> + + + + {(peer.allowedIPs || []).map((_ip: string, j: number) => ( + + { peer.allowedIPs[j] = e.target.value; refresh(); }} /> + {peer.allowedIPs.length > 1 && ( + + )} + + ))} + + + { peer.keepAlive = Number(v) || 0; refresh(); }} /> + +
+ ))} +
+ )} + + ); + + const renderStreamTab = () => { + const network = ib.stream?.network; + return ( + <> +
+ {ib.protocol !== Protocols.HYSTERIA && ( + + + + )} + + {network === 'tcp' && ( + <> + {canEnableTls && ( + + { ib.stream.tcp.acceptProxyProtocol = v; refresh(); }} /> + + )} + + { ib.stream.tcp.type = v ? 'http' : 'none'; refresh(); }} /> + + {ib.stream.tcp.type === 'http' && ( + <> + {t('pages.inbounds.stream.general.request')} + + { ib.stream.tcp.request.version = e.target.value; refresh(); }} /> + + + { ib.stream.tcp.request.method = e.target.value; refresh(); }} /> + + {t('pages.inbounds.stream.tcp.path')} }> + {(ib.stream.tcp.request.path || []).map((_p: string, idx: number) => ( + + { ib.stream.tcp.request.path[idx] = e.target.value; refresh(); }} /> + {ib.stream.tcp.request.path.length > 1 && ( + + )} + + ))} + + + + + {(ib.stream.tcp.request.headers || []).length > 0 && ( + + {(ib.stream.tcp.request.headers as { name: string; value: string }[]).map((h, idx) => ( + + {String(idx + 1)} + { h.name = e.target.value; refresh(); }} /> + { h.value = e.target.value; refresh(); }} /> + + + ))} + + )} + {t('pages.inbounds.stream.general.response')} + + { ib.stream.tcp.response.version = e.target.value; refresh(); }} /> + + + { ib.stream.tcp.response.status = e.target.value; refresh(); }} /> + + + { ib.stream.tcp.response.reason = e.target.value; refresh(); }} /> + + + + + {(ib.stream.tcp.response.headers || []).length > 0 && ( + + {(ib.stream.tcp.response.headers as { name: string; value: string }[]).map((h, idx) => ( + + {String(idx + 1)} + { h.name = e.target.value; refresh(); }} /> + { h.value = e.target.value; refresh(); }} /> + + + ))} + + )} + + )} + + )} + + {network === 'kcp' && ( + <> + { ib.stream.kcp.mtu = Number(v) || 0; refresh(); }} /> + { ib.stream.kcp.tti = Number(v) || 0; refresh(); }} /> + { ib.stream.kcp.upCap = Number(v) || 0; refresh(); }} /> + { ib.stream.kcp.downCap = Number(v) || 0; refresh(); }} /> + { ib.stream.kcp.cwndMultiplier = Number(v) || 0; refresh(); }} /> + { ib.stream.kcp.maxSendingWindow = Number(v) || 0; refresh(); }} /> + + )} + + {network === 'ws' && ( + <> + { ib.stream.ws.acceptProxyProtocol = v; refresh(); }} /> + { ib.stream.ws.host = e.target.value; refresh(); }} /> + { ib.stream.ws.path = e.target.value; refresh(); }} /> + { ib.stream.ws.heartbeatPeriod = Number(v) || 0; refresh(); }} /> + + + + {(ib.stream.ws.headers || []).length > 0 && ( + + {(ib.stream.ws.headers as { name: string; value: string }[]).map((h, idx) => ( + + {String(idx + 1)} + { h.name = e.target.value; refresh(); }} /> + { h.value = e.target.value; refresh(); }} /> + + + ))} + + )} + + )} + + {network === 'grpc' && ( + <> + { ib.stream.grpc.serviceName = e.target.value; refresh(); }} /> + { ib.stream.grpc.authority = e.target.value; refresh(); }} /> + { ib.stream.grpc.multiMode = v; refresh(); }} /> + + )} + + {network === 'httpupgrade' && ( + <> + { ib.stream.httpupgrade.acceptProxyProtocol = v; refresh(); }} /> + { ib.stream.httpupgrade.host = e.target.value; refresh(); }} /> + { ib.stream.httpupgrade.path = e.target.value; refresh(); }} /> + + + + {(ib.stream.httpupgrade.headers || []).length > 0 && ( + + {(ib.stream.httpupgrade.headers as { name: string; value: string }[]).map((h, idx) => ( + + {String(idx + 1)} + { h.name = e.target.value; refresh(); }} /> + { h.value = e.target.value; refresh(); }} /> + + + ))} + + )} + + )} + + {network === 'xhttp' && ( + <> + { ib.stream.xhttp.host = e.target.value; refresh(); }} /> + { ib.stream.xhttp.path = e.target.value; refresh(); }} /> + + + + {(ib.stream.xhttp.headers || []).length > 0 && ( + + {(ib.stream.xhttp.headers as { name: string; value: string }[]).map((h, idx) => ( + + {String(idx + 1)} + { h.name = e.target.value; refresh(); }} /> + { h.value = e.target.value; refresh(); }} /> + + + ))} + + )} + + + + {ib.stream.xhttp.mode === 'packet-up' && ( + <> + { ib.stream.xhttp.scMaxBufferedPosts = Number(v) || 0; refresh(); }} /> + { ib.stream.xhttp.scMaxEachPostBytes = e.target.value; refresh(); }} /> + + )} + {ib.stream.xhttp.mode === 'stream-up' && ( + { ib.stream.xhttp.scStreamUpServerSecs = e.target.value; refresh(); }} /> + )} + { ib.stream.xhttp.serverMaxHeaderBytes = Number(v) || 0; refresh(); }} /> + { ib.stream.xhttp.xPaddingBytes = e.target.value; refresh(); }} /> + { ib.stream.xhttp.xPaddingObfsMode = v; refresh(); }} /> + {ib.stream.xhttp.xPaddingObfsMode && ( + <> + { ib.stream.xhttp.xPaddingKey = e.target.value; refresh(); }} /> + { ib.stream.xhttp.xPaddingHeader = e.target.value; refresh(); }} /> + + + + + + + + )} + + + + {ib.stream.xhttp.sessionPlacement && ib.stream.xhttp.sessionPlacement !== 'path' && ( + { ib.stream.xhttp.sessionKey = e.target.value; refresh(); }} /> + )} + + + + {ib.stream.xhttp.seqPlacement && ib.stream.xhttp.seqPlacement !== 'path' && ( + { ib.stream.xhttp.seqKey = e.target.value; refresh(); }} /> + )} + {ib.stream.xhttp.mode === 'packet-up' && ( + + + + )} + {ib.stream.xhttp.mode === 'packet-up' && ib.stream.xhttp.uplinkDataPlacement && ib.stream.xhttp.uplinkDataPlacement !== 'body' && ( + { ib.stream.xhttp.uplinkDataKey = e.target.value; refresh(); }} /> + )} + { ib.stream.xhttp.noSSEHeader = v; refresh(); }} /> + + )} + + + + {externalProxyOn && ( + + )} + + {externalProxyOn && ( + + {(ib.stream.externalProxy as { forceTls: string; dest: string; port: number; remark: string }[]).map((row, idx) => ( + + + + + { row.dest = e.target.value; refresh(); }} /> + + { row.port = Number(v) || 0; refresh(); }} /> + + { row.remark = e.target.value; refresh(); }} /> + { ib.stream.externalProxy.splice(idx, 1); refresh(); }}> + + + + ))} + + )} + + { ib.stream.sockoptSwitch = v; refresh(); }} /> + {ib.stream.sockoptSwitch && ib.stream.sockopt && ( + <> + { ib.stream.sockopt.mark = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.tcpKeepAliveInterval = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.tcpKeepAliveIdle = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.tcpMaxSeg = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.tcpUserTimeout = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.tcpWindowClamp = Number(v) || 0; refresh(); }} /> + { ib.stream.sockopt.acceptProxyProtocol = v; refresh(); }} /> + { ib.stream.sockopt.tcpFastOpen = v; refresh(); }} /> + { ib.stream.sockopt.tcpMptcp = v; refresh(); }} /> + { ib.stream.sockopt.penetrate = v; refresh(); }} /> + { ib.stream.sockopt.V6Only = v; refresh(); }} /> + + + + + + + + + + { ib.stream.sockopt.dialerProxy = e.target.value; refresh(); }} /> + { ib.stream.sockopt.interfaceName = e.target.value; refresh(); }} /> + + + + + )} + + {ib.protocol === Protocols.HYSTERIA && ( + <> + Version}> + { ib.stream.hysteria.version = Number(v) || 2; refresh(); }} /> + + UDP idle timeout}> + { ib.stream.hysteria.udpIdleTimeout = Number(v) || 0; refresh(); }} /> + + + { ib.stream.hysteria.masqueradeSwitch = v; refresh(); }} /> + + {ib.stream.hysteria.masqueradeSwitch && ( + <> + + + + {ib.stream.hysteria.masquerade.type === 'proxy' && ( + <> + { ib.stream.hysteria.masquerade.url = e.target.value; refresh(); }} /> + { ib.stream.hysteria.masquerade.rewriteHost = v; refresh(); }} /> + { ib.stream.hysteria.masquerade.insecure = v; refresh(); }} /> + + )} + {ib.stream.hysteria.masquerade.type === 'file' && ( + { ib.stream.hysteria.masquerade.dir = e.target.value; refresh(); }} /> + )} + {ib.stream.hysteria.masquerade.type === 'string' && ( + <> +