diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 89d23d69..225e2cf7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -92,9 +92,12 @@ func (a *InboundController) getInbounds(c *gin.Context) { - Use `config.GetLogLevel()`, `config.GetDBPath()` helpers ### Internationalization -- Translation files: `web/translation/translate.*.toml` -- Access via `I18nWeb(c, "pages.login.loginAgain")` in controllers -- Use `locale.I18nType` enum (Web, Api, etc.) +- Translation files: `web/translation/.json` (one nested-namespace file per locale, + e.g. `en-US.json`). Vue SPA imports these via `import.meta.glob` from `frontend/src/i18n/`, + and the Go binary embeds the same files via `web/web.go`'s `//go:embed translation/*`. +- Access from Go via `locale.I18n(locale.Web, "pages.login.loginAgain")` (see + `web/locale/locale.go`); access from Vue via `useI18n()` and `t('pages.login.loginAgain')`. +- Use `locale.I18nType` enum (Web, Bot). ## External Dependencies & Integration diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 228c1625..a3fe93cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,6 +83,23 @@ jobs: go-version-file: go.mod check-latest: true + # Frontend dist must be built BEFORE go build — Go's //go:embed + # all:dist directive in web/web.go requires web/dist/ to exist + # at compile time. web/dist/ is .gitignored, so on a fresh CI + # checkout it doesn't exist until vite emits it. + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Build frontend bundle + run: | + npm ci + npm run build + working-directory: frontend + - name: Build 3X-UI run: | export CGO_ENABLED=1 @@ -209,6 +226,23 @@ jobs: go-version-file: go.mod check-latest: true + # Frontend dist must be built BEFORE go build — see comment on the + # Linux job above. This step is identical except npm runs on the + # Windows runner here. + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Build frontend bundle + shell: pwsh + run: | + npm ci + npm run build + working-directory: frontend + - name: Install MSYS2 uses: msys2/setup-msys2@v2 with: diff --git a/Dockerfile b/Dockerfile index dabaf7f1..31dcee4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,20 @@ +# ======================================================== +# Stage: Frontend (Vite) +# ======================================================== +# web/dist/ is .gitignored and embedded into the Go binary via +# //go:embed all:dist in web/web.go, so the SPA bundle MUST be built +# before the Go compile step. We build it in its own stage so the +# Go builder image doesn't need Node installed. +FROM node:22-alpine AS frontend +WORKDIR /src/frontend +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci +COPY frontend/ ./ +RUN npm run build +# Vite outDir is set to ../web/dist (see frontend/vite.config.js), so +# the bundle lands at /src/web/dist — that's what we copy into the +# next stage. + # ======================================================== # Stage: Builder # ======================================================== @@ -12,6 +29,7 @@ RUN apk --no-cache --update add \ unzip COPY . . +COPY --from=frontend /src/web/dist ./web/dist ENV CGO_ENABLED=1 ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" diff --git a/database/db.go b/database/db.go index 2e468587..80f9f655 100644 --- a/database/db.go +++ b/database/db.go @@ -39,6 +39,7 @@ func initModels() error { &xray.ClientTraffic{}, &model.HistoryOfSeeders{}, &model.CustomGeoResource{}, + &model.Node{}, } for _, model := range models { if err := db.AutoMigrate(model); err != nil { diff --git a/database/model/model.go b/database/model/model.go index 047780e5..3fab264a 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -66,6 +66,12 @@ type Inbound struct { StreamSettings string `json:"streamSettings" form:"streamSettings"` Tag string `json:"tag" form:"tag" gorm:"unique"` Sniffing string `json:"sniffing" form:"sniffing"` + + // NodeID points at the remote panel (Node) where this inbound's xray + // actually runs. NULL means the inbound runs on the local xray (the + // pre-multi-node behaviour). Existing rows migrate to NULL with no + // backfill. + NodeID *int `json:"nodeId,omitempty" form:"nodeId" gorm:"index"` } // OutboundTraffics tracks traffic statistics for Xray outbound connections. @@ -117,6 +123,37 @@ type Setting struct { Value string `json:"value" form:"value"` } +// Node represents a remote 3x-ui panel registered with the central panel. +// The central panel polls each node's existing /panel/api/server/status +// endpoint over HTTP using the per-node ApiToken to populate the runtime +// status fields below. +type Node struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"uniqueIndex"` + Remark string `json:"remark"` + Scheme string `json:"scheme"` // "https" | "http" + Address string `json:"address"` // host or IP + Port int `json:"port"` + BasePath string `json:"basePath"` // "/" or "/myprefix/" + ApiToken string `json:"apiToken"` // plaintext, matches existing tg/ldap pattern + Enable bool `json:"enable" gorm:"default:true"` + + // Heartbeat-updated fields. UpdatedAt advances on every probe even when + // the row is otherwise unchanged so the UI's "last seen" tooltip is + // truthful without us having to read LastHeartbeat separately. + Status string `json:"status" gorm:"default:unknown"` // online|offline|unknown + LastHeartbeat int64 `json:"lastHeartbeat"` // unix seconds, 0 = never + LatencyMs int `json:"latencyMs"` + XrayVersion string `json:"xrayVersion"` + CpuPct float64 `json:"cpuPct"` + MemPct float64 `json:"memPct"` + UptimeSecs uint64 `json:"uptimeSecs"` + LastError string `json:"lastError"` + + CreatedAt int64 `json:"createdAt" gorm:"autoCreateTime"` + UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime"` +} + type CustomGeoResource struct { Id int `json:"id" gorm:"primaryKey;autoIncrement"` Type string `json:"type" gorm:"not null;uniqueIndex:idx_custom_geo_type_alias;column:geo_type"` diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..4e97a76e --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.vite/ +*.log diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..e72a0d8b --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,75 @@ +# 3x-ui frontend + +Vue 3 + Ant Design Vue 4 + Vite. Multi-page app — one HTML entry per +panel route — built into `../web/dist/` and embedded into the Go binary +via `embed.FS`. + +## Dev + +```sh +npm install +npm run dev +``` + +Vite serves on `http://localhost:5173/`. API calls and `/panel/*` routes +proxy to the Go panel at `http://localhost:2053/`, so start the Go panel +first (`go run main.go`) and then Vite. + +The proxy auto-rewrites `/panel`, `/panel/settings`, `/panel/inbounds`, +`/panel/xray` to the matching Vite-served HTML in dev mode (see +`MIGRATED_ROUTES` in `vite.config.js`), so the sidebar's +production-style links work without round-tripping through Go. + +## Production build + +```sh +npm run build +``` + +Outputs to `../web/dist/` (HTML at the root, hashed JS/CSS under +`assets/`). The Go binary embeds this directory at compile time and +`web/controller/dist.go` serves the per-page HTML. + +## Lint + +```sh +npm run lint +``` + +ESLint 10 with `eslint.config.js` (flat config) — `vue3-recommended` +plus a few rule overrides for the project's formatting style. + +## Layout + +``` +frontend/ +├── *.html # Vite entry HTML, one per panel route +├── eslint.config.js +├── vite.config.js +└── src/ + ├── entries/ # Per-page bootstrap (createApp + mount) + ├── pages/ # One folder per route, each with the page + │ ├── index/ # component + helpers + sub-components + │ ├── login/ + │ ├── inbounds/ + │ ├── xray/ + │ ├── settings/ + │ └── sub/ + ├── components/ # Cross-page Vue components + ├── composables/ # Reusable reactive logic (useTheme, …) + ├── api/ # Axios setup, CSRF interceptor + ├── i18n/ # vue-i18n init (locales live in web/translation/) + ├── models/ # Inbound, Outbound, Status, … domain classes + └── utils/ # HttpUtil, ObjectUtil, LanguageManager, … +``` + +## Adding a new page + +1. Add `frontend/.html` referencing `/src/entries/.js`. +2. Add `src/entries/.js` that imports the page component and + mounts it. +3. Add the page component under `src/pages//`. +4. Register the entry in `rollupOptions.input` in `vite.config.js`. +5. If the page is reachable from the sidebar at `/panel/`, add + it to `MIGRATED_ROUTES` so the dev proxy serves the Vite HTML. +6. Wire the Go controller to `serveDistPage(c, ".html")`. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 00000000..c1749c08 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,61 @@ +import js from '@eslint/js'; +import vue from 'eslint-plugin-vue'; +import vueParser from 'vue-eslint-parser'; +import globals from 'globals'; + +export default [ + { ignores: ['node_modules/**', '../web/dist/**'] }, + js.configs.recommended, + ...vue.configs['flat/recommended'], + { + files: ['**/*.{js,vue}'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + parser: vueParser, + parserOptions: { + ecmaFeatures: { jsx: false }, + }, + globals: { + ...globals.browser, + ...globals.node, + // Legacy script tags inject a couple of helpers on window before + // the SPA boots; declared here so no-undef stops flagging them. + getRandomRealityTarget: 'readonly', + }, + }, + rules: { + 'no-unused-vars': ['warn', { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }], + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-case-declarations': 'off', + + // 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', + }, + }, +]; diff --git a/frontend/inbounds.html b/frontend/inbounds.html new file mode 100644 index 00000000..57485c92 --- /dev/null +++ b/frontend/inbounds.html @@ -0,0 +1,13 @@ + + + + + + 3x-ui · Inbounds + + +
+
+ + + diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 00000000..b2d45443 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + 3x-ui + + +
+
+ + + diff --git a/frontend/login.html b/frontend/login.html new file mode 100644 index 00000000..ba8e1e05 --- /dev/null +++ b/frontend/login.html @@ -0,0 +1,14 @@ + + + + + + + 3x-ui — Sign in + + +
+
+ + + diff --git a/frontend/nodes.html b/frontend/nodes.html new file mode 100644 index 00000000..fb607b19 --- /dev/null +++ b/frontend/nodes.html @@ -0,0 +1,13 @@ + + + + + + 3x-ui · Nodes + + +
+
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..29638400 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2785 @@ +{ + "name": "3x-ui-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "3x-ui-frontend", + "version": "0.0.0", + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "ant-design-vue": "^4.2.6", + "axios": "^1.7.9", + "dayjs": "^1.11.20", + "moment": "^2.30.1", + "otpauth": "^9.5.1", + "qrious": "^4.0.2", + "qs": "^6.13.1", + "vue": "^3.5.13", + "vue-i18n": "^11.1.4", + "vue3-persian-datetime-picker": "^1.2.2" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@vitejs/plugin-vue": "^6.0.6", + "eslint": "^10.3.0", + "eslint-plugin-vue": "^10.9.1", + "globals": "^17.6.0", + "vite": "^8.0.11", + "vue-eslint-parser": "^10.4.0" + } + }, + "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==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" + }, + "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==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "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==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "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==", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "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==", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "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==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@intlify/core-base": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.2.tgz", + "integrity": "sha512-7fpuCcVmeLv2T9qHsARqGvh8xt+sV2fH+Q+gMHFwB/rPXzo85DpbJFKn7dBH1L5p0c2cSh2DW+2h/64EKrISmA==", + "dependencies": { + "@intlify/devtools-types": "11.4.2", + "@intlify/message-compiler": "11.4.2", + "@intlify/shared": "11.4.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.2.tgz", + "integrity": "sha512-3u8EN1kB6EMSi96KXs5k7a8y2X2g4+h3X6iwVZU47cP4n+mTuq//WMjG588BzSp/2XQ/dTXo2BLUXX+XS+PNfA==", + "dependencies": { + "@intlify/core-base": "11.4.2", + "@intlify/shared": "11.4.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.2.tgz", + "integrity": "sha512-a6CDSGSMTGrg0BjD97x8TBYPf7qQMDlZipJ6UDfv/pd4OIym8TMlHu3MsH0bTNnRdAG2D6EFEykIgiQPqvtTkA==", + "dependencies": { + "@intlify/shared": "11.4.2", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.2.tgz", + "integrity": "sha512-NzpHbguRCsOHDwxmlBa9qu/imc+/QWgsYUaK6FZeNC0wK8QfAbhqrktEp/haVzxU1aikH8IX4ytD+mfFEMi/9A==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "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==" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.13", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.13.tgz", + "integrity": "sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==", + "dev": true + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "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", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.6.tgz", + "integrity": "sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==", + "dev": true, + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.13" + }, + "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==", + "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==", + "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==", + "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==", + "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==" + }, + "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==", + "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==", + "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==", + "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==", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "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==" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/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==", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.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==" + }, + "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==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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==" + }, + "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, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "engines": { + "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==" + }, + "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==" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "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==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", + "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "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 + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "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==" + }, + "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==" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==" + }, + "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==" + }, + "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==", + "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==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-jalaali": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/moment-jalaali/-/moment-jalaali-0.10.4.tgz", + "integrity": "sha512-/eD0HeyvATznb5iE0G1BHjKRZAFEpJ9ZNUkcHwXhNgt1WJJVVzHD7+uDmqzZWVFLdbGme2gvIXKb3ezDYOXcZA==", + "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==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.4.2.tgz", + "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/otpauth": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.5.1.tgz", + "integrity": "sha512-fJmDAHc8wImfqqqOXIlBvT1dEKrZK0Cmb2VEgScpNTolCz0PHh6ExUZGv4sLtOsWNaHCQlD+rRqaPgnoxFoZjQ==", + "dependencies": { + "@noble/hashes": "2.2.0" + }, + "funding": { + "url": "https://github.com/hectorm/otpauth?sponsor=1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "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, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrious": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz", + "integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g==" + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "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/rolldown": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dev": true, + "dependencies": { + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "dev": true + }, + "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==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "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==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==" + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "engines": { + "node": ">=12.22" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dev": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", + "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "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, + "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.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.2.tgz", + "integrity": "sha512-sADDeKXqAGsPX6tK3t3y2ZiMpbVWN12tG+MhTiJ06rVoh58eGtM4wFyw3uWGbVkXByVp9Ne/AP+nSSzI+J9OAQ==", + "dependencies": { + "@intlify/core-base": "11.4.2", + "@intlify/devtools-types": "11.4.2", + "@intlify/shared": "11.4.2", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "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==", + "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==", + "dependencies": { + "moment-jalaali": "^0.9.4" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..cf2c1a6b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "3x-ui-frontend", + "private": true, + "version": "0.0.1", + "type": "module", + "description": "3x-ui panel frontend (Vue 3 + Ant Design Vue 4 + Vite 8).", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src" + }, + "dependencies": { + "@ant-design/icons-vue": "^7.0.1", + "ant-design-vue": "^4.2.6", + "axios": "^1.7.9", + "dayjs": "^1.11.20", + "moment": "^2.30.1", + "otpauth": "^9.5.1", + "qrious": "^4.0.2", + "qs": "^6.13.1", + "vue": "^3.5.13", + "vue-i18n": "^11.1.4", + "vue3-persian-datetime-picker": "^1.2.2" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@vitejs/plugin-vue": "^6.0.6", + "eslint": "^10.3.0", + "eslint-plugin-vue": "^10.9.1", + "globals": "^17.6.0", + "vite": "^8.0.11", + "vue-eslint-parser": "^10.4.0" + }, + "overrides": { + "moment-jalaali": "^0.10.4" + } +} diff --git a/frontend/settings.html b/frontend/settings.html new file mode 100644 index 00000000..da144ba7 --- /dev/null +++ b/frontend/settings.html @@ -0,0 +1,13 @@ + + + + + + 3x-ui · Settings + + +
+
+ + + diff --git a/frontend/src/api/axios-init.js b/frontend/src/api/axios-init.js new file mode 100644 index 00000000..f651e82a --- /dev/null +++ b/frontend/src/api/axios-init.js @@ -0,0 +1,117 @@ +import axios from 'axios'; +import qs from 'qs'; + +const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']); +// Public CSRF endpoint — works pre-login (the panel-scoped +// /panel/csrf-token sits behind checkLogin and would 401 a fresh +// login page that hasn't authenticated yet). +const CSRF_TOKEN_PATH = '/csrf-token'; + +// Cached session CSRF token. The legacy panel injects it via a +// tag rendered by Go; the new SPA pages +// fetch it once from /panel/csrf-token instead. Module-level so +// every axios POST sees the latest value. +let csrfToken = null; +let csrfFetchPromise = null; + +function readMetaToken() { + return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || null; +} + +// Fetch the token via a bare fetch() (not axios) so the call doesn't +// recurse through this same interceptor. +async function fetchCsrfToken() { + try { + const res = await fetch(CSRF_TOKEN_PATH, { + method: 'GET', + credentials: 'same-origin', + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + }); + if (!res.ok) return null; + const json = await res.json(); + return json?.success && typeof json.obj === 'string' ? json.obj : null; + } catch (_e) { + return null; + } +} + +async function ensureCsrfToken() { + if (csrfToken) return csrfToken; + const meta = readMetaToken(); + if (meta) { + csrfToken = meta; + return csrfToken; + } + if (!csrfFetchPromise) csrfFetchPromise = fetchCsrfToken(); + const fetched = await csrfFetchPromise; + csrfFetchPromise = null; + if (fetched) csrfToken = fetched; + return csrfToken; +} + +// Apply the panel's axios defaults + interceptors. Call once at app +// startup before any HTTP call goes out. +export function setupAxios() { + axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; + axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; + + // Seed the cache from the meta tag if a server-rendered page injected + // one — saves a round trip on legacy templates that still embed it. + csrfToken = readMetaToken(); + + axios.interceptors.request.use( + async (config) => { + config.headers = config.headers || {}; + const method = (config.method || 'get').toUpperCase(); + if (!SAFE_METHODS.has(method)) { + const token = await ensureCsrfToken(); + if (token) config.headers['X-CSRF-Token'] = token; + } + if (config.data instanceof FormData) { + config.headers['Content-Type'] = 'multipart/form-data'; + } else { + config.data = qs.stringify(config.data, { arrayFormat: 'repeat' }); + } + return config; + }, + (error) => Promise.reject(error), + ); + + axios.interceptors.response.use( + (response) => response, + async (error) => { + const status = error.response?.status; + if (status === 401) { + // 401 → session is gone. In production, the panel routes + // are gated by Go's checkLogin which redirects to base_path + // serving the login page; a reload is enough. In dev, Vite + // serves /index.html directly at "/", so a reload would put + // the user right back on the dashboard and the interceptor + // would loop. Navigate to the dev login entry instead. + if (import.meta.env.DEV) { + const basePath = window.__X_UI_BASE_PATH__ || '/'; + window.location.href = `${basePath}login.html`; + } else { + window.location.reload(); + } + return Promise.reject(error); + } + // 403 with a stale/missing CSRF token: drop the cache, re-fetch, retry once. + const cfg = error.config; + if (status === 403 && cfg && !cfg.__csrfRetried) { + csrfToken = null; + cfg.__csrfRetried = true; + const token = await ensureCsrfToken(); + if (token) { + cfg.headers = cfg.headers || {}; + cfg.headers['X-CSRF-Token'] = token; + // axios re-stringifies on retry, so unwind our qs.stringify before + // letting the same request flow through the interceptor again. + if (typeof cfg.data === 'string') cfg.data = qs.parse(cfg.data); + return axios(cfg); + } + } + return Promise.reject(error); + }, + ); +} diff --git a/web/assets/js/websocket.js b/frontend/src/api/websocket.js similarity index 93% rename from web/assets/js/websocket.js rename to frontend/src/api/websocket.js index f10507d5..5076e53e 100644 --- a/web/assets/js/websocket.js +++ b/frontend/src/api/websocket.js @@ -15,7 +15,7 @@ * 'connected', 'disconnected', 'error', 'message', * plus any server-emitted message type (status, traffic, client_stats, ...). */ -class WebSocketClient { +export class WebSocketClient { static #MAX_PAYLOAD_BYTES = 10 * 1024 * 1024; // 10 MB, mirrors hub maxMessageSize. static #BASE_RECONNECT_MS = 1000; static #MAX_RECONNECT_MS = 30_000; @@ -140,8 +140,14 @@ class WebSocketClient { #buildUrl() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - let basePath = this.basePath || ''; - if (basePath && !basePath.endsWith('/')) basePath += '/'; + // basePath comes from window.__X_UI_BASE_PATH__ which is only injected + // by the Go binary in production. In dev (Vite serves directly) the + // global is missing and basePath would be '' — without the fallback to + // '/' we'd build `ws://host:portws` (no separator) and the WebSocket + // constructor throws a SyntaxError. + let basePath = this.basePath || '/'; + if (!basePath.startsWith('/')) basePath = '/' + basePath; + if (!basePath.endsWith('/')) basePath += '/'; return `${protocol}//${window.location.host}${basePath}ws`; } @@ -223,5 +229,3 @@ class WebSocketClient { } } -// Global instance — basePath is set by page.html before this script loads. -window.wsClient = new WebSocketClient(typeof basePath !== 'undefined' ? basePath : ''); diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue new file mode 100644 index 00000000..b72da591 --- /dev/null +++ b/frontend/src/components/AppSidebar.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/frontend/src/components/CustomStatistic.vue b/frontend/src/components/CustomStatistic.vue new file mode 100644 index 00000000..7ffadcd0 --- /dev/null +++ b/frontend/src/components/CustomStatistic.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/frontend/src/components/DateTimePicker.vue b/frontend/src/components/DateTimePicker.vue new file mode 100644 index 00000000..0e528c21 --- /dev/null +++ b/frontend/src/components/DateTimePicker.vue @@ -0,0 +1,384 @@ + + + + + + + + diff --git a/frontend/src/components/FinalMaskForm.vue b/frontend/src/components/FinalMaskForm.vue new file mode 100644 index 00000000..9613ae06 --- /dev/null +++ b/frontend/src/components/FinalMaskForm.vue @@ -0,0 +1,542 @@ + + + diff --git a/frontend/src/components/InfinityIcon.vue b/frontend/src/components/InfinityIcon.vue new file mode 100644 index 00000000..a03eb26a --- /dev/null +++ b/frontend/src/components/InfinityIcon.vue @@ -0,0 +1,25 @@ + + + diff --git a/frontend/src/components/PromptModal.vue b/frontend/src/components/PromptModal.vue new file mode 100644 index 00000000..f79a3974 --- /dev/null +++ b/frontend/src/components/PromptModal.vue @@ -0,0 +1,70 @@ + + + diff --git a/frontend/src/components/SettingListItem.vue b/frontend/src/components/SettingListItem.vue new file mode 100644 index 00000000..9cf3b46d --- /dev/null +++ b/frontend/src/components/SettingListItem.vue @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/components/Sparkline.vue b/frontend/src/components/Sparkline.vue new file mode 100644 index 00000000..bb626d62 --- /dev/null +++ b/frontend/src/components/Sparkline.vue @@ -0,0 +1,317 @@ + + + + + + + + diff --git a/frontend/src/components/TableSortable.vue b/frontend/src/components/TableSortable.vue new file mode 100644 index 00000000..e4d83087 --- /dev/null +++ b/frontend/src/components/TableSortable.vue @@ -0,0 +1,300 @@ + + + diff --git a/frontend/src/components/TextModal.vue b/frontend/src/components/TextModal.vue new file mode 100644 index 00000000..25bd2720 --- /dev/null +++ b/frontend/src/components/TextModal.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/frontend/src/components/ThemeSwitch.vue b/frontend/src/components/ThemeSwitch.vue new file mode 100644 index 00000000..12ed574d --- /dev/null +++ b/frontend/src/components/ThemeSwitch.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/frontend/src/components/ThemeSwitchLogin.vue b/frontend/src/components/ThemeSwitchLogin.vue new file mode 100644 index 00000000..c368ba02 --- /dev/null +++ b/frontend/src/components/ThemeSwitchLogin.vue @@ -0,0 +1,25 @@ + + + diff --git a/frontend/src/composables/useDatepicker.js b/frontend/src/composables/useDatepicker.js new file mode 100644 index 00000000..03eba91c --- /dev/null +++ b/frontend/src/composables/useDatepicker.js @@ -0,0 +1,45 @@ +// 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 new file mode 100644 index 00000000..a1861c93 --- /dev/null +++ b/frontend/src/composables/useMediaQuery.js @@ -0,0 +1,26 @@ +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/inbounds/ClientFormModal.vue b/frontend/src/pages/inbounds/ClientFormModal.vue new file mode 100644 index 00000000..aae4c208 --- /dev/null +++ b/frontend/src/pages/inbounds/ClientFormModal.vue @@ -0,0 +1,394 @@ + + + + + diff --git a/frontend/src/pages/inbounds/ClientRowTable.vue b/frontend/src/pages/inbounds/ClientRowTable.vue new file mode 100644 index 00000000..9d388209 --- /dev/null +++ b/frontend/src/pages/inbounds/ClientRowTable.vue @@ -0,0 +1,610 @@ + + + + + diff --git a/frontend/src/pages/inbounds/InboundFormModal.vue b/frontend/src/pages/inbounds/InboundFormModal.vue new file mode 100644 index 00000000..875d3c3e --- /dev/null +++ b/frontend/src/pages/inbounds/InboundFormModal.vue @@ -0,0 +1,1790 @@ + + + + + diff --git a/frontend/src/pages/inbounds/InboundInfoModal.vue b/frontend/src/pages/inbounds/InboundInfoModal.vue new file mode 100644 index 00000000..05af6a13 --- /dev/null +++ b/frontend/src/pages/inbounds/InboundInfoModal.vue @@ -0,0 +1,1012 @@ + + + + + diff --git a/frontend/src/pages/inbounds/InboundList.vue b/frontend/src/pages/inbounds/InboundList.vue new file mode 100644 index 00000000..e4051d65 --- /dev/null +++ b/frontend/src/pages/inbounds/InboundList.vue @@ -0,0 +1,621 @@ + + + + + diff --git a/frontend/src/pages/inbounds/InboundsPage.vue b/frontend/src/pages/inbounds/InboundsPage.vue new file mode 100644 index 00000000..a2b4f68f --- /dev/null +++ b/frontend/src/pages/inbounds/InboundsPage.vue @@ -0,0 +1,692 @@ + + + + + diff --git a/frontend/src/pages/inbounds/QrCodeModal.vue b/frontend/src/pages/inbounds/QrCodeModal.vue new file mode 100644 index 00000000..2db3223f --- /dev/null +++ b/frontend/src/pages/inbounds/QrCodeModal.vue @@ -0,0 +1,67 @@ + + + diff --git a/frontend/src/pages/inbounds/QrPanel.vue b/frontend/src/pages/inbounds/QrPanel.vue new file mode 100644 index 00000000..02ca4e71 --- /dev/null +++ b/frontend/src/pages/inbounds/QrPanel.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/frontend/src/pages/inbounds/useInbounds.js b/frontend/src/pages/inbounds/useInbounds.js new file mode 100644 index 00000000..ca252832 --- /dev/null +++ b/frontend/src/pages/inbounds/useInbounds.js @@ -0,0 +1,323 @@ +// Loads the inbound list + sidecar data the page needs (online users, +// last-online-map, default settings) and computes the per-inbound client +// roll-ups the legacy panel surfaces in the popovers. +// +// Live-update model: initial GET on mount, then the WebSocket delta path +// keeps the table fresh — the page subscribes to the server's `traffic`, +// `client_stats`, and `invalidate` events and merges them into local +// refs in-place. The manual refresh button is kept as a fallback. + +import { computed, ref, shallowRef } from 'vue'; +import { HttpUtil, ObjectUtil } from '@/utils'; +import { DBInbound } from '@/models/dbinbound.js'; +import { Protocols } from '@/models/inbound.js'; +import { setDatepicker } from '@/composables/useDatepicker.js'; + +export function useInbounds() { + const fetched = ref(false); + const refreshing = ref(false); + + // shallowRef because each refresh swaps the array; per-row reactivity is + // unnecessary at the page level (modals work on copies). + const dbInbounds = shallowRef([]); + const clientCount = ref({}); + const onlineClients = ref([]); + const lastOnlineMap = ref({}); + + // Default-settings sidecar fields the table needs for color/expiry math. + const expireDiff = ref(0); + const trafficDiff = ref(0); + const subSettings = ref({ + enable: false, + subTitle: '', + subURI: '', + subJsonURI: '', + subJsonEnable: false, + }); + const remarkModel = ref('-ieo'); + const datepicker = ref('gregorian'); + const tgBotEnable = ref(false); + const ipLimitEnable = ref(false); + const pageSize = ref(0); + + function isClientOnline(email) { + return onlineClients.value.includes(email); + } + + // Roll-up of {clients, active, deactive, depleted, expiring, online, + // comments} for a single inbound. Mirrors getClientCounts in the legacy + // template. Skipped for protocols that don't have multi-user clients + // (HTTP, MIXED, WireGuard) since their settings have no client list. + function rollupClients(dbInbound, inbound) { + const clientStats = Array.isArray(dbInbound.clientStats) ? dbInbound.clientStats : []; + const clients = inbound?.clients || []; + const active = []; + const deactive = []; + const depleted = []; + const expiring = []; + const online = []; + const comments = new Map(); + const now = Date.now(); + + if (dbInbound.enable) { + for (const client of clients) { + if (client.comment) comments.set(client.email, client.comment); + if (client.enable) { + active.push(client.email); + if (isClientOnline(client.email)) online.push(client.email); + } else { + deactive.push(client.email); + } + } + for (const stats of clientStats) { + const exhausted = stats.total > 0 && stats.up + stats.down >= stats.total; + const expired = stats.expiryTime > 0 && stats.expiryTime <= now; + if (expired || exhausted) { + depleted.push(stats.email); + } else { + const expiringSoon = + (stats.expiryTime > 0 && stats.expiryTime - now < expireDiff.value) || + (stats.total > 0 && stats.total - (stats.up + stats.down) < trafficDiff.value); + if (expiringSoon) expiring.push(stats.email); + } + } + } else { + for (const client of clients) deactive.push(client.email); + } + + return { + clients: clients.length, + active, + deactive, + depleted, + expiring, + online, + comments, + }; + } + + function setInbounds(rows) { + const next = []; + const counts = {}; + for (const row of rows) { + const dbInbound = new DBInbound(row); + const parsed = dbInbound.toInbound(); + next.push(dbInbound); + const tracked = [ + Protocols.VMESS, + Protocols.VLESS, + Protocols.TROJAN, + Protocols.SHADOWSOCKS, + Protocols.HYSTERIA, + ]; + if (tracked.includes(row.protocol)) { + if (dbInbound.isSS && !parsed.isSSMultiUser) continue; + counts[row.id] = rollupClients(dbInbound, parsed); + } + } + dbInbounds.value = next; + clientCount.value = counts; + fetched.value = true; + } + + async function fetchOnlineUsers() { + const msg = await HttpUtil.post('/panel/api/inbounds/onlines'); + if (msg?.success) onlineClients.value = msg.obj || []; + } + + async function fetchLastOnlineMap() { + const msg = await HttpUtil.post('/panel/api/inbounds/lastOnline'); + if (msg?.success && msg.obj) lastOnlineMap.value = msg.obj; + } + + async function fetchDefaultSettings() { + const msg = await HttpUtil.post('/panel/setting/defaultSettings'); + if (!msg?.success) return; + const s = msg.obj || {}; + expireDiff.value = (s.expireDiff ?? 0) * 86400000; + trafficDiff.value = (s.trafficDiff ?? 0) * 1073741824; + tgBotEnable.value = !!s.tgBotEnable; + subSettings.value = { + enable: !!s.subEnable, + subTitle: s.subTitle || '', + subURI: s.subURI || '', + subJsonURI: s.subJsonURI || '', + subJsonEnable: !!s.subJsonEnable, + }; + pageSize.value = s.pageSize ?? 0; + remarkModel.value = s.remarkModel || '-ieo'; + datepicker.value = s.datepicker || 'gregorian'; + // Mirror into the global composable so date-pickers in modals can + // pick the right calendar without re-fetching the settings. + setDatepicker(datepicker.value); + ipLimitEnable.value = !!s.ipLimitEnable; + } + + // ============ WebSocket live-update merge =========================== + // The xray traffic job and the node traffic sync job each broadcast + // a `traffic` payload every ~10s. We merge it into onlineClients + + // lastOnlineMap; per-inbound counters arrive in the parallel + // client_stats event below. + function applyTrafficEvent(payload) { + if (!payload || typeof payload !== 'object') return; + if (Array.isArray(payload.onlineClients)) { + onlineClients.value = payload.onlineClients; + } + if (payload.lastOnlineMap && typeof payload.lastOnlineMap === 'object') { + // Merge so a subsequent payload that drops a quiet client doesn't + // wipe their last-seen timestamp. + lastOnlineMap.value = { ...lastOnlineMap.value, ...payload.lastOnlineMap }; + } + // Recompute per-inbound rollups so the "online" badges in the + // expand-row table flip without waiting for a full refresh. + rebuildClientCount(); + } + + // The client_stats payload carries absolute traffic counters for the + // clients that had activity in the latest window plus per-inbound + // totals. Both are absolute (not deltas), so we overwrite in place. + function applyClientStatsEvent(payload) { + if (!payload || typeof payload !== 'object') return; + let touched = false; + + if (Array.isArray(payload.inbounds) && payload.inbounds.length > 0) { + const byId = new Map(); + for (const row of payload.inbounds) { + if (row && row.id != null) byId.set(row.id, row); + } + for (const ib of dbInbounds.value) { + const upd = byId.get(ib.id); + if (!upd) continue; + if (typeof upd.up === 'number') ib.up = upd.up; + if (typeof upd.down === 'number') ib.down = upd.down; + if (typeof upd.allTime === 'number') ib.allTime = upd.allTime; + touched = true; + } + } + + if (Array.isArray(payload.clients) && payload.clients.length > 0) { + const byEmail = new Map(); + for (const row of payload.clients) { + if (row && row.email) byEmail.set(row.email, row); + } + for (const ib of dbInbounds.value) { + if (!Array.isArray(ib.clientStats)) continue; + for (let i = 0; i < ib.clientStats.length; i++) { + const stat = ib.clientStats[i]; + const upd = byEmail.get(stat.email); + if (!upd) continue; + if (typeof upd.up === 'number') stat.up = upd.up; + if (typeof upd.down === 'number') stat.down = upd.down; + if (typeof upd.total === 'number') stat.total = upd.total; + if (typeof upd.expiryTime === 'number') stat.expiryTime = upd.expiryTime; + touched = true; + } + } + } + + if (touched) { + // shallowRef → trigger reactivity by reassigning the same array. + dbInbounds.value = [...dbInbounds.value]; + rebuildClientCount(); + } + } + + // The hub may decide a payload is too large to push directly and emit + // an `invalidate` event with the affected dataType instead. For the + // inbounds page that means "the inbound list changed elsewhere — go + // re-fetch via REST". + function applyInvalidate(payload) { + if (!payload || typeof payload !== 'object') return; + if (payload.dataType === 'inbounds') { + refresh(); + } + } + + // Recompute the per-inbound roll-up after any in-place mutation. + // Cheap because rollupClients only iterates a single inbound's + // clients + clientStats arrays. + function rebuildClientCount() { + const counts = {}; + const tracked = [ + Protocols.VMESS, + Protocols.VLESS, + Protocols.TROJAN, + Protocols.SHADOWSOCKS, + Protocols.HYSTERIA, + ]; + for (const dbInbound of dbInbounds.value) { + const parsed = dbInbound.toInbound(); + if (!tracked.includes(dbInbound.protocol)) continue; + if (dbInbound.isSS && !parsed.isSSMultiUser) continue; + counts[dbInbound.id] = rollupClients(dbInbound, parsed); + } + clientCount.value = counts; + } + + async function refresh() { + refreshing.value = true; + try { + const msg = await HttpUtil.get('/panel/api/inbounds/list'); + if (!msg?.success) return; + await fetchLastOnlineMap(); + await fetchOnlineUsers(); + setInbounds(Array.isArray(msg.obj) ? msg.obj : []); + } finally { + // Match legacy: keep the spinning-icon state visible briefly so + // a fast network doesn't make the button feel like it didn't fire. + setTimeout(() => { refreshing.value = false; }, 500); + } + } + + // Aggregate totals shown in the dashboard summary card. allTime falls + // back to up+down when the per-inbound counter isn't populated yet. + const totals = computed(() => { + let up = 0; + let down = 0; + let allTime = 0; + let clients = 0; + const deactive = []; + const depleted = []; + const expiring = []; + for (const ib of dbInbounds.value) { + up += ib.up || 0; + down += ib.down || 0; + allTime += ib.allTime || (ib.up + ib.down) || 0; + const c = clientCount.value[ib.id]; + if (c) { + clients += c.clients; + deactive.push(...c.deactive); + depleted.push(...c.depleted); + expiring.push(...c.expiring); + } + } + return { up, down, allTime, clients, deactive, depleted, expiring }; + }); + + // ObjectUtil reference is wired at module load — keeping a no-op import + // here so the linter doesn't drop it; the legacy search uses it. + void ObjectUtil; + + return { + fetched, + refreshing, + dbInbounds, + clientCount, + onlineClients, + lastOnlineMap, + totals, + expireDiff, + trafficDiff, + subSettings, + remarkModel, + datepicker, + tgBotEnable, + ipLimitEnable, + pageSize, + refresh, + fetchDefaultSettings, + applyTrafficEvent, + applyClientStatsEvent, + applyInvalidate, + }; +} diff --git a/frontend/src/pages/index/BackupModal.vue b/frontend/src/pages/index/BackupModal.vue new file mode 100644 index 00000000..a7a9fc4f --- /dev/null +++ b/frontend/src/pages/index/BackupModal.vue @@ -0,0 +1,101 @@ + + + + + diff --git a/frontend/src/pages/index/CustomGeoFormModal.vue b/frontend/src/pages/index/CustomGeoFormModal.vue new file mode 100644 index 00000000..c5c871d6 --- /dev/null +++ b/frontend/src/pages/index/CustomGeoFormModal.vue @@ -0,0 +1,106 @@ + + + diff --git a/frontend/src/pages/index/CustomGeoSection.vue b/frontend/src/pages/index/CustomGeoSection.vue new file mode 100644 index 00000000..55f13cec --- /dev/null +++ b/frontend/src/pages/index/CustomGeoSection.vue @@ -0,0 +1,311 @@ + + + + + diff --git a/frontend/src/pages/index/IndexPage.vue b/frontend/src/pages/index/IndexPage.vue new file mode 100644 index 00000000..ad6c4df0 --- /dev/null +++ b/frontend/src/pages/index/IndexPage.vue @@ -0,0 +1,394 @@ + + + + + diff --git a/frontend/src/pages/index/LogModal.vue b/frontend/src/pages/index/LogModal.vue new file mode 100644 index 00000000..735ea0ba --- /dev/null +++ b/frontend/src/pages/index/LogModal.vue @@ -0,0 +1,165 @@ + + + + + diff --git a/frontend/src/pages/index/PanelUpdateModal.vue b/frontend/src/pages/index/PanelUpdateModal.vue new file mode 100644 index 00000000..4d98796b --- /dev/null +++ b/frontend/src/pages/index/PanelUpdateModal.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/frontend/src/pages/index/StatusCard.vue b/frontend/src/pages/index/StatusCard.vue new file mode 100644 index 00000000..fb918e8e --- /dev/null +++ b/frontend/src/pages/index/StatusCard.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/frontend/src/pages/index/SystemHistoryModal.vue b/frontend/src/pages/index/SystemHistoryModal.vue new file mode 100644 index 00000000..aeb86d1c --- /dev/null +++ b/frontend/src/pages/index/SystemHistoryModal.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/frontend/src/pages/index/VersionModal.vue b/frontend/src/pages/index/VersionModal.vue new file mode 100644 index 00000000..78eb228a --- /dev/null +++ b/frontend/src/pages/index/VersionModal.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/frontend/src/pages/index/XrayLogModal.vue b/frontend/src/pages/index/XrayLogModal.vue new file mode 100644 index 00000000..cb128cd0 --- /dev/null +++ b/frontend/src/pages/index/XrayLogModal.vue @@ -0,0 +1,182 @@ + + + + + + + diff --git a/frontend/src/pages/index/XrayStatusCard.vue b/frontend/src/pages/index/XrayStatusCard.vue new file mode 100644 index 00000000..4eb36126 --- /dev/null +++ b/frontend/src/pages/index/XrayStatusCard.vue @@ -0,0 +1,144 @@ + + + + + + + diff --git a/frontend/src/pages/login/LoginPage.vue b/frontend/src/pages/login/LoginPage.vue new file mode 100644 index 00000000..d24965b5 --- /dev/null +++ b/frontend/src/pages/login/LoginPage.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/frontend/src/pages/nodes/NodeFormModal.vue b/frontend/src/pages/nodes/NodeFormModal.vue new file mode 100644 index 00000000..edaa9aa7 --- /dev/null +++ b/frontend/src/pages/nodes/NodeFormModal.vue @@ -0,0 +1,223 @@ + + + + + diff --git a/frontend/src/pages/nodes/NodeHistoryPanel.vue b/frontend/src/pages/nodes/NodeHistoryPanel.vue new file mode 100644 index 00000000..5a8357f4 --- /dev/null +++ b/frontend/src/pages/nodes/NodeHistoryPanel.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/pages/nodes/NodeList.vue b/frontend/src/pages/nodes/NodeList.vue new file mode 100644 index 00000000..a733eece --- /dev/null +++ b/frontend/src/pages/nodes/NodeList.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/frontend/src/pages/nodes/NodesPage.vue b/frontend/src/pages/nodes/NodesPage.vue new file mode 100644 index 00000000..d8a1a2ee --- /dev/null +++ b/frontend/src/pages/nodes/NodesPage.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/frontend/src/pages/nodes/useNodes.js b/frontend/src/pages/nodes/useNodes.js new file mode 100644 index 00000000..cb4a6e8c --- /dev/null +++ b/frontend/src/pages/nodes/useNodes.js @@ -0,0 +1,120 @@ +// Loads the node list and runs CRUD/probe actions against the +// /panel/api/nodes/* endpoints. Live updates arrive over WebSocket +// (pushed by NodeHeartbeatJob every 10s) so we don't poll. + +import { computed, onMounted, ref, shallowRef } from 'vue'; +import { HttpUtil } from '@/utils'; + +export function useNodes() { + const nodes = shallowRef([]); + const loading = ref(false); + const fetched = ref(false); + + async function refresh() { + loading.value = true; + try { + const msg = await HttpUtil.get('/panel/api/nodes/list'); + if (msg?.success) { + nodes.value = Array.isArray(msg.obj) ? msg.obj : []; + } + fetched.value = true; + } finally { + loading.value = false; + } + } + + // Replaces the local list with the snapshot pushed by the heartbeat job. + // shallowRef means a fresh assignment is enough to retrigger reactivity; + // we always assign a new array so Vue notices. + function applyNodesEvent(payload) { + if (Array.isArray(payload)) { + nodes.value = payload; + if (!fetched.value) fetched.value = true; + } + } + + async function create(payload) { + const msg = await HttpUtil.post('/panel/api/nodes/add', payload); + if (msg?.success) await refresh(); + return msg; + } + + async function update(id, payload) { + const msg = await HttpUtil.post(`/panel/api/nodes/update/${id}`, payload); + if (msg?.success) await refresh(); + return msg; + } + + async function remove(id) { + const msg = await HttpUtil.post(`/panel/api/nodes/del/${id}`); + if (msg?.success) await refresh(); + return msg; + } + + async function setEnable(id, enable) { + const msg = await HttpUtil.post(`/panel/api/nodes/setEnable/${id}`, { enable }); + if (msg?.success) await refresh(); + return msg; + } + + // testConnection probes a transient (unsaved) node config so the form + // can validate before save. Returns the ProbeResultUI shape from Go. + async function testConnection(payload) { + const msg = await HttpUtil.post('/panel/api/nodes/test', payload); + return msg; + } + + // probe forces an immediate heartbeat against an already-saved node. + async function probe(id) { + const msg = await HttpUtil.post(`/panel/api/nodes/probe/${id}`); + if (msg?.success) await refresh(); + return msg; + } + + // Aggregate cards on the dashboard. Computed off the live list so a + // refresh (or a WS push) picks up new totals automatically. + const totals = computed(() => { + const list = nodes.value; + let online = 0; + let offline = 0; + let latencySum = 0; + let latencyCount = 0; + for (const n of list) { + if (!n.enable) continue; + if (n.status === 'online') { + online += 1; + if (n.latencyMs > 0) { + latencySum += n.latencyMs; + latencyCount += 1; + } + } else if (n.status === 'offline') { + offline += 1; + } + } + return { + total: list.length, + online, + offline, + avgLatency: latencyCount > 0 ? Math.round(latencySum / latencyCount) : 0, + }; + }); + + // Initial fetch — WebSocket takes over after the first heartbeat tick + // (~10s) but the page should populate immediately on mount. + onMounted(refresh); + + return { + nodes, + loading, + fetched, + totals, + refresh, + applyNodesEvent, + create, + update, + remove, + setEnable, + testConnection, + probe, + }; +} diff --git a/frontend/src/pages/settings/GeneralTab.vue b/frontend/src/pages/settings/GeneralTab.vue new file mode 100644 index 00000000..2d85312b --- /dev/null +++ b/frontend/src/pages/settings/GeneralTab.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/frontend/src/pages/settings/SecurityTab.vue b/frontend/src/pages/settings/SecurityTab.vue new file mode 100644 index 00000000..9aed3c37 --- /dev/null +++ b/frontend/src/pages/settings/SecurityTab.vue @@ -0,0 +1,245 @@ + + + diff --git a/frontend/src/pages/settings/SettingsPage.vue b/frontend/src/pages/settings/SettingsPage.vue new file mode 100644 index 00000000..198fd8ad --- /dev/null +++ b/frontend/src/pages/settings/SettingsPage.vue @@ -0,0 +1,309 @@ + + + + + diff --git a/frontend/src/pages/settings/SubscriptionFormatsTab.vue b/frontend/src/pages/settings/SubscriptionFormatsTab.vue new file mode 100644 index 00000000..26301b4c --- /dev/null +++ b/frontend/src/pages/settings/SubscriptionFormatsTab.vue @@ -0,0 +1,433 @@ + + + + + diff --git a/frontend/src/pages/settings/SubscriptionGeneralTab.vue b/frontend/src/pages/settings/SubscriptionGeneralTab.vue new file mode 100644 index 00000000..35f6b7d0 --- /dev/null +++ b/frontend/src/pages/settings/SubscriptionGeneralTab.vue @@ -0,0 +1,196 @@ + + + diff --git a/frontend/src/pages/settings/TelegramTab.vue b/frontend/src/pages/settings/TelegramTab.vue new file mode 100644 index 00000000..ce82350f --- /dev/null +++ b/frontend/src/pages/settings/TelegramTab.vue @@ -0,0 +1,106 @@ + + + diff --git a/frontend/src/pages/settings/TwoFactorModal.vue b/frontend/src/pages/settings/TwoFactorModal.vue new file mode 100644 index 00000000..73c92c0d --- /dev/null +++ b/frontend/src/pages/settings/TwoFactorModal.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/frontend/src/pages/settings/useAllSetting.js b/frontend/src/pages/settings/useAllSetting.js new file mode 100644 index 00000000..8b09d00d --- /dev/null +++ b/frontend/src/pages/settings/useAllSetting.js @@ -0,0 +1,80 @@ +// Centralizes the AllSetting fetch/save lifecycle the legacy panel +// scattered across data() + methods + a busy-loop dirty checker. +// +// The dirty flag is recomputed once per second (matching the legacy +// `while (true) sleep(1000)` poll) — we don't deep-watch because the +// settings tree has many nested fields and a poll is cheap enough. + +import { onMounted, onUnmounted, reactive, ref } from 'vue'; +import { HttpUtil } from '@/utils'; +import { AllSetting } from '@/models/setting.js'; + +const DIRTY_POLL_MS = 1000; + +export function useAllSetting() { + const fetched = ref(false); + const spinning = ref(false); + const saveDisabled = ref(true); + + // Two reactive snapshots: the last server-side state and the one the + // user is editing. `equals` compares enumerable props field-by-field. + const oldAllSetting = reactive(new AllSetting()); + const allSetting = reactive(new AllSetting()); + + function applyServerState(obj) { + const fresh = new AllSetting(obj); + Object.assign(oldAllSetting, fresh); + Object.assign(allSetting, fresh); + saveDisabled.value = true; + } + + async function fetchAll() { + const msg = await HttpUtil.post('/panel/setting/all'); + if (msg?.success) { + fetched.value = true; + applyServerState(msg.obj); + } + } + + async function saveAll() { + spinning.value = true; + try { + const msg = await HttpUtil.post('/panel/setting/update', allSetting); + if (msg?.success) await fetchAll(); + } finally { + spinning.value = false; + } + } + + let timer = null; + function startDirtyPoll() { + if (timer != null) return; + timer = setInterval(() => { + // ObjectUtil.equals walks own enumerable props; reactive proxies + // expose them transparently so this works without cloning. + saveDisabled.value = oldAllSetting.equals(allSetting); + }, DIRTY_POLL_MS); + } + function stopDirtyPoll() { + if (timer != null) { + clearInterval(timer); + timer = null; + } + } + + onMounted(() => { + fetchAll(); + startDirtyPoll(); + }); + onUnmounted(stopDirtyPoll); + + return { + fetched, + spinning, + saveDisabled, + oldAllSetting, + allSetting, + fetchAll, + saveAll, + }; +} diff --git a/frontend/src/pages/sub/SubPage.vue b/frontend/src/pages/sub/SubPage.vue new file mode 100644 index 00000000..2b6ac2e9 --- /dev/null +++ b/frontend/src/pages/sub/SubPage.vue @@ -0,0 +1,465 @@ + + + + + diff --git a/frontend/src/pages/xray/BalancerFormModal.vue b/frontend/src/pages/xray/BalancerFormModal.vue new file mode 100644 index 00000000..07c6d780 --- /dev/null +++ b/frontend/src/pages/xray/BalancerFormModal.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/pages/xray/BalancersTab.vue b/frontend/src/pages/xray/BalancersTab.vue new file mode 100644 index 00000000..dc0145fa --- /dev/null +++ b/frontend/src/pages/xray/BalancersTab.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/frontend/src/pages/xray/BasicsTab.vue b/frontend/src/pages/xray/BasicsTab.vue new file mode 100644 index 00000000..196dcb06 --- /dev/null +++ b/frontend/src/pages/xray/BasicsTab.vue @@ -0,0 +1,500 @@ + + + + + diff --git a/frontend/src/pages/xray/DnsServerModal.vue b/frontend/src/pages/xray/DnsServerModal.vue new file mode 100644 index 00000000..e71c23d1 --- /dev/null +++ b/frontend/src/pages/xray/DnsServerModal.vue @@ -0,0 +1,168 @@ + + + diff --git a/frontend/src/pages/xray/DnsTab.vue b/frontend/src/pages/xray/DnsTab.vue new file mode 100644 index 00000000..522d86b9 --- /dev/null +++ b/frontend/src/pages/xray/DnsTab.vue @@ -0,0 +1,373 @@ + + + + + diff --git a/frontend/src/pages/xray/NordModal.vue b/frontend/src/pages/xray/NordModal.vue new file mode 100644 index 00000000..b23f4e83 --- /dev/null +++ b/frontend/src/pages/xray/NordModal.vue @@ -0,0 +1,379 @@ + + + + + diff --git a/frontend/src/pages/xray/OutboundFormModal.vue b/frontend/src/pages/xray/OutboundFormModal.vue new file mode 100644 index 00000000..324d91d2 --- /dev/null +++ b/frontend/src/pages/xray/OutboundFormModal.vue @@ -0,0 +1,1007 @@ + + + + + diff --git a/frontend/src/pages/xray/OutboundsTab.vue b/frontend/src/pages/xray/OutboundsTab.vue new file mode 100644 index 00000000..cef4ab4a --- /dev/null +++ b/frontend/src/pages/xray/OutboundsTab.vue @@ -0,0 +1,499 @@ + + + + + diff --git a/frontend/src/pages/xray/RoutingTab.vue b/frontend/src/pages/xray/RoutingTab.vue new file mode 100644 index 00000000..beb28ca9 --- /dev/null +++ b/frontend/src/pages/xray/RoutingTab.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/frontend/src/pages/xray/RuleFormModal.vue b/frontend/src/pages/xray/RuleFormModal.vue new file mode 100644 index 00000000..408cad67 --- /dev/null +++ b/frontend/src/pages/xray/RuleFormModal.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/frontend/src/pages/xray/WarpModal.vue b/frontend/src/pages/xray/WarpModal.vue new file mode 100644 index 00000000..3e81aad0 --- /dev/null +++ b/frontend/src/pages/xray/WarpModal.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/frontend/src/pages/xray/XrayPage.vue b/frontend/src/pages/xray/XrayPage.vue new file mode 100644 index 00000000..42723616 --- /dev/null +++ b/frontend/src/pages/xray/XrayPage.vue @@ -0,0 +1,431 @@ + + + + + diff --git a/frontend/src/pages/xray/useXraySetting.js b/frontend/src/pages/xray/useXraySetting.js new file mode 100644 index 00000000..fd74b103 --- /dev/null +++ b/frontend/src/pages/xray/useXraySetting.js @@ -0,0 +1,246 @@ +// Drives the xray page's fetch / dirty / save lifecycle. The Go side +// returns the live xraySetting (the full JSON config), the inboundTags +// list, and a few sidecar values (clientReverseTags, outboundTestUrl) +// the structured tabs need. We keep the JSON as a string here — pretty- +// printed for the textarea; tabs that want a parsed view can JSON.parse +// it themselves. + +import { onMounted, onUnmounted, ref, watch } from 'vue'; +import { HttpUtil, PromiseUtil } from '@/utils'; + +const DIRTY_POLL_MS = 1000; + +// Hoists the parsed `templateSettings` alongside the JSON string so +// structured tabs (Basics/Routing/Outbounds/etc.) can mutate fields +// directly while the Advanced (JSON) tab edits the same data as text. +// We keep both in sync with two cooperating watches: +// • mutating templateSettings re-stringifies into xraySetting; +// • editing the JSON text re-parses into templateSettings (only on +// valid JSON — invalid edits leave templateSettings untouched +// so the structured tabs don't blow up while the user types). +let syncing = false; + +export function useXraySetting() { + const fetched = ref(false); + const spinning = ref(false); + const saveDisabled = ref(true); + // Holds a user-facing message when fetchAll fails; lets the page + // render an error UI instead of an endless spinner. + const fetchError = ref(''); + + const xraySetting = ref(''); + const oldXraySetting = ref(''); + + // Parsed mirror — null until first successful fetch / parse. + const templateSettings = ref(null); + + const outboundTestUrl = ref('https://www.google.com/generate_204'); + const oldOutboundTestUrl = ref(''); + + const inboundTags = ref([]); + const clientReverseTags = ref([]); + const restartResult = ref(''); + + // Outbounds tab data — traffic stats + per-row test state. Test + // states are keyed by outbound index (sparse object), each entry + // is `{ testing, result }` where result is the wire response from + // /panel/xray/testOutbound or null while the test is in flight. + const outboundsTraffic = ref([]); + const outboundTestStates = ref({}); + + async function fetchAll() { + fetchError.value = ''; + const msg = await HttpUtil.post('/panel/xray/'); + if (!msg?.success) { + fetchError.value = msg?.msg || 'Failed to load xray config'; + // Mark as fetched so the spinner clears and the error UI renders. + fetched.value = true; + return; + } + let obj; + try { + obj = JSON.parse(msg.obj); + } catch (e) { + fetchError.value = `Malformed xray config response: ${e?.message || e}`; + fetched.value = true; + return; + } + const pretty = JSON.stringify(obj.xraySetting, null, 2); + syncing = true; + xraySetting.value = pretty; + oldXraySetting.value = pretty; + templateSettings.value = obj.xraySetting; + syncing = false; + inboundTags.value = obj.inboundTags || []; + clientReverseTags.value = obj.clientReverseTags || []; + outboundTestUrl.value = obj.outboundTestUrl || 'https://www.google.com/generate_204'; + oldOutboundTestUrl.value = outboundTestUrl.value; + fetched.value = true; + saveDisabled.value = true; + } + + // Structured tabs mutate templateSettings deeply. Re-stringify on + // change so the Advanced JSON view + the dirty-poll see the edits. + watch( + templateSettings, + (next) => { + if (syncing || !next) return; + syncing = true; + try { + xraySetting.value = JSON.stringify(next, null, 2); + } finally { + syncing = false; + } + }, + { deep: true }, + ); + + // Advanced JSON edits — only refresh templateSettings when the text + // parses, so structured tabs stay readable mid-edit. + watch(xraySetting, (next) => { + if (syncing) return; + try { + const parsed = JSON.parse(next); + syncing = true; + try { + templateSettings.value = parsed; + } finally { + syncing = false; + } + } catch (_e) { /* ignore — wait for user to finish */ } + }); + + async function saveAll() { + spinning.value = true; + try { + const msg = await HttpUtil.post('/panel/xray/update', { + xraySetting: xraySetting.value, + outboundTestUrl: outboundTestUrl.value || 'https://www.google.com/generate_204', + }); + if (msg?.success) await fetchAll(); + } finally { + spinning.value = false; + } + } + + async function fetchOutboundsTraffic() { + const msg = await HttpUtil.get('/panel/xray/getOutboundsTraffic'); + if (msg?.success) outboundsTraffic.value = msg.obj || []; + } + + async function resetOutboundsTraffic(tag) { + const msg = await HttpUtil.post('/panel/xray/resetOutboundsTraffic', { tag }); + if (msg?.success) await fetchOutboundsTraffic(); + } + + // Merges a WebSocket `outbounds` event into outboundsTraffic in place. + // The xray traffic job pushes the full snapshot every ~10s so the user + // doesn't have to click the (now-removed) refresh button. + function applyOutboundsEvent(payload) { + if (Array.isArray(payload)) outboundsTraffic.value = payload; + } + + async function testOutbound(index, outbound) { + if (!outbound) return null; + if (!outboundTestStates.value[index]) outboundTestStates.value[index] = {}; + outboundTestStates.value[index] = { testing: true, result: null }; + try { + const msg = await HttpUtil.post('/panel/xray/testOutbound', { + outbound: JSON.stringify(outbound), + allOutbounds: JSON.stringify(templateSettings.value?.outbounds || []), + }); + if (msg?.success) { + outboundTestStates.value[index] = { testing: false, result: msg.obj }; + return msg.obj; + } + outboundTestStates.value[index] = { + testing: false, + result: { success: false, error: msg?.msg || 'Unknown error' }, + }; + } catch (e) { + outboundTestStates.value[index] = { + testing: false, + result: { success: false, error: String(e) }, + }; + } + return null; + } + + async function resetToDefault() { + spinning.value = true; + try { + const msg = await HttpUtil.get('/panel/setting/getDefaultJsonConfig'); + if (msg?.success) { + // Mutate templateSettings — the watch above re-stringifies into + // xraySetting so the Advanced JSON tab and dirty-poll see it. + templateSettings.value = JSON.parse(JSON.stringify(msg.obj)); + } + } finally { + spinning.value = false; + } + } + + async function restartXray() { + spinning.value = true; + try { + const msg = await HttpUtil.post('/panel/api/server/restartXrayService'); + if (msg?.success) { + // Match legacy: short pause, then poll for the result blob so + // the popover surfaces any startup error from the new process. + await PromiseUtil.sleep(500); + const r = await HttpUtil.get('/panel/xray/getXrayResult'); + if (r?.success) restartResult.value = r.obj || ''; + } + } finally { + spinning.value = false; + } + } + + // Same 1s busy-loop pattern the settings page uses — keep it cheap + // and consistent. Real work (the JSON diff) is just a string compare. + let timer = null; + function startDirtyPoll() { + if (timer != null) return; + timer = setInterval(() => { + saveDisabled.value = + oldXraySetting.value === xraySetting.value + && oldOutboundTestUrl.value === outboundTestUrl.value; + }, DIRTY_POLL_MS); + } + function stopDirtyPoll() { + if (timer != null) { + clearInterval(timer); + timer = null; + } + } + + onMounted(() => { + fetchAll(); + fetchOutboundsTraffic(); + startDirtyPoll(); + }); + onUnmounted(stopDirtyPoll); + + return { + fetched, + spinning, + saveDisabled, + fetchError, + xraySetting, + templateSettings, + outboundTestUrl, + inboundTags, + clientReverseTags, + restartResult, + outboundsTraffic, + outboundTestStates, + fetchAll, + fetchOutboundsTraffic, + resetOutboundsTraffic, + applyOutboundsEvent, + testOutbound, + saveAll, + resetToDefault, + restartXray, + }; +} diff --git a/web/assets/js/util/index.js b/frontend/src/utils/index.js similarity index 87% rename from web/assets/js/util/index.js rename to frontend/src/utils/index.js index 1f481c85..9ebaff02 100644 --- a/web/assets/js/util/index.js +++ b/frontend/src/utils/index.js @@ -1,4 +1,7 @@ -class Msg { +import axios from 'axios'; +import { message as antMessage } from 'ant-design-vue'; + +export class Msg { constructor(success = false, msg = "", obj = null) { this.success = success; this.msg = msg; @@ -6,13 +9,13 @@ class Msg { } } -class HttpUtil { +export class HttpUtil { static _handleMsg(msg) { if (!(msg instanceof Msg) || msg.msg === "") { return; } const messageType = msg.success ? 'success' : 'error'; - Vue.prototype.$message[messageType](msg.msg); + antMessage[messageType](msg.msg); } static _respToMsg(resp) { @@ -72,7 +75,7 @@ class HttpUtil { } } -class PromiseUtil { +export class PromiseUtil { static async sleep(timeout) { await new Promise(resolve => { setTimeout(resolve, timeout) @@ -80,7 +83,7 @@ class PromiseUtil { } } -class RandomUtil { +export class RandomUtil { static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) { let seq = ''; @@ -138,10 +141,10 @@ class RandomUtil { } } - static randomShadowsocksPassword(method = SSMethods.BLAKE3_AES_256_GCM) { + static randomShadowsocksPassword(method = '2022-blake3-aes-256-gcm') { let length = 32; - if ([SSMethods.BLAKE3_AES_128_GCM].includes(method)) { + if (method === '2022-blake3-aes-128-gcm') { length = 16; } @@ -186,10 +189,10 @@ class RandomUtil { } } -class ObjectUtil { +export class ObjectUtil { static getPropIgnoreCase(obj, prop) { for (const name in obj) { - if (!obj.hasOwnProperty(name)) { + if (!Object.prototype.hasOwnProperty.call(obj, name)) { continue; } if (name.toLowerCase() === prop.toLowerCase()) { @@ -208,7 +211,7 @@ class ObjectUtil { } } else if (obj instanceof Object) { for (let name in obj) { - if (!obj.hasOwnProperty(name)) { + if (!Object.prototype.hasOwnProperty.call(obj, name)) { continue; } if (this.deepSearch(obj[name], key)) { @@ -276,9 +279,9 @@ class ObjectUtil { } const ignoreEmpty = this.isArrEmpty(ignoreProps); for (const key of Object.keys(src)) { - if (!src.hasOwnProperty(key)) { + if (!Object.prototype.hasOwnProperty.call(src, key)) { continue; - } else if (!dest.hasOwnProperty(key)) { + } else if (!Object.prototype.hasOwnProperty.call(dest, key)) { continue; } else if (src[key] === undefined) { continue; @@ -334,7 +337,7 @@ class ObjectUtil { } } -class Wireguard { +export class Wireguard { static gf(init) { var r = new Float64Array(16); if (init) { @@ -345,15 +348,16 @@ class Wireguard { } static pack(o, n) { - var b, m = this.gf(), t = this.gf(); - for (var i = 0; i < 16; ++i) + let b; + const m = this.gf(), t = this.gf(); + for (let i = 0; i < 16; ++i) t[i] = n[i]; this.carry(t); this.carry(t); this.carry(t); - for (var j = 0; j < 2; ++j) { + for (let j = 0; j < 2; ++j) { m[0] = t[0] - 0xffed; - for (var i = 1; i < 15; ++i) { + for (let i = 1; i < 15; ++i) { m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); m[i - 1] &= 0xffff; } @@ -362,23 +366,23 @@ class Wireguard { m[14] &= 0xffff; this.cswap(t, m, 1 - b); } - for (var i = 0; i < 16; ++i) { + for (let i = 0; i < 16; ++i) { o[2 * i] = t[i] & 0xff; o[2 * i + 1] = t[i] >> 8; } } static carry(o) { - var c; - for (var i = 0; i < 16; ++i) { + for (let i = 0; i < 16; ++i) { o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536); o[i] &= 0xffff; } } static cswap(p, q, b) { - var t, c = ~(b - 1); - for (var i = 0; i < 16; ++i) { + const c = ~(b - 1); + let t; + for (let i = 0; i < 16; ++i) { t = c & (p[i] ^ q[i]); p[i] ^= t; q[i] ^= t; @@ -386,39 +390,39 @@ class Wireguard { } static add(o, a, b) { - for (var i = 0; i < 16; ++i) + for (let i = 0; i < 16; ++i) o[i] = (a[i] + b[i]) | 0; } static subtract(o, a, b) { - for (var i = 0; i < 16; ++i) + for (let i = 0; i < 16; ++i) o[i] = (a[i] - b[i]) | 0; } static multmod(o, a, b) { - var t = new Float64Array(31); - for (var i = 0; i < 16; ++i) { - for (var j = 0; j < 16; ++j) + const t = new Float64Array(31); + for (let i = 0; i < 16; ++i) { + for (let j = 0; j < 16; ++j) t[i + j] += a[i] * b[j]; } - for (var i = 0; i < 15; ++i) + for (let i = 0; i < 15; ++i) t[i] += 38 * t[i + 16]; - for (var i = 0; i < 16; ++i) + for (let i = 0; i < 16; ++i) o[i] = t[i]; this.carry(o); this.carry(o); } static invert(o, i) { - var c = this.gf(); - for (var a = 0; a < 16; ++a) + const c = this.gf(); + for (let a = 0; a < 16; ++a) c[a] = i[a]; - for (var a = 253; a >= 0; --a) { + for (let a = 253; a >= 0; --a) { this.multmod(c, c, c); if (a !== 2 && a !== 4) this.multmod(c, c, i); } - for (var a = 0; a < 16; ++a) + for (let a = 0; a < 16; ++a) o[a] = c[a]; } @@ -428,8 +432,9 @@ class Wireguard { } static generatePublicKey(privateKey) { - var r, z = new Uint8Array(32); - var a = this.gf([1]), + let r; + const z = new Uint8Array(32); + const a = this.gf([1]), b = this.gf([9]), c = this.gf(), d = this.gf([1]), @@ -437,10 +442,10 @@ class Wireguard { f = this.gf(), _121665 = this.gf([0xdb41, 1]), _9 = this.gf([9]); - for (var i = 0; i < 32; ++i) + for (let i = 0; i < 32; ++i) z[i] = privateKey[i]; this.clamp(z); - for (var i = 254; i >= 0; --i) { + for (let i = 254; i >= 0; --i) { r = (z[i >>> 3] >>> (i & 7)) & 1; this.cswap(a, b, r); this.cswap(c, d, r); @@ -521,7 +526,7 @@ class Wireguard { } } -class ClipboardManager { +export class ClipboardManager { static copyText(content = "") { // !! here old way of copying is used because not everyone can afford https connection return new Promise((resolve) => { @@ -553,7 +558,7 @@ class ClipboardManager { } } -class Base64 { +export class Base64 { static encode(content = "", safe = false) { if (safe) { return Base64.encode(content) @@ -581,7 +586,7 @@ class Base64 { } } -class SizeFormatter { +export class SizeFormatter { static ONE_KB = 1024; static ONE_MB = this.ONE_KB * 1024; static ONE_GB = this.ONE_MB * 1024; @@ -599,7 +604,7 @@ class SizeFormatter { } } -class CPUFormatter { +export class CPUFormatter { static cpuSpeedFormat(speed) { return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz"; } @@ -609,7 +614,7 @@ class CPUFormatter { } } -class TimeFormatter { +export class TimeFormatter { static formatSecond(second) { if (second < 60) return second.toFixed(0) + 's'; if (second < 3600) return (second / 60).toFixed(0) + 'm'; @@ -620,7 +625,7 @@ class TimeFormatter { } } -class NumberFormatter { +export class NumberFormatter { static addZero(num) { return num < 10 ? "0" + num : num; } @@ -631,7 +636,7 @@ class NumberFormatter { } } -class Utils { +export class Utils { static debounce(fn, delay) { let timeoutID = null; return function () { @@ -643,7 +648,7 @@ class Utils { } } -class CookieManager { +export class CookieManager { static getCookie(cname) { let name = cname + '='; let ca = document.cookie.split(';'); @@ -667,7 +672,18 @@ class CookieManager { } } -class ColorUtils { +// AD-Vue 4 semantic palette — kept in one place so the client/inbound +// rows match the rest of the panel. Purple is reserved for the +// "no quota / no expiry / unlimited" sentinel since the AD-Vue green +// would otherwise read as "healthy / under limit". +const COLORS = { + success: '#52c41a', // AD-Vue success — within quota + warning: '#faad14', // AD-Vue gold — close to quota / about to expire + danger: '#ff4d4f', // AD-Vue red — depleted / expired + purple: '#722ed1', // AD-Vue purple — unlimited / no expiry +}; + +export class ColorUtils { static usageColor(data, threshold, total) { switch (true) { case data === null: return "purple"; @@ -681,10 +697,10 @@ class ColorUtils { static clientUsageColor(clientStats, trafficDiff) { switch (true) { - case !clientStats || clientStats.total == 0: return "#7a316f"; - case clientStats.up + clientStats.down < clientStats.total - trafficDiff: return "#008771"; - case clientStats.up + clientStats.down < clientStats.total: return "#f37b24"; - default: return "#cf3c3c"; + case !clientStats || clientStats.total == 0: return COLORS.purple; + case clientStats.up + clientStats.down < clientStats.total - trafficDiff: return COLORS.success; + case clientStats.up + clientStats.down < clientStats.total: return COLORS.warning; + default: return COLORS.danger; } } @@ -692,23 +708,23 @@ class ColorUtils { if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc'; let now = new Date().getTime(), expiry = client.expiryTime; switch (true) { - case expiry === null: return "#7a316f"; - case expiry < 0: return "#008771"; - case expiry == 0: return "#7a316f"; - case now < expiry - threshold: return "#008771"; - case now < expiry: return "#f37b24"; - default: return "#cf3c3c"; + case expiry === null: return COLORS.purple; + case expiry < 0: return COLORS.success; + case expiry == 0: return COLORS.purple; + case now < expiry - threshold: return COLORS.success; + case now < expiry: return COLORS.warning; + default: return COLORS.danger; } } } -class ArrayUtils { +export class ArrayUtils { static doAllItemsExist(array1, array2) { return array1.every(item => array2.includes(item)); } } -class URLBuilder { +export class URLBuilder { static buildURL({ host, port, isTLS, base, path }) { if (!host || host.length === 0) host = window.location.hostname; if (!port || port.length === 0) port = window.location.port; @@ -726,7 +742,7 @@ class URLBuilder { } } -class LanguageManager { +export class LanguageManager { static supportedLanguages = [ { name: "العربية", @@ -854,26 +870,7 @@ class LanguageManager { } } -const MediaQueryMixin = { - data() { - return { - isMobile: window.innerWidth <= 768, - }; - }, - methods: { - updateDeviceType() { - this.isMobile = window.innerWidth <= 768; - }, - }, - mounted() { - window.addEventListener('resize', this.updateDeviceType); - }, - beforeDestroy() { - window.removeEventListener('resize', this.updateDeviceType); - }, -} - -class FileManager { +export class FileManager { static downloadTextFile(content, filename = 'file.txt', options = { type: "text/plain" }) { let link = window.document.createElement('a'); @@ -893,9 +890,17 @@ class FileManager { } } -class IntlUtil { - static formatDate(date) { +export class IntlUtil { + // When `calendar` is "jalalian", append the BCP-47 calendar extension + // so Intl renders the date in the Persian (Jalali/Shamsi) calendar + // regardless of the UI language. Without it, only locales that + // default to Persian (e.g. fa-IR) would show Jalali; en-US/ru/etc. + // would keep showing Gregorian. + static formatDate(date, calendar = "gregorian") { const language = LanguageManager.getLanguage() + const locale = calendar === "jalalian" + ? `${language}-u-ca-persian` + : language let intlOptions = { year: "numeric", @@ -907,7 +912,7 @@ class IntlUtil { } const intl = new Intl.DateTimeFormat( - language, + locale, intlOptions ) diff --git a/frontend/subpage.html b/frontend/subpage.html new file mode 100644 index 00000000..11632ae8 --- /dev/null +++ b/frontend/subpage.html @@ -0,0 +1,14 @@ + + + + + + + Subscription + + +
+
+ + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 00000000..42e3ffcd --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,167 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import path from 'node:path'; + +// Output goes to web/dist/ at the repo root so the Go binary can embed it +// via embed.FS without reaching outside the web/ tree. +const outDir = path.resolve(__dirname, '../web/dist'); + +// In production the Go binary serves /panel/ from web/dist/.html. +// In dev the Vue app lives at /index.html, /settings.html, ... while AppSidebar +// links use the production-style /panel/ URLs. Map each migrated route +// to its Vite entry so the sidebar works without relying on the Go backend +// for already-ported pages. +const MIGRATED_ROUTES = { + '/panel': '/index.html', + '/panel/': '/index.html', + '/panel/settings': '/settings.html', + '/panel/settings/': '/settings.html', + '/panel/inbounds': '/inbounds.html', + '/panel/inbounds/': '/inbounds.html', + '/panel/xray': '/xray.html', + '/panel/xray/': '/xray.html', + '/panel/nodes': '/nodes.html', + '/panel/nodes/': '/nodes.html', +}; + +// Build a proxy config that suppresses ECONNREFUSED noise when the Go +// backend isn't running locally. Real errors (timeouts, 5xx, etc.) still +// surface in the Vite log. +function makeBackendProxy(target, patterns) { + const config = {}; + for (const pattern of patterns) { + config[pattern] = { + target, + changeOrigin: true, + // Returning a path from bypass tells Vite to serve that file from + // its own dev server instead of forwarding the request — used here + // to short-circuit /panel/ for pages we've already migrated. + // + // Only GETs get bypassed: the xray page reuses its page URL + // (`POST /panel/xray/`) for data, so a method-blind bypass would + // hand HTML back to fetch calls and break the page in dev. + bypass(req) { + if (req.method !== 'GET') return undefined; + const url = req.url.split('?')[0]; + if (Object.prototype.hasOwnProperty.call(MIGRATED_ROUTES, url)) { + return MIGRATED_ROUTES[url]; + } + return undefined; + }, + configure(proxy) { + let warned = false; + proxy.on('error', (err, req) => { + // Node wraps connection failures in an AggregateError when DNS + // returns multiple addresses (e.g. ::1 + 127.0.0.1) and all + // refuse — the code lands on the inner errors, not the outer. + const codes = new Set(); + if (err && err.code) codes.add(err.code); + if (err && Array.isArray(err.errors)) { + for (const inner of err.errors) { + if (inner && inner.code) codes.add(inner.code); + } + } + const offline = codes.has('ECONNREFUSED') || codes.has('ECONNRESET'); + if (offline) { + // Print a single friendly hint the first time, then stay quiet. + if (!warned) { + warned = true; + // eslint-disable-next-line no-console + console.warn( + `[proxy] backend ${target} is not reachable — start the Go server (e.g. \`go run main.go\`) to forward ${req?.url || 'requests'}.`, + ); + } + return; + } + // eslint-disable-next-line no-console + console.error('[proxy]', err); + }); + }, + }; + } + return config; +} + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + build: { + outDir, + emptyOutDir: true, + sourcemap: true, + target: 'es2020', + // ant-design-vue is intentionally bundled as one chunk (its + // components share internals — splitting it breaks Modal/Form/ + // Select interop). Minified it lands ~1.4MB but gzips to ~410kB, + // so the actual transfer is fine and caches across every page. + // Bump the warning past that ceiling so the build stays quiet. + chunkSizeWarningLimit: 1500, + // Multiple HTML entries — one per legacy page we migrate. + // As pages get ported in later phases, add their entrypoints here. + rollupOptions: { + input: { + index: path.resolve(__dirname, 'index.html'), + login: path.resolve(__dirname, 'login.html'), + settings: path.resolve(__dirname, 'settings.html'), + inbounds: path.resolve(__dirname, 'inbounds.html'), + xray: path.resolve(__dirname, 'xray.html'), + nodes: path.resolve(__dirname, 'nodes.html'), + subpage: path.resolve(__dirname, 'subpage.html'), + }, + output: { + // Split vendor deps into stable chunks so each page only pulls + // what it needs and the browser caches them across versions. + // Without this, ant-design-vue + vue + icons all end up in one + // 1.6MB blob attached to whichever page consumed them first. + manualChunks(id) { + if (!id.includes('node_modules')) return undefined; + if (id.includes('ant-design-vue')) return 'vendor-antd'; + if (id.includes('@ant-design/icons-vue')) return 'vendor-icons'; + if (id.includes('vue-i18n')) return 'vendor-i18n'; + if ( + id.includes('/node_modules/vue/') + || id.includes('/node_modules/@vue/') + ) return 'vendor-vue'; + if (id.includes('dayjs')) return 'vendor-dayjs'; + if (id.includes('qrious')) return 'vendor-qrious'; + if (id.includes('axios')) return 'vendor-axios'; + // The persian datepicker pulls in moment + moment-jalaali; bundle + // the trio together so unrelated pages don't pay the cost. + if ( + id.includes('vue3-persian-datetime-picker') + || id.includes('moment-jalaali') + || id.includes('jalaali-js') + || id.includes('/node_modules/moment/') + ) return 'vendor-jalali'; + return 'vendor'; + }, + }, + }, + }, + server: { + port: 5173, + strictPort: true, + proxy: { + ...makeBackendProxy('http://localhost:2053', [ + // Patterns are anchored regex so /login.html and /index.html + // (which Vite serves itself) are NOT forwarded — only the bare + // backend paths and their sub-routes. + '^/(login|logout|getTwoFactorEnable|csrf-token)$', + '^/(panel|server)(/|$)', + ]), + // The panel mounts the live-update WebSocket at /ws (basePath + + // "/ws"). Vite needs `ws: true` to forward the HTTP Upgrade to the + // Go backend; without it the dev server would 404 the upgrade and + // the page falls back to the no-data state. + '/ws': { + target: 'ws://localhost:2053', + ws: true, + changeOrigin: true, + }, + }, + }, +}); diff --git a/frontend/xray.html b/frontend/xray.html new file mode 100644 index 00000000..3c16d29f --- /dev/null +++ b/frontend/xray.html @@ -0,0 +1,13 @@ + + + + + + 3x-ui · Xray + + +
+
+ + + diff --git a/go.mod b/go.mod index 0a967ebf..0db38639 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/mymmrac/telego v1.8.0 github.com/nicksnyder/go-i18n/v2 v2.6.1 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 - github.com/pelletier/go-toml/v2 v2.3.1 github.com/robfig/cron/v3 v3.0.1 github.com/shirou/gopsutil/v4 v4.26.4 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e @@ -66,6 +65,7 @@ require ( github.com/miekg/dns v1.1.72 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.3.1 // indirect github.com/pires/go-proxyproto v0.12.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.6.0 // indirect diff --git a/sub/sub.go b/sub/sub.go index b940cc95..7a327cc4 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -5,13 +5,11 @@ package sub import ( "context" "crypto/tls" - "html/template" "io" "io/fs" "net" "net/http" "os" - "path/filepath" "strconv" "strings" @@ -26,21 +24,6 @@ import ( "github.com/gin-gonic/gin" ) -// setEmbeddedTemplates parses and sets embedded templates on the engine -func setEmbeddedTemplates(engine *gin.Engine) error { - t, err := template.New("").Funcs(engine.FuncMap).ParseFS( - webpkg.EmbeddedHTML(), - "html/common/page.html", - "html/component/aThemeSwitch.html", - "html/settings/panel/subscription/subpage.html", - ) - if err != nil { - return err - } - engine.SetHTMLTemplate(t) - return nil -} - // Server represents the subscription server that serves subscription links and JSON configurations. type Server struct { httpServer *http.Server @@ -190,45 +173,25 @@ func (s *Server) initRouter() (*gin.Engine, error) { // set per-request localizer from headers/cookies engine.Use(locale.LocalizerMiddleware()) - // register i18n function similar to web server - i18nWebFunc := func(key string, params ...string) string { - return locale.I18n(locale.Web, key, params...) - } - engine.SetFuncMap(map[string]any{"i18n": i18nWebFunc}) - - // Templates: prefer embedded; fallback to disk if necessary - if err := setEmbeddedTemplates(engine); err != nil { - logger.Warning("sub: failed to parse embedded templates:", err) - if files, derr := s.getHtmlFiles(); derr == nil { - engine.LoadHTMLFiles(files...) - } else { - logger.Error("sub: no templates available (embedded parse and disk load failed)", err, derr) - } - } - - // Assets: use disk if present, fallback to embedded - // Serve under both root (/assets) and under the subscription path prefix (LinksPath + "assets") - // so reverse proxies with a URI prefix can load assets correctly. - // Determine LinksPath earlier to compute prefixed assets mount. + // Mount the Vite-built dist/assets/ so the subscription page's JS/CSS + // bundles load from `/assets/...`. Also mount the same FS under the + // subscription path prefix (LinksPath + "assets") so reverse proxies + // running the panel under a URI prefix can resolve those URLs too. // Note: LinksPath always starts and ends with "/" (validated in settings). var linksPathForAssets string if LinksPath == "/" { linksPathForAssets = "/assets" } else { - // ensure single slash join linksPathForAssets = strings.TrimRight(LinksPath, "/") + "/assets" } - // Mount assets in multiple paths to handle different URL patterns var assetsFS http.FileSystem - if _, err := os.Stat("web/assets"); err == nil { - assetsFS = http.FS(os.DirFS("web/assets")) + if _, err := os.Stat("web/dist/assets"); err == nil { + assetsFS = http.FS(os.DirFS("web/dist/assets")) + } else if subFS, err := fs.Sub(webpkg.EmbeddedDist(), "dist/assets"); err == nil { + assetsFS = http.FS(subFS) } else { - if subFS, err := fs.Sub(webpkg.EmbeddedAssets(), "assets"); err == nil { - assetsFS = http.FS(subFS) - } else { - logger.Error("sub: failed to mount embedded assets:", err) - } + logger.Error("sub: failed to mount embedded dist assets:", err) } if assetsFS != nil { @@ -237,19 +200,17 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine.StaticFS(linksPathForAssets, assetsFS) } - // Add middleware to handle dynamic asset paths with subid + // Browser may resolve subpage assets relative to the request URL — + // /sub///assets/... — so route those to the same FS. if LinksPath != "/" { engine.Use(func(c *gin.Context) { path := c.Request.URL.Path - // Check if this is an asset request with subid pattern: /sub/path/{subid}/assets/... pathPrefix := strings.TrimRight(LinksPath, "/") + "/" if strings.HasPrefix(path, pathPrefix) && strings.Contains(path, "/assets/") { - // Extract the asset path after /assets/ assetsIndex := strings.Index(path, "/assets/") if assetsIndex != -1 { assetPath := path[assetsIndex+8:] // +8 to skip "/assets/" if assetPath != "" { - // Serve the asset file c.FileFromFS(assetPath, assetsFS) c.Abort() return @@ -271,30 +232,6 @@ func (s *Server) initRouter() (*gin.Engine, error) { return engine, nil } -// getHtmlFiles loads templates from local folder (used in debug mode) -func (s *Server) getHtmlFiles() ([]string, error) { - dir, _ := os.Getwd() - files := []string{} - // common layout - common := filepath.Join(dir, "web", "html", "common", "page.html") - if _, err := os.Stat(common); err == nil { - files = append(files, common) - } - // components used - theme := filepath.Join(dir, "web", "html", "component", "aThemeSwitch.html") - if _, err := os.Stat(theme); err == nil { - files = append(files, theme) - } - // page itself - page := filepath.Join(dir, "web", "html", "subpage.html") - if _, err := os.Stat(page); err == nil { - files = append(files, page) - } else { - return nil, err - } - return files, nil -} - // Start initializes and starts the subscription server with configured settings. func (s *Server) Start() (err error) { // This is an anonymous function, no function name diff --git a/sub/subClashService.go b/sub/subClashService.go index e2f61bec..c38687b1 100644 --- a/sub/subClashService.go +++ b/sub/subClashService.go @@ -29,6 +29,8 @@ func NewSubClashService(subService *SubService) *SubClashService { } func (s *SubClashService) GetClash(subId string, host string) (string, string, error) { + // Set per-request state so resolveInboundAddress sees the node map. + s.SubService.PrepareForRequest(host) inbounds, err := s.SubService.getInboundsBySubId(subId) if err != nil || len(inbounds) == 0 { return "", "", err @@ -38,6 +40,7 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e var clientTraffics []xray.ClientTraffic var proxies []map[string]any + seenEmails := make(map[string]struct{}) for _, inbound := range inbounds { clients, err := s.inboundService.GetClients(inbound) if err != nil { @@ -56,7 +59,7 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e } for _, client := range clients { if client.SubID == subId { - clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) + _, clientTraffics = s.SubService.appendUniqueTraffic(seenEmails, clientTraffics, inbound.ClientStats, client.Email) proxies = append(proxies, s.getProxies(inbound, client, host)...) } } @@ -117,11 +120,18 @@ func (s *SubClashService) GetClash(subId string, host string) (string, string, e func (s *SubClashService) getProxies(inbound *model.Inbound, client model.Client, host string) []map[string]any { stream := s.streamData(inbound.StreamSettings) + // For node-managed inbounds the Clash proxy "server" must be the + // node's address, not the request host. resolveInboundAddress handles + // the node→listen→request-host fallback chain. + defaultDest := s.SubService.resolveInboundAddress(inbound) + if defaultDest == "" { + defaultDest = host + } externalProxies, ok := stream["externalProxy"].([]any) if !ok || len(externalProxies) == 0 { externalProxies = []any{map[string]any{ "forceTls": "same", - "dest": host, + "dest": defaultDest, "port": float64(inbound.Port), "remark": "", }} diff --git a/sub/subController.go b/sub/subController.go index a765ef06..ddb24c11 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -1,12 +1,16 @@ package sub import ( + "bytes" "encoding/base64" + "encoding/json" "fmt" + "net/http" "strconv" "strings" - "github.com/mhsanaei/3x-ui/v2/config" + webpkg "github.com/mhsanaei/3x-ui/v2/web" + "github.com/mhsanaei/3x-ui/v2/web/service" "github.com/gin-gonic/gin" ) @@ -30,6 +34,7 @@ type SUBController struct { subService *SubService subJsonService *SubJsonService subClashService *SubClashService + settingService service.SettingService } // NewSUBController creates a new subscription controller with the given configuration. @@ -110,7 +115,6 @@ func (a *SUBController) subs(c *gin.Context) { // If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here accept := c.GetHeader("Accept") if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") { - // Build page data in service subURL, subJsonURL, subClashURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, a.subClashPath, subId) if !a.jsonEnabled { subJsonURL = "" @@ -118,43 +122,13 @@ func (a *SUBController) subs(c *gin.Context) { if !a.clashEnabled { subClashURL = "" } - // Get base_path from context (set by middleware) basePath, exists := c.Get("base_path") if !exists { basePath = "/" } - // Add subId to base_path for asset URLs basePathStr := basePath.(string) - if basePathStr == "/" { - basePathStr = "/" + subId + "/" - } else { - // Remove trailing slash if exists, add subId, then add trailing slash - basePathStr = strings.TrimRight(basePathStr, "/") + "/" + subId + "/" - } page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL, subClashURL, basePathStr) - c.HTML(200, "subpage.html", gin.H{ - "title": "subscription.title", - "cur_ver": config.GetVersion(), - "host": page.Host, - "base_path": page.BasePath, - "sId": page.SId, - "enabled": page.Enabled, - "download": page.Download, - "upload": page.Upload, - "total": page.Total, - "used": page.Used, - "remained": page.Remained, - "expire": page.Expire, - "lastOnline": page.LastOnline, - "datepicker": page.Datepicker, - "downloadByte": page.DownloadByte, - "uploadByte": page.UploadByte, - "totalByte": page.TotalByte, - "subUrl": page.SubUrl, - "subJsonUrl": page.SubJsonUrl, - "subClashUrl": page.SubClashUrl, - "result": page.Result, - }) + a.serveSubPage(c, basePathStr, page) return } @@ -174,6 +148,87 @@ func (a *SUBController) subs(c *gin.Context) { } } +// serveSubPage renders web/dist/subpage.html for the current subscription +// request. The Vite-built SPA reads window.__SUB_PAGE_DATA__ on mount — +// we inject that here, along with window.__X_UI_BASE_PATH__ so the +// page's static asset references resolve correctly when the panel runs +// behind a URL prefix. +func (a *SUBController) serveSubPage(c *gin.Context, basePath string, page PageData) { + dist := webpkg.EmbeddedDist() + body, err := dist.ReadFile("dist/subpage.html") + if err != nil { + c.String(http.StatusInternalServerError, "missing embedded subpage") + return + } + + // Vite emits absolute asset URLs (`/assets/...`); when the panel is + // installed under a custom URL prefix, rewrite them so the bundle + // loads from `assets/...` where the static handler is + // actually mounted. + if basePath != "/" && basePath != "" { + body = bytes.ReplaceAll(body, []byte(`src="/assets/`), []byte(`src="`+basePath+`assets/`)) + body = bytes.ReplaceAll(body, []byte(`href="/assets/`), []byte(`href="`+basePath+`assets/`)) + } + + // JSON-marshal the view-model so the SPA can read it as a plain + // object on mount. PageData fields are already in the shape the Vue + // component expects, plus a `links` array carrying the rendered + // share URLs. + // The panel's "Calendar Type" setting decides whether the SubPage + // renders dates in Gregorian or Jalali — surface it here so the SPA + // can match the rest of the panel without a round-trip. + datepicker, _ := a.settingService.GetDatepicker() + if datepicker == "" { + datepicker = "gregorian" + } + + subData := map[string]any{ + "sId": page.SId, + "enabled": page.Enabled, + "download": page.Download, + "upload": page.Upload, + "total": page.Total, + "used": page.Used, + "remained": page.Remained, + "expire": page.Expire, + "lastOnline": page.LastOnline, + "downloadByte": page.DownloadByte, + "uploadByte": page.UploadByte, + "totalByte": page.TotalByte, + "subUrl": page.SubUrl, + "subJsonUrl": page.SubJsonUrl, + "subClashUrl": page.SubClashUrl, + "links": page.Result, + "datepicker": datepicker, + } + subDataJSON, err := json.Marshal(subData) + if err != nil { + subDataJSON = []byte("{}") + } + + // Defense-in-depth string-escape for the basePath embed — admin- + // controlled but cheap to harden. + jsEscape := strings.NewReplacer( + `\`, `\\`, + `"`, `\"`, + "\n", `\n`, + "\r", `\r`, + "<", `<`, + ">", `>`, + "&", `&`, + ) + escapedBase := jsEscape.Replace(basePath) + + inject := []byte(``) + out := bytes.Replace(body, []byte(""), inject, 1) + + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + c.Header("Pragma", "no-cache") + c.Header("Expires", "0") + c.Data(http.StatusOK, "text/html; charset=utf-8", out) +} + // subJsons handles HTTP requests for JSON subscription configurations. func (a *SUBController) subJsons(c *gin.Context) { subId := c.Param("subid") diff --git a/sub/subJsonService.go b/sub/subJsonService.go index 5cbd1895..8d0514fd 100644 --- a/sub/subJsonService.go +++ b/sub/subJsonService.go @@ -87,6 +87,9 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string, // GetJson generates a JSON subscription configuration for the given subscription ID and host. func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) { + // Set per-request state on the shared SubService so any + // resolveInboundAddress call inside picks node-aware host values. + s.SubService.PrepareForRequest(host) inbounds, err := s.SubService.getInboundsBySubId(subId) if err != nil || len(inbounds) == 0 { return "", "", err @@ -97,6 +100,7 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err var clientTraffics []xray.ClientTraffic var configArray []json_util.RawMessage + seenEmails := make(map[string]struct{}) // Prepare Inbounds for _, inbound := range inbounds { clients, err := s.inboundService.GetClients(inbound) @@ -117,9 +121,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err for _, client := range clients { if client.SubID == subId { - clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) - newConfigs := s.getConfig(inbound, client, host) - configArray = append(configArray, newConfigs...) + _, clientTraffics = s.SubService.appendUniqueTraffic(seenEmails, clientTraffics, inbound.ClientStats, client.Email) + configArray = append(configArray, s.getConfig(inbound, client, host)...) } } } @@ -167,12 +170,22 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, var newJsonArray []json_util.RawMessage stream := s.streamData(inbound.StreamSettings) + // When externalProxy is empty the JSON config falls back to a + // synthetic one whose `dest` is the host the client connects to. + // For node-managed inbounds we want the node's address — request + // host won't reach the right xray. resolveInboundAddress already + // implements the node→listen→request-host fallback chain. + defaultDest := s.SubService.resolveInboundAddress(inbound) + if defaultDest == "" { + defaultDest = host + } + externalProxies, ok := stream["externalProxy"].([]any) if !ok || len(externalProxies) == 0 { externalProxies = []any{ map[string]any{ "forceTls": "same", - "dest": host, + "dest": defaultDest, "port": float64(inbound.Port), "remark": "", }, diff --git a/sub/subService.go b/sub/subService.go index 33605ad6..40365dce 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -30,6 +30,11 @@ type SubService struct { datepicker string inboundService service.InboundService settingService service.SettingService + // nodesByID is populated per request from the Node table so + // resolveInboundAddress can return the node's address for any + // inbound whose NodeID is set. Keeps the per-link host derivation + // O(1) instead of O(N) DB hits. + nodesByID map[int]*model.Node } // NewSubService creates a new subscription service with the given configuration. @@ -40,9 +45,19 @@ func NewSubService(showInfo bool, remarkModel string) *SubService { } } +// PrepareForRequest sets per-request state (host + nodes map) on the +// shared SubService. Called by every entry point — GetSubs, GetJson, +// GetClash — so resolveInboundAddress sees the right host and the +// freshly-loaded node map regardless of which sub flavour the client +// hit. +func (s *SubService) PrepareForRequest(host string) { + s.address = host + s.loadNodes() +} + // GetSubs retrieves subscription links for a given subscription ID and host. func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) { - s.address = host + s.PrepareForRequest(host) var result []string var traffic xray.ClientTraffic var lastOnline int64 @@ -61,6 +76,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C if err != nil { s.datepicker = "gregorian" } + seenEmails := make(map[string]struct{}) for _, inbound := range inbounds { clients, err := s.inboundService.GetClients(inbound) if err != nil { @@ -82,10 +98,9 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C if client.Enable { hasEnabledClient = true } - link := s.getLink(inbound, client.Email) - result = append(result, link) - ct := s.getClientTraffics(inbound.ClientStats, client.Email) - clientTraffics = append(clientTraffics, ct) + result = append(result, s.getLink(inbound, client.Email)) + var ct xray.ClientTraffic + ct, clientTraffics = s.appendUniqueTraffic(seenEmails, clientTraffics, inbound.ClientStats, client.Email) if ct.LastOnline > lastOnline { lastOnline = ct.LastOnline } @@ -138,6 +153,19 @@ func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) return inbounds, nil } +// appendUniqueTraffic resolves the traffic stats for email and appends them +// to acc only the first time email is seen. Shared-email mode lets one +// client_traffics row underpin several inbounds, so without dedupe its +// quota and usage would be counted once per inbound. +func (s *SubService) appendUniqueTraffic(seen map[string]struct{}, acc []xray.ClientTraffic, stats []xray.ClientTraffic, email string) (xray.ClientTraffic, []xray.ClientTraffic) { + ct := s.getClientTraffics(stats, email) + if _, dup := seen[email]; !dup { + seen[email] = struct{}{} + acc = append(acc, ct) + } + return ct, acc +} + func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic { for _, traffic := range traffics { if traffic.Email == email { @@ -509,7 +537,39 @@ func (s *SubService) genHysteriaLink(inbound *model.Inbound, email string) strin return url.String() } +// loadNodes refreshes nodesByID from the DB. Called once per request so +// the per-inbound resolveInboundAddress lookups are pure map reads. +// We filter to address != ” so a half-configured node row doesn't +// accidentally produce a useless host like "https://:2053". +func (s *SubService) loadNodes() { + db := database.GetDB() + var nodes []*model.Node + if err := db.Model(&model.Node{}).Where("address != ''").Find(&nodes).Error; err != nil { + logger.Warning("subscription: load nodes failed:", err) + s.nodesByID = nil + return + } + m := make(map[int]*model.Node, len(nodes)) + for _, n := range nodes { + m[n.Id] = n + } + s.nodesByID = m +} + +// resolveInboundAddress picks the host an external client should +// connect to. Order: +// 1. If the inbound is node-managed and the node has an address, use +// the node's address — central panel's hostname doesn't speak xray +// for that inbound. +// 2. If the inbound binds to a non-wildcard listen address, use it. +// 3. Otherwise fall back to the request's host (whatever the client +// subscribed against). func (s *SubService) resolveInboundAddress(inbound *model.Inbound) string { + if inbound.NodeID != nil && s.nodesByID != nil { + if n, ok := s.nodesByID[*inbound.NodeID]; ok && n.Address != "" { + return n.Address + } + } if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { return s.address } diff --git a/web/assets/Vazirmatn-UI-NL-Regular.woff2 b/web/assets/Vazirmatn-UI-NL-Regular.woff2 deleted file mode 100644 index 3ce577ae..00000000 Binary files a/web/assets/Vazirmatn-UI-NL-Regular.woff2 and /dev/null differ diff --git a/web/assets/ant-design-vue/antd.min.css b/web/assets/ant-design-vue/antd.min.css deleted file mode 100644 index aba01679..00000000 --- a/web/assets/ant-design-vue/antd.min.css +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * - * ant-design-vue v1.7.8 - * - * Copyright 2017-present, ant-design-vue. - * All rights reserved. - * - */body,html{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;color:rgba(0,0,0,.65);font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-variant:tabular-nums;line-height:1.5;background-color:#fff;font-feature-settings:"tnum"}[tabindex="-1"]:focus{outline:none!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;color:rgba(0,0,0,.85);font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=number],input[type=password],input[type=text],textarea{-webkit-appearance:none}dl,ol,ul{margin-top:0;margin-bottom:1em}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#008771;text-decoration:none;background-color:transparent;outline:none;cursor:pointer;transition:color .3s;-webkit-text-decoration-skip:objects}a:hover{color:#18947b}a:active{color:#006154}a:active,a:hover{text-decoration:none;outline:0}a[disabled]{color:rgba(0,0,0,.25);cursor:not-allowed;pointer-events:none}code,kbd,pre,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}[role=button],a,area,button,input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;color:rgba(0,0,0,.45);text-align:left;caption-side:bottom}th{text-align:inherit}button,input,optgroup,select,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}::-moz-selection{color:#fff;background:#008771}::selection{color:#fff;background:#008771}.clearfix{zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}.anticon{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.anticon>*{line-height:1}.anticon svg{display:inline-block}.anticon:before{display:none}.anticon .anticon-icon{display:block}.anticon[tabindex]{cursor:pointer}.anticon-spin,.anticon-spin:before{display:inline-block;animation:loadingCircle 1s linear infinite}.fade-appear,.fade-enter,.fade-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.fade-appear.fade-appear-active,.fade-enter.fade-enter-active{animation-name:antFadeIn;animation-play-state:running}.fade-leave.fade-leave-active{animation-name:antFadeOut;animation-play-state:running;pointer-events:none}.fade-appear,.fade-enter{opacity:0}.fade-appear,.fade-enter,.fade-leave{animation-timing-function:linear}@keyframes antFadeIn{0%{opacity:0}to{opacity:1}}@keyframes antFadeOut{0%{opacity:1}to{opacity:0}}.move-up-appear,.move-up-enter,.move-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-up-appear.move-up-appear-active,.move-up-enter.move-up-enter-active{animation-name:antMoveUpIn;animation-play-state:running}.move-up-leave.move-up-leave-active{animation-name:antMoveUpOut;animation-play-state:running;pointer-events:none}.move-up-appear,.move-up-enter{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-up-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-down-appear,.move-down-enter,.move-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-down-appear.move-down-appear-active,.move-down-enter.move-down-enter-active{animation-name:antMoveDownIn;animation-play-state:running}.move-down-leave.move-down-leave-active{animation-name:antMoveDownOut;animation-play-state:running;pointer-events:none}.move-down-appear,.move-down-enter{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-down-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-left-appear,.move-left-enter,.move-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-left-appear.move-left-appear-active,.move-left-enter.move-left-enter-active{animation-name:antMoveLeftIn;animation-play-state:running}.move-left-leave.move-left-leave-active{animation-name:antMoveLeftOut;animation-play-state:running;pointer-events:none}.move-left-appear,.move-left-enter{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-left-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}.move-right-appear,.move-right-enter,.move-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.move-right-appear.move-right-appear-active,.move-right-enter.move-right-enter-active{animation-name:antMoveRightIn;animation-play-state:running}.move-right-leave.move-right-leave-active{animation-name:antMoveRightOut;animation-play-state:running;pointer-events:none}.move-right-appear,.move-right-enter{opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.move-right-leave{animation-timing-function:cubic-bezier(.6,.04,.98,.34)}@keyframes antMoveDownIn{0%{transform:translateY(100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveDownOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveLeftIn{0%{transform:translateX(-100%);transform-origin:0 0;opacity:0}to{transform:translateX(0);transform-origin:0 0;opacity:1}}@keyframes antMoveLeftOut{0%{transform:translateX(0);transform-origin:0 0;opacity:1}to{transform:translateX(-100%);transform-origin:0 0;opacity:0}}@keyframes antMoveRightIn{0%{transform:translateX(100%);transform-origin:0 0;opacity:0}to{transform:translateX(0);transform-origin:0 0;opacity:1}}@keyframes antMoveRightOut{0%{transform:translateX(0);transform-origin:0 0;opacity:1}to{transform:translateX(100%);transform-origin:0 0;opacity:0}}@keyframes antMoveUpIn{0%{transform:translateY(-100%);transform-origin:0 0;opacity:0}to{transform:translateY(0);transform-origin:0 0;opacity:1}}@keyframes antMoveUpOut{0%{transform:translateY(0);transform-origin:0 0;opacity:1}to{transform:translateY(-100%);transform-origin:0 0;opacity:0}}@keyframes loadingCircle{to{transform:rotate(1turn)}}[ant-click-animating-without-extra-node=true],[ant-click-animating=true]{position:relative}html{--antd-wave-shadow-color:#008771}.ant-click-animating-node,[ant-click-animating-without-extra-node=true]:after{position:absolute;top:0;right:0;bottom:0;left:0;display:block;border-radius:inherit;box-shadow:0 0 0 0 #008771;box-shadow:0 0 0 0 var(--antd-wave-shadow-color);opacity:.2;animation:fadeEffect 2s cubic-bezier(.08,.82,.17,1),waveEffect .4s cubic-bezier(.08,.82,.17,1);animation-fill-mode:forwards;content:"";pointer-events:none}@keyframes waveEffect{to{box-shadow:0 0 0 #008771;box-shadow:0 0 0 6px var(--antd-wave-shadow-color)}}@keyframes fadeEffect{to{opacity:0}}.slide-up-appear,.slide-up-enter,.slide-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-up-appear.slide-up-appear-active,.slide-up-enter.slide-up-enter-active{animation-name:antSlideUpIn;animation-play-state:running}.slide-up-leave.slide-up-leave-active{animation-name:antSlideUpOut;animation-play-state:running;pointer-events:none}.slide-up-appear,.slide-up-enter{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-up-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-down-appear,.slide-down-enter,.slide-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-down-appear.slide-down-appear-active,.slide-down-enter.slide-down-enter-active{animation-name:antSlideDownIn;animation-play-state:running}.slide-down-leave.slide-down-leave-active{animation-name:antSlideDownOut;animation-play-state:running;pointer-events:none}.slide-down-appear,.slide-down-enter{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-down-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-left-appear,.slide-left-enter,.slide-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-left-appear.slide-left-appear-active,.slide-left-enter.slide-left-enter-active{animation-name:antSlideLeftIn;animation-play-state:running}.slide-left-leave.slide-left-leave-active{animation-name:antSlideLeftOut;animation-play-state:running;pointer-events:none}.slide-left-appear,.slide-left-enter{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-left-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}.slide-right-appear,.slide-right-enter,.slide-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.slide-right-appear.slide-right-appear-active,.slide-right-enter.slide-right-enter-active{animation-name:antSlideRightIn;animation-play-state:running}.slide-right-leave.slide-right-leave-active{animation-name:antSlideRightOut;animation-play-state:running;pointer-events:none}.slide-right-appear,.slide-right-enter{opacity:0;animation-timing-function:cubic-bezier(.23,1,.32,1)}.slide-right-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}@keyframes antSlideUpIn{0%{transform:scaleY(.8);transform-origin:0 0;opacity:0}to{transform:scaleY(1);transform-origin:0 0;opacity:1}}@keyframes antSlideUpOut{0%{transform:scaleY(1);transform-origin:0 0;opacity:1}to{transform:scaleY(.8);transform-origin:0 0;opacity:0}}@keyframes antSlideDownIn{0%{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}to{transform:scaleY(1);transform-origin:100% 100%;opacity:1}}@keyframes antSlideDownOut{0%{transform:scaleY(1);transform-origin:100% 100%;opacity:1}to{transform:scaleY(.8);transform-origin:100% 100%;opacity:0}}@keyframes antSlideLeftIn{0%{transform:scaleX(.8);transform-origin:0 0;opacity:0}to{transform:scaleX(1);transform-origin:0 0;opacity:1}}@keyframes antSlideLeftOut{0%{transform:scaleX(1);transform-origin:0 0;opacity:1}to{transform:scaleX(.8);transform-origin:0 0;opacity:0}}@keyframes antSlideRightIn{0%{transform:scaleX(.8);transform-origin:100% 0;opacity:0}to{transform:scaleX(1);transform-origin:100% 0;opacity:1}}@keyframes antSlideRightOut{0%{transform:scaleX(1);transform-origin:100% 0;opacity:1}to{transform:scaleX(.8);transform-origin:100% 0;opacity:0}}.swing-appear,.swing-enter{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.swing-appear.swing-appear-active,.swing-enter.swing-enter-active{animation-name:antSwingIn;animation-play-state:running}@keyframes antSwingIn{0%,to{transform:translateX(0)}20%{transform:translateX(-10px)}40%{transform:translateX(10px)}60%{transform:translateX(-5px)}80%{transform:translateX(5px)}}.zoom-appear,.zoom-enter,.zoom-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-appear.zoom-appear-active,.zoom-enter.zoom-enter-active{animation-name:antZoomIn;animation-play-state:running}.zoom-leave.zoom-leave-active{animation-name:antZoomOut;animation-play-state:running;pointer-events:none}.zoom-appear,.zoom-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-appear,.zoom-big-enter,.zoom-big-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-big-appear.zoom-big-appear-active,.zoom-big-enter.zoom-big-enter-active{animation-name:antZoomBigIn;animation-play-state:running}.zoom-big-leave.zoom-big-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.zoom-big-appear,.zoom-big-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-big-fast-appear,.zoom-big-fast-enter,.zoom-big-fast-leave{animation-duration:.1s;animation-fill-mode:both;animation-play-state:paused}.zoom-big-fast-appear.zoom-big-fast-appear-active,.zoom-big-fast-enter.zoom-big-fast-enter-active{animation-name:antZoomBigIn;animation-play-state:running}.zoom-big-fast-leave.zoom-big-fast-leave-active{animation-name:antZoomBigOut;animation-play-state:running;pointer-events:none}.zoom-big-fast-appear,.zoom-big-fast-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-big-fast-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-up-appear,.zoom-up-enter,.zoom-up-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-up-appear.zoom-up-appear-active,.zoom-up-enter.zoom-up-enter-active{animation-name:antZoomUpIn;animation-play-state:running}.zoom-up-leave.zoom-up-leave-active{animation-name:antZoomUpOut;animation-play-state:running;pointer-events:none}.zoom-up-appear,.zoom-up-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-up-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-down-appear,.zoom-down-enter,.zoom-down-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-down-appear.zoom-down-appear-active,.zoom-down-enter.zoom-down-enter-active{animation-name:antZoomDownIn;animation-play-state:running}.zoom-down-leave.zoom-down-leave-active{animation-name:antZoomDownOut;animation-play-state:running;pointer-events:none}.zoom-down-appear,.zoom-down-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-down-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-left-appear,.zoom-left-enter,.zoom-left-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-left-appear.zoom-left-appear-active,.zoom-left-enter.zoom-left-enter-active{animation-name:antZoomLeftIn;animation-play-state:running}.zoom-left-leave.zoom-left-leave-active{animation-name:antZoomLeftOut;animation-play-state:running;pointer-events:none}.zoom-left-appear,.zoom-left-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-left-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}.zoom-right-appear,.zoom-right-enter,.zoom-right-leave{animation-duration:.2s;animation-fill-mode:both;animation-play-state:paused}.zoom-right-appear.zoom-right-appear-active,.zoom-right-enter.zoom-right-enter-active{animation-name:antZoomRightIn;animation-play-state:running}.zoom-right-leave.zoom-right-leave-active{animation-name:antZoomRightOut;animation-play-state:running;pointer-events:none}.zoom-right-appear,.zoom-right-enter{transform:scale(0);opacity:0;animation-timing-function:cubic-bezier(.08,.82,.17,1)}.zoom-right-leave{animation-timing-function:cubic-bezier(.78,.14,.15,.86)}@keyframes antZoomIn{0%{transform:scale(.2);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomOut{0%{transform:scale(1)}to{transform:scale(.2);opacity:0}}@keyframes antZoomBigIn{0%{transform:scale(.8);opacity:0}to{transform:scale(1);opacity:1}}@keyframes antZoomBigOut{0%{transform:scale(1)}to{transform:scale(.8);opacity:0}}@keyframes antZoomUpIn{0%{transform:scale(.8);transform-origin:50% 0;opacity:0}to{transform:scale(1);transform-origin:50% 0}}@keyframes antZoomUpOut{0%{transform:scale(1);transform-origin:50% 0}to{transform:scale(.8);transform-origin:50% 0;opacity:0}}@keyframes antZoomLeftIn{0%{transform:scale(.8);transform-origin:0 50%;opacity:0}to{transform:scale(1);transform-origin:0 50%}}@keyframes antZoomLeftOut{0%{transform:scale(1);transform-origin:0 50%}to{transform:scale(.8);transform-origin:0 50%;opacity:0}}@keyframes antZoomRightIn{0%{transform:scale(.8);transform-origin:100% 50%;opacity:0}to{transform:scale(1);transform-origin:100% 50%}}@keyframes antZoomRightOut{0%{transform:scale(1);transform-origin:100% 50%}to{transform:scale(.8);transform-origin:100% 50%;opacity:0}}@keyframes antZoomDownIn{0%{transform:scale(.8);transform-origin:50% 100%;opacity:0}to{transform:scale(1);transform-origin:50% 100%}}@keyframes antZoomDownOut{0%{transform:scale(1);transform-origin:50% 100%}to{transform:scale(.8);transform-origin:50% 100%;opacity:0}}.ant-motion-collapse-legacy{overflow:hidden}.ant-motion-collapse,.ant-motion-collapse-legacy-active{transition:height .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)!important}.ant-motion-collapse{overflow:hidden}.ant-affix{position:fixed;z-index:10}.ant-alert{box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;padding:8px 15px 8px 37px;word-wrap:break-word;border-radius:1rem}.ant-alert.ant-alert-no-icon{padding:8px 15px}.ant-alert.ant-alert-closable{padding-right:30px}.ant-alert-icon{position:absolute;top:11.5px;left:16px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#b3c7c0;border:1px solid #53ad95}.ant-alert-success .ant-alert-icon{color:#008771}.ant-alert-info{background-color:#e6f7ff;border:1px solid #91d5ff}.ant-alert-info .ant-alert-icon{color:#1890ff}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff1f0;border:1px solid #ffa39e}.ant-alert-error .ant-alert-icon{color:#f5222d}.ant-alert-close-icon{position:absolute;top:8px;right:16px;padding:0;overflow:hidden;font-size:12px;line-height:22px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:rgba(0,0,0,.45);transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:rgba(0,0,0,.75)}.ant-alert-close-text{color:rgba(0,0,0,.45);transition:color .3s}.ant-alert-close-text:hover{color:rgba(0,0,0,.75)}.ant-alert-with-description{position:relative;padding:15px 15px 15px 64px;color:rgba(0,0,0,.65);line-height:1.5;border-radius:1rem}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{position:absolute;top:16px;left:24px;font-size:24px}.ant-alert-with-description .ant-alert-close-icon{position:absolute;top:16px;right:16px;font-size:14px;cursor:pointer}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:rgba(0,0,0,.85);font-size:16px}.ant-alert-message{color:rgba(0,0,0,.85)}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-closing{height:0!important;margin:0;padding-top:0;padding-bottom:0;transform-origin:50% 0;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert-slide-up-leave{animation:antAlertSlideUpOut .3s cubic-bezier(.78,.14,.15,.86);animation-fill-mode:both}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}@keyframes antAlertSlideUpIn{0%{transform:scaleY(0);transform-origin:0 0;opacity:0}to{transform:scaleY(1);transform-origin:0 0;opacity:1}}@keyframes antAlertSlideUpOut{0%{transform:scaleY(1);transform-origin:0 0;opacity:1}to{transform:scaleY(0);transform-origin:0 0;opacity:0}}.ant-anchor{box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;padding:0 0 0 2px}.ant-anchor-wrapper{margin-left:-4px;padding-left:4px;overflow:auto;background-color:#fff}.ant-anchor-ink{position:absolute;top:0;left:0;height:100%}.ant-anchor-ink:before{position:relative;display:block;width:2px;height:100%;margin:0 auto;background-color:#e8e8e8;content:" "}.ant-anchor-ink-ball{position:absolute;left:50%;display:none;width:8px;height:8px;background-color:#fff;border:2px solid #008771;border-radius:8px;transform:translateX(-50%);transition:top .3s ease-in-out}.ant-anchor-ink-ball.visible{display:inline-block}.ant-anchor.fixed .ant-anchor-ink .ant-anchor-ink-ball{display:none}.ant-anchor-link{padding:7px 0 7px 16px;line-height:1.143}.ant-anchor-link-title{position:relative;display:block;margin-bottom:6px;overflow:hidden;color:rgba(0,0,0,.65);white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-anchor-link-title:only-child{margin-bottom:0}.ant-anchor-link-active>.ant-anchor-link-title{color:#008771}.ant-anchor-link .ant-anchor-link{padding-top:5px;padding-bottom:5px}.ant-select-auto-complete{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-select-auto-complete.ant-select .ant-select-selection{border:0;box-shadow:none}.ant-select-auto-complete.ant-select .ant-select-selection__rendered{height:100%;margin-right:0;margin-left:0;line-height:32px}.ant-select-auto-complete.ant-select .ant-select-selection__placeholder{margin-right:12px;margin-left:12px}.ant-select-auto-complete.ant-select .ant-select-selection--single{height:auto}.ant-select-auto-complete.ant-select .ant-select-search--inline{position:static;float:left}.ant-select-auto-complete.ant-select-allow-clear .ant-select-selection:hover .ant-select-selection__rendered{margin-right:0!important}.ant-select-auto-complete.ant-select .ant-input{height:32px;line-height:1.5;background:transparent;border-width:1px}.ant-select-auto-complete.ant-select .ant-input:focus,.ant-select-auto-complete.ant-select .ant-input:hover{border-color:#18947b;border-right-width:1px!important}.ant-select-auto-complete.ant-select .ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-select-auto-complete.ant-select .ant-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-select-auto-complete.ant-select-lg .ant-select-selection__rendered{line-height:40px}.ant-select-auto-complete.ant-select-lg .ant-input{height:40px;padding-top:6px;padding-bottom:6px}.ant-select-auto-complete.ant-select-sm .ant-select-selection__rendered{line-height:24px}.ant-select-auto-complete.ant-select-sm .ant-input{height:24px;padding-top:1px;padding-bottom:1px}.ant-input-group>.ant-select-auto-complete .ant-select-search__field.ant-input-affix-wrapper{display:inline;float:none}.ant-select{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;font-feature-settings:"tnum";position:relative;display:inline-block;outline:0}.ant-select,.ant-select ol,.ant-select ul{margin:0;padding:0;list-style:none}.ant-select>ul>li>a{padding:0;background-color:#fff}.ant-select-arrow{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;right:11px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:1;transform-origin:50% 50%}.ant-select-arrow>*{line-height:1}.ant-select-arrow svg{display:inline-block}.ant-select-arrow:before{display:none}.ant-select-arrow .ant-select-arrow-icon{display:block}.ant-select-arrow .ant-select-arrow-icon svg{transition:transform .3s}.ant-select-selection{display:block;box-sizing:border-box;background-color:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-radius:1rem;outline:none;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-select-selection:hover{border-color:#18947b;border-right-width:1px!important}.ant-select-focused .ant-select-selection,.ant-select-selection:active,.ant-select-selection:focus{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-select-selection__clear{position:absolute;top:50%;right:11px;z-index:1;display:inline-block;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;font-style:normal;line-height:12px;text-align:center;text-transform:none;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease;text-rendering:auto}.ant-select-selection__clear:before{display:block}.ant-select-selection__clear:hover{color:rgba(0,0,0,.45)}.ant-select-selection:hover .ant-select-selection__clear{opacity:1}.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}.ant-select-disabled{color:rgba(0,0,0,.25)}.ant-select-disabled .ant-select-selection{background:#f5f5f5;cursor:not-allowed}.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice__remove{display:none}.ant-select-selection--single{position:relative;height:32px;cursor:pointer}.ant-select-selection--single .ant-select-selection__rendered{margin-right:24px}.ant-select-no-arrow .ant-select-selection__rendered{margin-right:11px}.ant-select-selection__rendered{position:relative;display:block;margin-right:11px;margin-left:11px;line-height:30px}.ant-select-selection__rendered:after{display:inline-block;width:0;visibility:hidden;content:".";pointer-events:none}.ant-select-lg{font-size:16px}.ant-select-lg .ant-select-selection--single{height:40px}.ant-select-lg .ant-select-selection__rendered{line-height:38px}.ant-select-lg .ant-select-selection--multiple{min-height:40px}.ant-select-lg .ant-select-selection--multiple .ant-select-selection__rendered li{height:32px;line-height:32px}.ant-select-lg .ant-select-selection--multiple .ant-select-arrow,.ant-select-lg .ant-select-selection--multiple .ant-select-selection__clear{top:20px}.ant-select-sm .ant-select-selection--single{height:24px}.ant-select-sm .ant-select-selection__rendered{margin-left:7px;line-height:22px}.ant-select-sm .ant-select-selection--multiple{min-height:24px}.ant-select-sm .ant-select-selection--multiple .ant-select-selection__rendered li{height:16px;line-height:14px}.ant-select-sm .ant-select-selection--multiple .ant-select-arrow,.ant-select-sm .ant-select-selection--multiple .ant-select-selection__clear{top:12px}.ant-select-sm .ant-select-arrow,.ant-select-sm .ant-select-selection__clear{right:8px}.ant-select-disabled .ant-select-selection__choice__remove{color:rgba(0,0,0,.25);cursor:default}.ant-select-disabled .ant-select-selection__choice__remove:hover{color:rgba(0,0,0,.25)}.ant-select-search__field__wrap{position:relative;display:inline-block}.ant-select-search__field__placeholder,.ant-select-selection__placeholder{position:absolute;top:50%;right:9px;left:0;max-width:100%;height:20px;margin-top:-10px;overflow:hidden;color:#bfbfbf;line-height:20px;white-space:nowrap;text-align:left;text-overflow:ellipsis}.ant-select-search__field__placeholder{left:12px}.ant-select-search__field__mirror{position:absolute;top:0;left:0;white-space:pre;opacity:0;pointer-events:none}.ant-select-search--inline{position:absolute;width:100%;height:100%}.ant-select-search--inline .ant-select-search__field__wrap{width:100%;height:100%}.ant-select-search--inline .ant-select-search__field{width:100%;height:100%;font-size:100%;line-height:1;background:transparent;border-width:0;border-radius:1rem;outline:0}.ant-select-search--inline>i{float:right}.ant-select-selection--multiple{min-height:32px;padding-bottom:3px;cursor:text;zoom:1}.ant-select-selection--multiple:after,.ant-select-selection--multiple:before{display:table;content:""}.ant-select-selection--multiple:after{clear:both}.ant-select-selection--multiple .ant-select-search--inline{position:static;float:left;width:auto;max-width:100%;padding:0}.ant-select-selection--multiple .ant-select-search--inline .ant-select-search__field{width:.75em;max-width:100%;padding:1px}.ant-select-selection--multiple .ant-select-selection__rendered{height:auto;margin-bottom:-3px;margin-left:5px}.ant-select-selection--multiple .ant-select-selection__placeholder{margin-left:6px}.ant-select-selection--multiple .ant-select-selection__rendered>ul>li,.ant-select-selection--multiple>ul>li{height:24px;margin-top:3px;line-height:22px}.ant-select-selection--multiple .ant-select-selection__choice{position:relative;float:left;max-width:99%;margin-right:4px;padding:0 20px 0 10px;overflow:hidden;color:rgba(0,0,0,.65);background-color:#fafafa;border:1px solid #e8e8e8;border-radius:2px;cursor:default;transition:padding .3s cubic-bezier(.645,.045,.355,1)}.ant-select-selection--multiple .ant-select-selection__choice__disabled{padding:0 10px}.ant-select-selection--multiple .ant-select-selection__choice__content{display:inline-block;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;transition:margin .3s cubic-bezier(.645,.045,.355,1)}.ant-select-selection--multiple .ant-select-selection__choice__remove{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;color:rgba(0,0,0,.45);font-weight:700;line-height:inherit;cursor:pointer;transition:all .3s;display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}.ant-select-selection--multiple .ant-select-selection__choice__remove>*{line-height:1}.ant-select-selection--multiple .ant-select-selection__choice__remove svg{display:inline-block}.ant-select-selection--multiple .ant-select-selection__choice__remove:before{display:none}.ant-select-selection--multiple .ant-select-selection__choice__remove .ant-select-selection--multiple .ant-select-selection__choice__remove-icon{display:block}:root .ant-select-selection--multiple .ant-select-selection__choice__remove{font-size:12px}.ant-select-selection--multiple .ant-select-selection__choice__remove:hover{color:rgba(0,0,0,.75)}.ant-select-selection--multiple .ant-select-arrow,.ant-select-selection--multiple .ant-select-selection__clear{top:16px}.ant-select-allow-clear .ant-select-selection--multiple .ant-select-selection__rendered,.ant-select-show-arrow .ant-select-selection--multiple .ant-select-selection__rendered{margin-right:20px}.ant-select-open .ant-select-arrow-icon svg{transform:rotate(180deg)}.ant-select-open .ant-select-selection{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-select-combobox .ant-select-arrow{display:none}.ant-select-combobox .ant-select-search--inline{float:none;width:100%;height:100%}.ant-select-combobox .ant-select-search__field__wrap{width:100%;height:100%}.ant-select-combobox .ant-select-search__field{position:relative;z-index:1;width:100%;height:100%;box-shadow:none;transition:all .3s cubic-bezier(.645,.045,.355,1),height 0s}.ant-select-combobox.ant-select-allow-clear .ant-select-selection:hover .ant-select-selection__rendered,.ant-select-combobox.ant-select-show-arrow .ant-select-selection:hover .ant-select-selection__rendered{margin-right:20px}.ant-select-dropdown{margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;font-size:14px;font-variant:normal;background-color:#fff;border-radius:1rem;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-select-dropdown.slide-up-appear.slide-up-appear-active.ant-select-dropdown-placement-bottomLeft,.ant-select-dropdown.slide-up-enter.slide-up-enter-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpIn}.ant-select-dropdown.slide-up-appear.slide-up-appear-active.ant-select-dropdown-placement-topLeft,.ant-select-dropdown.slide-up-enter.slide-up-enter-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownIn}.ant-select-dropdown.slide-up-leave.slide-up-leave-active.ant-select-dropdown-placement-bottomLeft{animation-name:antSlideUpOut}.ant-select-dropdown.slide-up-leave.slide-up-leave-active.ant-select-dropdown-placement-topLeft{animation-name:antSlideDownOut}.ant-select-dropdown-hidden{display:none}.ant-select-dropdown-menu{max-height:250px;margin-bottom:0;padding:4px 0;overflow:auto;list-style:none;outline:none}.ant-select-dropdown-menu-item-group-list{margin:0;padding:0}.ant-select-dropdown-menu-item-group-list>.ant-select-dropdown-menu-item{padding-left:20px}.ant-select-dropdown-menu-item-group-title{height:32px;padding:0 12px;color:rgba(0,0,0,.45);font-size:12px;line-height:32px}.ant-select-dropdown-menu-item-group-list .ant-select-dropdown-menu-item:first-child:not(:last-child),.ant-select-dropdown-menu-item-group:not(:last-child) .ant-select-dropdown-menu-item-group-list .ant-select-dropdown-menu-item:last-child{border-radius:0}.ant-select-dropdown-menu-item{position:relative;display:block;padding:5px 12px;overflow:hidden;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:background .3s ease}.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled){background-color:#b3c7c0}.ant-select-dropdown-menu-item-selected{color:rgba(0,0,0,.65);font-weight:600;background-color:#fafafa}.ant-select-dropdown-menu-item-disabled,.ant-select-dropdown-menu-item-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled){background-color:#b3c7c0}.ant-select-dropdown-menu-item-divider{height:1px;margin:1px 0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item{padding-right:32px}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item .ant-select-selected-icon{position:absolute;top:50%;right:12px;color:transparent;font-weight:700;font-size:12px;text-shadow:0 .1px 0,.1px 0 0,0 -.1px 0,-.1px 0;transform:translateY(-50%);transition:all .2s}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item:hover .ant-select-selected-icon{color:rgba(0,0,0,.87)}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-disabled .ant-select-selected-icon{display:none}.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected .ant-select-selected-icon,.ant-select-dropdown.ant-select-dropdown--multiple .ant-select-dropdown-menu-item-selected:hover .ant-select-selected-icon{display:inline-block;color:#008771}.ant-select-dropdown--empty.ant-select-dropdown--multiple .ant-select-dropdown-menu-item{padding-right:12px}.ant-select-dropdown-container-open .ant-select-dropdown,.ant-select-dropdown-open .ant-select-dropdown{display:block}.ant-empty{margin:0 8px;font-size:14px;line-height:22px;text-align:center}.ant-empty-image{height:100px;margin-bottom:8px}.ant-empty-image img{height:100%}.ant-empty-image svg{height:100%;margin:auto}.ant-empty-description{margin:0}.ant-empty-footer{margin-top:16px}.ant-empty-normal{margin:32px 0;color:rgba(0,0,0,.25)}.ant-empty-normal .ant-empty-image{height:40px}.ant-empty-small{margin:8px 0;color:rgba(0,0,0,.25)}.ant-empty-small .ant-empty-image{height:35px}.ant-input{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;width:100%;height:32px;padding:4px 11px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:1rem;transition:all .3s}.ant-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input:-ms-input-placeholder{color:#bfbfbf}.ant-input::-webkit-input-placeholder{color:#bfbfbf}.ant-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input:placeholder-shown{text-overflow:ellipsis}.ant-input:focus,.ant-input:hover{border-color:#18947b;border-right-width:1px!important}.ant-input:focus{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-input-sm{height:24px;padding:1px 7px}.ant-input-group{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:table;width:100%;border-collapse:separate;border-spacing:0}.ant-input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.ant-input-group>[class*=col-]{padding-right:8px}.ant-input-group>[class*=col-]:last-child{padding-right:0}.ant-input-group-addon,.ant-input-group-wrap,.ant-input-group>.ant-input{display:table-cell}.ant-input-group-addon:not(:first-child):not(:last-child),.ant-input-group-wrap:not(:first-child):not(:last-child),.ant-input-group>.ant-input:not(:first-child):not(:last-child){border-radius:0}.ant-input-group-addon,.ant-input-group-wrap{width:1px;white-space:nowrap;vertical-align:middle}.ant-input-group-wrap>*{display:block!important}.ant-input-group .ant-input{float:left;width:100%;margin-bottom:0;text-align:inherit}.ant-input-group .ant-input:focus,.ant-input-group .ant-input:hover{z-index:1;border-right-width:1px}.ant-input-group-addon{position:relative;padding:0 11px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;text-align:center;background-color:#fafafa;border:1px solid #d9d9d9;border-radius:1rem;transition:all .3s}.ant-input-group-addon .ant-select{margin:-5px -11px}.ant-input-group-addon .ant-select .ant-select-selection{margin:-1px;background-color:inherit;border:1px solid transparent;box-shadow:none}.ant-input-group-addon .ant-select-focused .ant-select-selection,.ant-input-group-addon .ant-select-open .ant-select-selection{color:#008771}.ant-input-group-addon>i:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;content:""}.ant-input-group-addon:first-child,.ant-input-group-addon:first-child .ant-select .ant-select-selection,.ant-input-group>.ant-input:first-child,.ant-input-group>.ant-input:first-child .ant-select .ant-select-selection{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:first-child) .ant-input{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group>.ant-input-affix-wrapper:not(:last-child) .ant-input{border-top-right-radius:0;border-bottom-right-radius:0}.ant-input-group-addon:first-child{border-right:0}.ant-input-group-addon:last-child{border-left:0}.ant-input-group-addon:last-child,.ant-input-group-addon:last-child .ant-select .ant-select-selection,.ant-input-group>.ant-input:last-child,.ant-input-group>.ant-input:last-child .ant-select .ant-select-selection{border-top-left-radius:0;border-bottom-left-radius:0}.ant-input-group-lg .ant-input,.ant-input-group-lg>.ant-input-group-addon{height:40px;padding:6px 11px;font-size:16px}.ant-input-group-sm .ant-input,.ant-input-group-sm>.ant-input-group-addon{height:24px;padding:1px 7px}.ant-input-group-lg .ant-select-selection--single{height:40px}.ant-input-group-sm .ant-select-selection--single{height:24px}.ant-input-group .ant-input-affix-wrapper{display:table-cell;float:left;width:100%}.ant-input-group.ant-input-group-compact{display:block;zoom:1}.ant-input-group.ant-input-group-compact:after,.ant-input-group.ant-input-group-compact:before{display:table;content:""}.ant-input-group.ant-input-group-compact:after{clear:both}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child),.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child){border-right-width:1px}.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(:last-child):hover,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):focus,.ant-input-group.ant-input-group-compact>.ant-input:not(:first-child):not(:last-child):hover{z-index:1}.ant-input-group.ant-input-group-compact>*{display:inline-block;float:none;vertical-align:top;border-radius:0}.ant-input-group.ant-input-group-compact>:not(:last-child){margin-right:-1px;border-right-width:1px}.ant-input-group.ant-input-group-compact .ant-input{float:none}.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input{border-right-width:1px;border-radius:0}.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-calendar-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-cascader-picker .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-input-group-wrapper .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor:focus,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper .ant-mention-editor:hover,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:focus,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input:hover,.ant-input-group.ant-input-group-compact>.ant-select-focused,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection:focus,.ant-input-group.ant-input-group-compact>.ant-select>.ant-select-selection:hover,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input:focus,.ant-input-group.ant-input-group-compact>.ant-time-picker .ant-time-picker-input:hover{z-index:1}.ant-input-group.ant-input-group-compact>.ant-calendar-picker:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper:first-child .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:first-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-select:first-child>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker:first-child .ant-time-picker-input,.ant-input-group.ant-input-group-compact>:first-child{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.ant-input-group.ant-input-group-compact>.ant-calendar-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker-focused:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-cascader-picker:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-mention-wrapper:last-child .ant-mention-editor,.ant-input-group.ant-input-group-compact>.ant-select-auto-complete:last-child .ant-input,.ant-input-group.ant-input-group-compact>.ant-select:last-child>.ant-select-selection,.ant-input-group.ant-input-group-compact>.ant-time-picker:last-child .ant-time-picker-input,.ant-input-group.ant-input-group-compact>:last-child{border-right-width:1px;border-top-right-radius:1rem;border-bottom-right-radius:1rem}.ant-input-group.ant-input-group-compact>.ant-select-auto-complete .ant-input{vertical-align:top}.ant-input-group-wrapper{display:inline-block;width:100%;text-align:start;vertical-align:top}.ant-input-affix-wrapper{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;width:100%;text-align:start}.ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#18947b;border-right-width:1px!important}.ant-input-affix-wrapper .ant-input{position:relative;text-align:inherit}.ant-input-affix-wrapper .ant-input-prefix,.ant-input-affix-wrapper .ant-input-suffix{position:absolute;top:50%;z-index:2;display:flex;align-items:center;color:rgba(0,0,0,.65);line-height:0;transform:translateY(-50%)}.ant-input-affix-wrapper .ant-input-prefix :not(.anticon),.ant-input-affix-wrapper .ant-input-suffix :not(.anticon){line-height:1.5}.ant-input-affix-wrapper .ant-input-disabled~.ant-input-suffix .anticon{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-input-affix-wrapper .ant-input-prefix{left:12px}.ant-input-affix-wrapper .ant-input-suffix{right:12px}.ant-input-affix-wrapper .ant-input:not(:first-child){padding-left:30px}.ant-input-affix-wrapper .ant-input:not(:last-child){padding-right:30px}.ant-input-affix-wrapper.ant-input-affix-wrapper-input-with-clear-btn .ant-input:not(:last-child){padding-right:49px}.ant-input-affix-wrapper.ant-input-affix-wrapper-textarea-with-clear-btn .ant-input{padding-right:22px}.ant-input-password-icon{color:rgba(0,0,0,.45);cursor:pointer;transition:all .3s}.ant-input-password-icon:hover{color:#333}.ant-input-clear-icon{color:rgba(0,0,0,.25);font-size:12px;cursor:pointer;transition:color .3s;vertical-align:0}.ant-input-clear-icon:hover{color:rgba(0,0,0,.45)}.ant-input-clear-icon:active{color:rgba(0,0,0,.65)}.ant-input-clear-icon+i{margin-left:6px}.ant-input-textarea-clear-icon{color:rgba(0,0,0,.25);font-size:12px;cursor:pointer;transition:color .3s;position:absolute;top:0;right:0;margin:8px 8px 0 0}.ant-input-textarea-clear-icon:hover{color:rgba(0,0,0,.45)}.ant-input-textarea-clear-icon:active{color:rgba(0,0,0,.65)}.ant-input-textarea-clear-icon+i{margin-left:6px}.ant-input-search-icon{color:rgba(0,0,0,.45);cursor:pointer;transition:all .3s}.ant-input-search-icon:hover{color:rgba(0,0,0,.8)}.ant-input-search-enter-button input{border-right:0}.ant-input-search-enter-button+.ant-input-group-addon,.ant-input-search-enter-button input+.ant-input-group-addon{padding:0;border:0}.ant-input-search-enter-button+.ant-input-group-addon .ant-input-search-button,.ant-input-search-enter-button input+.ant-input-group-addon .ant-input-search-button{border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn{line-height:1.499;position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;touch-action:manipulation;height:32px;padding:0 15px;font-size:14px;border-radius:1rem;color:rgba(0,0,0,.65);background-color:#fff;border:1px solid #d9d9d9}.ant-btn>.anticon{line-height:1}.ant-btn,.ant-btn:active,.ant-btn:focus{outline:0}.ant-btn:not([disabled]):hover{text-decoration:none}.ant-btn:not([disabled]):active{outline:0;box-shadow:none}.ant-btn.disabled,.ant-btn[disabled]{cursor:not-allowed}.ant-btn.disabled>*,.ant-btn[disabled]>*{pointer-events:none}.ant-btn-lg{height:40px;padding:0 15px;font-size:16px;border-radius:1rem}.ant-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:1rem}.ant-btn>a:only-child{color:currentColor}.ant-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn:focus,.ant-btn:hover{color:#18947b;background-color:#fff;border-color:#18947b}.ant-btn:focus>a:only-child,.ant-btn:hover>a:only-child{color:currentColor}.ant-btn:focus>a:only-child:after,.ant-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn.active,.ant-btn:active{color:#006154;background-color:#fff;border-color:#006154}.ant-btn.active>a:only-child,.ant-btn:active>a:only-child{color:currentColor}.ant-btn.active>a:only-child:after,.ant-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-disabled,.ant-btn-disabled.active,.ant-btn-disabled:active,.ant-btn-disabled:focus,.ant-btn-disabled:hover,.ant-btn.disabled,.ant-btn.disabled.active,.ant-btn.disabled:active,.ant-btn.disabled:focus,.ant-btn.disabled:hover,.ant-btn[disabled],.ant-btn[disabled].active,.ant-btn[disabled]:active,.ant-btn[disabled]:focus,.ant-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-disabled.active>a:only-child,.ant-btn-disabled:active>a:only-child,.ant-btn-disabled:focus>a:only-child,.ant-btn-disabled:hover>a:only-child,.ant-btn-disabled>a:only-child,.ant-btn.disabled.active>a:only-child,.ant-btn.disabled:active>a:only-child,.ant-btn.disabled:focus>a:only-child,.ant-btn.disabled:hover>a:only-child,.ant-btn.disabled>a:only-child,.ant-btn[disabled].active>a:only-child,.ant-btn[disabled]:active>a:only-child,.ant-btn[disabled]:focus>a:only-child,.ant-btn[disabled]:hover>a:only-child,.ant-btn[disabled]>a:only-child{color:currentColor}.ant-btn-disabled.active>a:only-child:after,.ant-btn-disabled:active>a:only-child:after,.ant-btn-disabled:focus>a:only-child:after,.ant-btn-disabled:hover>a:only-child:after,.ant-btn-disabled>a:only-child:after,.ant-btn.disabled.active>a:only-child:after,.ant-btn.disabled:active>a:only-child:after,.ant-btn.disabled:focus>a:only-child:after,.ant-btn.disabled:hover>a:only-child:after,.ant-btn.disabled>a:only-child:after,.ant-btn[disabled].active>a:only-child:after,.ant-btn[disabled]:active>a:only-child:after,.ant-btn[disabled]:focus>a:only-child:after,.ant-btn[disabled]:hover>a:only-child:after,.ant-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn.active,.ant-btn:active,.ant-btn:focus,.ant-btn:hover{text-decoration:none;background:#fff}.ant-btn>i,.ant-btn>span{display:inline-block;transition:margin-left .3s cubic-bezier(.645,.045,.355,1);pointer-events:none}.ant-btn-primary{color:#fff;background-color:#008771;border-color:#008771;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}.ant-btn-primary>a:only-child{color:currentColor}.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary:focus,.ant-btn-primary:hover{color:#fff;background-color:#18947b;border-color:#18947b}.ant-btn-primary:focus>a:only-child,.ant-btn-primary:hover>a:only-child{color:currentColor}.ant-btn-primary:focus>a:only-child:after,.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary.active,.ant-btn-primary:active{color:#fff;background-color:#006154;border-color:#006154}.ant-btn-primary.active>a:only-child,.ant-btn-primary:active>a:only-child{color:currentColor}.ant-btn-primary.active>a:only-child:after,.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-primary-disabled,.ant-btn-primary-disabled.active,.ant-btn-primary-disabled:active,.ant-btn-primary-disabled:focus,.ant-btn-primary-disabled:hover,.ant-btn-primary.disabled,.ant-btn-primary.disabled.active,.ant-btn-primary.disabled:active,.ant-btn-primary.disabled:focus,.ant-btn-primary.disabled:hover,.ant-btn-primary[disabled],.ant-btn-primary[disabled].active,.ant-btn-primary[disabled]:active,.ant-btn-primary[disabled]:focus,.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-primary-disabled.active>a:only-child,.ant-btn-primary-disabled:active>a:only-child,.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-primary-disabled>a:only-child,.ant-btn-primary.disabled.active>a:only-child,.ant-btn-primary.disabled:active>a:only-child,.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-primary.disabled>a:only-child,.ant-btn-primary[disabled].active>a:only-child,.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-primary[disabled]>a:only-child{color:currentColor}.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-primary-disabled>a:only-child:after,.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-primary.disabled>a:only-child:after,.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child){border-right-color:#18947b;border-left-color:#18947b}.ant-btn-group .ant-btn-primary:not(:first-child):not(:last-child):disabled{border-color:#d9d9d9}.ant-btn-group .ant-btn-primary:first-child:not(:last-child){border-right-color:#18947b}.ant-btn-group .ant-btn-primary:first-child:not(:last-child)[disabled]{border-right-color:#d9d9d9}.ant-btn-group .ant-btn-primary+.ant-btn-primary,.ant-btn-group .ant-btn-primary:last-child:not(:first-child){border-left-color:#18947b}.ant-btn-group .ant-btn-primary+.ant-btn-primary[disabled],.ant-btn-group .ant-btn-primary:last-child:not(:first-child)[disabled]{border-left-color:#d9d9d9}.ant-btn-ghost{color:rgba(0,0,0,.65);background-color:transparent;border-color:#d9d9d9}.ant-btn-ghost>a:only-child{color:currentColor}.ant-btn-ghost>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost:focus,.ant-btn-ghost:hover{color:#18947b;background-color:transparent;border-color:#18947b}.ant-btn-ghost:focus>a:only-child,.ant-btn-ghost:hover>a:only-child{color:currentColor}.ant-btn-ghost:focus>a:only-child:after,.ant-btn-ghost:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost.active,.ant-btn-ghost:active{color:#006154;background-color:transparent;border-color:#006154}.ant-btn-ghost.active>a:only-child,.ant-btn-ghost:active>a:only-child{color:currentColor}.ant-btn-ghost.active>a:only-child:after,.ant-btn-ghost:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-ghost-disabled,.ant-btn-ghost-disabled.active,.ant-btn-ghost-disabled:active,.ant-btn-ghost-disabled:focus,.ant-btn-ghost-disabled:hover,.ant-btn-ghost.disabled,.ant-btn-ghost.disabled.active,.ant-btn-ghost.disabled:active,.ant-btn-ghost.disabled:focus,.ant-btn-ghost.disabled:hover,.ant-btn-ghost[disabled],.ant-btn-ghost[disabled].active,.ant-btn-ghost[disabled]:active,.ant-btn-ghost[disabled]:focus,.ant-btn-ghost[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-ghost-disabled.active>a:only-child,.ant-btn-ghost-disabled:active>a:only-child,.ant-btn-ghost-disabled:focus>a:only-child,.ant-btn-ghost-disabled:hover>a:only-child,.ant-btn-ghost-disabled>a:only-child,.ant-btn-ghost.disabled.active>a:only-child,.ant-btn-ghost.disabled:active>a:only-child,.ant-btn-ghost.disabled:focus>a:only-child,.ant-btn-ghost.disabled:hover>a:only-child,.ant-btn-ghost.disabled>a:only-child,.ant-btn-ghost[disabled].active>a:only-child,.ant-btn-ghost[disabled]:active>a:only-child,.ant-btn-ghost[disabled]:focus>a:only-child,.ant-btn-ghost[disabled]:hover>a:only-child,.ant-btn-ghost[disabled]>a:only-child{color:currentColor}.ant-btn-ghost-disabled.active>a:only-child:after,.ant-btn-ghost-disabled:active>a:only-child:after,.ant-btn-ghost-disabled:focus>a:only-child:after,.ant-btn-ghost-disabled:hover>a:only-child:after,.ant-btn-ghost-disabled>a:only-child:after,.ant-btn-ghost.disabled.active>a:only-child:after,.ant-btn-ghost.disabled:active>a:only-child:after,.ant-btn-ghost.disabled:focus>a:only-child:after,.ant-btn-ghost.disabled:hover>a:only-child:after,.ant-btn-ghost.disabled>a:only-child:after,.ant-btn-ghost[disabled].active>a:only-child:after,.ant-btn-ghost[disabled]:active>a:only-child:after,.ant-btn-ghost[disabled]:focus>a:only-child:after,.ant-btn-ghost[disabled]:hover>a:only-child:after,.ant-btn-ghost[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed{color:rgba(0,0,0,.65);background-color:#fff;border-color:#d9d9d9;border-style:dashed}.ant-btn-dashed>a:only-child{color:currentColor}.ant-btn-dashed>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed:focus,.ant-btn-dashed:hover{color:#18947b;background-color:#fff;border-color:#18947b}.ant-btn-dashed:focus>a:only-child,.ant-btn-dashed:hover>a:only-child{color:currentColor}.ant-btn-dashed:focus>a:only-child:after,.ant-btn-dashed:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed.active,.ant-btn-dashed:active{color:#006154;background-color:#fff;border-color:#006154}.ant-btn-dashed.active>a:only-child,.ant-btn-dashed:active>a:only-child{color:currentColor}.ant-btn-dashed.active>a:only-child:after,.ant-btn-dashed:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-dashed-disabled,.ant-btn-dashed-disabled.active,.ant-btn-dashed-disabled:active,.ant-btn-dashed-disabled:focus,.ant-btn-dashed-disabled:hover,.ant-btn-dashed.disabled,.ant-btn-dashed.disabled.active,.ant-btn-dashed.disabled:active,.ant-btn-dashed.disabled:focus,.ant-btn-dashed.disabled:hover,.ant-btn-dashed[disabled],.ant-btn-dashed[disabled].active,.ant-btn-dashed[disabled]:active,.ant-btn-dashed[disabled]:focus,.ant-btn-dashed[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-dashed-disabled.active>a:only-child,.ant-btn-dashed-disabled:active>a:only-child,.ant-btn-dashed-disabled:focus>a:only-child,.ant-btn-dashed-disabled:hover>a:only-child,.ant-btn-dashed-disabled>a:only-child,.ant-btn-dashed.disabled.active>a:only-child,.ant-btn-dashed.disabled:active>a:only-child,.ant-btn-dashed.disabled:focus>a:only-child,.ant-btn-dashed.disabled:hover>a:only-child,.ant-btn-dashed.disabled>a:only-child,.ant-btn-dashed[disabled].active>a:only-child,.ant-btn-dashed[disabled]:active>a:only-child,.ant-btn-dashed[disabled]:focus>a:only-child,.ant-btn-dashed[disabled]:hover>a:only-child,.ant-btn-dashed[disabled]>a:only-child{color:currentColor}.ant-btn-dashed-disabled.active>a:only-child:after,.ant-btn-dashed-disabled:active>a:only-child:after,.ant-btn-dashed-disabled:focus>a:only-child:after,.ant-btn-dashed-disabled:hover>a:only-child:after,.ant-btn-dashed-disabled>a:only-child:after,.ant-btn-dashed.disabled.active>a:only-child:after,.ant-btn-dashed.disabled:active>a:only-child:after,.ant-btn-dashed.disabled:focus>a:only-child:after,.ant-btn-dashed.disabled:hover>a:only-child:after,.ant-btn-dashed.disabled>a:only-child:after,.ant-btn-dashed[disabled].active>a:only-child:after,.ant-btn-dashed[disabled]:active>a:only-child:after,.ant-btn-dashed[disabled]:focus>a:only-child:after,.ant-btn-dashed[disabled]:hover>a:only-child:after,.ant-btn-dashed[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger{color:#fff;background-color:#ff4d4f;border-color:#ff4d4f;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045)}.ant-btn-danger>a:only-child{color:currentColor}.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger:focus,.ant-btn-danger:hover{color:#fff;background-color:#ff7875;border-color:#ff7875}.ant-btn-danger:focus>a:only-child,.ant-btn-danger:hover>a:only-child{color:currentColor}.ant-btn-danger:focus>a:only-child:after,.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger.active,.ant-btn-danger:active{color:#fff;background-color:#d9363e;border-color:#d9363e}.ant-btn-danger.active>a:only-child,.ant-btn-danger:active>a:only-child{color:currentColor}.ant-btn-danger.active>a:only-child:after,.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-danger-disabled,.ant-btn-danger-disabled.active,.ant-btn-danger-disabled:active,.ant-btn-danger-disabled:focus,.ant-btn-danger-disabled:hover,.ant-btn-danger.disabled,.ant-btn-danger.disabled.active,.ant-btn-danger.disabled:active,.ant-btn-danger.disabled:focus,.ant-btn-danger.disabled:hover,.ant-btn-danger[disabled],.ant-btn-danger[disabled].active,.ant-btn-danger[disabled]:active,.ant-btn-danger[disabled]:focus,.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-danger-disabled.active>a:only-child,.ant-btn-danger-disabled:active>a:only-child,.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-danger-disabled>a:only-child,.ant-btn-danger.disabled.active>a:only-child,.ant-btn-danger.disabled:active>a:only-child,.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-danger.disabled>a:only-child,.ant-btn-danger[disabled].active>a:only-child,.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-danger[disabled]>a:only-child{color:currentColor}.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-danger-disabled>a:only-child:after,.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-danger.disabled>a:only-child:after,.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link{color:#008771;background-color:transparent;border-color:transparent;box-shadow:none}.ant-btn-link>a:only-child{color:currentColor}.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link:focus,.ant-btn-link:hover{color:#18947b;background-color:transparent;border-color:#18947b}.ant-btn-link:focus>a:only-child,.ant-btn-link:hover>a:only-child{color:currentColor}.ant-btn-link:focus>a:only-child:after,.ant-btn-link:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link.active,.ant-btn-link:active{color:#006154;background-color:transparent;border-color:#006154}.ant-btn-link.active>a:only-child,.ant-btn-link:active>a:only-child{color:currentColor}.ant-btn-link.active>a:only-child:after,.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-link-disabled,.ant-btn-link-disabled.active,.ant-btn-link-disabled:active,.ant-btn-link-disabled:focus,.ant-btn-link-disabled:hover,.ant-btn-link.disabled,.ant-btn-link.disabled.active,.ant-btn-link.disabled:active,.ant-btn-link.disabled:focus,.ant-btn-link.disabled:hover,.ant-btn-link[disabled],.ant-btn-link[disabled].active,.ant-btn-link[disabled]:active,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:hover{background-color:#f5f5f5;border-color:#d9d9d9}.ant-btn-link:active,.ant-btn-link:focus,.ant-btn-link:hover{border-color:transparent}.ant-btn-link-disabled,.ant-btn-link-disabled.active,.ant-btn-link-disabled:active,.ant-btn-link-disabled:focus,.ant-btn-link-disabled:hover,.ant-btn-link.disabled,.ant-btn-link.disabled.active,.ant-btn-link.disabled:active,.ant-btn-link.disabled:focus,.ant-btn-link.disabled:hover,.ant-btn-link[disabled],.ant-btn-link[disabled].active,.ant-btn-link[disabled]:active,.ant-btn-link[disabled]:focus,.ant-btn-link[disabled]:hover{color:rgba(0,0,0,.25);background-color:transparent;border-color:transparent;text-shadow:none;box-shadow:none}.ant-btn-link-disabled.active>a:only-child,.ant-btn-link-disabled:active>a:only-child,.ant-btn-link-disabled:focus>a:only-child,.ant-btn-link-disabled:hover>a:only-child,.ant-btn-link-disabled>a:only-child,.ant-btn-link.disabled.active>a:only-child,.ant-btn-link.disabled:active>a:only-child,.ant-btn-link.disabled:focus>a:only-child,.ant-btn-link.disabled:hover>a:only-child,.ant-btn-link.disabled>a:only-child,.ant-btn-link[disabled].active>a:only-child,.ant-btn-link[disabled]:active>a:only-child,.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-link[disabled]>a:only-child{color:currentColor}.ant-btn-link-disabled.active>a:only-child:after,.ant-btn-link-disabled:active>a:only-child:after,.ant-btn-link-disabled:focus>a:only-child:after,.ant-btn-link-disabled:hover>a:only-child:after,.ant-btn-link-disabled>a:only-child:after,.ant-btn-link.disabled.active>a:only-child:after,.ant-btn-link.disabled:active>a:only-child:after,.ant-btn-link.disabled:focus>a:only-child:after,.ant-btn-link.disabled:hover>a:only-child:after,.ant-btn-link.disabled>a:only-child:after,.ant-btn-link[disabled].active>a:only-child:after,.ant-btn-link[disabled]:active>a:only-child:after,.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-link[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-icon-only{width:32px;height:32px;padding:0;font-size:16px;border-radius:1rem}.ant-btn-icon-only.ant-btn-lg{width:40px;height:40px;padding:0;font-size:18px;border-radius:1rem}.ant-btn-icon-only.ant-btn-sm{width:24px;height:24px;padding:0;font-size:14px;border-radius:1rem}.ant-btn-icon-only>i{vertical-align:middle}.ant-btn-round{height:32px;padding:0 16px;font-size:14px;border-radius:32px}.ant-btn-round.ant-btn-lg{height:40px;padding:0 20px;font-size:16px;border-radius:40px}.ant-btn-round.ant-btn-sm{height:24px;padding:0 12px;font-size:14px;border-radius:24px}.ant-btn-round.ant-btn-icon-only{width:auto}.ant-btn-circle,.ant-btn-circle-outline{min-width:32px;padding-right:0;padding-left:0;text-align:center;border-radius:50%}.ant-btn-circle-outline.ant-btn-lg,.ant-btn-circle.ant-btn-lg{min-width:40px;border-radius:50%}.ant-btn-circle-outline.ant-btn-sm,.ant-btn-circle.ant-btn-sm{min-width:24px;border-radius:50%}.ant-btn:before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:1;display:none;background:#fff;border-radius:inherit;opacity:.35;transition:opacity .2s;content:"";pointer-events:none}.ant-btn .anticon{transition:margin-left .3s cubic-bezier(.645,.045,.355,1)}.ant-btn .anticon.anticon-minus>svg,.ant-btn .anticon.anticon-plus>svg{shape-rendering:optimizeSpeed}.ant-btn.ant-btn-loading{position:relative}.ant-btn.ant-btn-loading:not([disabled]){pointer-events:none}.ant-btn.ant-btn-loading:before{display:block}.ant-btn.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only){padding-left:29px}.ant-btn.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) .anticon:not(:last-child){margin-left:-14px}.ant-btn-sm.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only){padding-left:24px}.ant-btn-sm.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) .anticon{margin-left:-17px}.ant-btn-group{display:inline-flex}.ant-btn-group,.ant-btn-group>.ant-btn,.ant-btn-group>span>.ant-btn{position:relative}.ant-btn-group>.ant-btn.active,.ant-btn-group>.ant-btn:active,.ant-btn-group>.ant-btn:focus,.ant-btn-group>.ant-btn:hover,.ant-btn-group>span>.ant-btn.active,.ant-btn-group>span>.ant-btn:active,.ant-btn-group>span>.ant-btn:focus,.ant-btn-group>span>.ant-btn:hover{z-index:2}.ant-btn-group>.ant-btn:disabled,.ant-btn-group>span>.ant-btn:disabled{z-index:0}.ant-btn-group>.ant-btn-icon-only{font-size:14px}.ant-btn-group-lg>.ant-btn,.ant-btn-group-lg>span>.ant-btn{height:40px;padding:0 15px;font-size:16px;border-radius:0;line-height:38px}.ant-btn-group-lg>.ant-btn.ant-btn-icon-only{width:40px;height:40px;padding-right:0;padding-left:0}.ant-btn-group-sm>.ant-btn,.ant-btn-group-sm>span>.ant-btn{height:24px;padding:0 7px;font-size:14px;border-radius:0;line-height:22px}.ant-btn-group-sm>.ant-btn>.anticon,.ant-btn-group-sm>span>.ant-btn>.anticon{font-size:14px}.ant-btn-group-sm>.ant-btn.ant-btn-icon-only{width:24px;height:24px;padding-right:0;padding-left:0}.ant-btn+.ant-btn-group,.ant-btn-group+.ant-btn,.ant-btn-group+.ant-btn-group,.ant-btn-group .ant-btn+.ant-btn,.ant-btn-group .ant-btn+span,.ant-btn-group>span+span,.ant-btn-group span+.ant-btn{margin-left:-1px}.ant-btn-group .ant-btn-primary+.ant-btn:not(.ant-btn-primary):not([disabled]){border-left-color:transparent}.ant-btn-group .ant-btn{border-radius:0}.ant-btn-group>.ant-btn:first-child,.ant-btn-group>span:first-child>.ant-btn{margin-left:0}.ant-btn-group>.ant-btn:only-child,.ant-btn-group>span:only-child>.ant-btn{border-radius:1rem}.ant-btn-group>.ant-btn:first-child:not(:last-child),.ant-btn-group>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.ant-btn-group>.ant-btn:last-child:not(:first-child),.ant-btn-group>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:1rem;border-bottom-right-radius:1rem}.ant-btn-group-sm>.ant-btn:only-child,.ant-btn-group-sm>span:only-child>.ant-btn{border-radius:1rem}.ant-btn-group-sm>.ant-btn:first-child:not(:last-child),.ant-btn-group-sm>span:first-child:not(:last-child)>.ant-btn{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.ant-btn-group-sm>.ant-btn:last-child:not(:first-child),.ant-btn-group-sm>span:last-child:not(:first-child)>.ant-btn{border-top-right-radius:1rem;border-bottom-right-radius:1rem}.ant-btn-group>.ant-btn-group{float:left}.ant-btn-group>.ant-btn-group:not(:first-child):not(:last-child)>.ant-btn{border-radius:0}.ant-btn-group>.ant-btn-group:first-child:not(:last-child)>.ant-btn:last-child{padding-right:8px;border-top-right-radius:0;border-bottom-right-radius:0}.ant-btn-group>.ant-btn-group:last-child:not(:first-child)>.ant-btn:first-child{padding-left:8px;border-top-left-radius:0;border-bottom-left-radius:0}.ant-btn:active>span,.ant-btn:focus>span{position:relative}.ant-btn>.anticon+span,.ant-btn>span+.anticon{margin-left:8px}.ant-btn-background-ghost{color:#fff;background:transparent!important;border-color:#fff}.ant-btn-background-ghost.ant-btn-primary{color:#008771;background-color:transparent;border-color:#008771;text-shadow:none}.ant-btn-background-ghost.ant-btn-primary>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary:focus,.ant-btn-background-ghost.ant-btn-primary:hover{color:#18947b;background-color:transparent;border-color:#18947b}.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary.active,.ant-btn-background-ghost.ant-btn-primary:active{color:#006154;background-color:transparent;border-color:#006154}.ant-btn-background-ghost.ant-btn-primary.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-primary-disabled,.ant-btn-background-ghost.ant-btn-primary-disabled.active,.ant-btn-background-ghost.ant-btn-primary-disabled:active,.ant-btn-background-ghost.ant-btn-primary-disabled:focus,.ant-btn-background-ghost.ant-btn-primary-disabled:hover,.ant-btn-background-ghost.ant-btn-primary.disabled,.ant-btn-background-ghost.ant-btn-primary.disabled.active,.ant-btn-background-ghost.ant-btn-primary.disabled:active,.ant-btn-background-ghost.ant-btn-primary.disabled:focus,.ant-btn-background-ghost.ant-btn-primary.disabled:hover,.ant-btn-background-ghost.ant-btn-primary[disabled],.ant-btn-background-ghost.ant-btn-primary[disabled].active,.ant-btn-background-ghost.ant-btn-primary[disabled]:active,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-primary-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-primary-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-primary[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger{color:#ff4d4f;background-color:transparent;border-color:#ff4d4f;text-shadow:none}.ant-btn-background-ghost.ant-btn-danger>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger:focus,.ant-btn-background-ghost.ant-btn-danger:hover{color:#ff7875;background-color:transparent;border-color:#ff7875}.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger.active,.ant-btn-background-ghost.ant-btn-danger:active{color:#d9363e;background-color:transparent;border-color:#d9363e}.ant-btn-background-ghost.ant-btn-danger.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-danger-disabled,.ant-btn-background-ghost.ant-btn-danger-disabled.active,.ant-btn-background-ghost.ant-btn-danger-disabled:active,.ant-btn-background-ghost.ant-btn-danger-disabled:focus,.ant-btn-background-ghost.ant-btn-danger-disabled:hover,.ant-btn-background-ghost.ant-btn-danger.disabled,.ant-btn-background-ghost.ant-btn-danger.disabled.active,.ant-btn-background-ghost.ant-btn-danger.disabled:active,.ant-btn-background-ghost.ant-btn-danger.disabled:focus,.ant-btn-background-ghost.ant-btn-danger.disabled:hover,.ant-btn-background-ghost.ant-btn-danger[disabled],.ant-btn-background-ghost.ant-btn-danger[disabled].active,.ant-btn-background-ghost.ant-btn-danger[disabled]:active,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-danger-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-danger-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-danger[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-link{color:#008771;background-color:transparent;border-color:transparent;text-shadow:none;color:#fff}.ant-btn-background-ghost.ant-btn-link>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-link:focus,.ant-btn-background-ghost.ant-btn-link:hover{color:#18947b;background-color:transparent;border-color:transparent}.ant-btn-background-ghost.ant-btn-link:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link:hover>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-link.active,.ant-btn-background-ghost.ant-btn-link:active{color:#006154;background-color:transparent;border-color:transparent}.ant-btn-background-ghost.ant-btn-link.active>a:only-child,.ant-btn-background-ghost.ant-btn-link:active>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-background-ghost.ant-btn-link-disabled,.ant-btn-background-ghost.ant-btn-link-disabled.active,.ant-btn-background-ghost.ant-btn-link-disabled:active,.ant-btn-background-ghost.ant-btn-link-disabled:focus,.ant-btn-background-ghost.ant-btn-link-disabled:hover,.ant-btn-background-ghost.ant-btn-link.disabled,.ant-btn-background-ghost.ant-btn-link.disabled.active,.ant-btn-background-ghost.ant-btn-link.disabled:active,.ant-btn-background-ghost.ant-btn-link.disabled:focus,.ant-btn-background-ghost.ant-btn-link.disabled:hover,.ant-btn-background-ghost.ant-btn-link[disabled],.ant-btn-background-ghost.ant-btn-link[disabled].active,.ant-btn-background-ghost.ant-btn-link[disabled]:active,.ant-btn-background-ghost.ant-btn-link[disabled]:focus,.ant-btn-background-ghost.ant-btn-link[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-btn-background-ghost.ant-btn-link-disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link-disabled>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled.active>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:active>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link.disabled>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled].active>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:active>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:focus>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]:hover>a:only-child,.ant-btn-background-ghost.ant-btn-link[disabled]>a:only-child{color:currentColor}.ant-btn-background-ghost.ant-btn-link-disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link-disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled.active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link.disabled>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled].active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:active>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:focus>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]:hover>a:only-child:after,.ant-btn-background-ghost.ant-btn-link[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-btn-two-chinese-chars:first-letter{letter-spacing:.34em}.ant-btn-two-chinese-chars>:not(.anticon){margin-right:-.34em;letter-spacing:.34em}.ant-btn-block{width:100%}.ant-btn:empty{vertical-align:top}a.ant-btn{padding-top:.1px;line-height:30px}a.ant-btn-lg{line-height:38px}a.ant-btn-sm{line-height:22px}.ant-avatar{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;overflow:hidden;color:#fff;white-space:nowrap;text-align:center;vertical-align:middle;background:#ccc;width:32px;height:32px;line-height:32px;border-radius:50%}.ant-avatar-image{background:transparent}.ant-avatar-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar.ant-avatar-icon{font-size:18px}.ant-avatar-lg{width:40px;height:40px;line-height:40px;border-radius:50%}.ant-avatar-lg-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-lg.ant-avatar-icon{font-size:24px}.ant-avatar-sm{width:24px;height:24px;line-height:24px;border-radius:50%}.ant-avatar-sm-string{position:absolute;left:50%;transform-origin:0 center}.ant-avatar-sm.ant-avatar-icon{font-size:14px}.ant-avatar-square{border-radius:1rem}.ant-avatar>img{display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ant-back-top{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:fixed;right:100px;bottom:50px;z-index:10;width:40px;height:40px;cursor:pointer}.ant-back-top-content{width:40px;height:40px;overflow:hidden;color:#fff;text-align:center;background-color:rgba(0,0,0,.45);border-radius:20px;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-back-top-content:hover{background-color:rgba(0,0,0,.65);transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-back-top-icon{width:14px;height:16px;margin:12px auto;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAABGdBTUEAALGPC/xhBQAAAbtJREFUWAntmMtKw0AUhhMvS5cuxILgQlRUpIggIoKIIoigG1eC+AA+jo+i6FIXBfeuXIgoeKVeitVWJX5HWhhDksnUpp3FDPyZk3Nm5nycmZKkXhAEOXSA3lG7muTeRzmfy6HneUvIhnYkQK+Q9NhAA0Opg0vBEhjBKHiyb8iGMyQMOYuK41BcBSypAL+MYXSKjtFAW7EAGEO3qN4uMQbbAkXiSfRQJ1H6a+yhlkKRcAoVFYiweYNjtCVQJJpBz2GCiPt7fBOZQpFgDpUikse5HgnkM4Fi4QX0Fpc5wf9EbLqpUCy4jMoJSXWhFwbMNgWKhVbRhy5jirhs9fy/oFhgHVVTJEs7RLZ8sSEoJm6iz7SZDMbJ+/OKERQTttCXQRLToRUmrKWCYuA2+jbN0MB4OQobYShfdTCgn/sL1K36M7TLrN3n+758aPy2rrpR6+/od5E8tf/A1uLS9aId5T7J3CNYihkQ4D9PiMdMC7mp4rjB9kjFjZp8BlnVHJBuO1yFXIV0FdDF3RlyFdJVQBdv5AxVdIsq8apiZ2PyYO1EVykesGfZEESsCkweyR8MUW+V8uJ1gkYipmpdP1pm2aJVPEGzAAAAAElFTkSuQmCC) 100%/100% no-repeat}@media screen and (max-width:768px){.ant-back-top{right:60px}}@media screen and (max-width:480px){.ant-back-top{right:20px}}.ant-badge{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;color:unset;line-height:1}.ant-badge-count{min-width:20px;height:20px;padding:0 6px;color:#fff;font-weight:400;font-size:12px;line-height:20px;white-space:nowrap;text-align:center;background:#f5222d;border-radius:10px;box-shadow:0 0 0 1px #fff}.ant-badge-count a,.ant-badge-count a:hover{color:#fff}.ant-badge-multiple-words{padding:0 8px}.ant-badge-dot{width:6px;height:6px;background:#f5222d;border-radius:100%;box-shadow:0 0 0 1px #fff}.ant-badge-count,.ant-badge-dot,.ant-badge .ant-scroll-number-custom-component{position:absolute;top:0;right:0;z-index:1;transform:translate(50%,-50%);transform-origin:100% 0}.ant-badge-status{line-height:inherit;vertical-align:baseline}.ant-badge-status-dot{position:relative;top:-1px;display:inline-block;width:6px;height:6px;vertical-align:middle;border-radius:50%}.ant-badge-status-success{background-color:#008771}.ant-badge-status-processing{position:relative;background-color:#1890ff}.ant-badge-status-processing:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #1890ff;border-radius:50%;animation:antStatusProcessing 1.2s ease-in-out infinite;content:""}.ant-badge-status-default{background-color:#d9d9d9}.ant-badge-status-error{background-color:#f5222d}.ant-badge-status-warning{background-color:#faad14}.ant-badge-status-magenta,.ant-badge-status-pink{background:#eb2f96}.ant-badge-status-red{background:#f5222d}.ant-badge-status-volcano{background:#fa541c}.ant-badge-status-orange{background:#fa8c16}.ant-badge-status-yellow{background:#fadb14}.ant-badge-status-gold{background:#faad14}.ant-badge-status-cyan{background:#13c2c2}.ant-badge-status-lime{background:#a0d911}.ant-badge-status-green{background:#008771}.ant-badge-status-blue{background:#1890ff}.ant-badge-status-geekblue{background:#2f54eb}.ant-badge-status-purple{background:#722ed1}.ant-badge-status-text{margin-left:8px;color:rgba(0,0,0,.65);font-size:14px}.ant-badge-zoom-appear,.ant-badge-zoom-enter{animation:antZoomBadgeIn .3s cubic-bezier(.12,.4,.29,1.46);animation-fill-mode:both}.ant-badge-zoom-leave{animation:antZoomBadgeOut .3s cubic-bezier(.71,-.46,.88,.6);animation-fill-mode:both}.ant-badge-not-a-wrapper:not(.ant-badge-status){vertical-align:middle}.ant-badge-not-a-wrapper .ant-scroll-number{position:relative;top:auto;display:block}.ant-badge-not-a-wrapper .ant-badge-count{transform:none}@keyframes antStatusProcessing{0%{transform:scale(.8);opacity:.5}to{transform:scale(2.4);opacity:0}}.ant-scroll-number{overflow:hidden}.ant-scroll-number-only{display:inline-block;height:20px;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-scroll-number-only>p.ant-scroll-number-only-unit{height:20px;margin:0}.ant-scroll-number-symbol{vertical-align:top}@keyframes antZoomBadgeIn{0%{transform:scale(0) translate(50%,-50%);opacity:0}to{transform:scale(1) translate(50%,-50%)}}@keyframes antZoomBadgeOut{0%{transform:scale(1) translate(50%,-50%)}to{transform:scale(0) translate(50%,-50%);opacity:0}}.ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";color:rgba(0,0,0,.45);font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:rgba(0,0,0,.45);transition:color .3s}.ant-breadcrumb a:hover{color:#18947b}.ant-breadcrumb>span:last-child,.ant-breadcrumb>span:last-child a{color:rgba(0,0,0,.65)}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:rgba(0,0,0,.45)}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-menu{box-sizing:border-box;font-size:14px;font-variant:tabular-nums;line-height:1.5;font-feature-settings:"tnum";margin:0;padding:0;color:rgba(0,0,0,.65);line-height:0;list-style:none;background:#fff;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15);transition:background .3s,width .3s cubic-bezier(.2,0,0,1) 0s;zoom:1}.ant-menu:after,.ant-menu:before{display:table;content:""}.ant-menu:after{clear:both}.ant-menu ol,.ant-menu ul{margin:0;padding:0;list-style:none}.ant-menu-hidden{display:none}.ant-menu-item-group-title{padding:8px 16px;color:rgba(0,0,0,.45);font-size:14px;line-height:1.5;transition:all .3s}.ant-menu-submenu,.ant-menu-submenu-inline{transition:border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-selected{color:#008771}.ant-menu-item:active,.ant-menu-submenu-title:active{background:#b3c7c0}.ant-menu-submenu .ant-menu-sub{cursor:auto;transition:background .3s cubic-bezier(.645,.045,.355,1),padding .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item>a{display:block;color:rgba(0,0,0,.65)}.ant-menu-item>a:hover{color:#008771}.ant-menu-item>a:before{position:absolute;top:0;right:0;bottom:0;left:0;background-color:transparent;content:""}.ant-menu-item>.ant-badge>a{color:rgba(0,0,0,.65)}.ant-menu-item>.ant-badge>a:hover{color:#008771}.ant-menu-item-divider{height:1px;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-menu-item-active,.ant-menu-item:hover,.ant-menu-submenu-active,.ant-menu-submenu-title:hover,.ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open{color:#008771}.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#008771}.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color:#b3c7c0}.ant-menu-inline,.ant-menu-vertical,.ant-menu-vertical-left{border-right:1px solid #e8e8e8}.ant-menu-vertical-right{border-left:1px solid #e8e8e8}.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-vertical-left.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item:after,.ant-menu-vertical.ant-menu-sub .ant-menu-item:after{border-right:0}.ant-menu-vertical-left.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-left.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-item,.ant-menu-vertical-right.ant-menu-sub>.ant-menu-submenu,.ant-menu-vertical.ant-menu-sub>.ant-menu-item,.ant-menu-vertical.ant-menu-sub>.ant-menu-submenu{transform-origin:0 0}.ant-menu-horizontal.ant-menu-sub{min-width:114px}.ant-menu-item,.ant-menu-submenu-title{position:relative;display:block;margin:0;padding:0 20px;white-space:nowrap;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1),border-color .3s cubic-bezier(.645,.045,.355,1),background .3s cubic-bezier(.645,.045,.355,1),padding .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .anticon,.ant-menu-submenu-title .anticon{min-width:14px;margin-right:10px;font-size:14px;transition:font-size .15s cubic-bezier(.215,.61,.355,1),margin .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-item .anticon+span,.ant-menu-submenu-title .anticon+span{opacity:1;transition:opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1)}.ant-menu>.ant-menu-item-divider{height:1px;margin:1px 0;padding:0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-menu-submenu-popup{position:absolute;z-index:1050;border-radius:1rem}.ant-menu-submenu-popup .submenu-title-wrapper{padding-right:20px}.ant-menu-submenu-popup:before{position:absolute;top:-7px;right:0;bottom:0;left:0;opacity:.0001;content:" "}.ant-menu-submenu>.ant-menu{background-color:#fff;border-radius:1rem}.ant-menu-submenu>.ant-menu-submenu-title:after{transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow{position:absolute;top:50%;right:16px;width:10px;transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{position:absolute;width:6px;height:1.5px;background:#fff;background:rgba(0,0,0,.65)\9;background-image:linear-gradient(90deg,rgba(0,0,0,.65),rgba(0,0,0,.65));background-image:none\9;border-radius:2px;transition:background .3s cubic-bezier(.645,.045,.355,1),transform .3s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1);content:""}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(45deg) translateY(-2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(-45deg) translateY(2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-inline>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-left>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical-right>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before,.ant-menu-submenu-vertical>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:after,.ant-menu-submenu-vertical>.ant-menu-submenu-title:hover .ant-menu-submenu-arrow:before{background:linear-gradient(90deg,#008771,#008771)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(-45deg) translateX(2px)}.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(45deg) translateX(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow{transform:translateY(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:after{transform:rotate(-45deg) translateX(-2px)}.ant-menu-submenu-open.ant-menu-submenu-inline>.ant-menu-submenu-title .ant-menu-submenu-arrow:before{transform:rotate(45deg) translateX(2px)}.ant-menu-vertical-left .ant-menu-submenu-selected,.ant-menu-vertical-left .ant-menu-submenu-selected>a,.ant-menu-vertical-right .ant-menu-submenu-selected,.ant-menu-vertical-right .ant-menu-submenu-selected>a,.ant-menu-vertical .ant-menu-submenu-selected,.ant-menu-vertical .ant-menu-submenu-selected>a{color:#008771}.ant-menu-horizontal{line-height:46px;white-space:nowrap;border:0;border-bottom:1px solid #e8e8e8;box-shadow:none}.ant-menu-horizontal>.ant-menu-item,.ant-menu-horizontal>.ant-menu-submenu{position:relative;top:1px;display:inline-block;vertical-align:bottom;border-bottom:2px solid transparent}.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover{color:#008771;border-bottom:2px solid #008771}.ant-menu-horizontal>.ant-menu-item>a{display:block;color:rgba(0,0,0,.65)}.ant-menu-horizontal>.ant-menu-item>a:hover{color:#008771}.ant-menu-horizontal>.ant-menu-item>a:before{bottom:-2px}.ant-menu-horizontal>.ant-menu-item-selected>a{color:#008771}.ant-menu-horizontal:after{display:block;clear:both;height:0;content:"\20"}.ant-menu-inline .ant-menu-item,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical .ant-menu-item{position:relative}.ant-menu-inline .ant-menu-item:after,.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-vertical .ant-menu-item:after{position:absolute;top:0;right:0;bottom:0;border-right:3px solid #008771;transform:scaleY(.0001);opacity:0;transition:transform .15s cubic-bezier(.215,.61,.355,1),opacity .15s cubic-bezier(.215,.61,.355,1);content:""}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title,.ant-menu-vertical-left .ant-menu-item,.ant-menu-vertical-left .ant-menu-submenu-title,.ant-menu-vertical-right .ant-menu-item,.ant-menu-vertical-right .ant-menu-submenu-title,.ant-menu-vertical .ant-menu-item,.ant-menu-vertical .ant-menu-submenu-title{height:40px;margin-top:4px;margin-bottom:4px;padding:0 16px;overflow:hidden;font-size:14px;line-height:40px;text-overflow:ellipsis}.ant-menu-inline .ant-menu-submenu,.ant-menu-vertical-left .ant-menu-submenu,.ant-menu-vertical-right .ant-menu-submenu,.ant-menu-vertical .ant-menu-submenu{padding-bottom:.02px}.ant-menu-inline .ant-menu-item:not(:last-child),.ant-menu-vertical-left .ant-menu-item:not(:last-child),.ant-menu-vertical-right .ant-menu-item:not(:last-child),.ant-menu-vertical .ant-menu-item:not(:last-child){margin-bottom:8px}.ant-menu-inline>.ant-menu-item,.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-left>.ant-menu-item,.ant-menu-vertical-left>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical-right>.ant-menu-item,.ant-menu-vertical-right>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-vertical>.ant-menu-item,.ant-menu-vertical>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px}.ant-menu-inline{width:100%}.ant-menu-inline .ant-menu-item-selected:after,.ant-menu-inline .ant-menu-selected:after{transform:scaleY(1);opacity:1;transition:transform .15s cubic-bezier(.645,.045,.355,1),opacity .15s cubic-bezier(.645,.045,.355,1)}.ant-menu-inline .ant-menu-item,.ant-menu-inline .ant-menu-submenu-title{width:calc(100% + 1px)}.ant-menu-inline .ant-menu-submenu-title{padding-right:34px}.ant-menu-inline-collapsed{width:80px}.ant-menu-inline-collapsed>.ant-menu-item,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title{left:0;padding:0 32px!important;text-overflow:clip}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-item .ant-menu-submenu-arrow,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .ant-menu-submenu-arrow{display:none}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon,.ant-menu-inline-collapsed>.ant-menu-item .anticon,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon{margin:0;font-size:16px;line-height:40px}.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item .anticon+span,.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span,.ant-menu-inline-collapsed>.ant-menu-item .anticon+span,.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title .anticon+span{display:inline-block;max-width:0;opacity:0}.ant-menu-inline-collapsed-tooltip{pointer-events:none}.ant-menu-inline-collapsed-tooltip .anticon{display:none}.ant-menu-inline-collapsed-tooltip a{color:hsla(0,0%,100%,.85)}.ant-menu-inline-collapsed .ant-menu-item-group-title{padding-right:4px;padding-left:4px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-menu-item-group-list{margin:0;padding:0}.ant-menu-item-group-list .ant-menu-item,.ant-menu-item-group-list .ant-menu-submenu-title{padding:0 16px 0 28px}.ant-menu-root.ant-menu-inline,.ant-menu-root.ant-menu-vertical,.ant-menu-root.ant-menu-vertical-left,.ant-menu-root.ant-menu-vertical-right{box-shadow:none}.ant-menu-sub.ant-menu-inline{padding:0;border:0;border-radius:0;box-shadow:none}.ant-menu-sub.ant-menu-inline>.ant-menu-item,.ant-menu-sub.ant-menu-inline>.ant-menu-submenu>.ant-menu-submenu-title{height:40px;line-height:40px;list-style-position:inside;list-style-type:disc}.ant-menu-sub.ant-menu-inline .ant-menu-item-group-title{padding-left:32px}.ant-menu-item-disabled,.ant-menu-submenu-disabled{color:rgba(0,0,0,.25)!important;background:none;border-color:transparent!important;cursor:not-allowed}.ant-menu-item-disabled>a,.ant-menu-submenu-disabled>a{color:rgba(0,0,0,.25)!important;pointer-events:none}.ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-submenu-disabled>.ant-menu-submenu-title{color:rgba(0,0,0,.25)!important;cursor:not-allowed}.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:rgba(0,0,0,.25)!important}.ant-menu-dark,.ant-menu-dark .ant-menu-sub{color:hsla(0,0%,100%,.65);background:#001529}.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow{opacity:.45;transition:all .3s}.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-sub .ant-menu-submenu-title .ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title .ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark.ant-menu-submenu-popup{background:transparent}.ant-menu-dark .ant-menu-inline.ant-menu-sub{background:#000c17;box-shadow:inset 0 2px 8px rgba(0,0,0,.45)}.ant-menu-dark.ant-menu-horizontal{border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item,.ant-menu-dark.ant-menu-horizontal>.ant-menu-submenu{top:0;margin-top:0;border-color:#001529;border-bottom:0}.ant-menu-dark.ant-menu-horizontal>.ant-menu-item>a:before{bottom:0}.ant-menu-dark .ant-menu-item,.ant-menu-dark .ant-menu-item-group-title,.ant-menu-dark .ant-menu-item>a{color:hsla(0,0%,100%,.65)}.ant-menu-dark.ant-menu-inline,.ant-menu-dark.ant-menu-vertical,.ant-menu-dark.ant-menu-vertical-left,.ant-menu-dark.ant-menu-vertical-right{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item,.ant-menu-dark.ant-menu-vertical .ant-menu-item{left:0;margin-left:0;border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-left .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical-right .ant-menu-item:after,.ant-menu-dark.ant-menu-vertical .ant-menu-item:after{border-right:0}.ant-menu-dark.ant-menu-inline .ant-menu-item,.ant-menu-dark.ant-menu-inline .ant-menu-submenu-title{width:100%}.ant-menu-dark .ant-menu-item-active,.ant-menu-dark .ant-menu-item:hover,.ant-menu-dark .ant-menu-submenu-active,.ant-menu-dark .ant-menu-submenu-open,.ant-menu-dark .ant-menu-submenu-selected,.ant-menu-dark .ant-menu-submenu-title:hover{color:#fff;background-color:transparent}.ant-menu-dark .ant-menu-item-active>a,.ant-menu-dark .ant-menu-item:hover>a,.ant-menu-dark .ant-menu-submenu-active>a,.ant-menu-dark .ant-menu-submenu-open>a,.ant-menu-dark .ant-menu-submenu-selected>a,.ant-menu-dark .ant-menu-submenu-title:hover>a{color:#fff}.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow{opacity:1}.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-active>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-open>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-selected>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title:hover>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-title:hover>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:#fff}.ant-menu-dark .ant-menu-item:hover{background-color:transparent}.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}.ant-menu-dark .ant-menu-item-selected:after{border-right:0}.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#fff}.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#008771}.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}.ant-tooltip{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1060;display:block;max-width:250px;visibility:visible}.ant-tooltip-hidden{display:none}.ant-tooltip-placement-top,.ant-tooltip-placement-topLeft,.ant-tooltip-placement-topRight{padding-bottom:8px}.ant-tooltip-placement-right,.ant-tooltip-placement-rightBottom,.ant-tooltip-placement-rightTop{padding-left:8px}.ant-tooltip-placement-bottom,.ant-tooltip-placement-bottomLeft,.ant-tooltip-placement-bottomRight{padding-top:8px}.ant-tooltip-placement-left,.ant-tooltip-placement-leftBottom,.ant-tooltip-placement-leftTop{padding-right:8px}.ant-tooltip-inner{min-width:30px;min-height:32px;padding:6px 8px;color:#fff;text-align:left;text-decoration:none;word-wrap:break-word;background-color:rgba(0,0,0,.75);border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-tooltip-arrow{position:absolute;display:block;width:13.07106781px;height:13.07106781px;overflow:hidden;background:transparent;pointer-events:none}.ant-tooltip-arrow:before{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:5px;height:5px;margin:auto;background-color:rgba(0,0,0,.75);content:"";pointer-events:auto}.ant-tooltip-placement-top .ant-tooltip-arrow,.ant-tooltip-placement-topLeft .ant-tooltip-arrow,.ant-tooltip-placement-topRight .ant-tooltip-arrow{bottom:-5.07106781px}.ant-tooltip-placement-top .ant-tooltip-arrow:before,.ant-tooltip-placement-topLeft .ant-tooltip-arrow:before,.ant-tooltip-placement-topRight .ant-tooltip-arrow:before{box-shadow:3px 3px 7px rgba(0,0,0,.07);transform:translateY(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-top .ant-tooltip-arrow{left:50%;transform:translateX(-50%)}.ant-tooltip-placement-topLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-topRight .ant-tooltip-arrow{right:13px}.ant-tooltip-placement-right .ant-tooltip-arrow,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow,.ant-tooltip-placement-rightTop .ant-tooltip-arrow{left:-5.07106781px}.ant-tooltip-placement-right .ant-tooltip-arrow:before,.ant-tooltip-placement-rightBottom .ant-tooltip-arrow:before,.ant-tooltip-placement-rightTop .ant-tooltip-arrow:before{box-shadow:-3px 3px 7px rgba(0,0,0,.07);transform:translateX(6.53553391px) rotate(45deg)}.ant-tooltip-placement-right .ant-tooltip-arrow{top:50%;transform:translateY(-50%)}.ant-tooltip-placement-rightTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-rightBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-left .ant-tooltip-arrow,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow,.ant-tooltip-placement-leftTop .ant-tooltip-arrow{right:-5.07106781px}.ant-tooltip-placement-left .ant-tooltip-arrow:before,.ant-tooltip-placement-leftBottom .ant-tooltip-arrow:before,.ant-tooltip-placement-leftTop .ant-tooltip-arrow:before{box-shadow:3px -3px 7px rgba(0,0,0,.07);transform:translateX(-6.53553391px) rotate(45deg)}.ant-tooltip-placement-left .ant-tooltip-arrow{top:50%;transform:translateY(-50%)}.ant-tooltip-placement-leftTop .ant-tooltip-arrow{top:5px}.ant-tooltip-placement-leftBottom .ant-tooltip-arrow{bottom:5px}.ant-tooltip-placement-bottom .ant-tooltip-arrow,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{top:-5.07106781px}.ant-tooltip-placement-bottom .ant-tooltip-arrow:before,.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow:before,.ant-tooltip-placement-bottomRight .ant-tooltip-arrow:before{box-shadow:-3px -3px 7px rgba(0,0,0,.07);transform:translateY(6.53553391px) rotate(45deg)}.ant-tooltip-placement-bottom .ant-tooltip-arrow{left:50%;transform:translateX(-50%)}.ant-tooltip-placement-bottomLeft .ant-tooltip-arrow{left:13px}.ant-tooltip-placement-bottomRight .ant-tooltip-arrow{right:13px}.ant-dropdown{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;display:block}.ant-dropdown:before{position:absolute;top:-7px;right:0;bottom:-7px;left:-7px;z-index:-9999;opacity:.0001;content:" "}.ant-dropdown-wrap{position:relative}.ant-dropdown-wrap .ant-btn>.anticon-down{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-wrap .ant-btn>.anticon-down{font-size:12px}.ant-dropdown-wrap .anticon-down:before{transition:transform .2s}.ant-dropdown-wrap-open .anticon-down:before{transform:rotate(180deg)}.ant-dropdown-hidden,.ant-dropdown-menu-hidden{display:none}.ant-dropdown-menu{position:relative;margin:0;padding:4px 0;text-align:left;list-style-type:none;background-color:#fff;background-clip:padding-box;border-radius:1rem;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15);-webkit-transform:translateZ(0)}.ant-dropdown-menu-item-group-title{padding:5px 12px;color:rgba(0,0,0,.45);transition:all .3s}.ant-dropdown-menu-submenu-popup{position:absolute;z-index:1050}.ant-dropdown-menu-submenu-popup>.ant-dropdown-menu{transform-origin:0 0}.ant-dropdown-menu-submenu-popup li,.ant-dropdown-menu-submenu-popup ul{list-style:none}.ant-dropdown-menu-submenu-popup ul{margin-right:.3em;margin-left:.3em;padding:0}.ant-dropdown-menu-item,.ant-dropdown-menu-submenu-title{clear:both;margin:0;padding:5px 12px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px;line-height:22px;white-space:nowrap;cursor:pointer;transition:all .3s}.ant-dropdown-menu-item>.anticon:first-child,.ant-dropdown-menu-item>span>.anticon:first-child,.ant-dropdown-menu-submenu-title>.anticon:first-child,.ant-dropdown-menu-submenu-title>span>.anticon:first-child{min-width:12px;margin-right:8px;font-size:12px}.ant-dropdown-menu-item>a,.ant-dropdown-menu-submenu-title>a{display:block;margin:-5px -12px;padding:5px 12px;color:rgba(0,0,0,.65);transition:all .3s}.ant-dropdown-menu-item-selected,.ant-dropdown-menu-item-selected>a,.ant-dropdown-menu-submenu-title-selected,.ant-dropdown-menu-submenu-title-selected>a{color:#008771;background-color:#b3c7c0}.ant-dropdown-menu-item:hover,.ant-dropdown-menu-submenu-title:hover{background-color:#b3c7c0}.ant-dropdown-menu-item-disabled,.ant-dropdown-menu-submenu-title-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-dropdown-menu-item-disabled:hover,.ant-dropdown-menu-submenu-title-disabled:hover{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-item-divider,.ant-dropdown-menu-submenu-title-divider{height:1px;margin:4px 0;overflow:hidden;line-height:0;background-color:#e8e8e8}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow{position:absolute;right:8px}.ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow-icon,.ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:rgba(0,0,0,.45);font-style:normal;display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow-icon,:root .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{font-size:12px}.ant-dropdown-menu-item-group-list{margin:0 8px;padding:0;list-style:none}.ant-dropdown-menu-submenu-title{padding-right:26px}.ant-dropdown-menu-submenu-vertical{position:relative}.ant-dropdown-menu-submenu-vertical>.ant-dropdown-menu{position:absolute;top:0;left:100%;min-width:100%;margin-left:4px;transform-origin:0 0}.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-submenu.ant-dropdown-menu-submenu-disabled .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow-icon{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-dropdown-menu-submenu-selected .ant-dropdown-menu-submenu-title{color:#008771}.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-appear.slide-down-appear-active.ant-dropdown-placement-bottomRight,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-enter.slide-down-enter-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpIn}.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-appear.slide-up-appear-active.ant-dropdown-placement-topRight,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-enter.slide-up-enter-active.ant-dropdown-placement-topRight{animation-name:antSlideDownIn}.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomCenter,.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomLeft,.ant-dropdown.slide-down-leave.slide-down-leave-active.ant-dropdown-placement-bottomRight{animation-name:antSlideUpOut}.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topCenter,.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topLeft,.ant-dropdown.slide-up-leave.slide-up-leave-active.ant-dropdown-placement-topRight{animation-name:antSlideDownOut}.ant-dropdown-link>.anticon.anticon-down,.ant-dropdown-trigger>.anticon.anticon-down{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-link>.anticon.anticon-down,:root .ant-dropdown-trigger>.anticon.anticon-down{font-size:12px}.ant-dropdown-button{white-space:nowrap}.ant-dropdown-button.ant-btn-group>.ant-btn:last-child:not(:first-child){padding-right:8px;padding-left:8px}.ant-dropdown-button .anticon.anticon-down{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}:root .ant-dropdown-button .anticon.anticon-down{font-size:12px}.ant-dropdown-menu-dark,.ant-dropdown-menu-dark .ant-dropdown-menu{background:#001529}.ant-dropdown-menu-dark .ant-dropdown-menu-item,.ant-dropdown-menu-dark .ant-dropdown-menu-item .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a .ant-dropdown-menu-submenu-arrow:after,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title .ant-dropdown-menu-submenu-arrow:after{color:hsla(0,0%,100%,.65)}.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item>a:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-submenu-title:hover{color:#fff;background:transparent}.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected:hover,.ant-dropdown-menu-dark .ant-dropdown-menu-item-selected>a{color:#fff;background:#008771}.ant-fullcalendar{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";border-top:1px solid #d9d9d9;outline:none}.ant-select.ant-fullcalendar-year-select{min-width:90px}.ant-select.ant-fullcalendar-year-select.ant-select-sm{min-width:70px}.ant-select.ant-fullcalendar-month-select{min-width:80px;margin-left:8px}.ant-select.ant-fullcalendar-month-select.ant-select-sm{min-width:70px}.ant-fullcalendar-header{padding:11px 16px 11px 0;text-align:right}.ant-fullcalendar-header .ant-select-dropdown{text-align:left}.ant-fullcalendar-header .ant-radio-group{margin-left:8px;text-align:left}.ant-fullcalendar-header label.ant-radio-button{height:22px;padding:0 10px;line-height:20px}.ant-fullcalendar-date-panel{position:relative;outline:none}.ant-fullcalendar-calendar-body{padding:8px 12px}.ant-fullcalendar table{width:100%;max-width:100%;height:256px;background-color:transparent;border-collapse:collapse}.ant-fullcalendar table,.ant-fullcalendar td,.ant-fullcalendar th{border:0}.ant-fullcalendar td{position:relative}.ant-fullcalendar-calendar-table{margin-bottom:0;border-spacing:0}.ant-fullcalendar-column-header{width:33px;padding:0;line-height:18px;text-align:center}.ant-fullcalendar-column-header .ant-fullcalendar-column-header-inner{display:block;font-weight:400}.ant-fullcalendar-week-number-header .ant-fullcalendar-column-header-inner{display:none}.ant-fullcalendar-date,.ant-fullcalendar-month{text-align:center;transition:all .3s}.ant-fullcalendar-value{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:24px;background:transparent;border-radius:2px;transition:all .3s}.ant-fullcalendar-value:hover{background:#b3c7c0;cursor:pointer}.ant-fullcalendar-value:active{color:#fff;background:#008771}.ant-fullcalendar-month-panel-cell .ant-fullcalendar-value{width:48px}.ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-value,.ant-fullcalendar-today .ant-fullcalendar-value{box-shadow:inset 0 0 0 1px #008771}.ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-value,.ant-fullcalendar-selected-day .ant-fullcalendar-value{color:#fff;background:#008771}.ant-fullcalendar-disabled-cell-first-of-row .ant-fullcalendar-value{border-top-left-radius:1rem;border-bottom-left-radius:1rem}.ant-fullcalendar-disabled-cell-last-of-row .ant-fullcalendar-value{border-top-right-radius:1rem;border-bottom-right-radius:1rem}.ant-fullcalendar-last-month-cell .ant-fullcalendar-value,.ant-fullcalendar-next-month-btn-day .ant-fullcalendar-value{color:rgba(0,0,0,.25)}.ant-fullcalendar-month-panel-table{width:100%;table-layout:fixed;border-collapse:separate}.ant-fullcalendar-content{position:absolute;bottom:-9px;left:0;width:100%}.ant-fullcalendar-fullscreen{border-top:0}.ant-fullcalendar-fullscreen .ant-fullcalendar-table{table-layout:fixed}.ant-fullcalendar-fullscreen .ant-fullcalendar-header .ant-radio-group{margin-left:16px}.ant-fullcalendar-fullscreen .ant-fullcalendar-header label.ant-radio-button{height:32px;line-height:30px}.ant-fullcalendar-fullscreen .ant-fullcalendar-date,.ant-fullcalendar-fullscreen .ant-fullcalendar-month{display:block;height:116px;margin:0 4px;padding:4px 8px;color:rgba(0,0,0,.65);text-align:left;border-top:2px solid #e8e8e8;transition:background .3s}.ant-fullcalendar-fullscreen .ant-fullcalendar-date:hover,.ant-fullcalendar-fullscreen .ant-fullcalendar-month:hover{background:#b3c7c0;cursor:pointer}.ant-fullcalendar-fullscreen .ant-fullcalendar-date:active,.ant-fullcalendar-fullscreen .ant-fullcalendar-month:active{background:#77baa6}.ant-fullcalendar-fullscreen .ant-fullcalendar-column-header{padding-right:12px;padding-bottom:5px;text-align:right}.ant-fullcalendar-fullscreen .ant-fullcalendar-value{width:auto;text-align:right;background:transparent}.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-value{color:rgba(0,0,0,.65)}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-month,.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-date{background:transparent;border-top-color:#008771}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-current-cell .ant-fullcalendar-value,.ant-fullcalendar-fullscreen .ant-fullcalendar-today .ant-fullcalendar-value{box-shadow:none}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-month,.ant-fullcalendar-fullscreen .ant-fullcalendar-selected-day .ant-fullcalendar-date{background:#b3c7c0}.ant-fullcalendar-fullscreen .ant-fullcalendar-month-panel-selected-cell .ant-fullcalendar-value,.ant-fullcalendar-fullscreen .ant-fullcalendar-selected-day .ant-fullcalendar-value{color:#008771}.ant-fullcalendar-fullscreen .ant-fullcalendar-last-month-cell .ant-fullcalendar-date,.ant-fullcalendar-fullscreen .ant-fullcalendar-next-month-btn-day .ant-fullcalendar-date{color:rgba(0,0,0,.25)}.ant-fullcalendar-fullscreen .ant-fullcalendar-content{position:static;width:auto;height:88px;overflow-y:auto}.ant-fullcalendar-disabled-cell .ant-fullcalendar-date,.ant-fullcalendar-disabled-cell .ant-fullcalendar-date:hover{cursor:not-allowed}.ant-fullcalendar-disabled-cell:not(.ant-fullcalendar-today) .ant-fullcalendar-date,.ant-fullcalendar-disabled-cell:not(.ant-fullcalendar-today) .ant-fullcalendar-date:hover{background:transparent}.ant-fullcalendar-disabled-cell .ant-fullcalendar-value{width:auto;color:rgba(0,0,0,.25);border-radius:0;cursor:not-allowed}.ant-radio-group{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-radio-wrapper{margin:0 8px 0 0}.ant-radio,.ant-radio-wrapper{box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;white-space:nowrap;cursor:pointer}.ant-radio{margin:0;line-height:1;vertical-align:sub;outline:none}.ant-radio-input:focus+.ant-radio-inner,.ant-radio-wrapper:hover .ant-radio,.ant-radio:hover .ant-radio-inner{border-color:#008771}.ant-radio-input:focus+.ant-radio-inner{box-shadow:0 0 0 3px rgba(0,135,113,.08)}.ant-radio-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #008771;border-radius:50%;visibility:hidden;animation:antRadioEffect .36s ease-in-out;animation-fill-mode:both;content:""}.ant-radio-wrapper:hover .ant-radio:after,.ant-radio:hover:after{visibility:visible}.ant-radio-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:100px;transition:all .3s}.ant-radio-inner:after{position:absolute;top:3px;left:3px;display:table;width:8px;height:8px;background-color:#008771;border-top:0;border-left:0;border-radius:8px;transform:scale(0);opacity:0;transition:all .3s cubic-bezier(.78,.14,.15,.86);content:" "}.ant-radio-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;cursor:pointer;opacity:0}.ant-radio-checked .ant-radio-inner{border-color:#008771}.ant-radio-checked .ant-radio-inner:after{transform:scale(1);opacity:1;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-radio-disabled .ant-radio-inner{background-color:#f5f5f5;border-color:#d9d9d9!important;cursor:not-allowed}.ant-radio-disabled .ant-radio-inner:after{background-color:rgba(0,0,0,.2)}.ant-radio-disabled .ant-radio-input{cursor:not-allowed}.ant-radio-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}span.ant-radio+*{padding-right:8px;padding-left:8px}.ant-radio-button-wrapper{position:relative;display:inline-block;height:32px;margin:0;padding:0 15px;color:rgba(0,0,0,.65);line-height:30px;background:#fff;border:1px solid #d9d9d9;border-top:1.02px solid #d9d9d9;border-left:0;cursor:pointer;transition:color .3s,background .3s,border-color .3s,box-shadow .3s}.ant-radio-button-wrapper a{color:rgba(0,0,0,.65)}.ant-radio-button-wrapper>.ant-radio-button{display:block;width:0;height:0;margin-left:0}.ant-radio-group-large .ant-radio-button-wrapper{height:40px;font-size:16px;line-height:38px}.ant-radio-group-small .ant-radio-button-wrapper{height:24px;padding:0 7px;line-height:22px}.ant-radio-button-wrapper:not(:first-child):before{position:absolute;top:-1px;left:-1px;display:block;box-sizing:content-box;width:1px;height:100%;padding:1px 0;background-color:#d9d9d9;transition:background-color .3s;content:""}.ant-radio-button-wrapper:first-child{border-left:1px solid #d9d9d9;border-radius:1rem 0 0 1rem}.ant-radio-button-wrapper:last-child{border-radius:0 1rem 1rem 0}.ant-radio-button-wrapper:first-child:last-child{border-radius:1rem}.ant-radio-button-wrapper:hover{position:relative;color:#008771}.ant-radio-button-wrapper:focus-within{box-shadow:0 0 0 3px rgba(0,135,113,.08)}.ant-radio-button-wrapper .ant-radio-inner,.ant-radio-button-wrapper input[type=checkbox],.ant-radio-button-wrapper input[type=radio]{width:0;height:0;opacity:0;pointer-events:none}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){z-index:1;color:#008771;background:#fff;border-color:#008771}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):before{background-color:#008771}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child{border-color:#008771}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#18947b;border-color:#18947b}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover:before{background-color:#18947b}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#006154;border-color:#006154}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active:before{background-color:#006154}.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px rgba(0,135,113,.08)}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled){color:#fff;background:#008771;border-color:#008771}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover{color:#fff;background:#18947b;border-color:#18947b}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active{color:#fff;background:#006154;border-color:#006154}.ant-radio-group-solid .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):focus-within{box-shadow:0 0 0 3px rgba(0,135,113,.08)}.ant-radio-button-wrapper-disabled{cursor:not-allowed}.ant-radio-button-wrapper-disabled,.ant-radio-button-wrapper-disabled:first-child,.ant-radio-button-wrapper-disabled:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9}.ant-radio-button-wrapper-disabled:first-child{border-left-color:#d9d9d9}.ant-radio-button-wrapper-disabled.ant-radio-button-wrapper-checked{color:#fff;background-color:#e6e6e6;border-color:#d9d9d9;box-shadow:none}@keyframes antRadioEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}@supports (-moz-appearance:meterbar) and (background-blend-mode:difference,normal){.ant-radio{vertical-align:text-bottom}}.ant-card{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;background:#fff;border-radius:2px;transition:all .3s}.ant-card-hoverable{cursor:pointer}.ant-card-hoverable:hover{border-color:rgba(0,0,0,.09);box-shadow:0 2px 8px rgba(0,0,0,.09)}.ant-card-bordered{border:1px solid #e8e8e8}.ant-card-head{min-height:48px;margin-bottom:-1px;padding:0 24px;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;background:transparent;border-bottom:1px solid #e8e8e8;border-radius:2px 2px 0 0;zoom:1}.ant-card-head:after,.ant-card-head:before{display:table;content:""}.ant-card-head:after{clear:both}.ant-card-head-wrapper{display:flex;align-items:center}.ant-card-head-title{display:inline-block;flex:1;padding:16px 0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-card-head .ant-tabs{clear:both;margin-bottom:-17px;color:rgba(0,0,0,.65);font-weight:400;font-size:14px}.ant-card-head .ant-tabs-bar{border-bottom:1px solid #e8e8e8}.ant-card-extra{float:right;margin-left:auto;padding:16px 0;color:rgba(0,0,0,.65);font-weight:400;font-size:14px}.ant-card-body{padding:24px;zoom:1}.ant-card-body:after,.ant-card-body:before{display:table;content:""}.ant-card-body:after{clear:both}.ant-card-contain-grid:not(.ant-card-loading) .ant-card-body{margin:-1px 0 0 -1px;padding:0}.ant-card-grid{float:left;width:33.33%;padding:24px;border:0;border-radius:0;box-shadow:1px 0 0 0 #e8e8e8,0 1px 0 0 #e8e8e8,1px 1px 0 0 #e8e8e8,inset 1px 0 0 0 #e8e8e8,inset 0 1px 0 0 #e8e8e8;transition:all .3s}.ant-card-grid-hoverable:hover{position:relative;z-index:1;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-card-contain-tabs>.ant-card-head .ant-card-head-title{min-height:32px;padding-bottom:0}.ant-card-contain-tabs>.ant-card-head .ant-card-extra{padding-bottom:0}.ant-card-cover>*{display:block;width:100%}.ant-card-cover img{border-radius:2px 2px 0 0}.ant-card-actions{margin:0;padding:0;list-style:none;background:#fafafa;border-top:1px solid #e8e8e8;zoom:1}.ant-card-actions:after,.ant-card-actions:before{display:table;content:""}.ant-card-actions:after{clear:both}.ant-card-actions>li{float:left;margin:12px 0;color:rgba(0,0,0,.45);text-align:center}.ant-card-actions>li>span{position:relative;display:block;min-width:32px;font-size:14px;line-height:22px;cursor:pointer}.ant-card-actions>li>span:hover{color:#008771;transition:color .3s}.ant-card-actions>li>span>.anticon,.ant-card-actions>li>span a:not(.ant-btn){display:inline-block;width:100%;color:rgba(0,0,0,.45);line-height:22px;transition:color .3s}.ant-card-actions>li>span>.anticon:hover,.ant-card-actions>li>span a:not(.ant-btn):hover{color:#008771}.ant-card-actions>li>span>.anticon{font-size:16px;line-height:22px}.ant-card-actions>li:not(:last-child){border-right:1px solid #e8e8e8}.ant-card-type-inner .ant-card-head{padding:0 24px;background:#fafafa}.ant-card-type-inner .ant-card-head-title{padding:12px 0;font-size:14px}.ant-card-type-inner .ant-card-body{padding:16px 24px}.ant-card-type-inner .ant-card-extra{padding:13.5px 0}.ant-card-meta{margin:-4px 0;zoom:1}.ant-card-meta:after,.ant-card-meta:before{display:table;content:""}.ant-card-meta:after{clear:both}.ant-card-meta-avatar{float:left;padding-right:16px}.ant-card-meta-detail{overflow:hidden}.ant-card-meta-detail>div:not(:last-child){margin-bottom:8px}.ant-card-meta-title{overflow:hidden;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-overflow:ellipsis}.ant-card-meta-description{color:rgba(0,0,0,.45)}.ant-card-loading{overflow:hidden}.ant-card-loading .ant-card-body{-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-card-loading-content p{margin:0}.ant-card-loading-block{height:14px;margin:4px 0;background:linear-gradient(90deg,rgba(207,216,220,.2),rgba(207,216,220,.4),rgba(207,216,220,.2));background-size:600% 600%;border-radius:2px;animation:card-loading 1.4s ease infinite}@keyframes card-loading{0%,to{background-position:0 50%}50%{background-position:100% 50%}}.ant-card-small>.ant-card-head{min-height:36px;padding:0 12px;font-size:14px}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-head-title{padding:8px 0}.ant-card-small>.ant-card-head>.ant-card-head-wrapper>.ant-card-extra{padding:8px 0;font-size:14px}.ant-card-small>.ant-card-body{padding:12px}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-container{height:40px}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-ink-bar{visibility:hidden}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab{height:40px;margin:0 2px 0 0;padding:0 16px;line-height:38px;background:#fafafa;border:1px solid #e8e8e8;border-radius:1rem 1rem 0 0;transition:all .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active{height:40px;color:#008771;background:#fff;border-color:#e8e8e8;border-bottom:1px solid #fff}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-active:before{border-top:2px solid transparent}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-disabled{color:#008771;color:rgba(0,0,0,.25)}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab-inactive{padding:0}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-nav-wrap{margin-bottom:0}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab .ant-tabs-close-x{width:16px;height:16px;height:14px;margin-right:-5px;margin-left:3px;overflow:hidden;color:rgba(0,0,0,.45);font-size:12px;vertical-align:middle;transition:all .3s}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab .ant-tabs-close-x:hover{color:rgba(0,0,0,.85)}.ant-tabs.ant-tabs-card .ant-tabs-card-content>.ant-tabs-tabpane,.ant-tabs.ant-tabs-editable-card .ant-tabs-card-content>.ant-tabs-tabpane{transition:none!important}.ant-tabs.ant-tabs-card .ant-tabs-card-content>.ant-tabs-tabpane-inactive,.ant-tabs.ant-tabs-editable-card .ant-tabs-card-content>.ant-tabs-tabpane-inactive{overflow:hidden}.ant-tabs.ant-tabs-card .ant-tabs-card-bar .ant-tabs-tab:hover .anticon-close{opacity:1}.ant-tabs-extra-content{line-height:45px}.ant-tabs-extra-content .ant-tabs-new-tab{position:relative;width:20px;height:20px;color:rgba(0,0,0,.65);font-size:12px;line-height:20px;text-align:center;border:1px solid #e8e8e8;border-radius:2px;cursor:pointer;transition:all .3s}.ant-tabs-extra-content .ant-tabs-new-tab:hover{color:#008771;border-color:#008771}.ant-tabs-extra-content .ant-tabs-new-tab svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-tabs.ant-tabs-large .ant-tabs-extra-content{line-height:56px}.ant-tabs.ant-tabs-small .ant-tabs-extra-content{line-height:37px}.ant-tabs.ant-tabs-card .ant-tabs-extra-content{line-height:40px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-nav-container{height:100%}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab{margin-bottom:8px;border-bottom:1px solid #e8e8e8}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab-active,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{padding-bottom:4px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab:last-child,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab:last-child{margin-bottom:8px}.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-new-tab,.ant-tabs-vertical.ant-tabs-card .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-new-tab{width:90%}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-nav-wrap{margin-right:0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab{margin-right:1px;border-right:0;border-radius:1rem 0 0 1rem}.ant-tabs-vertical.ant-tabs-card.ant-tabs-left .ant-tabs-card-bar.ant-tabs-left-bar .ant-tabs-tab-active{margin-right:-1px;padding-right:18px}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-nav-wrap{margin-left:0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab{margin-left:1px;border-left:0;border-radius:0 1rem 1rem 0}.ant-tabs-vertical.ant-tabs-card.ant-tabs-right .ant-tabs-card-bar.ant-tabs-right-bar .ant-tabs-tab-active{margin-left:-1px;padding-left:18px}.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab{height:auto;border-top:0;border-bottom:1px solid #e8e8e8;border-radius:0 0 1rem 1rem}.ant-tabs .ant-tabs-card-bar.ant-tabs-bottom-bar .ant-tabs-tab-active{padding-top:1px;padding-bottom:0;color:#008771}.ant-tabs{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;overflow:hidden;zoom:1}.ant-tabs:after,.ant-tabs:before{display:table;content:""}.ant-tabs:after{clear:both}.ant-tabs-ink-bar{position:absolute;bottom:1px;left:0;z-index:1;box-sizing:border-box;width:0;height:2px;background-color:#008771;transform-origin:0 0}.ant-tabs-bar{margin:0 0 16px;border-bottom:1px solid #e8e8e8;outline:none}.ant-tabs-bar,.ant-tabs-nav-container{transition:padding .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav-container{position:relative;box-sizing:border-box;margin-bottom:-1px;overflow:hidden;font-size:14px;line-height:1.5;white-space:nowrap;zoom:1}.ant-tabs-nav-container:after,.ant-tabs-nav-container:before{display:table;content:""}.ant-tabs-nav-container:after{clear:both}.ant-tabs-nav-container-scrolling{padding-right:32px;padding-left:32px}.ant-tabs-bottom .ant-tabs-bottom-bar{margin-top:16px;margin-bottom:0;border-top:1px solid #e8e8e8;border-bottom:none}.ant-tabs-bottom .ant-tabs-bottom-bar .ant-tabs-ink-bar{top:1px;bottom:auto}.ant-tabs-bottom .ant-tabs-bottom-bar .ant-tabs-nav-container{margin-top:-1px;margin-bottom:0}.ant-tabs-tab-next,.ant-tabs-tab-prev{position:absolute;z-index:2;width:0;height:100%;color:rgba(0,0,0,.45);text-align:center;background-color:transparent;border:0;cursor:pointer;opacity:0;transition:width .3s cubic-bezier(.645,.045,.355,1),opacity .3s cubic-bezier(.645,.045,.355,1),color .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}.ant-tabs-tab-next.ant-tabs-tab-arrow-show,.ant-tabs-tab-prev.ant-tabs-tab-arrow-show{width:32px;height:100%;opacity:1;pointer-events:auto}.ant-tabs-tab-next:hover,.ant-tabs-tab-prev:hover{color:rgba(0,0,0,.65)}.ant-tabs-tab-next-icon,.ant-tabs-tab-prev-icon{position:absolute;top:50%;left:50%;font-weight:700;font-style:normal;font-variant:normal;line-height:inherit;text-align:center;text-transform:none;transform:translate(-50%,-50%)}.ant-tabs-tab-next-icon-target,.ant-tabs-tab-prev-icon-target{display:block;display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg)}:root .ant-tabs-tab-next-icon-target,:root .ant-tabs-tab-prev-icon-target{font-size:12px}.ant-tabs-tab-btn-disabled{cursor:not-allowed}.ant-tabs-tab-btn-disabled,.ant-tabs-tab-btn-disabled:hover{color:rgba(0,0,0,.25)}.ant-tabs-tab-next{right:2px}.ant-tabs-tab-prev{left:0}:root .ant-tabs-tab-prev{filter:none}.ant-tabs-nav-wrap{margin-bottom:-1px;overflow:hidden}.ant-tabs-nav-scroll{overflow:hidden;white-space:nowrap}.ant-tabs-nav{position:relative;display:inline-block;box-sizing:border-box;margin:0;padding-left:0;list-style:none;transition:transform .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav:after,.ant-tabs-nav:before{display:table;content:" "}.ant-tabs-nav:after{clear:both}.ant-tabs-nav .ant-tabs-tab{position:relative;display:inline-block;box-sizing:border-box;height:100%;margin:0 32px 0 0;padding:12px 16px;text-decoration:none;cursor:pointer;transition:color .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-nav .ant-tabs-tab:before{position:absolute;top:-1px;left:0;width:100%;border-top:2px solid transparent;border-radius:1rem 1rem 0 0;transition:all .3s;content:"";pointer-events:none}.ant-tabs-nav .ant-tabs-tab:last-child{margin-right:0}.ant-tabs-nav .ant-tabs-tab:hover{color:#18947b}.ant-tabs-nav .ant-tabs-tab:active{color:#006154}.ant-tabs-nav .ant-tabs-tab .anticon{margin-right:8px}.ant-tabs-nav .ant-tabs-tab-active{color:#008771;text-shadow:0 0 .25px currentColor}.ant-tabs-nav .ant-tabs-tab-disabled,.ant-tabs-nav .ant-tabs-tab-disabled:hover{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-tabs .ant-tabs-large-bar .ant-tabs-nav-container{font-size:16px}.ant-tabs .ant-tabs-large-bar .ant-tabs-tab{padding:16px}.ant-tabs .ant-tabs-small-bar .ant-tabs-nav-container{font-size:14px}.ant-tabs .ant-tabs-small-bar .ant-tabs-tab{padding:8px 16px}.ant-tabs-content:before{display:block;overflow:hidden;content:""}.ant-tabs .ant-tabs-bottom-content,.ant-tabs .ant-tabs-top-content{width:100%}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane{flex-shrink:0;width:100%;-webkit-backface-visibility:hidden;opacity:1;transition:opacity .45s}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane-inactive,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs .ant-tabs-bottom-content>.ant-tabs-tabpane-inactive input,.ant-tabs .ant-tabs-top-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-tabs .ant-tabs-bottom-content.ant-tabs-content-animated,.ant-tabs .ant-tabs-top-content.ant-tabs-content-animated{display:flex;flex-direction:row;transition:margin-left .3s cubic-bezier(.645,.045,.355,1);will-change:margin-left}.ant-tabs .ant-tabs-left-bar,.ant-tabs .ant-tabs-right-bar{height:100%;border-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-arrow-show,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-arrow-show{width:100%;height:32px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab{display:block;float:none;margin:0 0 16px;padding:8px 24px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab:last-child,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab:last-child{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-extra-content,.ant-tabs .ant-tabs-right-bar .ant-tabs-extra-content{text-align:center}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-scroll,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-scroll{width:auto}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{height:100%}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container.ant-tabs-nav-container-scrolling,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container.ant-tabs-nav-container-scrolling{padding:32px 0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{margin-bottom:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav{width:100%}.ant-tabs .ant-tabs-left-bar .ant-tabs-ink-bar,.ant-tabs .ant-tabs-right-bar .ant-tabs-ink-bar{top:0;bottom:auto;left:auto;width:2px;height:0}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-next,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-next{right:0;bottom:0;width:100%;height:32px}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab-prev,.ant-tabs .ant-tabs-right-bar .ant-tabs-tab-prev{top:0;width:100%;height:32px}.ant-tabs .ant-tabs-left-content,.ant-tabs .ant-tabs-right-content{width:auto;margin-top:0!important;overflow:hidden}.ant-tabs .ant-tabs-left-bar{float:left;margin-right:-1px;margin-bottom:0;border-right:1px solid #e8e8e8}.ant-tabs .ant-tabs-left-bar .ant-tabs-tab{text-align:right}.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-left-bar .ant-tabs-nav-wrap{margin-right:-1px}.ant-tabs .ant-tabs-left-bar .ant-tabs-ink-bar{right:1px}.ant-tabs .ant-tabs-left-content{padding-left:24px;border-left:1px solid #e8e8e8}.ant-tabs .ant-tabs-right-bar{float:right;margin-bottom:0;margin-left:-1px;border-left:1px solid #e8e8e8}.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-container,.ant-tabs .ant-tabs-right-bar .ant-tabs-nav-wrap{margin-left:-1px}.ant-tabs .ant-tabs-right-bar .ant-tabs-ink-bar{left:1px}.ant-tabs .ant-tabs-right-content{padding-right:24px;border-right:1px solid #e8e8e8}.ant-tabs-bottom .ant-tabs-ink-bar-animated,.ant-tabs-top .ant-tabs-ink-bar-animated{transition:transform .3s cubic-bezier(.645,.045,.355,1),width .2s cubic-bezier(.645,.045,.355,1),left .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-left .ant-tabs-ink-bar-animated,.ant-tabs-right .ant-tabs-ink-bar-animated{transition:transform .3s cubic-bezier(.645,.045,.355,1),height .2s cubic-bezier(.645,.045,.355,1),top .3s cubic-bezier(.645,.045,.355,1)}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-content-animated,.no-flex>.ant-tabs-content>.ant-tabs-content-animated{margin-left:0!important;transform:none!important}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-tabpane-inactive,.no-flex>.ant-tabs-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs-no-animation>.ant-tabs-content>.ant-tabs-tabpane-inactive input,.no-flex>.ant-tabs-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-tabs-left-content>.ant-tabs-content-animated,.ant-tabs-right-content>.ant-tabs-content-animated{margin-left:0!important;transform:none!important}.ant-tabs-left-content>.ant-tabs-tabpane-inactive,.ant-tabs-right-content>.ant-tabs-tabpane-inactive{height:0;padding:0!important;overflow:hidden;opacity:0;pointer-events:none}.ant-tabs-left-content>.ant-tabs-tabpane-inactive input,.ant-tabs-right-content>.ant-tabs-tabpane-inactive input{visibility:hidden}.ant-row{position:relative;height:auto;margin-right:0;margin-left:0;zoom:1;display:block;box-sizing:border-box}.ant-row:after,.ant-row:before{display:table;content:""}.ant-row+.ant-row:before,.ant-row:after{clear:both}.ant-row-flex{display:flex;flex-flow:row wrap}.ant-row-flex:after,.ant-row-flex:before{display:flex}.ant-row-flex-start{justify-content:flex-start}.ant-row-flex-center{justify-content:center}.ant-row-flex-end{justify-content:flex-end}.ant-row-flex-space-between{justify-content:space-between}.ant-row-flex-space-around{justify-content:space-around}.ant-row-flex-top{align-items:flex-start}.ant-row-flex-middle{align-items:center}.ant-row-flex-bottom{align-items:flex-end}.ant-col{position:relative;min-height:1px}.ant-col-1,.ant-col-2,.ant-col-3,.ant-col-4,.ant-col-5,.ant-col-6,.ant-col-7,.ant-col-8,.ant-col-9,.ant-col-10,.ant-col-11,.ant-col-12,.ant-col-13,.ant-col-14,.ant-col-15,.ant-col-16,.ant-col-17,.ant-col-18,.ant-col-19,.ant-col-20,.ant-col-21,.ant-col-22,.ant-col-23,.ant-col-24,.ant-col-lg-1,.ant-col-lg-2,.ant-col-lg-3,.ant-col-lg-4,.ant-col-lg-5,.ant-col-lg-6,.ant-col-lg-7,.ant-col-lg-8,.ant-col-lg-9,.ant-col-lg-10,.ant-col-lg-11,.ant-col-lg-12,.ant-col-lg-13,.ant-col-lg-14,.ant-col-lg-15,.ant-col-lg-16,.ant-col-lg-17,.ant-col-lg-18,.ant-col-lg-19,.ant-col-lg-20,.ant-col-lg-21,.ant-col-lg-22,.ant-col-lg-23,.ant-col-lg-24,.ant-col-md-1,.ant-col-md-2,.ant-col-md-3,.ant-col-md-4,.ant-col-md-5,.ant-col-md-6,.ant-col-md-7,.ant-col-md-8,.ant-col-md-9,.ant-col-md-10,.ant-col-md-11,.ant-col-md-12,.ant-col-md-13,.ant-col-md-14,.ant-col-md-15,.ant-col-md-16,.ant-col-md-17,.ant-col-md-18,.ant-col-md-19,.ant-col-md-20,.ant-col-md-21,.ant-col-md-22,.ant-col-md-23,.ant-col-md-24,.ant-col-sm-1,.ant-col-sm-2,.ant-col-sm-3,.ant-col-sm-4,.ant-col-sm-5,.ant-col-sm-6,.ant-col-sm-7,.ant-col-sm-8,.ant-col-sm-9,.ant-col-sm-10,.ant-col-sm-11,.ant-col-sm-12,.ant-col-sm-13,.ant-col-sm-14,.ant-col-sm-15,.ant-col-sm-16,.ant-col-sm-17,.ant-col-sm-18,.ant-col-sm-19,.ant-col-sm-20,.ant-col-sm-21,.ant-col-sm-22,.ant-col-sm-23,.ant-col-sm-24,.ant-col-xs-1,.ant-col-xs-2,.ant-col-xs-3,.ant-col-xs-4,.ant-col-xs-5,.ant-col-xs-6,.ant-col-xs-7,.ant-col-xs-8,.ant-col-xs-9,.ant-col-xs-10,.ant-col-xs-11,.ant-col-xs-12,.ant-col-xs-13,.ant-col-xs-14,.ant-col-xs-15,.ant-col-xs-16,.ant-col-xs-17,.ant-col-xs-18,.ant-col-xs-19,.ant-col-xs-20,.ant-col-xs-21,.ant-col-xs-22,.ant-col-xs-23,.ant-col-xs-24{position:relative;padding-right:0;padding-left:0}.ant-col-1,.ant-col-2,.ant-col-3,.ant-col-4,.ant-col-5,.ant-col-6,.ant-col-7,.ant-col-8,.ant-col-9,.ant-col-10,.ant-col-11,.ant-col-12,.ant-col-13,.ant-col-14,.ant-col-15,.ant-col-16,.ant-col-17,.ant-col-18,.ant-col-19,.ant-col-20,.ant-col-21,.ant-col-22,.ant-col-23,.ant-col-24{flex:0 0 auto;float:left}.ant-col-24{display:block;box-sizing:border-box;width:100%}.ant-col-push-24{left:100%}.ant-col-pull-24{right:100%}.ant-col-offset-24{margin-left:100%}.ant-col-order-24{order:24}.ant-col-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-push-23{left:95.83333333%}.ant-col-pull-23{right:95.83333333%}.ant-col-offset-23{margin-left:95.83333333%}.ant-col-order-23{order:23}.ant-col-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-push-22{left:91.66666667%}.ant-col-pull-22{right:91.66666667%}.ant-col-offset-22{margin-left:91.66666667%}.ant-col-order-22{order:22}.ant-col-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-push-21{left:87.5%}.ant-col-pull-21{right:87.5%}.ant-col-offset-21{margin-left:87.5%}.ant-col-order-21{order:21}.ant-col-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-push-20{left:83.33333333%}.ant-col-pull-20{right:83.33333333%}.ant-col-offset-20{margin-left:83.33333333%}.ant-col-order-20{order:20}.ant-col-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-push-19{left:79.16666667%}.ant-col-pull-19{right:79.16666667%}.ant-col-offset-19{margin-left:79.16666667%}.ant-col-order-19{order:19}.ant-col-18{display:block;box-sizing:border-box;width:75%}.ant-col-push-18{left:75%}.ant-col-pull-18{right:75%}.ant-col-offset-18{margin-left:75%}.ant-col-order-18{order:18}.ant-col-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-push-17{left:70.83333333%}.ant-col-pull-17{right:70.83333333%}.ant-col-offset-17{margin-left:70.83333333%}.ant-col-order-17{order:17}.ant-col-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-push-16{left:66.66666667%}.ant-col-pull-16{right:66.66666667%}.ant-col-offset-16{margin-left:66.66666667%}.ant-col-order-16{order:16}.ant-col-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-push-15{left:62.5%}.ant-col-pull-15{right:62.5%}.ant-col-offset-15{margin-left:62.5%}.ant-col-order-15{order:15}.ant-col-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-push-14{left:58.33333333%}.ant-col-pull-14{right:58.33333333%}.ant-col-offset-14{margin-left:58.33333333%}.ant-col-order-14{order:14}.ant-col-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-push-13{left:54.16666667%}.ant-col-pull-13{right:54.16666667%}.ant-col-offset-13{margin-left:54.16666667%}.ant-col-order-13{order:13}.ant-col-12{display:block;box-sizing:border-box;width:50%}.ant-col-push-12{left:50%}.ant-col-pull-12{right:50%}.ant-col-offset-12{margin-left:50%}.ant-col-order-12{order:12}.ant-col-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-push-11{left:45.83333333%}.ant-col-pull-11{right:45.83333333%}.ant-col-offset-11{margin-left:45.83333333%}.ant-col-order-11{order:11}.ant-col-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-push-10{left:41.66666667%}.ant-col-pull-10{right:41.66666667%}.ant-col-offset-10{margin-left:41.66666667%}.ant-col-order-10{order:10}.ant-col-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-push-9{left:37.5%}.ant-col-pull-9{right:37.5%}.ant-col-offset-9{margin-left:37.5%}.ant-col-order-9{order:9}.ant-col-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-push-8{left:33.33333333%}.ant-col-pull-8{right:33.33333333%}.ant-col-offset-8{margin-left:33.33333333%}.ant-col-order-8{order:8}.ant-col-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-push-7{left:29.16666667%}.ant-col-pull-7{right:29.16666667%}.ant-col-offset-7{margin-left:29.16666667%}.ant-col-order-7{order:7}.ant-col-6{display:block;box-sizing:border-box;width:25%}.ant-col-push-6{left:25%}.ant-col-pull-6{right:25%}.ant-col-offset-6{margin-left:25%}.ant-col-order-6{order:6}.ant-col-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-push-5{left:20.83333333%}.ant-col-pull-5{right:20.83333333%}.ant-col-offset-5{margin-left:20.83333333%}.ant-col-order-5{order:5}.ant-col-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-push-4{left:16.66666667%}.ant-col-pull-4{right:16.66666667%}.ant-col-offset-4{margin-left:16.66666667%}.ant-col-order-4{order:4}.ant-col-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-push-3{left:12.5%}.ant-col-pull-3{right:12.5%}.ant-col-offset-3{margin-left:12.5%}.ant-col-order-3{order:3}.ant-col-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-push-2{left:8.33333333%}.ant-col-pull-2{right:8.33333333%}.ant-col-offset-2{margin-left:8.33333333%}.ant-col-order-2{order:2}.ant-col-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-push-1{left:4.16666667%}.ant-col-pull-1{right:4.16666667%}.ant-col-offset-1{margin-left:4.16666667%}.ant-col-order-1{order:1}.ant-col-0{display:none}.ant-col-offset-0{margin-left:0}.ant-col-order-0{order:0}.ant-col-xs-1,.ant-col-xs-2,.ant-col-xs-3,.ant-col-xs-4,.ant-col-xs-5,.ant-col-xs-6,.ant-col-xs-7,.ant-col-xs-8,.ant-col-xs-9,.ant-col-xs-10,.ant-col-xs-11,.ant-col-xs-12,.ant-col-xs-13,.ant-col-xs-14,.ant-col-xs-15,.ant-col-xs-16,.ant-col-xs-17,.ant-col-xs-18,.ant-col-xs-19,.ant-col-xs-20,.ant-col-xs-21,.ant-col-xs-22,.ant-col-xs-23,.ant-col-xs-24{flex:0 0 auto;float:left}.ant-col-xs-24{display:block;box-sizing:border-box;width:100%}.ant-col-xs-push-24{left:100%}.ant-col-xs-pull-24{right:100%}.ant-col-xs-offset-24{margin-left:100%}.ant-col-xs-order-24{order:24}.ant-col-xs-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-xs-push-23{left:95.83333333%}.ant-col-xs-pull-23{right:95.83333333%}.ant-col-xs-offset-23{margin-left:95.83333333%}.ant-col-xs-order-23{order:23}.ant-col-xs-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-xs-push-22{left:91.66666667%}.ant-col-xs-pull-22{right:91.66666667%}.ant-col-xs-offset-22{margin-left:91.66666667%}.ant-col-xs-order-22{order:22}.ant-col-xs-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-xs-push-21{left:87.5%}.ant-col-xs-pull-21{right:87.5%}.ant-col-xs-offset-21{margin-left:87.5%}.ant-col-xs-order-21{order:21}.ant-col-xs-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-xs-push-20{left:83.33333333%}.ant-col-xs-pull-20{right:83.33333333%}.ant-col-xs-offset-20{margin-left:83.33333333%}.ant-col-xs-order-20{order:20}.ant-col-xs-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-xs-push-19{left:79.16666667%}.ant-col-xs-pull-19{right:79.16666667%}.ant-col-xs-offset-19{margin-left:79.16666667%}.ant-col-xs-order-19{order:19}.ant-col-xs-18{display:block;box-sizing:border-box;width:75%}.ant-col-xs-push-18{left:75%}.ant-col-xs-pull-18{right:75%}.ant-col-xs-offset-18{margin-left:75%}.ant-col-xs-order-18{order:18}.ant-col-xs-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-xs-push-17{left:70.83333333%}.ant-col-xs-pull-17{right:70.83333333%}.ant-col-xs-offset-17{margin-left:70.83333333%}.ant-col-xs-order-17{order:17}.ant-col-xs-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-xs-push-16{left:66.66666667%}.ant-col-xs-pull-16{right:66.66666667%}.ant-col-xs-offset-16{margin-left:66.66666667%}.ant-col-xs-order-16{order:16}.ant-col-xs-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-xs-push-15{left:62.5%}.ant-col-xs-pull-15{right:62.5%}.ant-col-xs-offset-15{margin-left:62.5%}.ant-col-xs-order-15{order:15}.ant-col-xs-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-xs-push-14{left:58.33333333%}.ant-col-xs-pull-14{right:58.33333333%}.ant-col-xs-offset-14{margin-left:58.33333333%}.ant-col-xs-order-14{order:14}.ant-col-xs-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-xs-push-13{left:54.16666667%}.ant-col-xs-pull-13{right:54.16666667%}.ant-col-xs-offset-13{margin-left:54.16666667%}.ant-col-xs-order-13{order:13}.ant-col-xs-12{display:block;box-sizing:border-box;width:50%}.ant-col-xs-push-12{left:50%}.ant-col-xs-pull-12{right:50%}.ant-col-xs-offset-12{margin-left:50%}.ant-col-xs-order-12{order:12}.ant-col-xs-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-xs-push-11{left:45.83333333%}.ant-col-xs-pull-11{right:45.83333333%}.ant-col-xs-offset-11{margin-left:45.83333333%}.ant-col-xs-order-11{order:11}.ant-col-xs-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-xs-push-10{left:41.66666667%}.ant-col-xs-pull-10{right:41.66666667%}.ant-col-xs-offset-10{margin-left:41.66666667%}.ant-col-xs-order-10{order:10}.ant-col-xs-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-xs-push-9{left:37.5%}.ant-col-xs-pull-9{right:37.5%}.ant-col-xs-offset-9{margin-left:37.5%}.ant-col-xs-order-9{order:9}.ant-col-xs-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-xs-push-8{left:33.33333333%}.ant-col-xs-pull-8{right:33.33333333%}.ant-col-xs-offset-8{margin-left:33.33333333%}.ant-col-xs-order-8{order:8}.ant-col-xs-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-xs-push-7{left:29.16666667%}.ant-col-xs-pull-7{right:29.16666667%}.ant-col-xs-offset-7{margin-left:29.16666667%}.ant-col-xs-order-7{order:7}.ant-col-xs-6{display:block;box-sizing:border-box;width:25%}.ant-col-xs-push-6{left:25%}.ant-col-xs-pull-6{right:25%}.ant-col-xs-offset-6{margin-left:25%}.ant-col-xs-order-6{order:6}.ant-col-xs-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-xs-push-5{left:20.83333333%}.ant-col-xs-pull-5{right:20.83333333%}.ant-col-xs-offset-5{margin-left:20.83333333%}.ant-col-xs-order-5{order:5}.ant-col-xs-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-xs-push-4{left:16.66666667%}.ant-col-xs-pull-4{right:16.66666667%}.ant-col-xs-offset-4{margin-left:16.66666667%}.ant-col-xs-order-4{order:4}.ant-col-xs-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-xs-push-3{left:12.5%}.ant-col-xs-pull-3{right:12.5%}.ant-col-xs-offset-3{margin-left:12.5%}.ant-col-xs-order-3{order:3}.ant-col-xs-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-xs-push-2{left:8.33333333%}.ant-col-xs-pull-2{right:8.33333333%}.ant-col-xs-offset-2{margin-left:8.33333333%}.ant-col-xs-order-2{order:2}.ant-col-xs-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-xs-push-1{left:4.16666667%}.ant-col-xs-pull-1{right:4.16666667%}.ant-col-xs-offset-1{margin-left:4.16666667%}.ant-col-xs-order-1{order:1}.ant-col-xs-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xs-push-0{left:auto}.ant-col-xs-pull-0{right:auto}.ant-col-xs-offset-0{margin-left:0}.ant-col-xs-order-0{order:0}@media (min-width:576px){.ant-col-sm-1,.ant-col-sm-2,.ant-col-sm-3,.ant-col-sm-4,.ant-col-sm-5,.ant-col-sm-6,.ant-col-sm-7,.ant-col-sm-8,.ant-col-sm-9,.ant-col-sm-10,.ant-col-sm-11,.ant-col-sm-12,.ant-col-sm-13,.ant-col-sm-14,.ant-col-sm-15,.ant-col-sm-16,.ant-col-sm-17,.ant-col-sm-18,.ant-col-sm-19,.ant-col-sm-20,.ant-col-sm-21,.ant-col-sm-22,.ant-col-sm-23,.ant-col-sm-24{flex:0 0 auto;float:left}.ant-col-sm-24{display:block;box-sizing:border-box;width:100%}.ant-col-sm-push-24{left:100%}.ant-col-sm-pull-24{right:100%}.ant-col-sm-offset-24{margin-left:100%}.ant-col-sm-order-24{order:24}.ant-col-sm-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-sm-push-23{left:95.83333333%}.ant-col-sm-pull-23{right:95.83333333%}.ant-col-sm-offset-23{margin-left:95.83333333%}.ant-col-sm-order-23{order:23}.ant-col-sm-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-sm-push-22{left:91.66666667%}.ant-col-sm-pull-22{right:91.66666667%}.ant-col-sm-offset-22{margin-left:91.66666667%}.ant-col-sm-order-22{order:22}.ant-col-sm-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-sm-push-21{left:87.5%}.ant-col-sm-pull-21{right:87.5%}.ant-col-sm-offset-21{margin-left:87.5%}.ant-col-sm-order-21{order:21}.ant-col-sm-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-sm-push-20{left:83.33333333%}.ant-col-sm-pull-20{right:83.33333333%}.ant-col-sm-offset-20{margin-left:83.33333333%}.ant-col-sm-order-20{order:20}.ant-col-sm-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-sm-push-19{left:79.16666667%}.ant-col-sm-pull-19{right:79.16666667%}.ant-col-sm-offset-19{margin-left:79.16666667%}.ant-col-sm-order-19{order:19}.ant-col-sm-18{display:block;box-sizing:border-box;width:75%}.ant-col-sm-push-18{left:75%}.ant-col-sm-pull-18{right:75%}.ant-col-sm-offset-18{margin-left:75%}.ant-col-sm-order-18{order:18}.ant-col-sm-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-sm-push-17{left:70.83333333%}.ant-col-sm-pull-17{right:70.83333333%}.ant-col-sm-offset-17{margin-left:70.83333333%}.ant-col-sm-order-17{order:17}.ant-col-sm-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-sm-push-16{left:66.66666667%}.ant-col-sm-pull-16{right:66.66666667%}.ant-col-sm-offset-16{margin-left:66.66666667%}.ant-col-sm-order-16{order:16}.ant-col-sm-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-sm-push-15{left:62.5%}.ant-col-sm-pull-15{right:62.5%}.ant-col-sm-offset-15{margin-left:62.5%}.ant-col-sm-order-15{order:15}.ant-col-sm-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-sm-push-14{left:58.33333333%}.ant-col-sm-pull-14{right:58.33333333%}.ant-col-sm-offset-14{margin-left:58.33333333%}.ant-col-sm-order-14{order:14}.ant-col-sm-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-sm-push-13{left:54.16666667%}.ant-col-sm-pull-13{right:54.16666667%}.ant-col-sm-offset-13{margin-left:54.16666667%}.ant-col-sm-order-13{order:13}.ant-col-sm-12{display:block;box-sizing:border-box;width:50%}.ant-col-sm-push-12{left:50%}.ant-col-sm-pull-12{right:50%}.ant-col-sm-offset-12{margin-left:50%}.ant-col-sm-order-12{order:12}.ant-col-sm-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-sm-push-11{left:45.83333333%}.ant-col-sm-pull-11{right:45.83333333%}.ant-col-sm-offset-11{margin-left:45.83333333%}.ant-col-sm-order-11{order:11}.ant-col-sm-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-sm-push-10{left:41.66666667%}.ant-col-sm-pull-10{right:41.66666667%}.ant-col-sm-offset-10{margin-left:41.66666667%}.ant-col-sm-order-10{order:10}.ant-col-sm-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-sm-push-9{left:37.5%}.ant-col-sm-pull-9{right:37.5%}.ant-col-sm-offset-9{margin-left:37.5%}.ant-col-sm-order-9{order:9}.ant-col-sm-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-sm-push-8{left:33.33333333%}.ant-col-sm-pull-8{right:33.33333333%}.ant-col-sm-offset-8{margin-left:33.33333333%}.ant-col-sm-order-8{order:8}.ant-col-sm-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-sm-push-7{left:29.16666667%}.ant-col-sm-pull-7{right:29.16666667%}.ant-col-sm-offset-7{margin-left:29.16666667%}.ant-col-sm-order-7{order:7}.ant-col-sm-6{display:block;box-sizing:border-box;width:25%}.ant-col-sm-push-6{left:25%}.ant-col-sm-pull-6{right:25%}.ant-col-sm-offset-6{margin-left:25%}.ant-col-sm-order-6{order:6}.ant-col-sm-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-sm-push-5{left:20.83333333%}.ant-col-sm-pull-5{right:20.83333333%}.ant-col-sm-offset-5{margin-left:20.83333333%}.ant-col-sm-order-5{order:5}.ant-col-sm-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-sm-push-4{left:16.66666667%}.ant-col-sm-pull-4{right:16.66666667%}.ant-col-sm-offset-4{margin-left:16.66666667%}.ant-col-sm-order-4{order:4}.ant-col-sm-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-sm-push-3{left:12.5%}.ant-col-sm-pull-3{right:12.5%}.ant-col-sm-offset-3{margin-left:12.5%}.ant-col-sm-order-3{order:3}.ant-col-sm-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-sm-push-2{left:8.33333333%}.ant-col-sm-pull-2{right:8.33333333%}.ant-col-sm-offset-2{margin-left:8.33333333%}.ant-col-sm-order-2{order:2}.ant-col-sm-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-sm-push-1{left:4.16666667%}.ant-col-sm-pull-1{right:4.16666667%}.ant-col-sm-offset-1{margin-left:4.16666667%}.ant-col-sm-order-1{order:1}.ant-col-sm-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-sm-push-0{left:auto}.ant-col-sm-pull-0{right:auto}.ant-col-sm-offset-0{margin-left:0}.ant-col-sm-order-0{order:0}}@media (min-width:768px){.ant-col-md-1,.ant-col-md-2,.ant-col-md-3,.ant-col-md-4,.ant-col-md-5,.ant-col-md-6,.ant-col-md-7,.ant-col-md-8,.ant-col-md-9,.ant-col-md-10,.ant-col-md-11,.ant-col-md-12,.ant-col-md-13,.ant-col-md-14,.ant-col-md-15,.ant-col-md-16,.ant-col-md-17,.ant-col-md-18,.ant-col-md-19,.ant-col-md-20,.ant-col-md-21,.ant-col-md-22,.ant-col-md-23,.ant-col-md-24{flex:0 0 auto;float:left}.ant-col-md-24{display:block;box-sizing:border-box;width:100%}.ant-col-md-push-24{left:100%}.ant-col-md-pull-24{right:100%}.ant-col-md-offset-24{margin-left:100%}.ant-col-md-order-24{order:24}.ant-col-md-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-md-push-23{left:95.83333333%}.ant-col-md-pull-23{right:95.83333333%}.ant-col-md-offset-23{margin-left:95.83333333%}.ant-col-md-order-23{order:23}.ant-col-md-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-md-push-22{left:91.66666667%}.ant-col-md-pull-22{right:91.66666667%}.ant-col-md-offset-22{margin-left:91.66666667%}.ant-col-md-order-22{order:22}.ant-col-md-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-md-push-21{left:87.5%}.ant-col-md-pull-21{right:87.5%}.ant-col-md-offset-21{margin-left:87.5%}.ant-col-md-order-21{order:21}.ant-col-md-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-md-push-20{left:83.33333333%}.ant-col-md-pull-20{right:83.33333333%}.ant-col-md-offset-20{margin-left:83.33333333%}.ant-col-md-order-20{order:20}.ant-col-md-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-md-push-19{left:79.16666667%}.ant-col-md-pull-19{right:79.16666667%}.ant-col-md-offset-19{margin-left:79.16666667%}.ant-col-md-order-19{order:19}.ant-col-md-18{display:block;box-sizing:border-box;width:75%}.ant-col-md-push-18{left:75%}.ant-col-md-pull-18{right:75%}.ant-col-md-offset-18{margin-left:75%}.ant-col-md-order-18{order:18}.ant-col-md-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-md-push-17{left:70.83333333%}.ant-col-md-pull-17{right:70.83333333%}.ant-col-md-offset-17{margin-left:70.83333333%}.ant-col-md-order-17{order:17}.ant-col-md-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-md-push-16{left:66.66666667%}.ant-col-md-pull-16{right:66.66666667%}.ant-col-md-offset-16{margin-left:66.66666667%}.ant-col-md-order-16{order:16}.ant-col-md-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-md-push-15{left:62.5%}.ant-col-md-pull-15{right:62.5%}.ant-col-md-offset-15{margin-left:62.5%}.ant-col-md-order-15{order:15}.ant-col-md-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-md-push-14{left:58.33333333%}.ant-col-md-pull-14{right:58.33333333%}.ant-col-md-offset-14{margin-left:58.33333333%}.ant-col-md-order-14{order:14}.ant-col-md-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-md-push-13{left:54.16666667%}.ant-col-md-pull-13{right:54.16666667%}.ant-col-md-offset-13{margin-left:54.16666667%}.ant-col-md-order-13{order:13}.ant-col-md-12{display:block;box-sizing:border-box;width:50%}.ant-col-md-push-12{left:50%}.ant-col-md-pull-12{right:50%}.ant-col-md-offset-12{margin-left:50%}.ant-col-md-order-12{order:12}.ant-col-md-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-md-push-11{left:45.83333333%}.ant-col-md-pull-11{right:45.83333333%}.ant-col-md-offset-11{margin-left:45.83333333%}.ant-col-md-order-11{order:11}.ant-col-md-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-md-push-10{left:41.66666667%}.ant-col-md-pull-10{right:41.66666667%}.ant-col-md-offset-10{margin-left:41.66666667%}.ant-col-md-order-10{order:10}.ant-col-md-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-md-push-9{left:37.5%}.ant-col-md-pull-9{right:37.5%}.ant-col-md-offset-9{margin-left:37.5%}.ant-col-md-order-9{order:9}.ant-col-md-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-md-push-8{left:33.33333333%}.ant-col-md-pull-8{right:33.33333333%}.ant-col-md-offset-8{margin-left:33.33333333%}.ant-col-md-order-8{order:8}.ant-col-md-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-md-push-7{left:29.16666667%}.ant-col-md-pull-7{right:29.16666667%}.ant-col-md-offset-7{margin-left:29.16666667%}.ant-col-md-order-7{order:7}.ant-col-md-6{display:block;box-sizing:border-box;width:25%}.ant-col-md-push-6{left:25%}.ant-col-md-pull-6{right:25%}.ant-col-md-offset-6{margin-left:25%}.ant-col-md-order-6{order:6}.ant-col-md-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-md-push-5{left:20.83333333%}.ant-col-md-pull-5{right:20.83333333%}.ant-col-md-offset-5{margin-left:20.83333333%}.ant-col-md-order-5{order:5}.ant-col-md-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-md-push-4{left:16.66666667%}.ant-col-md-pull-4{right:16.66666667%}.ant-col-md-offset-4{margin-left:16.66666667%}.ant-col-md-order-4{order:4}.ant-col-md-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-md-push-3{left:12.5%}.ant-col-md-pull-3{right:12.5%}.ant-col-md-offset-3{margin-left:12.5%}.ant-col-md-order-3{order:3}.ant-col-md-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-md-push-2{left:8.33333333%}.ant-col-md-pull-2{right:8.33333333%}.ant-col-md-offset-2{margin-left:8.33333333%}.ant-col-md-order-2{order:2}.ant-col-md-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-md-push-1{left:4.16666667%}.ant-col-md-pull-1{right:4.16666667%}.ant-col-md-offset-1{margin-left:4.16666667%}.ant-col-md-order-1{order:1}.ant-col-md-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-md-push-0{left:auto}.ant-col-md-pull-0{right:auto}.ant-col-md-offset-0{margin-left:0}.ant-col-md-order-0{order:0}}@media (min-width:992px){.ant-col-lg-1,.ant-col-lg-2,.ant-col-lg-3,.ant-col-lg-4,.ant-col-lg-5,.ant-col-lg-6,.ant-col-lg-7,.ant-col-lg-8,.ant-col-lg-9,.ant-col-lg-10,.ant-col-lg-11,.ant-col-lg-12,.ant-col-lg-13,.ant-col-lg-14,.ant-col-lg-15,.ant-col-lg-16,.ant-col-lg-17,.ant-col-lg-18,.ant-col-lg-19,.ant-col-lg-20,.ant-col-lg-21,.ant-col-lg-22,.ant-col-lg-23,.ant-col-lg-24{flex:0 0 auto;float:left}.ant-col-lg-24{display:block;box-sizing:border-box;width:100%}.ant-col-lg-push-24{left:100%}.ant-col-lg-pull-24{right:100%}.ant-col-lg-offset-24{margin-left:100%}.ant-col-lg-order-24{order:24}.ant-col-lg-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-lg-push-23{left:95.83333333%}.ant-col-lg-pull-23{right:95.83333333%}.ant-col-lg-offset-23{margin-left:95.83333333%}.ant-col-lg-order-23{order:23}.ant-col-lg-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-lg-push-22{left:91.66666667%}.ant-col-lg-pull-22{right:91.66666667%}.ant-col-lg-offset-22{margin-left:91.66666667%}.ant-col-lg-order-22{order:22}.ant-col-lg-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-lg-push-21{left:87.5%}.ant-col-lg-pull-21{right:87.5%}.ant-col-lg-offset-21{margin-left:87.5%}.ant-col-lg-order-21{order:21}.ant-col-lg-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-lg-push-20{left:83.33333333%}.ant-col-lg-pull-20{right:83.33333333%}.ant-col-lg-offset-20{margin-left:83.33333333%}.ant-col-lg-order-20{order:20}.ant-col-lg-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-lg-push-19{left:79.16666667%}.ant-col-lg-pull-19{right:79.16666667%}.ant-col-lg-offset-19{margin-left:79.16666667%}.ant-col-lg-order-19{order:19}.ant-col-lg-18{display:block;box-sizing:border-box;width:75%}.ant-col-lg-push-18{left:75%}.ant-col-lg-pull-18{right:75%}.ant-col-lg-offset-18{margin-left:75%}.ant-col-lg-order-18{order:18}.ant-col-lg-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-lg-push-17{left:70.83333333%}.ant-col-lg-pull-17{right:70.83333333%}.ant-col-lg-offset-17{margin-left:70.83333333%}.ant-col-lg-order-17{order:17}.ant-col-lg-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-lg-push-16{left:66.66666667%}.ant-col-lg-pull-16{right:66.66666667%}.ant-col-lg-offset-16{margin-left:66.66666667%}.ant-col-lg-order-16{order:16}.ant-col-lg-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-lg-push-15{left:62.5%}.ant-col-lg-pull-15{right:62.5%}.ant-col-lg-offset-15{margin-left:62.5%}.ant-col-lg-order-15{order:15}.ant-col-lg-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-lg-push-14{left:58.33333333%}.ant-col-lg-pull-14{right:58.33333333%}.ant-col-lg-offset-14{margin-left:58.33333333%}.ant-col-lg-order-14{order:14}.ant-col-lg-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-lg-push-13{left:54.16666667%}.ant-col-lg-pull-13{right:54.16666667%}.ant-col-lg-offset-13{margin-left:54.16666667%}.ant-col-lg-order-13{order:13}.ant-col-lg-12{display:block;box-sizing:border-box;width:50%}.ant-col-lg-push-12{left:50%}.ant-col-lg-pull-12{right:50%}.ant-col-lg-offset-12{margin-left:50%}.ant-col-lg-order-12{order:12}.ant-col-lg-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-lg-push-11{left:45.83333333%}.ant-col-lg-pull-11{right:45.83333333%}.ant-col-lg-offset-11{margin-left:45.83333333%}.ant-col-lg-order-11{order:11}.ant-col-lg-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-lg-push-10{left:41.66666667%}.ant-col-lg-pull-10{right:41.66666667%}.ant-col-lg-offset-10{margin-left:41.66666667%}.ant-col-lg-order-10{order:10}.ant-col-lg-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-lg-push-9{left:37.5%}.ant-col-lg-pull-9{right:37.5%}.ant-col-lg-offset-9{margin-left:37.5%}.ant-col-lg-order-9{order:9}.ant-col-lg-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-lg-push-8{left:33.33333333%}.ant-col-lg-pull-8{right:33.33333333%}.ant-col-lg-offset-8{margin-left:33.33333333%}.ant-col-lg-order-8{order:8}.ant-col-lg-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-lg-push-7{left:29.16666667%}.ant-col-lg-pull-7{right:29.16666667%}.ant-col-lg-offset-7{margin-left:29.16666667%}.ant-col-lg-order-7{order:7}.ant-col-lg-6{display:block;box-sizing:border-box;width:25%}.ant-col-lg-push-6{left:25%}.ant-col-lg-pull-6{right:25%}.ant-col-lg-offset-6{margin-left:25%}.ant-col-lg-order-6{order:6}.ant-col-lg-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-lg-push-5{left:20.83333333%}.ant-col-lg-pull-5{right:20.83333333%}.ant-col-lg-offset-5{margin-left:20.83333333%}.ant-col-lg-order-5{order:5}.ant-col-lg-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-lg-push-4{left:16.66666667%}.ant-col-lg-pull-4{right:16.66666667%}.ant-col-lg-offset-4{margin-left:16.66666667%}.ant-col-lg-order-4{order:4}.ant-col-lg-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-lg-push-3{left:12.5%}.ant-col-lg-pull-3{right:12.5%}.ant-col-lg-offset-3{margin-left:12.5%}.ant-col-lg-order-3{order:3}.ant-col-lg-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-lg-push-2{left:8.33333333%}.ant-col-lg-pull-2{right:8.33333333%}.ant-col-lg-offset-2{margin-left:8.33333333%}.ant-col-lg-order-2{order:2}.ant-col-lg-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-lg-push-1{left:4.16666667%}.ant-col-lg-pull-1{right:4.16666667%}.ant-col-lg-offset-1{margin-left:4.16666667%}.ant-col-lg-order-1{order:1}.ant-col-lg-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-lg-push-0{left:auto}.ant-col-lg-pull-0{right:auto}.ant-col-lg-offset-0{margin-left:0}.ant-col-lg-order-0{order:0}}@media (min-width:1200px){.ant-col-xl-1,.ant-col-xl-2,.ant-col-xl-3,.ant-col-xl-4,.ant-col-xl-5,.ant-col-xl-6,.ant-col-xl-7,.ant-col-xl-8,.ant-col-xl-9,.ant-col-xl-10,.ant-col-xl-11,.ant-col-xl-12,.ant-col-xl-13,.ant-col-xl-14,.ant-col-xl-15,.ant-col-xl-16,.ant-col-xl-17,.ant-col-xl-18,.ant-col-xl-19,.ant-col-xl-20,.ant-col-xl-21,.ant-col-xl-22,.ant-col-xl-23,.ant-col-xl-24{flex:0 0 auto;float:left}.ant-col-xl-24{display:block;box-sizing:border-box;width:100%}.ant-col-xl-push-24{left:100%}.ant-col-xl-pull-24{right:100%}.ant-col-xl-offset-24{margin-left:100%}.ant-col-xl-order-24{order:24}.ant-col-xl-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-xl-push-23{left:95.83333333%}.ant-col-xl-pull-23{right:95.83333333%}.ant-col-xl-offset-23{margin-left:95.83333333%}.ant-col-xl-order-23{order:23}.ant-col-xl-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-xl-push-22{left:91.66666667%}.ant-col-xl-pull-22{right:91.66666667%}.ant-col-xl-offset-22{margin-left:91.66666667%}.ant-col-xl-order-22{order:22}.ant-col-xl-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-xl-push-21{left:87.5%}.ant-col-xl-pull-21{right:87.5%}.ant-col-xl-offset-21{margin-left:87.5%}.ant-col-xl-order-21{order:21}.ant-col-xl-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-xl-push-20{left:83.33333333%}.ant-col-xl-pull-20{right:83.33333333%}.ant-col-xl-offset-20{margin-left:83.33333333%}.ant-col-xl-order-20{order:20}.ant-col-xl-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-xl-push-19{left:79.16666667%}.ant-col-xl-pull-19{right:79.16666667%}.ant-col-xl-offset-19{margin-left:79.16666667%}.ant-col-xl-order-19{order:19}.ant-col-xl-18{display:block;box-sizing:border-box;width:75%}.ant-col-xl-push-18{left:75%}.ant-col-xl-pull-18{right:75%}.ant-col-xl-offset-18{margin-left:75%}.ant-col-xl-order-18{order:18}.ant-col-xl-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-xl-push-17{left:70.83333333%}.ant-col-xl-pull-17{right:70.83333333%}.ant-col-xl-offset-17{margin-left:70.83333333%}.ant-col-xl-order-17{order:17}.ant-col-xl-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-xl-push-16{left:66.66666667%}.ant-col-xl-pull-16{right:66.66666667%}.ant-col-xl-offset-16{margin-left:66.66666667%}.ant-col-xl-order-16{order:16}.ant-col-xl-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-xl-push-15{left:62.5%}.ant-col-xl-pull-15{right:62.5%}.ant-col-xl-offset-15{margin-left:62.5%}.ant-col-xl-order-15{order:15}.ant-col-xl-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-xl-push-14{left:58.33333333%}.ant-col-xl-pull-14{right:58.33333333%}.ant-col-xl-offset-14{margin-left:58.33333333%}.ant-col-xl-order-14{order:14}.ant-col-xl-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-xl-push-13{left:54.16666667%}.ant-col-xl-pull-13{right:54.16666667%}.ant-col-xl-offset-13{margin-left:54.16666667%}.ant-col-xl-order-13{order:13}.ant-col-xl-12{display:block;box-sizing:border-box;width:50%}.ant-col-xl-push-12{left:50%}.ant-col-xl-pull-12{right:50%}.ant-col-xl-offset-12{margin-left:50%}.ant-col-xl-order-12{order:12}.ant-col-xl-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-xl-push-11{left:45.83333333%}.ant-col-xl-pull-11{right:45.83333333%}.ant-col-xl-offset-11{margin-left:45.83333333%}.ant-col-xl-order-11{order:11}.ant-col-xl-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-xl-push-10{left:41.66666667%}.ant-col-xl-pull-10{right:41.66666667%}.ant-col-xl-offset-10{margin-left:41.66666667%}.ant-col-xl-order-10{order:10}.ant-col-xl-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-xl-push-9{left:37.5%}.ant-col-xl-pull-9{right:37.5%}.ant-col-xl-offset-9{margin-left:37.5%}.ant-col-xl-order-9{order:9}.ant-col-xl-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-xl-push-8{left:33.33333333%}.ant-col-xl-pull-8{right:33.33333333%}.ant-col-xl-offset-8{margin-left:33.33333333%}.ant-col-xl-order-8{order:8}.ant-col-xl-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-xl-push-7{left:29.16666667%}.ant-col-xl-pull-7{right:29.16666667%}.ant-col-xl-offset-7{margin-left:29.16666667%}.ant-col-xl-order-7{order:7}.ant-col-xl-6{display:block;box-sizing:border-box;width:25%}.ant-col-xl-push-6{left:25%}.ant-col-xl-pull-6{right:25%}.ant-col-xl-offset-6{margin-left:25%}.ant-col-xl-order-6{order:6}.ant-col-xl-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-xl-push-5{left:20.83333333%}.ant-col-xl-pull-5{right:20.83333333%}.ant-col-xl-offset-5{margin-left:20.83333333%}.ant-col-xl-order-5{order:5}.ant-col-xl-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-xl-push-4{left:16.66666667%}.ant-col-xl-pull-4{right:16.66666667%}.ant-col-xl-offset-4{margin-left:16.66666667%}.ant-col-xl-order-4{order:4}.ant-col-xl-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-xl-push-3{left:12.5%}.ant-col-xl-pull-3{right:12.5%}.ant-col-xl-offset-3{margin-left:12.5%}.ant-col-xl-order-3{order:3}.ant-col-xl-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-xl-push-2{left:8.33333333%}.ant-col-xl-pull-2{right:8.33333333%}.ant-col-xl-offset-2{margin-left:8.33333333%}.ant-col-xl-order-2{order:2}.ant-col-xl-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-xl-push-1{left:4.16666667%}.ant-col-xl-pull-1{right:4.16666667%}.ant-col-xl-offset-1{margin-left:4.16666667%}.ant-col-xl-order-1{order:1}.ant-col-xl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xl-push-0{left:auto}.ant-col-xl-pull-0{right:auto}.ant-col-xl-offset-0{margin-left:0}.ant-col-xl-order-0{order:0}}@media (min-width:1600px){.ant-col-xxl-1,.ant-col-xxl-2,.ant-col-xxl-3,.ant-col-xxl-4,.ant-col-xxl-5,.ant-col-xxl-6,.ant-col-xxl-7,.ant-col-xxl-8,.ant-col-xxl-9,.ant-col-xxl-10,.ant-col-xxl-11,.ant-col-xxl-12,.ant-col-xxl-13,.ant-col-xxl-14,.ant-col-xxl-15,.ant-col-xxl-16,.ant-col-xxl-17,.ant-col-xxl-18,.ant-col-xxl-19,.ant-col-xxl-20,.ant-col-xxl-21,.ant-col-xxl-22,.ant-col-xxl-23,.ant-col-xxl-24{flex:0 0 auto;float:left}.ant-col-xxl-24{display:block;box-sizing:border-box;width:100%}.ant-col-xxl-push-24{left:100%}.ant-col-xxl-pull-24{right:100%}.ant-col-xxl-offset-24{margin-left:100%}.ant-col-xxl-order-24{order:24}.ant-col-xxl-23{display:block;box-sizing:border-box;width:95.83333333%}.ant-col-xxl-push-23{left:95.83333333%}.ant-col-xxl-pull-23{right:95.83333333%}.ant-col-xxl-offset-23{margin-left:95.83333333%}.ant-col-xxl-order-23{order:23}.ant-col-xxl-22{display:block;box-sizing:border-box;width:91.66666667%}.ant-col-xxl-push-22{left:91.66666667%}.ant-col-xxl-pull-22{right:91.66666667%}.ant-col-xxl-offset-22{margin-left:91.66666667%}.ant-col-xxl-order-22{order:22}.ant-col-xxl-21{display:block;box-sizing:border-box;width:87.5%}.ant-col-xxl-push-21{left:87.5%}.ant-col-xxl-pull-21{right:87.5%}.ant-col-xxl-offset-21{margin-left:87.5%}.ant-col-xxl-order-21{order:21}.ant-col-xxl-20{display:block;box-sizing:border-box;width:83.33333333%}.ant-col-xxl-push-20{left:83.33333333%}.ant-col-xxl-pull-20{right:83.33333333%}.ant-col-xxl-offset-20{margin-left:83.33333333%}.ant-col-xxl-order-20{order:20}.ant-col-xxl-19{display:block;box-sizing:border-box;width:79.16666667%}.ant-col-xxl-push-19{left:79.16666667%}.ant-col-xxl-pull-19{right:79.16666667%}.ant-col-xxl-offset-19{margin-left:79.16666667%}.ant-col-xxl-order-19{order:19}.ant-col-xxl-18{display:block;box-sizing:border-box;width:75%}.ant-col-xxl-push-18{left:75%}.ant-col-xxl-pull-18{right:75%}.ant-col-xxl-offset-18{margin-left:75%}.ant-col-xxl-order-18{order:18}.ant-col-xxl-17{display:block;box-sizing:border-box;width:70.83333333%}.ant-col-xxl-push-17{left:70.83333333%}.ant-col-xxl-pull-17{right:70.83333333%}.ant-col-xxl-offset-17{margin-left:70.83333333%}.ant-col-xxl-order-17{order:17}.ant-col-xxl-16{display:block;box-sizing:border-box;width:66.66666667%}.ant-col-xxl-push-16{left:66.66666667%}.ant-col-xxl-pull-16{right:66.66666667%}.ant-col-xxl-offset-16{margin-left:66.66666667%}.ant-col-xxl-order-16{order:16}.ant-col-xxl-15{display:block;box-sizing:border-box;width:62.5%}.ant-col-xxl-push-15{left:62.5%}.ant-col-xxl-pull-15{right:62.5%}.ant-col-xxl-offset-15{margin-left:62.5%}.ant-col-xxl-order-15{order:15}.ant-col-xxl-14{display:block;box-sizing:border-box;width:58.33333333%}.ant-col-xxl-push-14{left:58.33333333%}.ant-col-xxl-pull-14{right:58.33333333%}.ant-col-xxl-offset-14{margin-left:58.33333333%}.ant-col-xxl-order-14{order:14}.ant-col-xxl-13{display:block;box-sizing:border-box;width:54.16666667%}.ant-col-xxl-push-13{left:54.16666667%}.ant-col-xxl-pull-13{right:54.16666667%}.ant-col-xxl-offset-13{margin-left:54.16666667%}.ant-col-xxl-order-13{order:13}.ant-col-xxl-12{display:block;box-sizing:border-box;width:50%}.ant-col-xxl-push-12{left:50%}.ant-col-xxl-pull-12{right:50%}.ant-col-xxl-offset-12{margin-left:50%}.ant-col-xxl-order-12{order:12}.ant-col-xxl-11{display:block;box-sizing:border-box;width:45.83333333%}.ant-col-xxl-push-11{left:45.83333333%}.ant-col-xxl-pull-11{right:45.83333333%}.ant-col-xxl-offset-11{margin-left:45.83333333%}.ant-col-xxl-order-11{order:11}.ant-col-xxl-10{display:block;box-sizing:border-box;width:41.66666667%}.ant-col-xxl-push-10{left:41.66666667%}.ant-col-xxl-pull-10{right:41.66666667%}.ant-col-xxl-offset-10{margin-left:41.66666667%}.ant-col-xxl-order-10{order:10}.ant-col-xxl-9{display:block;box-sizing:border-box;width:37.5%}.ant-col-xxl-push-9{left:37.5%}.ant-col-xxl-pull-9{right:37.5%}.ant-col-xxl-offset-9{margin-left:37.5%}.ant-col-xxl-order-9{order:9}.ant-col-xxl-8{display:block;box-sizing:border-box;width:33.33333333%}.ant-col-xxl-push-8{left:33.33333333%}.ant-col-xxl-pull-8{right:33.33333333%}.ant-col-xxl-offset-8{margin-left:33.33333333%}.ant-col-xxl-order-8{order:8}.ant-col-xxl-7{display:block;box-sizing:border-box;width:29.16666667%}.ant-col-xxl-push-7{left:29.16666667%}.ant-col-xxl-pull-7{right:29.16666667%}.ant-col-xxl-offset-7{margin-left:29.16666667%}.ant-col-xxl-order-7{order:7}.ant-col-xxl-6{display:block;box-sizing:border-box;width:25%}.ant-col-xxl-push-6{left:25%}.ant-col-xxl-pull-6{right:25%}.ant-col-xxl-offset-6{margin-left:25%}.ant-col-xxl-order-6{order:6}.ant-col-xxl-5{display:block;box-sizing:border-box;width:20.83333333%}.ant-col-xxl-push-5{left:20.83333333%}.ant-col-xxl-pull-5{right:20.83333333%}.ant-col-xxl-offset-5{margin-left:20.83333333%}.ant-col-xxl-order-5{order:5}.ant-col-xxl-4{display:block;box-sizing:border-box;width:16.66666667%}.ant-col-xxl-push-4{left:16.66666667%}.ant-col-xxl-pull-4{right:16.66666667%}.ant-col-xxl-offset-4{margin-left:16.66666667%}.ant-col-xxl-order-4{order:4}.ant-col-xxl-3{display:block;box-sizing:border-box;width:12.5%}.ant-col-xxl-push-3{left:12.5%}.ant-col-xxl-pull-3{right:12.5%}.ant-col-xxl-offset-3{margin-left:12.5%}.ant-col-xxl-order-3{order:3}.ant-col-xxl-2{display:block;box-sizing:border-box;width:8.33333333%}.ant-col-xxl-push-2{left:8.33333333%}.ant-col-xxl-pull-2{right:8.33333333%}.ant-col-xxl-offset-2{margin-left:8.33333333%}.ant-col-xxl-order-2{order:2}.ant-col-xxl-1{display:block;box-sizing:border-box;width:4.16666667%}.ant-col-xxl-push-1{left:4.16666667%}.ant-col-xxl-pull-1{right:4.16666667%}.ant-col-xxl-offset-1{margin-left:4.16666667%}.ant-col-xxl-order-1{order:1}.ant-col-xxl-0{display:none}.ant-col-push-0{left:auto}.ant-col-pull-0{right:auto}.ant-col-xxl-push-0{left:auto}.ant-col-xxl-pull-0{right:auto}.ant-col-xxl-offset-0{margin-left:0}.ant-col-xxl-order-0{order:0}}.ant-carousel{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-carousel .slick-slider{position:relative;display:block;box-sizing:border-box;-webkit-touch-callout:none;touch-action:pan-y;-webkit-tap-highlight-color:transparent}.ant-carousel .slick-list{position:relative;display:block;margin:0;padding:0;overflow:hidden}.ant-carousel .slick-list:focus{outline:none}.ant-carousel .slick-list.dragging{cursor:pointer}.ant-carousel .slick-list .slick-slide{pointer-events:none}.ant-carousel .slick-list .slick-slide input.ant-checkbox-input,.ant-carousel .slick-list .slick-slide input.ant-radio-input{visibility:hidden}.ant-carousel .slick-list .slick-slide.slick-active{pointer-events:auto}.ant-carousel .slick-list .slick-slide.slick-active input.ant-checkbox-input,.ant-carousel .slick-list .slick-slide.slick-active input.ant-radio-input{visibility:visible}.ant-carousel .slick-slider .slick-list,.ant-carousel .slick-slider .slick-track{transform:translateZ(0)}.ant-carousel .slick-track{position:relative;top:0;left:0;display:block}.ant-carousel .slick-track:after,.ant-carousel .slick-track:before{display:table;content:""}.ant-carousel .slick-track:after{clear:both}.slick-loading .ant-carousel .slick-track{visibility:hidden}.ant-carousel .slick-slide{display:none;float:left;height:100%;min-height:1px}[dir=rtl] .ant-carousel .slick-slide{float:right}.ant-carousel .slick-slide img{display:block}.ant-carousel .slick-slide.slick-loading img{display:none}.ant-carousel .slick-slide.dragging img{pointer-events:none}.ant-carousel .slick-initialized .slick-slide{display:block}.ant-carousel .slick-loading .slick-slide{visibility:hidden}.ant-carousel .slick-vertical .slick-slide{display:block;height:auto;border:1px solid transparent}.ant-carousel .slick-arrow.slick-hidden{display:none}.ant-carousel .slick-next,.ant-carousel .slick-prev{position:absolute;top:50%;display:block;width:20px;height:20px;margin-top:-10px;padding:0;font-size:0;line-height:0;border:0;cursor:pointer}.ant-carousel .slick-next,.ant-carousel .slick-next:focus,.ant-carousel .slick-next:hover,.ant-carousel .slick-prev,.ant-carousel .slick-prev:focus,.ant-carousel .slick-prev:hover{color:transparent;background:transparent;outline:none}.ant-carousel .slick-next:focus:before,.ant-carousel .slick-next:hover:before,.ant-carousel .slick-prev:focus:before,.ant-carousel .slick-prev:hover:before{opacity:1}.ant-carousel .slick-next.slick-disabled:before,.ant-carousel .slick-prev.slick-disabled:before{opacity:.25}.ant-carousel .slick-prev{left:-25px}.ant-carousel .slick-prev:before{content:"←"}.ant-carousel .slick-next{right:-25px}.ant-carousel .slick-next:before{content:"→"}.ant-carousel .slick-dots{position:absolute;display:block;width:100%;height:3px;margin:0;padding:0;text-align:center;list-style:none}.ant-carousel .slick-dots-bottom{bottom:12px}.ant-carousel .slick-dots-top{top:12px}.ant-carousel .slick-dots li{position:relative;display:inline-block;margin:0 2px;padding:0;text-align:center;vertical-align:top}.ant-carousel .slick-dots li button{display:block;width:16px;height:3px;padding:0;color:transparent;font-size:0;background:#fff;border:0;border-radius:1px;outline:none;cursor:pointer;opacity:.3;transition:all .5s}.ant-carousel .slick-dots li button:focus,.ant-carousel .slick-dots li button:hover{opacity:.75}.ant-carousel .slick-dots li.slick-active button{width:24px;background:#fff;opacity:1}.ant-carousel .slick-dots li.slick-active button:focus,.ant-carousel .slick-dots li.slick-active button:hover{opacity:1}.ant-carousel-vertical .slick-dots{top:50%;bottom:auto;width:3px;height:auto;transform:translateY(-50%)}.ant-carousel-vertical .slick-dots-left{left:12px}.ant-carousel-vertical .slick-dots-right{right:12px}.ant-carousel-vertical .slick-dots li{margin:0 2px;vertical-align:baseline}.ant-carousel-vertical .slick-dots li button{width:3px;height:16px}.ant-carousel-vertical .slick-dots li.slick-active button{width:3px;height:24px}.ant-cascader{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-cascader-input.ant-input{position:static;width:100%;padding-right:24px;background-color:transparent!important;cursor:pointer}.ant-cascader-picker-show-search .ant-cascader-input.ant-input{position:relative}.ant-cascader-picker{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;background-color:#fff;border-radius:1rem;outline:0;cursor:pointer;transition:color .3s}.ant-cascader-picker-with-value .ant-cascader-picker-label{color:transparent}.ant-cascader-picker-disabled{color:rgba(0,0,0,.25);background:#f5f5f5;cursor:not-allowed}.ant-cascader-picker-disabled .ant-cascader-input{cursor:not-allowed}.ant-cascader-picker:focus .ant-cascader-input{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-cascader-picker-show-search.ant-cascader-picker-focused{color:rgba(0,0,0,.25)}.ant-cascader-picker-label{position:absolute;top:50%;left:0;width:100%;height:20px;margin-top:-10px;padding:0 20px 0 12px;overflow:hidden;line-height:20px;white-space:nowrap;text-overflow:ellipsis}.ant-cascader-picker-clear{position:absolute;top:50%;right:12px;z-index:2;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:12px;background:#fff;cursor:pointer;opacity:0;transition:color .3s ease,opacity .15s ease}.ant-cascader-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-cascader-picker:hover .ant-cascader-picker-clear{opacity:1}.ant-cascader-picker-arrow{position:absolute;top:50%;right:12px;z-index:1;width:12px;height:12px;margin-top:-6px;color:rgba(0,0,0,.25);font-size:12px;line-height:12px;transition:transform .2s}.ant-cascader-picker-arrow.ant-cascader-picker-arrow-expand{transform:rotate(180deg)}.ant-cascader-picker-label:hover+.ant-cascader-input{border-color:#18947b;border-right-width:1px!important}.ant-cascader-picker-small .ant-cascader-picker-arrow,.ant-cascader-picker-small .ant-cascader-picker-clear{right:8px}.ant-cascader-menus{position:absolute;z-index:1050;font-size:14px;white-space:nowrap;background:#fff;border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-cascader-menus ol,.ant-cascader-menus ul{margin:0;list-style:none}.ant-cascader-menus-empty,.ant-cascader-menus-hidden{display:none}.ant-cascader-menus.slide-up-appear.slide-up-appear-active.ant-cascader-menus-placement-bottomLeft,.ant-cascader-menus.slide-up-enter.slide-up-enter-active.ant-cascader-menus-placement-bottomLeft{animation-name:antSlideUpIn}.ant-cascader-menus.slide-up-appear.slide-up-appear-active.ant-cascader-menus-placement-topLeft,.ant-cascader-menus.slide-up-enter.slide-up-enter-active.ant-cascader-menus-placement-topLeft{animation-name:antSlideDownIn}.ant-cascader-menus.slide-up-leave.slide-up-leave-active.ant-cascader-menus-placement-bottomLeft{animation-name:antSlideUpOut}.ant-cascader-menus.slide-up-leave.slide-up-leave-active.ant-cascader-menus-placement-topLeft{animation-name:antSlideDownOut}.ant-cascader-menu{display:inline-block;min-width:111px;height:180px;margin:0;padding:4px 0;overflow:auto;vertical-align:top;list-style:none;border-right:1px solid #e8e8e8;-ms-overflow-style:-ms-autohiding-scrollbar}.ant-cascader-menu:first-child{border-radius:1rem 0 0 1rem}.ant-cascader-menu:last-child{margin-right:-1px;border-right-color:transparent;border-radius:0 1rem 1rem 0}.ant-cascader-menu:only-child{border-radius:1rem}.ant-cascader-menu-item{padding:5px 12px;line-height:22px;white-space:nowrap;cursor:pointer;transition:all .3s}.ant-cascader-menu-item:hover{background:#b3c7c0}.ant-cascader-menu-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-cascader-menu-item-disabled:hover{background:transparent}.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled),.ant-cascader-menu-item-active:not(.ant-cascader-menu-item-disabled):hover{font-weight:600;background-color:#fafafa}.ant-cascader-menu-item-expand{position:relative;padding-right:24px}.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-loading-icon{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);position:absolute;right:12px;color:rgba(0,0,0,.45)}:root .ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,:root .ant-cascader-menu-item-loading-icon{font-size:12px}.ant-cascader-menu-item-disabled.ant-cascader-menu-item-expand .ant-cascader-menu-item-expand-icon,.ant-cascader-menu-item-disabled.ant-cascader-menu-item-loading-icon{color:rgba(0,0,0,.25)}.ant-cascader-menu-item .ant-cascader-menu-item-keyword{color:#f5222d}.ant-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-checkbox-input:focus+.ant-checkbox-inner,.ant-checkbox-wrapper:hover .ant-checkbox-inner,.ant-checkbox:hover .ant-checkbox-inner{border-color:#008771}.ant-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #008771;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-checkbox-wrapper:hover .ant-checkbox:after,.ant-checkbox:hover:after{visibility:visible}.ant-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-checkbox-checked .ant-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-checkbox-checked .ant-checkbox-inner{background-color:#008771;border-color:#008771}.ant-checkbox-disabled{cursor:not-allowed}.ant-checkbox-disabled.ant-checkbox-checked .ant-checkbox-inner:after{border-color:rgba(0,0,0,.25);animation-name:none}.ant-checkbox-disabled .ant-checkbox-input{cursor:not-allowed}.ant-checkbox-disabled .ant-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-checkbox-disabled .ant-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-checkbox-disabled:hover:after,.ant-checkbox-wrapper:hover .ant-checkbox-disabled:after{visibility:hidden}.ant-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block;line-height:unset;cursor:pointer}.ant-checkbox-wrapper.ant-checkbox-wrapper-disabled{cursor:not-allowed}.ant-checkbox-wrapper+.ant-checkbox-wrapper{margin-left:8px}.ant-checkbox+span{padding-right:8px;padding-left:8px}.ant-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-checkbox-group-item{display:inline-block;margin-right:8px}.ant-checkbox-group-item:last-child{margin-right:0}.ant-checkbox-group-item+.ant-checkbox-group-item{margin-left:0}.ant-checkbox-indeterminate .ant-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-checkbox-indeterminate .ant-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#008771;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-checkbox-indeterminate.ant-checkbox-disabled .ant-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-collapse{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background-color:#fafafa;border:1px solid #d9d9d9;border-bottom:0;border-radius:1rem}.ant-collapse>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse>.ant-collapse-item:last-child,.ant-collapse>.ant-collapse-item:last-child>.ant-collapse-header{border-radius:0 0 1rem 1rem}.ant-collapse>.ant-collapse-item>.ant-collapse-header{position:relative;padding:12px 16px 12px 40px;color:rgba(0,0,0,.85);line-height:22px;cursor:pointer;transition:all .3s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;top:50%;left:16px;display:inline-block;font-size:12px;transform:translateY(-50%)}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow>*{line-height:1}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{display:inline-block}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow:before{display:none}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow .ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow-icon{display:block}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow svg{transition:transform .24s}.ant-collapse>.ant-collapse-item>.ant-collapse-header .ant-collapse-extra{float:right}.ant-collapse>.ant-collapse-item>.ant-collapse-header:focus{outline:none}.ant-collapse>.ant-collapse-item.ant-collapse-no-arrow>.ant-collapse-header{padding-left:12px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header{padding:12px 40px 12px 16px}.ant-collapse-icon-position-right>.ant-collapse-item>.ant-collapse-header .ant-collapse-arrow{right:16px;left:auto}.ant-collapse-anim-active{transition:height .2s cubic-bezier(.215,.61,.355,1)}.ant-collapse-content{overflow:hidden;color:rgba(0,0,0,.65);background-color:#fff;border-top:1px solid #d9d9d9}.ant-collapse-content>.ant-collapse-content-box{padding:16px}.ant-collapse-content-inactive{display:none}.ant-collapse-item:last-child>.ant-collapse-content{border-radius:0 0 1rem 1rem}.ant-collapse-borderless{background-color:#fafafa;border:0}.ant-collapse-borderless>.ant-collapse-item{border-bottom:1px solid #d9d9d9}.ant-collapse-borderless>.ant-collapse-item:last-child,.ant-collapse-borderless>.ant-collapse-item:last-child .ant-collapse-header{border-radius:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content{background-color:transparent;border-top:0}.ant-collapse-borderless>.ant-collapse-item>.ant-collapse-content>.ant-collapse-content-box{padding-top:4px}.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header,.ant-collapse .ant-collapse-item-disabled>.ant-collapse-header>.arrow{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-comment{position:relative}.ant-comment-inner{display:flex;padding:16px 0}.ant-comment-avatar{position:relative;flex-shrink:0;margin-right:12px;cursor:pointer}.ant-comment-avatar img{width:32px;height:32px;border-radius:50%}.ant-comment-content{position:relative;flex:1 1 auto;min-width:1px;font-size:14px;word-wrap:break-word}.ant-comment-content-author{display:flex;flex-wrap:wrap;justify-content:flex-start;margin-bottom:4px;font-size:14px}.ant-comment-content-author>a,.ant-comment-content-author>span{padding-right:8px;font-size:12px;line-height:18px}.ant-comment-content-author-name{color:rgba(0,0,0,.45);font-size:14px;transition:color .3s}.ant-comment-content-author-name>*,.ant-comment-content-author-name>:hover{color:rgba(0,0,0,.45)}.ant-comment-content-author-time{color:#ccc;white-space:nowrap;cursor:auto}.ant-comment-content-detail p{white-space:pre-wrap}.ant-comment-actions{margin-top:12px;padding-left:0}.ant-comment-actions>li{display:inline-block;color:rgba(0,0,0,.45)}.ant-comment-actions>li>span{padding-right:10px;color:rgba(0,0,0,.45);font-size:12px;cursor:pointer;transition:color .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-comment-actions>li>span:hover{color:#595959}.ant-comment-nested{margin-left:44px}.ant-calendar-picker-container{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1050;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-topRight,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-topRight{animation-name:antSlideDownIn}.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-appear.slide-up-appear-active.ant-calendar-picker-container-placement-bottomRight,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-enter.slide-up-enter-active.ant-calendar-picker-container-placement-bottomRight{animation-name:antSlideUpIn}.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-topLeft,.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-topRight{animation-name:antSlideDownOut}.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-bottomLeft,.ant-calendar-picker-container.slide-up-leave.slide-up-leave-active.ant-calendar-picker-container-placement-bottomRight{animation-name:antSlideUpOut}.ant-calendar-picker{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;outline:none;cursor:text;transition:opacity .3s}.ant-calendar-picker-input{outline:none}.ant-calendar-picker-input.ant-input{line-height:1.5}.ant-calendar-picker-input.ant-input-sm{padding-top:0;padding-bottom:0}.ant-calendar-picker:hover .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#18947b}.ant-calendar-picker:focus .ant-calendar-picker-input:not(.ant-input-disabled){border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-calendar-picker-clear,.ant-calendar-picker-icon{position:absolute;top:50%;right:12px;z-index:1;width:14px;height:14px;margin-top:-7px;font-size:12px;line-height:14px;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-calendar-picker-clear{z-index:2;color:rgba(0,0,0,.25);font-size:14px;background:#fff;cursor:pointer;opacity:0;pointer-events:none}.ant-calendar-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-calendar-picker:hover .ant-calendar-picker-clear{opacity:1;pointer-events:auto}.ant-calendar-picker-icon{display:inline-block;color:rgba(0,0,0,.25);font-size:14px;line-height:1}.ant-input-disabled+.ant-calendar-picker-icon{cursor:not-allowed}.ant-calendar-picker-small .ant-calendar-picker-clear,.ant-calendar-picker-small .ant-calendar-picker-icon{right:8px}.ant-calendar{position:relative;width:280px;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #fff;border-radius:1rem;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-calendar-input-wrap{height:34px;padding:6px 10px;border-bottom:1px solid #e8e8e8}.ant-calendar-input{width:100%;height:22px;color:rgba(0,0,0,.65);background:#fff;border:0;outline:0;cursor:auto}.ant-calendar-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-week-number{width:286px}.ant-calendar-week-number-cell{text-align:center}.ant-calendar-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-calendar-header a:hover{color:#18947b}.ant-calendar-header .ant-calendar-century-select,.ant-calendar-header .ant-calendar-decade-select,.ant-calendar-header .ant-calendar-month-select,.ant-calendar-header .ant-calendar-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-header .ant-calendar-century-select-arrow,.ant-calendar-header .ant-calendar-decade-select-arrow,.ant-calendar-header .ant-calendar-month-select-arrow,.ant-calendar-header .ant-calendar-year-select-arrow{display:none}.ant-calendar-header .ant-calendar-next-century-btn,.ant-calendar-header .ant-calendar-next-decade-btn,.ant-calendar-header .ant-calendar-next-month-btn,.ant-calendar-header .ant-calendar-next-year-btn,.ant-calendar-header .ant-calendar-prev-century-btn,.ant-calendar-header .ant-calendar-prev-decade-btn,.ant-calendar-header .ant-calendar-prev-month-btn,.ant-calendar-header .ant-calendar-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,Hiragino Sans GB,Microsoft Yahei,"Microsoft Sans Serif",sans-serif;line-height:40px}.ant-calendar-header .ant-calendar-prev-century-btn,.ant-calendar-header .ant-calendar-prev-decade-btn,.ant-calendar-header .ant-calendar-prev-year-btn{left:7px;height:100%}.ant-calendar-header .ant-calendar-prev-century-btn:after,.ant-calendar-header .ant-calendar-prev-century-btn:before,.ant-calendar-header .ant-calendar-prev-decade-btn:after,.ant-calendar-header .ant-calendar-prev-decade-btn:before,.ant-calendar-header .ant-calendar-prev-year-btn:after,.ant-calendar-header .ant-calendar-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-header .ant-calendar-prev-century-btn:hover:after,.ant-calendar-header .ant-calendar-prev-century-btn:hover:before,.ant-calendar-header .ant-calendar-prev-decade-btn:hover:after,.ant-calendar-header .ant-calendar-prev-decade-btn:hover:before,.ant-calendar-header .ant-calendar-prev-year-btn:hover:after,.ant-calendar-header .ant-calendar-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-prev-century-btn:after,.ant-calendar-header .ant-calendar-prev-decade-btn:after,.ant-calendar-header .ant-calendar-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-header .ant-calendar-next-century-btn,.ant-calendar-header .ant-calendar-next-decade-btn,.ant-calendar-header .ant-calendar-next-year-btn{right:7px;height:100%}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-header .ant-calendar-next-century-btn:hover:after,.ant-calendar-header .ant-calendar-next-century-btn:hover:before,.ant-calendar-header .ant-calendar-next-decade-btn:hover:after,.ant-calendar-header .ant-calendar-next-decade-btn:hover:before,.ant-calendar-header .ant-calendar-next-year-btn:hover:after,.ant-calendar-header .ant-calendar-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:after{display:none}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-header .ant-calendar-next-century-btn:before,.ant-calendar-header .ant-calendar-next-decade-btn:before,.ant-calendar-header .ant-calendar-next-year-btn:before{position:relative;left:3px}.ant-calendar-header .ant-calendar-next-century-btn:after,.ant-calendar-header .ant-calendar-next-decade-btn:after,.ant-calendar-header .ant-calendar-next-year-btn:after{display:inline-block}.ant-calendar-header .ant-calendar-prev-month-btn{left:29px;height:100%}.ant-calendar-header .ant-calendar-prev-month-btn:after,.ant-calendar-header .ant-calendar-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-header .ant-calendar-prev-month-btn:hover:after,.ant-calendar-header .ant-calendar-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-prev-month-btn:after{display:none}.ant-calendar-header .ant-calendar-next-month-btn{right:29px;height:100%}.ant-calendar-header .ant-calendar-next-month-btn:after,.ant-calendar-header .ant-calendar-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-header .ant-calendar-next-month-btn:hover:after,.ant-calendar-header .ant-calendar-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-header .ant-calendar-next-month-btn:after{display:none}.ant-calendar-header .ant-calendar-next-month-btn:after,.ant-calendar-header .ant-calendar-next-month-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-body{padding:8px 12px}.ant-calendar table{width:100%;max-width:100%;background-color:transparent;border-collapse:collapse}.ant-calendar table,.ant-calendar td,.ant-calendar th{text-align:center;border:0}.ant-calendar-calendar-table{margin-bottom:0;border-spacing:0}.ant-calendar-column-header{width:33px;padding:6px 0;line-height:18px;text-align:center}.ant-calendar-column-header .ant-calendar-column-header-inner{display:block;font-weight:400}.ant-calendar-week-number-header .ant-calendar-column-header-inner{display:none}.ant-calendar-cell{height:30px;padding:3px 0}.ant-calendar-date{display:block;width:24px;height:24px;margin:0 auto;padding:0;color:rgba(0,0,0,.65);line-height:22px;text-align:center;background:transparent;border:1px solid transparent;border-radius:2px;transition:background .3s ease}.ant-calendar-date-panel{position:relative;outline:none}.ant-calendar-date:hover{background:#b3c7c0;cursor:pointer}.ant-calendar-date:active{color:#fff;background:#18947b}.ant-calendar-today .ant-calendar-date{color:#008771;font-weight:700;border-color:#008771}.ant-calendar-selected-day .ant-calendar-date{background:#77baa6}.ant-calendar-last-month-cell .ant-calendar-date,.ant-calendar-last-month-cell .ant-calendar-date:hover,.ant-calendar-next-month-btn-day .ant-calendar-date,.ant-calendar-next-month-btn-day .ant-calendar-date:hover{color:rgba(0,0,0,.25);background:transparent;border-color:transparent}.ant-calendar-disabled-cell .ant-calendar-date{position:relative;width:auto;color:rgba(0,0,0,.25);background:#f5f5f5;border:1px solid transparent;border-radius:0;cursor:not-allowed}.ant-calendar-disabled-cell .ant-calendar-date:hover{background:#f5f5f5}.ant-calendar-disabled-cell.ant-calendar-selected-day .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;background:rgba(0,0,0,.1);border-radius:2px;content:""}.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date{position:relative;padding-right:5px;padding-left:5px}.ant-calendar-disabled-cell.ant-calendar-today .ant-calendar-date:before{position:absolute;top:-1px;left:5px;width:24px;height:24px;border:1px solid rgba(0,0,0,.25);border-radius:2px;content:" "}.ant-calendar-disabled-cell-first-of-row .ant-calendar-date{border-top-left-radius:4px;border-bottom-left-radius:4px}.ant-calendar-disabled-cell-last-of-row .ant-calendar-date{border-top-right-radius:4px;border-bottom-right-radius:4px}.ant-calendar-footer{padding:0 12px;line-height:38px;border-top:1px solid #e8e8e8}.ant-calendar-footer:empty{border-top:0}.ant-calendar-footer-btn{display:block;text-align:center}.ant-calendar-footer-extra{text-align:left}.ant-calendar .ant-calendar-clear-btn,.ant-calendar .ant-calendar-today-btn{display:inline-block;margin:0 0 0 8px;text-align:center}.ant-calendar .ant-calendar-clear-btn-disabled,.ant-calendar .ant-calendar-today-btn-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-calendar .ant-calendar-clear-btn:only-child,.ant-calendar .ant-calendar-today-btn:only-child{margin:0}.ant-calendar .ant-calendar-clear-btn{position:absolute;top:7px;right:5px;display:none;width:20px;height:20px;margin:0;overflow:hidden;line-height:20px;text-align:center;text-indent:-76px}.ant-calendar .ant-calendar-clear-btn:after{display:inline-block;width:20px;color:rgba(0,0,0,.25);font-size:14px;line-height:1;text-indent:43px;transition:color .3s ease}.ant-calendar .ant-calendar-clear-btn:hover:after{color:rgba(0,0,0,.45)}.ant-calendar .ant-calendar-ok-btn{position:relative;display:inline-block;font-weight:400;white-space:nowrap;text-align:center;background-image:none;box-shadow:0 2px 0 rgba(0,0,0,.015);cursor:pointer;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none;touch-action:manipulation;height:32px;color:#fff;background-color:#008771;border:1px solid #008771;text-shadow:0 -1px 0 rgba(0,0,0,.12);box-shadow:0 2px 0 rgba(0,0,0,.045);height:24px;padding:0 7px;font-size:14px;border-radius:1rem;line-height:22px}.ant-calendar .ant-calendar-ok-btn>.anticon{line-height:1}.ant-calendar .ant-calendar-ok-btn,.ant-calendar .ant-calendar-ok-btn:active,.ant-calendar .ant-calendar-ok-btn:focus{outline:0}.ant-calendar .ant-calendar-ok-btn:not([disabled]):hover{text-decoration:none}.ant-calendar .ant-calendar-ok-btn:not([disabled]):active{outline:0;box-shadow:none}.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn[disabled]{cursor:not-allowed}.ant-calendar .ant-calendar-ok-btn.disabled>*,.ant-calendar .ant-calendar-ok-btn[disabled]>*{pointer-events:none}.ant-calendar .ant-calendar-ok-btn-lg{height:40px;padding:0 15px;font-size:16px;border-radius:1rem}.ant-calendar .ant-calendar-ok-btn-sm{height:24px;padding:0 7px;font-size:14px;border-radius:1rem}.ant-calendar .ant-calendar-ok-btn>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-calendar .ant-calendar-ok-btn:focus,.ant-calendar .ant-calendar-ok-btn:hover{color:#fff;background-color:#18947b;border-color:#18947b}.ant-calendar .ant-calendar-ok-btn:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:hover>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-calendar .ant-calendar-ok-btn.active,.ant-calendar .ant-calendar-ok-btn:active{color:#fff;background-color:#006154;border-color:#006154}.ant-calendar .ant-calendar-ok-btn.active>a:only-child,.ant-calendar .ant-calendar-ok-btn:active>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn:active>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-calendar .ant-calendar-ok-btn-disabled,.ant-calendar .ant-calendar-ok-btn-disabled.active,.ant-calendar .ant-calendar-ok-btn-disabled:active,.ant-calendar .ant-calendar-ok-btn-disabled:focus,.ant-calendar .ant-calendar-ok-btn-disabled:hover,.ant-calendar .ant-calendar-ok-btn.disabled,.ant-calendar .ant-calendar-ok-btn.disabled.active,.ant-calendar .ant-calendar-ok-btn.disabled:active,.ant-calendar .ant-calendar-ok-btn.disabled:focus,.ant-calendar .ant-calendar-ok-btn.disabled:hover,.ant-calendar .ant-calendar-ok-btn[disabled],.ant-calendar .ant-calendar-ok-btn[disabled].active,.ant-calendar .ant-calendar-ok-btn[disabled]:active,.ant-calendar .ant-calendar-ok-btn[disabled]:focus,.ant-calendar .ant-calendar-ok-btn[disabled]:hover{color:rgba(0,0,0,.25);background-color:#f5f5f5;border-color:#d9d9d9;text-shadow:none;box-shadow:none}.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child{color:currentColor}.ant-calendar .ant-calendar-ok-btn-disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn-disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled.active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn.disabled>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled].active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:active>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:focus>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]:hover>a:only-child:after,.ant-calendar .ant-calendar-ok-btn[disabled]>a:only-child:after{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;content:""}.ant-calendar-range-picker-input{width:44%;height:99%;text-align:center;background-color:transparent;border:0;outline:0}.ant-calendar-range-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-range-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-range-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-range-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-range-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-range-picker-input[disabled]{cursor:not-allowed}.ant-calendar-range-picker-separator{display:inline-block;min-width:10px;height:100%;color:rgba(0,0,0,.45);white-space:nowrap;text-align:center;vertical-align:top;pointer-events:none}.ant-input-disabled .ant-calendar-range-picker-separator{color:rgba(0,0,0,.25)}.ant-calendar-range{width:552px;overflow:hidden}.ant-calendar-range .ant-calendar-date-panel:after{display:block;clear:both;height:0;visibility:hidden;content:"."}.ant-calendar-range-part{position:relative;width:50%}.ant-calendar-range-left{float:left}.ant-calendar-range-left .ant-calendar-time-picker-inner{border-right:1px solid #e8e8e8}.ant-calendar-range-right{float:right}.ant-calendar-range-right .ant-calendar-time-picker-inner{border-left:1px solid #e8e8e8}.ant-calendar-range-middle{position:absolute;left:50%;z-index:1;height:34px;margin:1px 0 0;padding:0 200px 0 0;color:rgba(0,0,0,.45);line-height:34px;text-align:center;transform:translateX(-50%);pointer-events:none}.ant-calendar-range-right .ant-calendar-date-input-wrap{margin-left:-90px}.ant-calendar-range.ant-calendar-time .ant-calendar-range-middle{padding:0 10px 0 0;transform:translateX(-50%)}.ant-calendar-range .ant-calendar-today :not(.ant-calendar-disabled-cell) :not(.ant-calendar-last-month-cell) :not(.ant-calendar-next-month-btn-day) .ant-calendar-date{color:#008771;background:#77baa6;border-color:#008771}.ant-calendar-range .ant-calendar-selected-end-date .ant-calendar-date,.ant-calendar-range .ant-calendar-selected-start-date .ant-calendar-date{color:#fff;background:#008771;border:1px solid transparent}.ant-calendar-range .ant-calendar-selected-end-date .ant-calendar-date:hover,.ant-calendar-range .ant-calendar-selected-start-date .ant-calendar-date:hover{background:#008771}.ant-calendar-range.ant-calendar-time .ant-calendar-range-right .ant-calendar-date-input-wrap{margin-left:0}.ant-calendar-range .ant-calendar-input-wrap{position:relative;height:34px}.ant-calendar-range .ant-calendar-input,.ant-calendar-range .ant-calendar-time-picker-input{position:relative;display:inline-block;width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border-radius:1rem;transition:all .3s;height:24px;padding:4px 0;line-height:24px;border:0;box-shadow:none}.ant-calendar-range .ant-calendar-input::-moz-placeholder,.ant-calendar-range .ant-calendar-time-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-calendar-range .ant-calendar-input:-ms-input-placeholder,.ant-calendar-range .ant-calendar-time-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-calendar-range .ant-calendar-input::-webkit-input-placeholder,.ant-calendar-range .ant-calendar-time-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-calendar-range .ant-calendar-input:-moz-placeholder-shown,.ant-calendar-range .ant-calendar-time-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-calendar-range .ant-calendar-input:placeholder-shown,.ant-calendar-range .ant-calendar-time-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-calendar-range .ant-calendar-input:hover,.ant-calendar-range .ant-calendar-time-picker-input:hover{border-color:#18947b;border-right-width:1px!important}.ant-calendar-range .ant-calendar-input:focus,.ant-calendar-range .ant-calendar-time-picker-input:focus{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-calendar-range .ant-calendar-input-disabled,.ant-calendar-range .ant-calendar-time-picker-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-calendar-range .ant-calendar-input-disabled:hover,.ant-calendar-range .ant-calendar-time-picker-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-calendar-range .ant-calendar-input[disabled],.ant-calendar-range .ant-calendar-time-picker-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-calendar-range .ant-calendar-input[disabled]:hover,.ant-calendar-range .ant-calendar-time-picker-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-calendar-range .ant-calendar-input,textarea.ant-calendar-range .ant-calendar-time-picker-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-calendar-range .ant-calendar-input-lg,.ant-calendar-range .ant-calendar-time-picker-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-calendar-range .ant-calendar-input-sm,.ant-calendar-range .ant-calendar-time-picker-input-sm{height:24px;padding:1px 7px}.ant-calendar-range .ant-calendar-input:focus,.ant-calendar-range .ant-calendar-time-picker-input:focus{box-shadow:none}.ant-calendar-range .ant-calendar-time-picker-icon{display:none}.ant-calendar-range.ant-calendar-week-number{width:574px}.ant-calendar-range.ant-calendar-week-number .ant-calendar-range-part{width:286px}.ant-calendar-range .ant-calendar-decade-panel,.ant-calendar-range .ant-calendar-month-panel,.ant-calendar-range .ant-calendar-year-panel{top:34px}.ant-calendar-range .ant-calendar-month-panel .ant-calendar-year-panel{top:0}.ant-calendar-range .ant-calendar-decade-panel-table,.ant-calendar-range .ant-calendar-month-panel-table,.ant-calendar-range .ant-calendar-year-panel-table{height:208px}.ant-calendar-range .ant-calendar-in-range-cell{position:relative;border-radius:0}.ant-calendar-range .ant-calendar-in-range-cell>div{position:relative;z-index:1}.ant-calendar-range .ant-calendar-in-range-cell:before{position:absolute;top:4px;right:0;bottom:4px;left:0;display:block;background:#b3c7c0;border:0;border-radius:0;content:""}.ant-calendar-range .ant-calendar-footer-extra{float:left}div.ant-calendar-range-quick-selector{text-align:left}div.ant-calendar-range-quick-selector>a{margin-right:8px}.ant-calendar-range .ant-calendar-decade-panel-header,.ant-calendar-range .ant-calendar-header,.ant-calendar-range .ant-calendar-month-panel-header,.ant-calendar-range .ant-calendar-year-panel-header{border-bottom:0}.ant-calendar-range .ant-calendar-body,.ant-calendar-range .ant-calendar-decade-panel-body,.ant-calendar-range .ant-calendar-month-panel-body,.ant-calendar-range .ant-calendar-year-panel-body{border-top:1px solid #e8e8e8}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker{top:68px;z-index:2;width:100%;height:207px}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-panel{height:267px;margin-top:-34px}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-inner{height:100%;padding-top:40px;background:none}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-combobox{display:inline-block;height:100%;background-color:#fff;border-top:1px solid #e8e8e8}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-select{height:100%}.ant-calendar-range.ant-calendar-time .ant-calendar-time-picker-select ul{max-height:100%}.ant-calendar-range.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn{margin-right:8px}.ant-calendar-range.ant-calendar-time .ant-calendar-today-btn{height:22px;margin:8px 12px;line-height:22px}.ant-calendar-range-with-ranges.ant-calendar-time .ant-calendar-time-picker{height:233px}.ant-calendar-range.ant-calendar-show-time-picker .ant-calendar-body{border-top-color:transparent}.ant-calendar-time-picker{position:absolute;top:40px;width:100%;background-color:#fff}.ant-calendar-time-picker-panel{position:absolute;z-index:1050;width:100%}.ant-calendar-time-picker-inner{position:relative;display:inline-block;width:100%;overflow:hidden;font-size:14px;line-height:1.5;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;outline:none}.ant-calendar-time-picker-column-1,.ant-calendar-time-picker-column-1 .ant-calendar-time-picker-select,.ant-calendar-time-picker-combobox{width:100%}.ant-calendar-time-picker-column-2 .ant-calendar-time-picker-select{width:50%}.ant-calendar-time-picker-column-3 .ant-calendar-time-picker-select{width:33.33%}.ant-calendar-time-picker-column-4 .ant-calendar-time-picker-select{width:25%}.ant-calendar-time-picker-input-wrap{display:none}.ant-calendar-time-picker-select{position:relative;float:left;height:226px;overflow:hidden;font-size:14px;border-right:1px solid #e8e8e8}.ant-calendar-time-picker-select:hover{overflow-y:auto}.ant-calendar-time-picker-select:first-child{margin-left:0;border-left:0}.ant-calendar-time-picker-select:last-child{border-right:0}.ant-calendar-time-picker-select ul{width:100%;max-height:206px;margin:0;padding:0;list-style:none}.ant-calendar-time-picker-select li{width:100%;height:24px;margin:0;line-height:24px;text-align:center;list-style:none;cursor:pointer;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-calendar-time-picker-select li:last-child:after{display:block;height:202px;content:""}.ant-calendar-time-picker-select li:hover{background:#b3c7c0}.ant-calendar-time-picker-select li:focus{color:#008771;font-weight:600;outline:none}li.ant-calendar-time-picker-select-option-selected{font-weight:600;background:#f5f5f5}li.ant-calendar-time-picker-select-option-disabled{color:rgba(0,0,0,.25)}li.ant-calendar-time-picker-select-option-disabled:hover{background:transparent;cursor:not-allowed}.ant-calendar-time .ant-calendar-day-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:34px}.ant-calendar-time .ant-calendar-footer{position:relative;height:auto}.ant-calendar-time .ant-calendar-footer-btn{text-align:right}.ant-calendar-time .ant-calendar-footer .ant-calendar-today-btn{float:left;margin:0}.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn{display:inline-block;margin-right:8px}.ant-calendar-time .ant-calendar-footer .ant-calendar-time-picker-btn-disabled{color:rgba(0,0,0,.25)}.ant-calendar-month-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;background:#fff;border-radius:1rem;outline:none}.ant-calendar-month-panel>div{display:flex;flex-direction:column;height:100%}.ant-calendar-month-panel-hidden{display:none}.ant-calendar-month-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative}.ant-calendar-month-panel-header a:hover{color:#18947b}.ant-calendar-month-panel-header .ant-calendar-month-panel-century-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-month-select,.ant-calendar-month-panel-header .ant-calendar-month-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-month-panel-header .ant-calendar-month-panel-century-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-decade-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-month-select-arrow,.ant-calendar-month-panel-header .ant-calendar-month-panel-year-select-arrow{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,Hiragino Sans GB,Microsoft Yahei,"Microsoft Sans Serif",sans-serif;line-height:40px}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn{right:7px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:hover:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:before,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-century-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-decade-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-year-btn:after{display:inline-block}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-prev-month-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn{right:29px;height:100%}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:hover:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after{display:none}.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:after,.ant-calendar-month-panel-header .ant-calendar-month-panel-next-month-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-month-panel-body{flex:1}.ant-calendar-month-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-month-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-month-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month,.ant-calendar-month-panel-selected-cell .ant-calendar-month-panel-month:hover{color:#fff;background:#008771}.ant-calendar-month-panel-cell{text-align:center}.ant-calendar-month-panel-cell-disabled .ant-calendar-month-panel-month,.ant-calendar-month-panel-cell-disabled .ant-calendar-month-panel-month:hover{color:rgba(0,0,0,.25);background:#f5f5f5;cursor:not-allowed}.ant-calendar-month-panel-month{display:inline-block;height:24px;margin:0 auto;padding:0 8px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;transition:background .3s ease}.ant-calendar-month-panel-month:hover{background:#b3c7c0;cursor:pointer}.ant-calendar-year-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;background:#fff;border-radius:1rem;outline:none}.ant-calendar-year-panel>div{display:flex;flex-direction:column;height:100%}.ant-calendar-year-panel-hidden{display:none}.ant-calendar-year-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative}.ant-calendar-year-panel-header a:hover{color:#18947b}.ant-calendar-year-panel-header .ant-calendar-year-panel-century-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-month-select,.ant-calendar-year-panel-header .ant-calendar-year-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-year-panel-header .ant-calendar-year-panel-century-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-decade-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-month-select-arrow,.ant-calendar-year-panel-header .ant-calendar-year-panel-year-select-arrow{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,Hiragino Sans GB,Microsoft Yahei,"Microsoft Sans Serif",sans-serif;line-height:40px}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn{right:7px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:hover:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:before,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-century-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-decade-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-year-btn:after{display:inline-block}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-prev-month-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn{right:29px;height:100%}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:hover:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after{display:none}.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:after,.ant-calendar-year-panel-header .ant-calendar-year-panel-next-month-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-year-panel-body{flex:1}.ant-calendar-year-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-year-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-year-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-year-panel-cell{text-align:center}.ant-calendar-year-panel-cell-disabled .ant-calendar-year-panel-year,.ant-calendar-year-panel-cell-disabled .ant-calendar-year-panel-year:hover{color:rgba(0,0,0,.25);background:#f5f5f5;cursor:not-allowed}.ant-calendar-year-panel-year{display:inline-block;height:24px;margin:0 auto;padding:0 8px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;transition:background .3s ease}.ant-calendar-year-panel-year:hover{background:#b3c7c0;cursor:pointer}.ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year,.ant-calendar-year-panel-selected-cell .ant-calendar-year-panel-year:hover{color:#fff;background:#008771}.ant-calendar-year-panel-last-decade-cell .ant-calendar-year-panel-year,.ant-calendar-year-panel-next-decade-cell .ant-calendar-year-panel-year{color:rgba(0,0,0,.25);-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-calendar-decade-panel{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:flex;flex-direction:column;background:#fff;border-radius:1rem;outline:none}.ant-calendar-decade-panel-hidden{display:none}.ant-calendar-decade-panel-header{height:40px;line-height:40px;text-align:center;border-bottom:1px solid #e8e8e8;-webkit-user-select:none;-moz-user-select:none;user-select:none;position:relative}.ant-calendar-decade-panel-header a:hover{color:#18947b}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-century-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-decade-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-month-select,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-year-select{display:inline-block;padding:0 2px;color:rgba(0,0,0,.85);font-weight:500;line-height:40px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-century-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-decade-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-month-select-arrow,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-year-select-arrow{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn{position:absolute;top:0;display:inline-block;padding:0 5px;color:rgba(0,0,0,.45);font-size:16px;font-family:Arial,Hiragino Sans GB,Microsoft Yahei,"Microsoft Sans Serif",sans-serif;line-height:40px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn{left:7px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-year-btn:after{display:none;position:relative;left:-3px;display:inline-block}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn{right:7px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:hover:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:before,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:before{position:relative;left:3px}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-century-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-decade-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-year-btn:after{display:inline-block}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn{left:29px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-prev-month-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn{right:29px;height:100%}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:before{position:relative;top:-1px;display:inline-block;width:8px;height:8px;vertical-align:middle;border:0 solid #aaa;border-width:1.5px 0 0 1.5px;border-radius:1px;transform:rotate(-45deg) scale(.8);transition:all .3s;content:""}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:hover:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:hover:before{border-color:rgba(0,0,0,.65)}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after{display:none}.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:after,.ant-calendar-decade-panel-header .ant-calendar-decade-panel-next-month-btn:before{transform:rotate(135deg) scale(.8)}.ant-calendar-decade-panel-body{flex:1}.ant-calendar-decade-panel-footer{border-top:1px solid #e8e8e8}.ant-calendar-decade-panel-footer .ant-calendar-footer-extra{padding:0 12px}.ant-calendar-decade-panel-table{width:100%;height:100%;table-layout:fixed;border-collapse:separate}.ant-calendar-decade-panel-cell{white-space:nowrap;text-align:center}.ant-calendar-decade-panel-decade{display:inline-block;height:24px;margin:0 auto;padding:0 6px;color:rgba(0,0,0,.65);line-height:24px;text-align:center;background:transparent;border-radius:2px;transition:background .3s ease}.ant-calendar-decade-panel-decade:hover{background:#b3c7c0;cursor:pointer}.ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade,.ant-calendar-decade-panel-selected-cell .ant-calendar-decade-panel-decade:hover{color:#fff;background:#008771}.ant-calendar-decade-panel-last-century-cell .ant-calendar-decade-panel-decade,.ant-calendar-decade-panel-next-century-cell .ant-calendar-decade-panel-decade{color:rgba(0,0,0,.25);-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-calendar-month .ant-calendar-month-header-wrap{position:relative;height:288px}.ant-calendar-month .ant-calendar-month-panel,.ant-calendar-month .ant-calendar-year-panel{top:0;height:100%}.ant-calendar-week-number-cell{opacity:.5}.ant-calendar-week-number .ant-calendar-body tr{cursor:pointer;transition:all .3s}.ant-calendar-week-number .ant-calendar-body tr:hover{background:#b3c7c0}.ant-calendar-week-number .ant-calendar-body tr.ant-calendar-active-week{font-weight:700;background:#77baa6}.ant-calendar-week-number .ant-calendar-body tr .ant-calendar-selected-day .ant-calendar-date,.ant-calendar-week-number .ant-calendar-body tr .ant-calendar-selected-day:hover .ant-calendar-date{color:rgba(0,0,0,.65);background:transparent}.ant-time-picker-panel{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;z-index:1050;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.ant-time-picker-panel-inner{position:relative;left:-2px;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border-radius:1rem;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-time-picker-panel-input{width:100%;max-width:154px;margin:0;padding:0;line-height:normal;border:0;outline:0;cursor:auto}.ant-time-picker-panel-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-time-picker-panel-input:-ms-input-placeholder{color:#bfbfbf}.ant-time-picker-panel-input::-webkit-input-placeholder{color:#bfbfbf}.ant-time-picker-panel-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-time-picker-panel-input:placeholder-shown{text-overflow:ellipsis}.ant-time-picker-panel-input-wrap{position:relative;padding:7px 2px 7px 12px;border-bottom:1px solid #e8e8e8}.ant-time-picker-panel-input-invalid{border-color:#f5222d}.ant-time-picker-panel-narrow .ant-time-picker-panel-input-wrap{max-width:112px}.ant-time-picker-panel-select{position:relative;float:left;width:56px;max-height:192px;overflow:hidden;font-size:14px;border-left:1px solid #e8e8e8}.ant-time-picker-panel-select:hover{overflow-y:auto}.ant-time-picker-panel-select:first-child{margin-left:0;border-left:0}.ant-time-picker-panel-select:last-child{border-right:0}.ant-time-picker-panel-select:only-child{width:100%}.ant-time-picker-panel-select ul{width:56px;margin:0;padding:0 0 160px;list-style:none}.ant-time-picker-panel-select li{width:100%;height:32px;margin:0;padding:0 0 0 12px;line-height:32px;text-align:left;list-style:none;cursor:pointer;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-time-picker-panel-select li:focus{color:#008771;font-weight:600;outline:none}.ant-time-picker-panel-select li:hover{background:#b3c7c0}li.ant-time-picker-panel-select-option-selected{font-weight:600;background:#f5f5f5}li.ant-time-picker-panel-select-option-selected:hover{background:#f5f5f5}li.ant-time-picker-panel-select-option-disabled{color:rgba(0,0,0,.25)}li.ant-time-picker-panel-select-option-disabled:hover{background:transparent;cursor:not-allowed}li.ant-time-picker-panel-select-option-disabled:focus{color:rgba(0,0,0,.25);font-weight:inherit}.ant-time-picker-panel-combobox{zoom:1}.ant-time-picker-panel-combobox:after,.ant-time-picker-panel-combobox:before{display:table;content:""}.ant-time-picker-panel-combobox:after{clear:both}.ant-time-picker-panel-addon{padding:8px;border-top:1px solid #e8e8e8}.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-topRight,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-topRight{animation-name:antSlideDownIn}.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-appear.slide-up-appear-active.ant-time-picker-panel-placement-bottomRight,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-enter.slide-up-enter-active.ant-time-picker-panel-placement-bottomRight{animation-name:antSlideUpIn}.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-topLeft,.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-topRight{animation-name:antSlideDownOut}.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-bottomLeft,.ant-time-picker-panel.slide-up-leave.slide-up-leave-active.ant-time-picker-panel-placement-bottomRight{animation-name:antSlideUpOut}.ant-time-picker{box-sizing:border-box;margin:0;padding:0;font-size:14px;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";width:128px;outline:none;cursor:text;transition:opacity .3s}.ant-time-picker,.ant-time-picker-input{color:rgba(0,0,0,.65);line-height:1.5;position:relative;display:inline-block}.ant-time-picker-input{width:100%;height:32px;padding:4px 11px;font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:1rem;transition:all .3s}.ant-time-picker-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-time-picker-input:-ms-input-placeholder{color:#bfbfbf}.ant-time-picker-input::-webkit-input-placeholder{color:#bfbfbf}.ant-time-picker-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-time-picker-input:placeholder-shown{text-overflow:ellipsis}.ant-time-picker-input:focus,.ant-time-picker-input:hover{border-color:#18947b;border-right-width:1px!important}.ant-time-picker-input:focus{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-time-picker-input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-time-picker-input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-time-picker-input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-time-picker-input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-time-picker-input-sm{height:24px;padding:1px 7px}.ant-time-picker-input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-time-picker-input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-time-picker-open{opacity:0}.ant-time-picker-clear,.ant-time-picker-icon{position:absolute;top:50%;right:11px;z-index:1;width:14px;height:14px;margin-top:-7px;color:rgba(0,0,0,.25);line-height:14px;transition:all .3s cubic-bezier(.645,.045,.355,1);-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-time-picker-clear .ant-time-picker-clock-icon,.ant-time-picker-icon .ant-time-picker-clock-icon{display:block;color:rgba(0,0,0,.25);line-height:1}.ant-time-picker-clear{z-index:2;background:#fff;opacity:0;pointer-events:none}.ant-time-picker-clear:hover{color:rgba(0,0,0,.45)}.ant-time-picker:hover .ant-time-picker-clear{opacity:1;pointer-events:auto}.ant-time-picker-large .ant-time-picker-input{height:40px;padding:6px 11px;font-size:16px}.ant-time-picker-small .ant-time-picker-input{height:24px;padding:1px 7px}.ant-time-picker-small .ant-time-picker-clear,.ant-time-picker-small .ant-time-picker-icon{right:7px}@media not all and (min-resolution:0.001dpcm){@supports (-webkit-appearance:none) and (stroke-color:transparent){.ant-input{line-height:1.5}}}.ant-tag{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block;height:auto;margin:0 8px 0 0;padding:0 7px;font-size:12px;line-height:20px;white-space:nowrap;background:#fafafa;border:1px solid #d9d9d9;border-radius:1rem;cursor:default;opacity:1;transition:all .3s cubic-bezier(.78,.14,.15,.86)}.ant-tag:hover{opacity:.85}.ant-tag,.ant-tag a,.ant-tag a:hover{color:rgba(0,0,0,.65)}.ant-tag>a:first-child:last-child{display:inline-block;margin:0 -8px;padding:0 8px}.ant-tag .anticon-close{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);margin-left:3px;color:rgba(0,0,0,.45);font-weight:700;cursor:pointer;transition:all .3s cubic-bezier(.78,.14,.15,.86)}:root .ant-tag .anticon-close{font-size:12px}.ant-tag .anticon-close:hover{color:rgba(0,0,0,.85)}.ant-tag-has-color{border-color:transparent}.ant-tag-has-color,.ant-tag-has-color .anticon-close,.ant-tag-has-color .anticon-close:hover,.ant-tag-has-color a,.ant-tag-has-color a:hover{color:#fff}.ant-tag-checkable{background-color:transparent;border-color:transparent}.ant-tag-checkable:not(.ant-tag-checkable-checked):hover{color:#008771}.ant-tag-checkable-checked,.ant-tag-checkable:active{color:#fff}.ant-tag-checkable-checked{background-color:#008771}.ant-tag-checkable:active{background-color:#006154}.ant-tag-hidden{display:none}.ant-tag-pink{color:#eb2f96;background:#fff0f6;border-color:#ffadd2}.ant-tag-pink-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-magenta{color:#eb2f96;background:#fff0f6;border-color:#ffadd2}.ant-tag-magenta-inverse{color:#fff;background:#eb2f96;border-color:#eb2f96}.ant-tag-red{color:#f5222d;background:#fff1f0;border-color:#ffa39e}.ant-tag-red-inverse{color:#fff;background:#f5222d;border-color:#f5222d}.ant-tag-volcano{color:#fa541c;background:#fff2e8;border-color:#ffbb96}.ant-tag-volcano-inverse{color:#fff;background:#fa541c;border-color:#fa541c}.ant-tag-orange{color:#fa8c16;background:#fff7e6;border-color:#ffd591}.ant-tag-orange-inverse{color:#fff;background:#fa8c16;border-color:#fa8c16}.ant-tag-yellow{color:#fadb14;background:#feffe6;border-color:#fffb8f}.ant-tag-yellow-inverse{color:#fff;background:#fadb14;border-color:#fadb14}.ant-tag-gold{color:#faad14;background:#fffbe6;border-color:#ffe58f}.ant-tag-gold-inverse{color:#fff;background:#faad14;border-color:#faad14}.ant-tag-cyan{color:#13c2c2;background:#e6fffb;border-color:#87e8de}.ant-tag-cyan-inverse{color:#fff;background:#13c2c2;border-color:#13c2c2}.ant-tag-lime{color:#a0d911;background:#fcffe6;border-color:#eaff8f}.ant-tag-lime-inverse{color:#fff;background:#a0d911;border-color:#a0d911}.ant-tag-green{color:#008771;background:#b3c7c0;border-color:#53ad95}.ant-tag-green-inverse{color:#fff;background:#008771;border-color:#008771}.ant-tag-blue{color:#1890ff;background:#e6f7ff;border-color:#91d5ff}.ant-tag-blue-inverse{color:#fff;background:#1890ff;border-color:#1890ff}.ant-tag-geekblue{color:#2f54eb;background:#f0f5ff;border-color:#adc6ff}.ant-tag-geekblue-inverse{color:#fff;background:#2f54eb;border-color:#2f54eb}.ant-tag-purple{color:#722ed1;background:#f9f0ff;border-color:#d3adf7}.ant-tag-purple-inverse{color:#fff;background:#722ed1;border-color:#722ed1}.ant-descriptions-title{margin-bottom:20px;color:rgba(0,0,0,.85);font-weight:700;font-size:16px;line-height:1.5}.ant-descriptions-view{width:100%;overflow:hidden;border-radius:1rem}.ant-descriptions-view table{width:100%;table-layout:fixed}.ant-descriptions-row>td,.ant-descriptions-row>th{padding-bottom:16px}.ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-item-label{color:rgba(0,0,0,.85);font-weight:400;font-size:14px;line-height:1.5}.ant-descriptions-item-label:after{position:relative;top:-.5px;margin:0 8px 0 2px;content:" "}.ant-descriptions-item-colon:after{content:":"}.ant-descriptions-item-no-label:after{margin:0;content:""}.ant-descriptions-item-content{display:table-cell;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5}.ant-descriptions-item{padding-bottom:0}.ant-descriptions-item>span{display:inline-block}.ant-descriptions-middle .ant-descriptions-row>td,.ant-descriptions-middle .ant-descriptions-row>th{padding-bottom:12px}.ant-descriptions-small .ant-descriptions-row>td,.ant-descriptions-small .ant-descriptions-row>th{padding-bottom:8px}.ant-descriptions-bordered .ant-descriptions-view{border:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-view>table{table-layout:auto}.ant-descriptions-bordered .ant-descriptions-item-content,.ant-descriptions-bordered .ant-descriptions-item-label{padding:16px 24px;border-right:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-item-content:last-child,.ant-descriptions-bordered .ant-descriptions-item-label:last-child{border-right:none}.ant-descriptions-bordered .ant-descriptions-item-label{background-color:#fafafa}.ant-descriptions-bordered .ant-descriptions-item-label:after{display:none}.ant-descriptions-bordered .ant-descriptions-row{border-bottom:1px solid #e8e8e8}.ant-descriptions-bordered .ant-descriptions-row:last-child{border-bottom:none}.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-content,.ant-descriptions-bordered.ant-descriptions-middle .ant-descriptions-item-label{padding:12px 24px}.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-content,.ant-descriptions-bordered.ant-descriptions-small .ant-descriptions-item-label{padding:8px 16px}.ant-divider{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";background:#e8e8e8}.ant-divider,.ant-divider-vertical{position:relative;top:-.06em;display:inline-block;width:1px;height:.9em;margin:0 8px;vertical-align:middle}.ant-divider-horizontal{display:block;clear:both;width:100%;min-width:100%;height:1px;margin:24px 0}.ant-divider-horizontal.ant-divider-with-text-center,.ant-divider-horizontal.ant-divider-with-text-left,.ant-divider-horizontal.ant-divider-with-text-right{display:table;margin:16px 0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;white-space:nowrap;text-align:center;background:transparent}.ant-divider-horizontal.ant-divider-with-text-center:after,.ant-divider-horizontal.ant-divider-with-text-center:before,.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-left:before,.ant-divider-horizontal.ant-divider-with-text-right:after,.ant-divider-horizontal.ant-divider-with-text-right:before{position:relative;top:50%;display:table-cell;width:50%;border-top:1px solid #e8e8e8;transform:translateY(50%);content:""}.ant-divider-horizontal.ant-divider-with-text-left .ant-divider-inner-text,.ant-divider-horizontal.ant-divider-with-text-right .ant-divider-inner-text{display:inline-block;padding:0 10px}.ant-divider-horizontal.ant-divider-with-text-left:before{top:50%;width:5%}.ant-divider-horizontal.ant-divider-with-text-left:after,.ant-divider-horizontal.ant-divider-with-text-right:before{top:50%;width:95%}.ant-divider-horizontal.ant-divider-with-text-right:after{top:50%;width:5%}.ant-divider-inner-text{display:inline-block;padding:0 24px}.ant-divider-dashed{background:none;border:dashed #e8e8e8;border-width:1px 0 0}.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed{border-top:0}.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-center.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-left.ant-divider-dashed:before,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed:after,.ant-divider-horizontal.ant-divider-with-text-right.ant-divider-dashed:before{border-style:dashed none none}.ant-divider-vertical.ant-divider-dashed{border-width:0 0 0 1px}.ant-drawer{position:fixed;z-index:1000;width:0;height:100%;transition:transform .3s cubic-bezier(.7,.3,.1,1),height 0s ease .3s,width 0s ease .3s}.ant-drawer>*{transition:transform .3s cubic-bezier(.7,.3,.1,1),box-shadow .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-content-wrapper{position:absolute}.ant-drawer .ant-drawer-content{width:100%;height:100%}.ant-drawer-left,.ant-drawer-right{top:0;width:0;height:100%}.ant-drawer-left .ant-drawer-content-wrapper,.ant-drawer-right .ant-drawer-content-wrapper{height:100%}.ant-drawer-left.ant-drawer-open,.ant-drawer-right.ant-drawer-open{width:100%;transition:transform .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-left.ant-drawer-open.no-mask,.ant-drawer-right.ant-drawer-open.no-mask{width:0}.ant-drawer-left.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:2px 0 8px rgba(0,0,0,.15)}.ant-drawer-right,.ant-drawer-right .ant-drawer-content-wrapper{right:0}.ant-drawer-right.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:-2px 0 8px rgba(0,0,0,.15)}.ant-drawer-right.ant-drawer-open.no-mask{right:1px;transform:translateX(1px)}.ant-drawer-bottom,.ant-drawer-top{left:0;width:100%;height:0%}.ant-drawer-bottom .ant-drawer-content-wrapper,.ant-drawer-top .ant-drawer-content-wrapper{width:100%}.ant-drawer-bottom.ant-drawer-open,.ant-drawer-top.ant-drawer-open{height:100%;transition:transform .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-bottom.ant-drawer-open.no-mask,.ant-drawer-top.ant-drawer-open.no-mask{height:0%}.ant-drawer-top{top:0}.ant-drawer-top.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-drawer-bottom,.ant-drawer-bottom .ant-drawer-content-wrapper{bottom:0}.ant-drawer-bottom.ant-drawer-open .ant-drawer-content-wrapper{box-shadow:0 -2px 8px rgba(0,0,0,.15)}.ant-drawer-bottom.ant-drawer-open.no-mask{bottom:1px;transform:translateY(1px)}.ant-drawer.ant-drawer-open .ant-drawer-mask{height:100%;opacity:1;transition:none;animation:antdDrawerFadeIn .3s cubic-bezier(.7,.3,.1,1)}.ant-drawer-title{margin:0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:22px}.ant-drawer-content{position:relative;z-index:1;overflow:auto;background-color:#fff;background-clip:padding-box;border:0}.ant-drawer-close{position:absolute;top:0;right:0;z-index:10;display:block;width:56px;height:56px;padding:0;color:rgba(0,0,0,.45);font-weight:700;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s;text-rendering:auto}.ant-drawer-close:focus,.ant-drawer-close:hover{color:rgba(0,0,0,.75);text-decoration:none}.ant-drawer-header{position:relative;padding:16px 24px;border-bottom:1px solid #e8e8e8;border-radius:1rem 1rem 0 0}.ant-drawer-header,.ant-drawer-header-no-title{color:rgba(0,0,0,.65);background:#fff}.ant-drawer-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}.ant-drawer-wrapper-body{height:100%;overflow:auto}.ant-drawer-mask{position:absolute;top:0;left:0;width:100%;height:0;background-color:rgba(0,0,0,.45);opacity:0;filter:alpha(opacity=45);transition:opacity .3s linear,height 0s ease .3s}.ant-drawer-open-content{box-shadow:0 4px 12px rgba(0,0,0,.15)}@keyframes antdDrawerFadeIn{0%{opacity:0}to{opacity:1}}.ant-form{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-form legend{display:block;width:100%;margin-bottom:20px;padding:0;color:rgba(0,0,0,.45);font-size:16px;line-height:inherit;border:0;border-bottom:1px solid #d9d9d9}.ant-form label{font-size:14px}.ant-form input[type=search]{box-sizing:border-box}.ant-form input[type=checkbox],.ant-form input[type=radio]{line-height:normal}.ant-form input[type=file]{display:block}.ant-form input[type=range]{display:block;width:100%}.ant-form select[multiple],.ant-form select[size]{height:auto}.ant-form input[type=checkbox]:focus,.ant-form input[type=file]:focus,.ant-form input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.ant-form output{display:block;padding-top:15px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5}.ant-form-item-required:before{display:inline-block;margin-right:4px;color:#f5222d;font-size:14px;font-family:SimSun,sans-serif;line-height:1;content:"*"}.ant-form-hide-required-mark .ant-form-item-required:before{display:none}.ant-form-item-label>label{color:rgba(0,0,0,.85)}.ant-form-item-label>label:after{content:":";position:relative;top:-.5px;margin:0 8px 0 2px}.ant-form-item-label>label.ant-form-item-no-colon:after{content:" "}.ant-form-item{box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";margin:0 0 24px;vertical-align:top}.ant-form-item label{position:relative}.ant-form-item label>.anticon{font-size:14px;vertical-align:top}.ant-form-item-control{position:relative;line-height:40px;zoom:1}.ant-form-item-control:after,.ant-form-item-control:before{display:table;content:""}.ant-form-item-control:after{clear:both}.ant-form-item-children{position:relative}.ant-form-item-with-help{margin-bottom:5px}.ant-form-item-label{display:inline-block;overflow:hidden;line-height:39.9999px;white-space:nowrap;text-align:right;vertical-align:middle}.ant-form-item-label-left{text-align:left}.ant-form-item .ant-switch{margin:2px 0 4px}.ant-form-explain,.ant-form-extra{clear:both;min-height:22px;margin-top:-2px;color:rgba(0,0,0,.45);font-size:14px;line-height:1.5;transition:color .3s cubic-bezier(.215,.61,.355,1)}.ant-form-explain{margin-bottom:-1px}.ant-form-extra{padding-top:4px}.ant-form-text{display:inline-block;padding-right:8px}.ant-form-split{display:block;text-align:center}form .has-feedback .ant-input{padding-right:30px}form .has-feedback .ant-input-affix-wrapper .ant-input-suffix{padding-right:18px}form .has-feedback .ant-input-affix-wrapper .ant-input{padding-right:49px}form .has-feedback .ant-input-affix-wrapper.ant-input-affix-wrapper-input-with-clear-btn .ant-input{padding-right:68px}form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-arrow,form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection__clear,form .has-feedback>.ant-select .ant-select-arrow,form .has-feedback>.ant-select .ant-select-selection__clear{right:28px}form .has-feedback :not(.ant-input-group-addon)>.ant-select .ant-select-selection-selected-value,form .has-feedback>.ant-select .ant-select-selection-selected-value{padding-right:42px}form .has-feedback .ant-cascader-picker-arrow{margin-right:17px}form .has-feedback .ant-calendar-picker-clear,form .has-feedback .ant-calendar-picker-icon,form .has-feedback .ant-cascader-picker-clear,form .has-feedback .ant-input-search:not(.ant-input-search-enter-button) .ant-input-suffix,form .has-feedback .ant-time-picker-clear,form .has-feedback .ant-time-picker-icon{right:28px}form .ant-mentions,form textarea.ant-input{height:auto;margin-bottom:4px}form .ant-upload{background:transparent}form input[type=checkbox],form input[type=radio]{width:14px;height:14px}form .ant-checkbox-inline,form .ant-radio-inline{display:inline-block;margin-left:8px;font-weight:400;vertical-align:middle;cursor:pointer}form .ant-checkbox-inline:first-child,form .ant-radio-inline:first-child{margin-left:0}form .ant-checkbox-vertical,form .ant-radio-vertical{display:block}form .ant-checkbox-vertical+.ant-checkbox-vertical,form .ant-radio-vertical+.ant-radio-vertical{margin-left:0}form .ant-input-number+.ant-form-text{margin-left:8px}form .ant-input-number-handler-wrap{z-index:2}form .ant-cascader-picker,form .ant-select{width:100%}form .ant-input-group .ant-cascader-picker,form .ant-input-group .ant-select{width:auto}form .ant-input-group-wrapper,form :not(.ant-input-group-wrapper)>.ant-input-group{display:inline-block;vertical-align:middle}form:not(.ant-form-vertical) .ant-input-group-wrapper,form:not(.ant-form-vertical) :not(.ant-input-group-wrapper)>.ant-input-group{position:relative;top:-1px}.ant-col-24.ant-form-item-label,.ant-col-xl-24.ant-form-item-label,.ant-form-vertical .ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-24.ant-form-item-label label:after,.ant-col-xl-24.ant-form-item-label label:after,.ant-form-vertical .ant-form-item-label label:after{display:none}.ant-form-vertical .ant-form-item{padding-bottom:8px}.ant-form-vertical .ant-form-item-control{line-height:1.5}.ant-form-vertical .ant-form-explain{margin-top:2px;margin-bottom:-5px}.ant-form-vertical .ant-form-extra{margin-top:2px;margin-bottom:-4px}@media (max-width:575px){.ant-form-item-control-wrapper,.ant-form-item-label{display:block;width:100%}.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-form-item-label label:after{display:none}.ant-col-xs-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-xs-24.ant-form-item-label label:after{display:none}}@media (max-width:767px){.ant-col-sm-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-sm-24.ant-form-item-label label:after{display:none}}@media (max-width:991px){.ant-col-md-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-md-24.ant-form-item-label label:after{display:none}}@media (max-width:1199px){.ant-col-lg-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-lg-24.ant-form-item-label label:after{display:none}}@media (max-width:1599px){.ant-col-xl-24.ant-form-item-label{display:block;margin:0;padding:0 0 8px;line-height:1.5;white-space:normal;text-align:left}.ant-col-xl-24.ant-form-item-label label:after{display:none}}.ant-form-inline .ant-form-item{display:inline-block;margin-right:16px;margin-bottom:0}.ant-form-inline .ant-form-item-with-help{margin-bottom:24px}.ant-form-inline .ant-form-item>.ant-form-item-control-wrapper,.ant-form-inline .ant-form-item>.ant-form-item-label{display:inline-block;vertical-align:top}.ant-form-inline .ant-form-text,.ant-form-inline .has-feedback{display:inline-block}.has-error.has-feedback .ant-form-item-children-icon,.has-success.has-feedback .ant-form-item-children-icon,.has-warning.has-feedback .ant-form-item-children-icon,.is-validating.has-feedback .ant-form-item-children-icon{position:absolute;top:50%;right:0;z-index:1;width:32px;height:20px;margin-top:-10px;font-size:14px;line-height:20px;text-align:center;visibility:visible;animation:zoomIn .3s cubic-bezier(.12,.4,.29,1.46);pointer-events:none}.has-error.has-feedback .ant-form-item-children-icon svg,.has-success.has-feedback .ant-form-item-children-icon svg,.has-warning.has-feedback .ant-form-item-children-icon svg,.is-validating.has-feedback .ant-form-item-children-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.has-success.has-feedback .ant-form-item-children-icon{color:#008771;animation-name:diffZoomIn1!important}.has-warning .ant-form-explain,.has-warning .ant-form-split{color:#faad14}.has-warning .ant-input,.has-warning .ant-input:hover{background-color:#fff;border-color:#faad14}.has-warning .ant-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input:not([disabled]):hover{border-color:#faad14}.has-warning .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-affix-wrapper .ant-input,.has-warning .ant-input-affix-wrapper .ant-input:hover{background-color:#fff;border-color:#faad14}.has-warning .ant-input-affix-wrapper .ant-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#faad14}.has-warning .ant-input-prefix{color:#faad14}.has-warning .ant-input-group-addon{color:#faad14;background-color:#fff;border-color:#faad14}.has-warning .has-feedback{color:#faad14}.has-warning.has-feedback .ant-form-item-children-icon{color:#faad14;animation-name:diffZoomIn3!important}.has-warning .ant-select-selection,.has-warning .ant-select-selection:hover{border-color:#faad14}.has-warning .ant-select-focused .ant-select-selection,.has-warning .ant-select-open .ant-select-selection{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-calendar-picker-icon:after,.has-warning .ant-cascader-picker-arrow,.has-warning .ant-picker-icon:after,.has-warning .ant-select-arrow,.has-warning .ant-time-picker-icon:after{color:#faad14}.has-warning .ant-input-number,.has-warning .ant-time-picker-input{border-color:#faad14}.has-warning .ant-input-number-focused,.has-warning .ant-input-number:focus,.has-warning .ant-time-picker-input-focused,.has-warning .ant-time-picker-input:focus{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-input-number:not([disabled]):hover,.has-warning .ant-time-picker-input:not([disabled]):hover{border-color:#faad14}.has-warning .ant-cascader-picker:focus .ant-cascader-input{border-color:#ffc53d;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(250,173,20,.2)}.has-warning .ant-cascader-picker:hover .ant-cascader-input{border-color:#faad14}.has-error .ant-form-explain,.has-error .ant-form-split{color:#f5222d}.has-error .ant-input,.has-error .ant-input:hover{background-color:#fff;border-color:#f5222d}.has-error .ant-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input:not([disabled]):hover{border-color:#f5222d}.has-error .ant-calendar-picker-open .ant-calendar-picker-input{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-affix-wrapper .ant-input,.has-error .ant-input-affix-wrapper .ant-input:hover{background-color:#fff;border-color:#f5222d}.has-error .ant-input-affix-wrapper .ant-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-affix-wrapper:hover .ant-input:not(.ant-input-disabled){border-color:#f5222d}.has-error .ant-input-prefix{color:#f5222d}.has-error .ant-input-group-addon{color:#f5222d;background-color:#fff;border-color:#f5222d}.has-error .has-feedback{color:#f5222d}.has-error.has-feedback .ant-form-item-children-icon{color:#f5222d;animation-name:diffZoomIn2!important}.has-error .ant-select-selection,.has-error .ant-select-selection:hover{border-color:#f5222d}.has-error .ant-select-focused .ant-select-selection,.has-error .ant-select-open .ant-select-selection{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-select.ant-select-auto-complete .ant-input:focus{border-color:#f5222d}.has-error .ant-input-group-addon .ant-select-selection{border-color:transparent;box-shadow:none}.has-error .ant-calendar-picker-icon:after,.has-error .ant-cascader-picker-arrow,.has-error .ant-picker-icon:after,.has-error .ant-select-arrow,.has-error .ant-time-picker-icon:after{color:#f5222d}.has-error .ant-input-number,.has-error .ant-time-picker-input{border-color:#f5222d}.has-error .ant-input-number-focused,.has-error .ant-input-number:focus,.has-error .ant-time-picker-input-focused,.has-error .ant-time-picker-input:focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-input-number:not([disabled]):hover,.has-error .ant-mention-wrapper .ant-mention-editor,.has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):hover,.has-error .ant-time-picker-input:not([disabled]):hover{border-color:#f5222d}.has-error .ant-cascader-picker:focus .ant-cascader-input,.has-error .ant-mention-wrapper.ant-mention-active:not([disabled]) .ant-mention-editor,.has-error .ant-mention-wrapper .ant-mention-editor:not([disabled]):focus{border-color:#ff4d4f;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(245,34,45,.2)}.has-error .ant-cascader-picker:hover .ant-cascader-input,.has-error .ant-transfer-list{border-color:#f5222d}.has-error .ant-transfer-list-search:not([disabled]){border-color:#d9d9d9}.has-error .ant-transfer-list-search:not([disabled]):hover{border-color:#18947b;border-right-width:1px!important}.has-error .ant-transfer-list-search:not([disabled]):focus{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.is-validating.has-feedback .ant-form-item-children-icon{display:inline-block;color:#008771}.ant-advanced-search-form .ant-form-item{margin-bottom:24px}.ant-advanced-search-form .ant-form-item-with-help{margin-bottom:5px}.show-help-appear,.show-help-enter,.show-help-leave{animation-duration:.3s;animation-fill-mode:both;animation-play-state:paused}.show-help-appear.show-help-appear-active,.show-help-enter.show-help-enter-active{animation-name:antShowHelpIn;animation-play-state:running}.show-help-leave.show-help-leave-active{animation-name:antShowHelpOut;animation-play-state:running;pointer-events:none}.show-help-appear,.show-help-enter{opacity:0}.show-help-appear,.show-help-enter,.show-help-leave{animation-timing-function:cubic-bezier(.645,.045,.355,1)}@keyframes antShowHelpIn{0%{transform:translateY(-5px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes antShowHelpOut{to{transform:translateY(-5px);opacity:0}}@keyframes diffZoomIn1{0%{transform:scale(0)}to{transform:scale(1)}}@keyframes diffZoomIn2{0%{transform:scale(0)}to{transform:scale(1)}}@keyframes diffZoomIn3{0%{transform:scale(0)}to{transform:scale(1)}}.ant-input-number{box-sizing:border-box;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";position:relative;width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;transition:all .3s;display:inline-block;width:90px;margin:0;padding:0;border:1px solid #d9d9d9;border-radius:1rem}.ant-input-number::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input-number:-ms-input-placeholder{color:#bfbfbf}.ant-input-number::-webkit-input-placeholder{color:#bfbfbf}.ant-input-number:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number:placeholder-shown{text-overflow:ellipsis}.ant-input-number:focus{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-input-number[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-number[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-input-number-lg{height:40px;padding:6px 11px}.ant-input-number-sm{height:24px;padding:1px 7px}.ant-input-number-handler{position:relative;display:block;width:100%;height:50%;overflow:hidden;color:rgba(0,0,0,.45);font-weight:700;line-height:0;text-align:center;transition:all .1s linear}.ant-input-number-handler:active{background:#f4f4f4}.ant-input-number-handler:hover .ant-input-number-handler-down-inner,.ant-input-number-handler:hover .ant-input-number-handler-up-inner{color:#18947b}.ant-input-number-handler-down-inner,.ant-input-number-handler-up-inner{display:inline-block;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;position:absolute;right:4px;width:12px;height:12px;color:rgba(0,0,0,.45);line-height:12px;transition:all .1s linear;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-input-number-handler-down-inner>*,.ant-input-number-handler-up-inner>*{line-height:1}.ant-input-number-handler-down-inner svg,.ant-input-number-handler-up-inner svg{display:inline-block}.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}.ant-input-number-focused,.ant-input-number:hover{border-color:#18947b;border-right-width:1px!important}.ant-input-number-focused{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}.ant-input-number-disabled .ant-input-number-handler-wrap{display:none}.ant-input-number-input{width:100%;height:30px;padding:0 11px;text-align:left;background-color:transparent;border:0;border-radius:1rem;outline:0;transition:all .3s linear;-moz-appearance:textfield!important}.ant-input-number-input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-input-number-input:-ms-input-placeholder{color:#bfbfbf}.ant-input-number-input::-webkit-input-placeholder{color:#bfbfbf}.ant-input-number-input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-input-number-input:placeholder-shown{text-overflow:ellipsis}.ant-input-number-input[type=number]::-webkit-inner-spin-button,.ant-input-number-input[type=number]::-webkit-outer-spin-button{margin:0;-webkit-appearance:none}.ant-input-number-lg{padding:0;font-size:16px}.ant-input-number-lg input{height:38px}.ant-input-number-sm{padding:0}.ant-input-number-sm input{height:22px;padding:0 7px}.ant-input-number-handler-wrap{position:absolute;top:0;right:0;width:22px;height:100%;background:#fff;border-left:1px solid #d9d9d9;border-radius:0 1rem 1rem 0;opacity:0;transition:opacity .24s linear .1s}.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner,.ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner{display:inline-block;font-size:12px;font-size:7px\9;transform:scale(.58333333) rotate(0deg);min-width:auto;margin-right:0}:root .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-down-inner,:root .ant-input-number-handler-wrap .ant-input-number-handler .ant-input-number-handler-up-inner{font-size:12px}.ant-input-number-handler-wrap:hover .ant-input-number-handler{height:40%}.ant-input-number:hover .ant-input-number-handler-wrap{opacity:1}.ant-input-number-handler-up{border-top-right-radius:1rem;cursor:pointer}.ant-input-number-handler-up-inner{top:50%;margin-top:-5px;text-align:center}.ant-input-number-handler-up:hover{height:60%!important}.ant-input-number-handler-down{top:0;border-top:1px solid #d9d9d9;border-bottom-right-radius:1rem;cursor:pointer}.ant-input-number-handler-down-inner{top:50%;margin-top:-6px;text-align:center}.ant-input-number-handler-down:hover{height:60%!important}.ant-input-number-handler-down-disabled,.ant-input-number-handler-up-disabled{cursor:not-allowed}.ant-input-number-handler-down-disabled:hover .ant-input-number-handler-down-inner,.ant-input-number-handler-up-disabled:hover .ant-input-number-handler-up-inner{color:rgba(0,0,0,.25)}.ant-layout{display:flex;flex:auto;flex-direction:column;min-height:0;background:#f0f2f5}.ant-layout,.ant-layout *{box-sizing:border-box}.ant-layout.ant-layout-has-sider{flex-direction:row}.ant-layout.ant-layout-has-sider>.ant-layout,.ant-layout.ant-layout-has-sider>.ant-layout-content{overflow-x:hidden}.ant-layout-footer,.ant-layout-header{flex:0 0 auto}.ant-layout-header{height:64px;padding:0 50px;line-height:64px;background:#001529}.ant-layout-footer{padding:24px 50px;color:rgba(0,0,0,.65);font-size:14px;background:#f0f2f5}.ant-layout-content{flex:auto;min-height:0}.ant-layout-sider{position:relative;min-width:0;background:#001529;transition:all .2s}.ant-layout-sider-children{height:100%;margin-top:-.1px;padding-top:.1px}.ant-layout-sider-has-trigger{padding-bottom:48px}.ant-layout-sider-right{order:1}.ant-layout-sider-trigger{position:fixed;bottom:0;z-index:1;height:48px;color:#fff;line-height:48px;text-align:center;background:#002140;cursor:pointer;transition:all .2s}.ant-layout-sider-zero-width>*{overflow:hidden}.ant-layout-sider-zero-width-trigger{position:absolute;top:64px;right:-36px;z-index:1;width:36px;height:42px;color:#fff;font-size:18px;line-height:42px;text-align:center;background:#001529;border-radius:0 1rem 1rem 0;cursor:pointer;transition:background .3s ease}.ant-layout-sider-zero-width-trigger:hover{background:#192c3e}.ant-layout-sider-zero-width-trigger-right{left:-36px;border-radius:1rem 0 0 1rem}.ant-layout-sider-light{background:#fff}.ant-layout-sider-light .ant-layout-sider-trigger,.ant-layout-sider-light .ant-layout-sider-zero-width-trigger{color:rgba(0,0,0,.65);background:#fff}.ant-list{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}.ant-list *{outline:none}.ant-list-pagination{margin-top:24px;text-align:right}.ant-list-pagination .ant-pagination-options{text-align:left}.ant-list-more{margin-top:12px;text-align:center}.ant-list-more button{padding-right:32px;padding-left:32px}.ant-list-spin{min-height:40px;text-align:center}.ant-list-empty-text{padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center}.ant-list-items{margin:0;padding:0;list-style:none}.ant-list-item{display:flex;align-items:center;justify-content:space-between;padding:12px 0}.ant-list-item-content{color:rgba(0,0,0,.65)}.ant-list-item-meta{display:flex;flex:1;align-items:flex-start;font-size:0}.ant-list-item-meta-avatar{margin-right:16px}.ant-list-item-meta-content{flex:1 0}.ant-list-item-meta-title{margin-bottom:4px;color:rgba(0,0,0,.65);font-size:14px;line-height:22px}.ant-list-item-meta-title>a{color:rgba(0,0,0,.65);transition:all .3s}.ant-list-item-meta-title>a:hover{color:#008771}.ant-list-item-meta-description{color:rgba(0,0,0,.45);font-size:14px;line-height:22px}.ant-list-item-action{flex:0 0 auto;margin-left:48px;padding:0;font-size:0;list-style:none}.ant-list-item-action>li{position:relative;display:inline-block;padding:0 8px;color:rgba(0,0,0,.45);font-size:14px;line-height:22px;text-align:center;cursor:pointer}.ant-list-item-action>li:first-child{padding-left:0}.ant-list-item-action-split{position:absolute;top:50%;right:0;width:1px;height:14px;margin-top:-7px;background-color:#e8e8e8}.ant-list-footer,.ant-list-header{background:transparent}.ant-list-footer,.ant-list-header{padding-top:12px;padding-bottom:12px}.ant-list-empty{padding:16px 0;color:rgba(0,0,0,.45);font-size:12px;text-align:center}.ant-list-split .ant-list-item{border-bottom:1px solid #e8e8e8}.ant-list-split .ant-list-item:last-child{border-bottom:none}.ant-list-split .ant-list-header{border-bottom:1px solid #e8e8e8}.ant-list-loading .ant-list-spin-nested-loading{min-height:32px}.ant-list-something-after-last-item .ant-spin-container>.ant-list-items>.ant-list-item:last-child{border-bottom:1px solid #e8e8e8}.ant-list-lg .ant-list-item{padding-top:16px;padding-bottom:16px}.ant-list-sm .ant-list-item{padding-top:8px;padding-bottom:8px}.ant-list-vertical .ant-list-item{align-items:normal}.ant-list-vertical .ant-list-item-main{display:block;flex:1}.ant-list-vertical .ant-list-item-extra{margin-left:40px}.ant-list-vertical .ant-list-item-meta{margin-bottom:16px}.ant-list-vertical .ant-list-item-meta-title{margin-bottom:12px;color:rgba(0,0,0,.85);font-size:16px;line-height:24px}.ant-list-vertical .ant-list-item-action{margin-top:16px;margin-left:auto}.ant-list-vertical .ant-list-item-action>li{padding:0 16px}.ant-list-vertical .ant-list-item-action>li:first-child{padding-left:0}.ant-list-grid .ant-col>.ant-list-item{display:block;max-width:100%;margin-bottom:16px;padding-top:0;padding-bottom:0;border-bottom:none}.ant-list-item-no-flex{display:block}.ant-list:not(.ant-list-vertical) .ant-list-item-no-flex .ant-list-item-action{float:right}.ant-list-bordered{border:1px solid #d9d9d9;border-radius:1rem}.ant-list-bordered .ant-list-footer,.ant-list-bordered .ant-list-header,.ant-list-bordered .ant-list-item{padding-right:24px;padding-left:24px}.ant-list-bordered .ant-list-item{border-bottom:1px solid #e8e8e8}.ant-list-bordered .ant-list-pagination{margin:16px 24px}.ant-list-bordered.ant-list-sm .ant-list-item{padding-right:16px;padding-left:16px}.ant-list-bordered.ant-list-sm .ant-list-footer,.ant-list-bordered.ant-list-sm .ant-list-header{padding:8px 16px}.ant-list-bordered.ant-list-lg .ant-list-footer,.ant-list-bordered.ant-list-lg .ant-list-header{padding:16px 24px}@media screen and (max-width:768px){.ant-list-item-action,.ant-list-vertical .ant-list-item-extra{margin-left:24px}}@media screen and (max-width:576px){.ant-list-item{flex-wrap:wrap}.ant-list-item-action{margin-left:12px}.ant-list-vertical .ant-list-item{flex-wrap:wrap-reverse}.ant-list-vertical .ant-list-item-main{min-width:220px}.ant-list-vertical .ant-list-item-extra{margin:auto auto 16px}}.ant-spin{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#008771;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none\9;width:100%;height:100%;background:#fff;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;overflow:hidden;opacity:.5;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:rgba(0,0,0,.45)}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#008771;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s linear infinite alternate}.ant-spin-dot-item:first-child{top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(45deg);animation:antRotate 1.2s linear infinite}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.ant-spin-blur{background:#fff;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(405deg)}}.ant-pagination{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;font-feature-settings:"tnum"}.ant-pagination,.ant-pagination ol,.ant-pagination ul{margin:0;padding:0;list-style:none}.ant-pagination:after{display:block;clear:both;height:0;overflow:hidden;visibility:hidden;content:" "}.ant-pagination-item,.ant-pagination-total-text{display:inline-block;height:32px;margin-right:8px;line-height:30px;vertical-align:middle}.ant-pagination-item{min-width:32px;font-family:Arial;text-align:center;list-style:none;background-color:#fff;border:1px solid #d9d9d9;border-radius:1rem;outline:0;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-pagination-item a{display:block;padding:0 6px;color:rgba(0,0,0,.65);transition:none}.ant-pagination-item a:hover{text-decoration:none}.ant-pagination-item:focus,.ant-pagination-item:hover{border-color:#008771;transition:all .3s}.ant-pagination-item:focus a,.ant-pagination-item:hover a{color:#008771}.ant-pagination-item-active{font-weight:500;background:#fff;border-color:#008771}.ant-pagination-item-active a{color:#008771}.ant-pagination-item-active:focus,.ant-pagination-item-active:hover{border-color:#18947b}.ant-pagination-item-active:focus a,.ant-pagination-item-active:hover a{color:#18947b}.ant-pagination-jump-next,.ant-pagination-jump-prev{outline:0}.ant-pagination-jump-next .ant-pagination-item-container,.ant-pagination-jump-prev .ant-pagination-item-container{position:relative}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon{display:inline-block;font-size:12px;font-size:12px\9;transform:scale(1) rotate(0deg);color:#008771;letter-spacing:-1px;opacity:0;transition:all .2s}:root .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon,:root .ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon{font-size:12px}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon-svg,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-link-icon-svg{top:0;right:0;bottom:0;left:0;margin:auto}.ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis,.ant-pagination-jump-prev .ant-pagination-item-container .ant-pagination-item-ellipsis{position:absolute;top:0;right:0;bottom:0;left:0;display:block;margin:auto;color:rgba(0,0,0,.25);letter-spacing:2px;text-align:center;text-indent:.13em;opacity:1;transition:all .2s}.ant-pagination-jump-next:focus .ant-pagination-item-link-icon,.ant-pagination-jump-next:hover .ant-pagination-item-link-icon,.ant-pagination-jump-prev:focus .ant-pagination-item-link-icon,.ant-pagination-jump-prev:hover .ant-pagination-item-link-icon{opacity:1}.ant-pagination-jump-next:focus .ant-pagination-item-ellipsis,.ant-pagination-jump-next:hover .ant-pagination-item-ellipsis,.ant-pagination-jump-prev:focus .ant-pagination-item-ellipsis,.ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis{opacity:0}.ant-pagination-jump-next,.ant-pagination-jump-prev,.ant-pagination-prev{margin-right:8px}.ant-pagination-jump-next,.ant-pagination-jump-prev,.ant-pagination-next,.ant-pagination-prev{display:inline-block;min-width:32px;height:32px;color:rgba(0,0,0,.65);font-family:Arial;line-height:32px;text-align:center;vertical-align:middle;list-style:none;border-radius:1rem;cursor:pointer;transition:all .3s}.ant-pagination-next,.ant-pagination-prev{outline:0}.ant-pagination-next a,.ant-pagination-prev a{color:rgba(0,0,0,.65);-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-pagination-next:hover a,.ant-pagination-prev:hover a{border-color:#18947b}.ant-pagination-next .ant-pagination-item-link,.ant-pagination-prev .ant-pagination-item-link{display:block;height:100%;font-size:12px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:1rem;outline:none;transition:all .3s}.ant-pagination-next:focus .ant-pagination-item-link,.ant-pagination-next:hover .ant-pagination-item-link,.ant-pagination-prev:focus .ant-pagination-item-link,.ant-pagination-prev:hover .ant-pagination-item-link{color:#008771;border-color:#008771}.ant-pagination-disabled,.ant-pagination-disabled:focus,.ant-pagination-disabled:hover{cursor:not-allowed}.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination-disabled:focus .ant-pagination-item-link,.ant-pagination-disabled:focus a,.ant-pagination-disabled:hover .ant-pagination-item-link,.ant-pagination-disabled:hover a,.ant-pagination-disabled a{color:rgba(0,0,0,.25);border-color:#d9d9d9;cursor:not-allowed}.ant-pagination-slash{margin:0 10px 0 5px}.ant-pagination-options{display:inline-block;margin-left:16px;vertical-align:middle}.ant-pagination-options-size-changer.ant-select{display:inline-block;width:auto;margin-right:8px}.ant-pagination-options-quick-jumper{display:inline-block;height:32px;line-height:32px;vertical-align:top}.ant-pagination-options-quick-jumper input{position:relative;display:inline-block;width:100%;height:32px;padding:4px 11px;color:rgba(0,0,0,.65);font-size:14px;line-height:1.5;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:1rem;transition:all .3s;width:50px;margin:0 8px}.ant-pagination-options-quick-jumper input::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-pagination-options-quick-jumper input:-ms-input-placeholder{color:#bfbfbf}.ant-pagination-options-quick-jumper input::-webkit-input-placeholder{color:#bfbfbf}.ant-pagination-options-quick-jumper input:-moz-placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:placeholder-shown{text-overflow:ellipsis}.ant-pagination-options-quick-jumper input:focus,.ant-pagination-options-quick-jumper input:hover{border-color:#18947b;border-right-width:1px!important}.ant-pagination-options-quick-jumper input:focus{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-pagination-options-quick-jumper input-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-pagination-options-quick-jumper input[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-pagination-options-quick-jumper input[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-pagination-options-quick-jumper input{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-pagination-options-quick-jumper input-lg{height:40px;padding:6px 11px;font-size:16px}.ant-pagination-options-quick-jumper input-sm{height:24px;padding:1px 7px}.ant-pagination-simple .ant-pagination-next,.ant-pagination-simple .ant-pagination-prev{height:24px;line-height:24px;vertical-align:top}.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link,.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link{height:24px;border:0}.ant-pagination-simple .ant-pagination-next .ant-pagination-item-link:after,.ant-pagination-simple .ant-pagination-prev .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination-simple .ant-pagination-simple-pager{display:inline-block;height:24px;margin-right:8px}.ant-pagination-simple .ant-pagination-simple-pager input{box-sizing:border-box;height:100%;margin-right:8px;padding:0 6px;text-align:center;background-color:#fff;border:1px solid #d9d9d9;border-radius:1rem;outline:none;transition:border-color .3s}.ant-pagination-simple .ant-pagination-simple-pager input:hover{border-color:#008771}.ant-pagination.mini .ant-pagination-simple-pager,.ant-pagination.mini .ant-pagination-total-text{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-item{min-width:24px;height:24px;margin:0;line-height:22px}.ant-pagination.mini .ant-pagination-item:not(.ant-pagination-item-active){background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-next,.ant-pagination.mini .ant-pagination-prev{min-width:24px;height:24px;margin:0;line-height:24px}.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link,.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link{background:transparent;border-color:transparent}.ant-pagination.mini .ant-pagination-next .ant-pagination-item-link:after,.ant-pagination.mini .ant-pagination-prev .ant-pagination-item-link:after{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-jump-next,.ant-pagination.mini .ant-pagination-jump-prev{height:24px;margin-right:0;line-height:24px}.ant-pagination.mini .ant-pagination-options{margin-left:2px}.ant-pagination.mini .ant-pagination-options-quick-jumper{height:24px;line-height:24px}.ant-pagination.mini .ant-pagination-options-quick-jumper input{height:24px;padding:1px 7px;width:44px}.ant-pagination.ant-pagination-disabled{cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item{background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item a{color:rgba(0,0,0,.25);background:transparent;border:none;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active{background:#dbdbdb;border-color:transparent}.ant-pagination.ant-pagination-disabled .ant-pagination-item-active a{color:#fff}.ant-pagination.ant-pagination-disabled .ant-pagination-item-link,.ant-pagination.ant-pagination-disabled .ant-pagination-item-link:focus,.ant-pagination.ant-pagination-disabled .ant-pagination-item-link:hover{color:rgba(0,0,0,.45);background:#f5f5f5;border-color:#d9d9d9;cursor:not-allowed}.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:focus .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:hover .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:focus .ant-pagination-item-link-icon,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:hover .ant-pagination-item-link-icon{opacity:0}.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:focus .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-next:hover .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:focus .ant-pagination-item-ellipsis,.ant-pagination.ant-pagination-disabled .ant-pagination-jump-prev:hover .ant-pagination-item-ellipsis{opacity:1}@media only screen and (max-width:992px){.ant-pagination-item-after-jump-prev,.ant-pagination-item-before-jump-next{display:none}}@media only screen and (max-width:576px){.ant-pagination-options{display:none}}.ant-mentions{box-sizing:border-box;margin:0;font-variant:tabular-nums;list-style:none;font-feature-settings:"tnum";width:100%;height:32px;color:rgba(0,0,0,.65);font-size:14px;background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:1rem;transition:all .3s;position:relative;display:inline-block;height:auto;padding:0;overflow:hidden;line-height:1.5;white-space:pre-wrap;vertical-align:bottom}.ant-mentions::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-mentions:-ms-input-placeholder{color:#bfbfbf}.ant-mentions::-webkit-input-placeholder{color:#bfbfbf}.ant-mentions:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions:placeholder-shown{text-overflow:ellipsis}.ant-mentions:focus,.ant-mentions:hover{border-color:#18947b;border-right-width:1px!important}.ant-mentions:focus{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-mentions-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mentions[disabled]{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions[disabled]:hover{border-color:#d9d9d9;border-right-width:1px!important}textarea.ant-mentions{max-width:100%;height:auto;min-height:32px;line-height:1.5;vertical-align:bottom;transition:all .3s,height 0s}.ant-mentions-lg{height:40px;padding:6px 11px;font-size:16px}.ant-mentions-sm{height:24px;padding:1px 7px}.ant-mentions-disabled>textarea{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}.ant-mentions-disabled>textarea:hover{border-color:#d9d9d9;border-right-width:1px!important}.ant-mentions-focused{border-color:#18947b;border-right-width:1px!important;outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-mentions-measure,.ant-mentions>textarea{min-height:30px;margin:0;padding:4px 11px;overflow:inherit;overflow-x:hidden;overflow-y:auto;font-weight:inherit;font-size:inherit;font-family:inherit;font-style:inherit;font-variant:inherit;font-size-adjust:inherit;font-stretch:inherit;line-height:inherit;direction:inherit;letter-spacing:inherit;white-space:inherit;text-align:inherit;vertical-align:top;word-wrap:break-word;word-break:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit}.ant-mentions>textarea{width:100%;border:none;outline:none;resize:none}.ant-mentions>textarea::-moz-placeholder{color:#bfbfbf;opacity:1}.ant-mentions>textarea:-ms-input-placeholder{color:#bfbfbf}.ant-mentions>textarea::-webkit-input-placeholder{color:#bfbfbf}.ant-mentions>textarea:-moz-placeholder-shown{text-overflow:ellipsis}.ant-mentions>textarea:placeholder-shown{text-overflow:ellipsis}.ant-mentions>textarea:-moz-read-only{cursor:default}.ant-mentions>textarea:read-only{cursor:default}.ant-mentions-measure{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;color:transparent;pointer-events:none}.ant-mentions-measure>span{display:inline-block;min-height:1em}.ant-mentions-dropdown{margin:0;padding:0;color:rgba(0,0,0,.65);font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;top:-9999px;left:-9999px;z-index:1050;box-sizing:border-box;font-size:14px;font-variant:normal;background-color:#fff;border-radius:1rem;outline:none;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-mentions-dropdown-hidden{display:none}.ant-mentions-dropdown-menu{max-height:250px;margin-bottom:0;padding-left:0;overflow:auto;list-style:none;outline:none}.ant-mentions-dropdown-menu-item{position:relative;display:block;min-width:100px;padding:5px 12px;overflow:hidden;color:rgba(0,0,0,.65);font-weight:400;line-height:22px;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;transition:background .3s ease}.ant-mentions-dropdown-menu-item:hover{background-color:#b3c7c0}.ant-mentions-dropdown-menu-item:first-child{border-radius:1rem 1rem 0 0}.ant-mentions-dropdown-menu-item:last-child{border-radius:0 0 1rem 1rem}.ant-mentions-dropdown-menu-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-mentions-dropdown-menu-item-disabled:hover{color:rgba(0,0,0,.25);background-color:#fff;cursor:not-allowed}.ant-mentions-dropdown-menu-item-selected{color:rgba(0,0,0,.65);font-weight:600;background-color:#fafafa}.ant-mentions-dropdown-menu-item-active{background-color:#b3c7c0}.ant-message{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:fixed;top:16px;left:0;z-index:1010;width:100%;pointer-events:none}.ant-message-notice{padding:8px;text-align:center}.ant-message-notice:first-child{margin-top:-8px}.ant-message-notice-content{display:inline-block;padding:10px 16px;background:#fff;border-radius:1rem;box-shadow:0 4px 12px rgba(0,0,0,.15);pointer-events:all}.ant-message-success .anticon{color:#008771}.ant-message-error .anticon{color:#f5222d}.ant-message-warning .anticon{color:#faad14}.ant-message-info .anticon,.ant-message-loading .anticon{color:#1890ff}.ant-message .anticon{position:relative;top:1px;margin-right:8px;font-size:16px}.ant-message-notice.move-up-leave.move-up-leave-active{overflow:hidden;animation-name:MessageMoveOut;animation-duration:.3s}@keyframes MessageMoveOut{0%{max-height:150px;padding:8px;opacity:1}to{max-height:0;padding:0;opacity:0}}.ant-modal{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:100px;width:auto;margin:0 auto;padding:0 0 24px;pointer-events:none}.ant-modal-wrap{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;overflow:auto;outline:0;-webkit-overflow-scrolling:touch}.ant-modal-title{margin:0;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:22px;word-wrap:break-word}.ant-modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:0;border-radius:1rem;box-shadow:0 4px 12px rgba(0,0,0,.15);pointer-events:auto}.ant-modal-close{position:absolute;top:0;right:0;z-index:10;padding:0;color:rgba(0,0,0,.45);font-weight:700;line-height:1;text-decoration:none;background:transparent;border:0;outline:0;cursor:pointer;transition:color .3s}.ant-modal-close-x{display:block;width:56px;height:56px;font-size:16px;font-style:normal;line-height:56px;text-align:center;text-transform:none;text-rendering:auto}.ant-modal-close:focus,.ant-modal-close:hover{color:rgba(0,0,0,.75);text-decoration:none}.ant-modal-header{padding:16px 24px;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:1rem 1rem 0 0}.ant-modal-body{padding:24px;font-size:14px;line-height:1.5;word-wrap:break-word}.ant-modal-footer{padding:10px 16px;text-align:right;background:transparent;border-top:1px solid #e8e8e8;border-radius:0 0 1rem 1rem}.ant-modal-footer button+button{margin-bottom:0;margin-left:8px}.ant-modal.zoom-appear,.ant-modal.zoom-enter{transform:none;opacity:0;animation-duration:.3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-modal-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;height:100%;background-color:rgba(0,0,0,.45);filter:alpha(opacity=50)}.ant-modal-mask-hidden{display:none}.ant-modal-open{overflow:hidden}.ant-modal-centered{text-align:center}.ant-modal-centered:before{display:inline-block;width:0;height:100%;vertical-align:middle;content:""}.ant-modal-centered .ant-modal{top:0;display:inline-block;text-align:left;vertical-align:middle}@media (max-width:767px){.ant-modal{max-width:calc(100vw - 16px);margin:8px auto}.ant-modal-centered .ant-modal{flex:1}}.ant-modal-confirm .ant-modal-header{display:none}.ant-modal-confirm .ant-modal-body{padding:32px 32px 24px}.ant-modal-confirm-body-wrapper{zoom:1}.ant-modal-confirm-body-wrapper:after,.ant-modal-confirm-body-wrapper:before{display:table;content:""}.ant-modal-confirm-body-wrapper:after{clear:both}.ant-modal-confirm-body .ant-modal-confirm-title{display:block;overflow:hidden;color:rgba(0,0,0,.85);font-weight:500;font-size:16px;line-height:1.4}.ant-modal-confirm-body .ant-modal-confirm-content{margin-top:8px;color:rgba(0,0,0,.65);font-size:14px}.ant-modal-confirm-body>.anticon{float:left;margin-right:16px;font-size:22px}.ant-modal-confirm-body>.anticon+.ant-modal-confirm-title+.ant-modal-confirm-content{margin-left:38px}.ant-modal-confirm .ant-modal-confirm-btns{float:right;margin-top:24px}.ant-modal-confirm .ant-modal-confirm-btns button+button{margin-bottom:0;margin-left:8px}.ant-modal-confirm-error .ant-modal-confirm-body>.anticon{color:#f5222d}.ant-modal-confirm-confirm .ant-modal-confirm-body>.anticon,.ant-modal-confirm-warning .ant-modal-confirm-body>.anticon{color:#faad14}.ant-modal-confirm-info .ant-modal-confirm-body>.anticon{color:#1890ff}.ant-modal-confirm-success .ant-modal-confirm-body>.anticon{color:#008771}.ant-notification{box-sizing:border-box;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:fixed;z-index:1010;width:384px;max-width:calc(100vw - 32px);margin:0 24px 0 0}.ant-notification-bottomLeft,.ant-notification-topLeft{margin-right:0;margin-left:24px}.ant-notification-bottomLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-bottomLeft .ant-notification-fade-enter.ant-notification-fade-enter-active,.ant-notification-topLeft .ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-topLeft .ant-notification-fade-enter.ant-notification-fade-enter-active{animation-name:NotificationLeftFadeIn}.ant-notification-close-icon{font-size:14px;cursor:pointer}.ant-notification-notice{position:relative;margin-bottom:16px;padding:16px 24px;overflow:hidden;line-height:1.5;background:#fff;border-radius:1rem;box-shadow:0 4px 12px rgba(0,0,0,.15)}.ant-notification-notice-message{display:inline-block;margin-bottom:8px;color:rgba(0,0,0,.85);font-size:16px;line-height:24px}.ant-notification-notice-message-single-line-auto-margin{display:block;width:calc(264px - 100%);max-width:4px;background-color:transparent;pointer-events:none}.ant-notification-notice-message-single-line-auto-margin:before{display:block;content:""}.ant-notification-notice-description{font-size:14px}.ant-notification-notice-closable .ant-notification-notice-message{padding-right:24px}.ant-notification-notice-with-icon .ant-notification-notice-message{margin-bottom:4px;margin-left:48px;font-size:16px}.ant-notification-notice-with-icon .ant-notification-notice-description{margin-left:48px;font-size:14px}.ant-notification-notice-icon{position:absolute;margin-left:4px;font-size:24px;line-height:24px}.anticon.ant-notification-notice-icon-success{color:#008771}.anticon.ant-notification-notice-icon-info{color:#1890ff}.anticon.ant-notification-notice-icon-warning{color:#faad14}.anticon.ant-notification-notice-icon-error{color:#f5222d}.ant-notification-notice-close{position:absolute;top:16px;right:22px;color:rgba(0,0,0,.45);outline:none}.ant-notification-notice-close:hover{color:rgba(0,0,0,.67)}.ant-notification-notice-btn{float:right;margin-top:16px}.ant-notification .notification-fade-effect{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both}.ant-notification-fade-appear,.ant-notification-fade-enter{opacity:0;animation-play-state:paused}.ant-notification-fade-appear,.ant-notification-fade-enter,.ant-notification-fade-leave{animation-duration:.24s;animation-timing-function:cubic-bezier(.645,.045,.355,1);animation-fill-mode:both}.ant-notification-fade-leave{animation-duration:.2s;animation-play-state:paused}.ant-notification-fade-appear.ant-notification-fade-appear-active,.ant-notification-fade-enter.ant-notification-fade-enter-active{animation-name:NotificationFadeIn;animation-play-state:running}.ant-notification-fade-leave.ant-notification-fade-leave-active{animation-name:NotificationFadeOut;animation-play-state:running}@keyframes NotificationFadeIn{0%{left:384px;opacity:0}to{left:0;opacity:1}}@keyframes NotificationLeftFadeIn{0%{right:384px;opacity:0}to{right:0;opacity:1}}@keyframes NotificationFadeOut{0%{max-height:150px;margin-bottom:16px;padding-top:16px 24px;padding-bottom:16px 24px;opacity:1}to{max-height:0;margin-bottom:0;padding-top:0;padding-bottom:0;opacity:0}}.ant-page-header{box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;padding:16px 24px;background-color:#fff}.ant-page-header-ghost{background-color:inherit}.ant-page-header.has-breadcrumb{padding-top:12px}.ant-page-header.has-footer{padding-bottom:0}.ant-page-header-back{float:left;margin:8px 16px 8px 0;font-size:16px;line-height:1}.ant-page-header-back-button{color:#008771;text-decoration:none;outline:none;transition:color .3s;color:#000;cursor:pointer}.ant-page-header-back-button:focus,.ant-page-header-back-button:hover{color:#18947b}.ant-page-header-back-button:active{color:#006154}.ant-page-header .ant-divider-vertical{height:14px;margin:0 12px;vertical-align:middle}.ant-breadcrumb+.ant-page-header-heading{margin-top:8px}.ant-page-header-heading{width:100%;overflow:hidden}.ant-page-header-heading-title{display:block;float:left;margin-bottom:0;padding-right:12px;color:rgba(0,0,0,.85);font-weight:600;font-size:20px;line-height:32px}.ant-page-header-heading .ant-avatar{float:left;margin-right:12px}.ant-page-header-heading-sub-title{float:left;margin:5px 12px 5px 0;color:rgba(0,0,0,.45);font-size:14px;line-height:22px}.ant-page-header-heading-tags{float:left;margin:4px 0}.ant-page-header-heading-extra{float:right}.ant-page-header-heading-extra>*{margin-left:8px}.ant-page-header-heading-extra>:first-child{margin-left:0}.ant-page-header-content{padding-top:12px;overflow:hidden}.ant-page-header-footer{margin-top:16px}.ant-page-header-footer .ant-tabs-bar{margin-bottom:1px;border-bottom:0}.ant-page-header-footer .ant-tabs-bar .ant-tabs-nav .ant-tabs-tab{padding:8px;font-size:16px}@media (max-width:576px){.ant-page-header-heading-extra{display:block;float:unset;width:100%;padding-top:12px;overflow:hidden}}.ant-popover{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:absolute;top:0;left:0;z-index:1030;font-weight:400;white-space:normal;text-align:left;cursor:auto;-webkit-user-select:text;-moz-user-select:text;user-select:text}.ant-popover:after{position:absolute;background:hsla(0,0%,100%,.01);content:""}.ant-popover-hidden{display:none}.ant-popover-placement-top,.ant-popover-placement-topLeft,.ant-popover-placement-topRight{padding-bottom:10px}.ant-popover-placement-right,.ant-popover-placement-rightBottom,.ant-popover-placement-rightTop{padding-left:10px}.ant-popover-placement-bottom,.ant-popover-placement-bottomLeft,.ant-popover-placement-bottomRight{padding-top:10px}.ant-popover-placement-left,.ant-popover-placement-leftBottom,.ant-popover-placement-leftTop{padding-right:10px}.ant-popover-inner{background-color:#fff;background-clip:padding-box;border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15);box-shadow:0 0 8px rgba(0,0,0,.15)\9}@media (-ms-high-contrast:none),screen and (-ms-high-contrast:active){.ant-popover-inner{box-shadow:0 2px 8px rgba(0,0,0,.15)}}.ant-popover-title{min-width:177px;min-height:32px;margin:0;padding:5px 16px 4px;color:rgba(0,0,0,.85);font-weight:500;border-bottom:1px solid #e8e8e8}.ant-popover-inner-content{padding:12px 16px;color:rgba(0,0,0,.65)}.ant-popover-message{position:relative;padding:4px 0 12px;color:rgba(0,0,0,.65);font-size:14px}.ant-popover-message>.anticon{position:absolute;top:8px;color:#faad14;font-size:14px}.ant-popover-message-title{padding-left:22px}.ant-popover-buttons{margin-bottom:4px;text-align:right}.ant-popover-buttons button{margin-left:8px}.ant-popover-arrow{position:absolute;display:block;width:8.48528137px;height:8.48528137px;background:transparent;border-style:solid;border-width:4.24264069px;transform:rotate(45deg)}.ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-topLeft>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-topRight>.ant-popover-content>.ant-popover-arrow{bottom:6.2px;border-color:transparent #fff #fff transparent;box-shadow:3px 3px 7px rgba(0,0,0,.07)}.ant-popover-placement-top>.ant-popover-content>.ant-popover-arrow{left:50%;transform:translateX(-50%) rotate(45deg)}.ant-popover-placement-topLeft>.ant-popover-content>.ant-popover-arrow{left:16px}.ant-popover-placement-topRight>.ant-popover-content>.ant-popover-arrow{right:16px}.ant-popover-placement-right>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-rightBottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-rightTop>.ant-popover-content>.ant-popover-arrow{left:6px;border-color:transparent transparent #fff #fff;box-shadow:-3px 3px 7px rgba(0,0,0,.07)}.ant-popover-placement-right>.ant-popover-content>.ant-popover-arrow{top:50%;transform:translateY(-50%) rotate(45deg)}.ant-popover-placement-rightTop>.ant-popover-content>.ant-popover-arrow{top:12px}.ant-popover-placement-rightBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}.ant-popover-placement-bottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-bottomLeft>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-bottomRight>.ant-popover-content>.ant-popover-arrow{top:6px;border-color:#fff transparent transparent #fff;box-shadow:-2px -2px 5px rgba(0,0,0,.06)}.ant-popover-placement-bottom>.ant-popover-content>.ant-popover-arrow{left:50%;transform:translateX(-50%) rotate(45deg)}.ant-popover-placement-bottomLeft>.ant-popover-content>.ant-popover-arrow{left:16px}.ant-popover-placement-bottomRight>.ant-popover-content>.ant-popover-arrow{right:16px}.ant-popover-placement-left>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow,.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{right:6px;border-color:#fff #fff transparent transparent;box-shadow:3px -3px 7px rgba(0,0,0,.07)}.ant-popover-placement-left>.ant-popover-content>.ant-popover-arrow{top:50%;transform:translateY(-50%) rotate(45deg)}.ant-popover-placement-leftTop>.ant-popover-content>.ant-popover-arrow{top:12px}.ant-popover-placement-leftBottom>.ant-popover-content>.ant-popover-arrow{bottom:12px}.ant-progress{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-progress-line{position:relative;width:100%;font-size:14px}.ant-progress-small.ant-progress-line,.ant-progress-small.ant-progress-line .ant-progress-text .anticon{font-size:12px}.ant-progress-outer{display:inline-block;width:100%;margin-right:0;padding-right:0}.ant-progress-show-info .ant-progress-outer{margin-right:calc(-2em - 8px);padding-right:calc(2em + 8px)}.ant-progress-inner{position:relative;display:inline-block;width:100%;overflow:hidden;vertical-align:middle;background-color:#ededed;border-radius:100px}.ant-progress-circle-trail{stroke:#ededed}.ant-progress-circle-path{animation:ant-progress-appear .3s}.ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#1890ff}.ant-progress-bg,.ant-progress-success-bg{position:relative;background-color:#1890ff;border-radius:100px;transition:all .4s cubic-bezier(.08,.82,.17,1) 0s}.ant-progress-success-bg{position:absolute;top:0;left:0;background-color:#008771}.ant-progress-text{display:inline-block;width:2em;margin-left:8px;color:rgba(0,0,0,.45);font-size:1em;line-height:1;white-space:nowrap;text-align:left;vertical-align:middle;word-break:normal}.ant-progress-text .anticon{font-size:14px}.ant-progress-status-active .ant-progress-bg:before{position:absolute;top:0;right:0;bottom:0;left:0;background:#fff;border-radius:10px;opacity:0;animation:ant-progress-active 2.4s cubic-bezier(.23,1,.32,1) infinite;content:""}.ant-progress-status-exception .ant-progress-bg{background-color:#f5222d}.ant-progress-status-exception .ant-progress-text{color:#f5222d}.ant-progress-status-exception .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#f5222d}.ant-progress-status-success .ant-progress-bg{background-color:#008771}.ant-progress-status-success .ant-progress-text{color:#008771}.ant-progress-status-success .ant-progress-inner:not(.ant-progress-circle-gradient) .ant-progress-circle-path{stroke:#008771}.ant-progress-circle .ant-progress-inner{position:relative;line-height:1;background-color:transparent}.ant-progress-circle .ant-progress-text{position:absolute;top:50%;left:50%;width:100%;margin:0;padding:0;color:rgba(0,0,0,.65);line-height:1;white-space:normal;text-align:center;transform:translate(-50%,-50%)}.ant-progress-circle .ant-progress-text .anticon{font-size:1.16666667em}.ant-progress-circle.ant-progress-status-exception .ant-progress-text{color:#f5222d}.ant-progress-circle.ant-progress-status-success .ant-progress-text{color:#008771}@keyframes ant-progress-active{0%{width:0;opacity:.1}20%{width:0;opacity:.5}to{width:100%;opacity:0}}.ant-rate{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;font-feature-settings:"tnum";display:inline-block;margin:0;padding:0;color:#fadb14;font-size:20px;line-height:unset;list-style:none;outline:none}.ant-rate-disabled .ant-rate-star{cursor:default}.ant-rate-disabled .ant-rate-star:hover{transform:scale(1)}.ant-rate-star{position:relative;display:inline-block;margin:0;padding:0;color:inherit;cursor:pointer;transition:all .3s}.ant-rate-star:not(:last-child){margin-right:8px}.ant-rate-star>div:focus{outline:0}.ant-rate-star>div:focus,.ant-rate-star>div:hover{transform:scale(1.1)}.ant-rate-star-first,.ant-rate-star-second{color:#e8e8e8;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-rate-star-first .anticon,.ant-rate-star-second .anticon{vertical-align:middle}.ant-rate-star-first{position:absolute;top:0;left:0;width:50%;height:100%;overflow:hidden;opacity:0}.ant-rate-star-half .ant-rate-star-first,.ant-rate-star-half .ant-rate-star-second{opacity:1}.ant-rate-star-full .ant-rate-star-second,.ant-rate-star-half .ant-rate-star-first{color:inherit}.ant-rate-text{display:inline-block;margin-left:8px;font-size:14px}.ant-result{padding:48px 32px}.ant-result-success .ant-result-icon>.anticon{color:#008771}.ant-result-error .ant-result-icon>.anticon{color:#f5222d}.ant-result-info .ant-result-icon>.anticon{color:#1890ff}.ant-result-warning .ant-result-icon>.anticon{color:#faad14}.ant-result-image{width:250px;height:295px;margin:auto}.ant-result-icon{margin-bottom:24px;text-align:center}.ant-result-icon>.anticon{font-size:72px}.ant-result-title{color:rgba(0,0,0,.85);font-size:24px;line-height:1.8;text-align:center}.ant-result-subtitle{color:rgba(0,0,0,.45);font-size:14px;line-height:1.6;text-align:center}.ant-result-extra{margin-top:32px;text-align:center}.ant-result-extra>*{margin-right:8px}.ant-result-extra>:last-child{margin-right:0}.ant-result-content{margin-top:24px;padding:24px 40px;background-color:#fafafa}.ant-skeleton{display:table;width:100%}.ant-skeleton-header{display:table-cell;padding-right:16px;vertical-align:top}.ant-skeleton-header .ant-skeleton-avatar{display:inline-block;vertical-align:top;background:#f2f2f2;width:32px;height:32px;line-height:32px}.ant-skeleton-header .ant-skeleton-avatar.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-lg{width:40px;height:40px;line-height:40px}.ant-skeleton-header .ant-skeleton-avatar-lg.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-header .ant-skeleton-avatar-sm{width:24px;height:24px;line-height:24px}.ant-skeleton-header .ant-skeleton-avatar-sm.ant-skeleton-avatar-circle{border-radius:50%}.ant-skeleton-content{display:table-cell;width:100%;vertical-align:top}.ant-skeleton-content .ant-skeleton-title{width:100%;height:16px;margin-top:16px;background:#f2f2f2}.ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:24px}.ant-skeleton-content .ant-skeleton-paragraph{padding:0}.ant-skeleton-content .ant-skeleton-paragraph>li{width:100%;height:16px;list-style:none;background:#f2f2f2}.ant-skeleton-content .ant-skeleton-paragraph>li:last-child:not(:first-child):not(:nth-child(2)){width:61%}.ant-skeleton-content .ant-skeleton-paragraph>li+li{margin-top:16px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title{margin-top:12px}.ant-skeleton-with-avatar .ant-skeleton-content .ant-skeleton-title+.ant-skeleton-paragraph{margin-top:28px}.ant-skeleton.ant-skeleton-active .ant-skeleton-avatar,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-paragraph>li,.ant-skeleton.ant-skeleton-active .ant-skeleton-content .ant-skeleton-title{background:linear-gradient(90deg,#f2f2f2 25%,#e6e6e6 37%,#f2f2f2 63%);background-size:400% 100%;animation:ant-skeleton-loading 1.4s ease infinite}@keyframes ant-skeleton-loading{0%{background-position:100% 50%}to{background-position:0 50%}}.ant-slider{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;height:12px;margin:14px 6px 10px;padding:4px 0;cursor:pointer;touch-action:none}.ant-slider-vertical{width:12px;height:100%;margin:6px 10px;padding:0 4px}.ant-slider-vertical .ant-slider-rail{width:4px;height:100%}.ant-slider-vertical .ant-slider-track{width:4px}.ant-slider-vertical .ant-slider-handle{margin-top:-6px;margin-left:-5px}.ant-slider-vertical .ant-slider-mark{top:0;left:12px;width:18px;height:100%}.ant-slider-vertical .ant-slider-mark-text{left:4px;white-space:nowrap}.ant-slider-vertical .ant-slider-step{width:4px;height:100%}.ant-slider-vertical .ant-slider-dot{top:auto;left:2px;margin-bottom:-4px}.ant-slider-tooltip .ant-tooltip-inner{min-width:unset}.ant-slider-with-marks{margin-bottom:28px}.ant-slider-rail{width:100%;background-color:#f5f5f5;border-radius:2px}.ant-slider-rail,.ant-slider-track{position:absolute;height:4px;transition:background-color .3s}.ant-slider-track{background-color:#53ad95;border-radius:1rem}.ant-slider-handle{position:absolute;width:14px;height:14px;margin-top:-5px;background-color:#fff;border:2px solid #53ad95;border-radius:50%;box-shadow:0;cursor:pointer;transition:border-color .3s,box-shadow .6s,transform .3s cubic-bezier(.18,.89,.32,1.28)}.ant-slider-handle:focus{border-color:#339f8d;outline:none;box-shadow:0 0 0 5px rgba(0,135,113,.2)}.ant-slider-handle.ant-tooltip-open{border-color:#008771}.ant-slider:hover .ant-slider-rail{background-color:#e1e1e1}.ant-slider:hover .ant-slider-track{background-color:#33a187}.ant-slider:hover .ant-slider-handle:not(.ant-tooltip-open){border-color:#33a187}.ant-slider-mark{position:absolute;top:14px;left:0;width:100%;font-size:14px}.ant-slider-mark-text{position:absolute;display:inline-block;color:rgba(0,0,0,.45);text-align:center;word-break:keep-all;cursor:pointer}.ant-slider-mark-text-active{color:rgba(0,0,0,.65)}.ant-slider-step{position:absolute;width:100%;height:4px;background:transparent}.ant-slider-dot{position:absolute;top:-2px;width:8px;height:8px;background-color:#fff;border:2px solid #e8e8e8;border-radius:50%;cursor:pointer}.ant-slider-dot,.ant-slider-dot:first-child,.ant-slider-dot:last-child{margin-left:-4px}.ant-slider-dot-active{border-color:#80c3b8}.ant-slider-disabled{cursor:not-allowed}.ant-slider-disabled .ant-slider-track{background-color:rgba(0,0,0,.25)!important}.ant-slider-disabled .ant-slider-dot,.ant-slider-disabled .ant-slider-handle{background-color:#fff;border-color:rgba(0,0,0,.25)!important;box-shadow:none;cursor:not-allowed}.ant-slider-disabled .ant-slider-dot,.ant-slider-disabled .ant-slider-mark-text{cursor:not-allowed!important}.ant-space{display:inline-flex}.ant-space-vertical{flex-direction:column}.ant-space-align-center{align-items:center}.ant-space-align-start{align-items:flex-start}.ant-space-align-end{align-items:flex-end}.ant-space-align-baseline{align-items:baseline}.ant-statistic{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-statistic-title{margin-bottom:4px;color:rgba(0,0,0,.45);font-size:14px}.ant-statistic-content{color:rgba(0,0,0,.85);font-size:24px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}.ant-statistic-content-value-decimal{font-size:16px}.ant-statistic-content-prefix,.ant-statistic-content-suffix{display:inline-block}.ant-statistic-content-prefix{margin-right:4px}.ant-statistic-content-suffix{margin-left:4px;font-size:16px}.ant-steps{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:flex;width:100%;font-size:0}.ant-steps-item{position:relative;display:inline-block;flex:1;overflow:hidden;vertical-align:top}.ant-steps-item-container{outline:none}.ant-steps-item:last-child{flex:none}.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after,.ant-steps-item:last-child>.ant-steps-item-container>.ant-steps-item-tail{display:none}.ant-steps-item-content,.ant-steps-item-icon{display:inline-block;vertical-align:top}.ant-steps-item-icon{width:32px;height:32px;margin-right:8px;font-size:16px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;line-height:32px;text-align:center;border:1px solid rgba(0,0,0,.25);border-radius:32px;transition:background-color .3s,border-color .3s}.ant-steps-item-icon>.ant-steps-icon{position:relative;top:-1px;color:#008771;line-height:1}.ant-steps-item-tail{position:absolute;top:12px;left:0;width:100%;padding:0 10px}.ant-steps-item-tail:after{display:inline-block;width:100%;height:1px;background:#e8e8e8;border-radius:1px;transition:background .3s;content:""}.ant-steps-item-title{position:relative;display:inline-block;padding-right:16px;color:rgba(0,0,0,.65);font-size:16px;line-height:32px}.ant-steps-item-title:after{position:absolute;top:16px;left:100%;display:block;width:9999px;height:1px;background:#e8e8e8;content:""}.ant-steps-item-subtitle{display:inline;margin-left:8px;font-weight:400}.ant-steps-item-description,.ant-steps-item-subtitle{color:rgba(0,0,0,.45);font-size:14px}.ant-steps-item-wait .ant-steps-item-icon{background-color:#fff;border-color:rgba(0,0,0,.25)}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon{color:rgba(0,0,0,.25)}.ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:rgba(0,0,0,.25)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.45)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.45)}.ant-steps-item-wait>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item-process .ant-steps-item-icon{background-color:#fff;border-color:#008771}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#008771}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#008771}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.85)}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.65)}.ant-steps-item-process>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item-process .ant-steps-item-icon{background:#008771}.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#fff}.ant-steps-item-process .ant-steps-item-title{font-weight:500}.ant-steps-item-finish .ant-steps-item-icon{background-color:#fff;border-color:#008771}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon{color:#008771}.ant-steps-item-finish .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#008771}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:rgba(0,0,0,.65)}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#008771}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:rgba(0,0,0,.45)}.ant-steps-item-finish>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#008771}.ant-steps-item-error .ant-steps-item-icon{background-color:#fff;border-color:#f5222d}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon{color:#f5222d}.ant-steps-item-error .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title{color:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{background-color:#e8e8e8}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-description{color:#f5222d}.ant-steps-item-error>.ant-steps-item-container>.ant-steps-item-tail:after{background-color:#e8e8e8}.ant-steps-item.ant-steps-next-error .ant-steps-item-title:after{background:#f5222d}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]{cursor:pointer}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-icon .ant-steps-icon,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button] .ant-steps-item-title{transition:color .3s}.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-description,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-subtitle,.ant-steps .ant-steps-item:not(.ant-steps-item-active)>.ant-steps-item-container[role=button]:hover .ant-steps-item-title{color:#008771}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon{border-color:#008771}.ant-steps .ant-steps-item:not(.ant-steps-item-active):not(.ant-steps-item-process)>.ant-steps-item-container[role=button]:hover .ant-steps-item-icon .ant-steps-icon{color:#008771}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{margin-right:16px;white-space:nowrap}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child{margin-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child .ant-steps-item-title{padding-right:0}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-tail{display:none}.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item-description{max-width:140px;white-space:normal}.ant-steps-item-custom .ant-steps-item-icon{height:auto;background:none;border:0}.ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{top:0;left:.5px;width:32px;height:32px;font-size:24px;line-height:32px}.ant-steps-item-custom.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon{color:#008771}.ant-steps:not(.ant-steps-vertical) .ant-steps-item-custom .ant-steps-item-icon{width:auto}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item{margin-right:12px}.ant-steps-small.ant-steps-horizontal:not(.ant-steps-label-vertical) .ant-steps-item:last-child{margin-right:0}.ant-steps-small .ant-steps-item-icon{width:24px;height:24px;font-size:12px;line-height:24px;text-align:center;border-radius:24px}.ant-steps-small .ant-steps-item-title{padding-right:12px;font-size:14px;line-height:24px}.ant-steps-small .ant-steps-item-title:after{top:12px}.ant-steps-small .ant-steps-item-description{color:rgba(0,0,0,.45);font-size:14px}.ant-steps-small .ant-steps-item-tail{top:8px}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon{width:inherit;height:inherit;line-height:inherit;background:none;border:0;border-radius:0}.ant-steps-small .ant-steps-item-custom .ant-steps-item-icon>.ant-steps-icon{font-size:24px;line-height:24px;transform:none}.ant-steps-vertical{display:block}.ant-steps-vertical .ant-steps-item{display:block;overflow:visible}.ant-steps-vertical .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-vertical .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-vertical .ant-steps-item-title{line-height:32px}.ant-steps-vertical .ant-steps-item-description{padding-bottom:12px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-vertical>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-vertical>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-vertical.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}@media (max-width:480px){.ant-steps-horizontal.ant-steps-label-horizontal{display:block}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item{display:block;overflow:visible}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-icon{float:left;margin-right:16px}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-content{display:block;min-height:48px;overflow:hidden}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-title{line-height:32px}.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item-description{padding-bottom:12px}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{position:absolute;top:0;left:16px;width:1px;height:100%;padding:38px 0 6px}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail:after{width:1px;height:100%}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item:not(:last-child)>.ant-steps-item-container>.ant-steps-item-tail{display:block}.ant-steps-horizontal.ant-steps-label-horizontal>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-content>.ant-steps-item-title:after{display:none}.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item-container .ant-steps-item-tail{position:absolute;top:0;left:12px;padding:30px 0 6px}.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item-container .ant-steps-item-title{line-height:24px}}.ant-steps-label-vertical .ant-steps-item{overflow:visible}.ant-steps-label-vertical .ant-steps-item-tail{margin-left:58px;padding:3.5px 24px}.ant-steps-label-vertical .ant-steps-item-content{display:block;width:116px;margin-top:8px;text-align:center}.ant-steps-label-vertical .ant-steps-item-icon{display:inline-block;margin-left:42px}.ant-steps-label-vertical .ant-steps-item-title{padding-right:0}.ant-steps-label-vertical .ant-steps-item-title:after{display:none}.ant-steps-label-vertical .ant-steps-item-subtitle{display:block;margin-bottom:4px;margin-left:0;line-height:1.5}.ant-steps-label-vertical.ant-steps-small:not(.ant-steps-dot) .ant-steps-item-icon{margin-left:46px}.ant-steps-dot .ant-steps-item-title,.ant-steps-dot.ant-steps-small .ant-steps-item-title{line-height:1.5}.ant-steps-dot .ant-steps-item-tail,.ant-steps-dot.ant-steps-small .ant-steps-item-tail{top:2px;width:100%;margin:0 0 0 70px;padding:0}.ant-steps-dot .ant-steps-item-tail:after,.ant-steps-dot.ant-steps-small .ant-steps-item-tail:after{width:calc(100% - 20px);height:3px;margin-left:12px}.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item:first-child .ant-steps-icon-dot{left:2px}.ant-steps-dot .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-icon{width:8px;height:8px;margin-left:67px;padding-right:0;line-height:8px;background:transparent;border:0}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot{position:relative;float:left;width:100%;height:100%;border-radius:100px;transition:all .3s}.ant-steps-dot .ant-steps-item-icon .ant-steps-icon-dot:after,.ant-steps-dot.ant-steps-small .ant-steps-item-icon .ant-steps-icon-dot:after{position:absolute;top:-12px;left:-26px;width:60px;height:32px;background:rgba(0,0,0,.001);content:""}.ant-steps-dot .ant-steps-item-content,.ant-steps-dot.ant-steps-small .ant-steps-item-content{width:140px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon{width:10px;height:10px;line-height:10px}.ant-steps-dot .ant-steps-item-process .ant-steps-item-icon .ant-steps-icon-dot,.ant-steps-dot.ant-steps-small .ant-steps-item-process .ant-steps-item-icon .ant-steps-icon-dot{top:-1px}.ant-steps-vertical.ant-steps-dot .ant-steps-item-icon{margin-top:8px;margin-left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{top:2px;left:-9px;margin:0;padding:22px 0 4px}.ant-steps-vertical.ant-steps-dot .ant-steps-item:first-child .ant-steps-icon-dot{left:0}.ant-steps-vertical.ant-steps-dot .ant-steps-item-process .ant-steps-icon-dot{left:-2px}.ant-steps-navigation{padding-top:12px}.ant-steps-navigation.ant-steps-small .ant-steps-item-container{margin-left:-12px}.ant-steps-navigation .ant-steps-item{overflow:visible;text-align:center}.ant-steps-navigation .ant-steps-item-container{display:inline-block;height:100%;margin-left:-16px;padding-bottom:12px;text-align:left;transition:opacity .3s}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-content{max-width:auto}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title{max-width:100%;padding-right:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-steps-navigation .ant-steps-item-container .ant-steps-item-title:after{display:none}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]{cursor:pointer}.ant-steps-navigation .ant-steps-item:not(.ant-steps-item-active) .ant-steps-item-container[role=button]:hover{opacity:.85}.ant-steps-navigation .ant-steps-item:last-child{flex:1}.ant-steps-navigation .ant-steps-item:last-child:after{display:none}.ant-steps-navigation .ant-steps-item:after{position:absolute;top:50%;left:100%;display:inline-block;width:12px;height:12px;margin-top:-14px;margin-left:-2px;border:1px solid rgba(0,0,0,.25);border-bottom:none;border-left:none;transform:rotate(45deg);content:""}.ant-steps-navigation .ant-steps-item:before{position:absolute;bottom:0;left:50%;display:inline-block;width:0;height:3px;background-color:#008771;transition:width .3s,left .3s;transition-timing-function:ease-out;content:""}.ant-steps-navigation .ant-steps-item.ant-steps-item-active:before{left:0;width:100%}@media (max-width:480px){.ant-steps-navigation>.ant-steps-item{margin-right:0!important}.ant-steps-navigation>.ant-steps-item:before{display:none}.ant-steps-navigation>.ant-steps-item.ant-steps-item-active:before{top:0;right:0;left:unset;display:block;width:3px;height:calc(100% - 24px)}.ant-steps-navigation>.ant-steps-item:after{position:relative;top:-2px;left:50%;display:block;width:8px;height:8px;margin-bottom:8px;text-align:center;transform:rotate(135deg)}.ant-steps-navigation>.ant-steps-item>.ant-steps-item-container>.ant-steps-item-tail{visibility:hidden}}.ant-steps-flex-not-supported.ant-steps-horizontal.ant-steps-label-horizontal .ant-steps-item{margin-left:-16px;padding-left:16px;background:#fff}.ant-steps-flex-not-supported.ant-steps-horizontal.ant-steps-label-horizontal.ant-steps-small .ant-steps-item{margin-left:-12px;padding-left:12px}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item:last-child{overflow:hidden}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item:last-child .ant-steps-icon-dot:after{right:-200px;width:200px}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:after,.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:before{position:absolute;top:0;left:-10px;width:10px;height:8px;background:#fff;content:""}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item .ant-steps-icon-dot:after{right:-10px;left:auto}.ant-steps-flex-not-supported.ant-steps-dot .ant-steps-item-wait .ant-steps-item-icon>.ant-steps-icon .ant-steps-icon-dot{background:#ccc}.ant-switch{margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;display:inline-block;box-sizing:border-box;min-width:44px;height:22px;line-height:20px;vertical-align:middle;background-color:rgba(0,0,0,.25);border:1px solid transparent;border-radius:100px;cursor:pointer;transition:all .36s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-switch-inner{display:block;margin-right:6px;margin-left:24px;color:#fff;font-size:12px}.ant-switch-loading-icon,.ant-switch:after{position:absolute;top:1px;left:1px;width:18px;height:18px;background-color:#fff;border-radius:18px;cursor:pointer;transition:all .36s cubic-bezier(.78,.14,.15,.86);content:" "}.ant-switch:after{box-shadow:0 2px 4px 0 rgba(0,35,11,.2)}.ant-switch:not(.ant-switch-disabled):active:after,.ant-switch:not(.ant-switch-disabled):active:before{width:24px}.ant-switch-loading-icon{z-index:1;display:none;font-size:12px;background:transparent}.ant-switch-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-switch-loading .ant-switch-loading-icon{display:inline-block;color:rgba(0,0,0,.65)}.ant-switch-checked.ant-switch-loading .ant-switch-loading-icon{color:#008771}.ant-switch:focus{outline:0;box-shadow:0 0 0 2px rgba(0,135,113,.2)}.ant-switch:focus:hover{box-shadow:none}.ant-switch-small{min-width:28px;height:16px;line-height:14px}.ant-switch-small .ant-switch-inner{margin-right:3px;margin-left:18px;font-size:12px}.ant-switch-small:after{width:12px;height:12px}.ant-switch-small:active:after,.ant-switch-small:active:before{width:16px}.ant-switch-small .ant-switch-loading-icon{width:12px;height:12px}.ant-switch-small.ant-switch-checked .ant-switch-inner{margin-right:18px;margin-left:3px}.ant-switch-small.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-13px}.ant-switch-small.ant-switch-loading .ant-switch-loading-icon{font-weight:700;transform:scale(.66667)}.ant-switch-checked{background-color:#008771}.ant-switch-checked .ant-switch-inner{margin-right:24px;margin-left:6px}.ant-switch-checked:after{left:100%;margin-left:-1px;transform:translateX(-100%)}.ant-switch-checked .ant-switch-loading-icon{left:100%;margin-left:-19px}.ant-switch-disabled,.ant-switch-loading{cursor:not-allowed;opacity:.4}.ant-switch-disabled *,.ant-switch-disabled:after,.ant-switch-disabled:before,.ant-switch-loading *,.ant-switch-loading:after,.ant-switch-loading:before{cursor:not-allowed}@keyframes AntSwitchSmallLoadingCircle{0%{transform:rotate(0deg) scale(.66667);transform-origin:50% 50%}to{transform:rotate(1turn) scale(.66667);transform-origin:50% 50%}}.ant-table-wrapper{zoom:1}.ant-table-wrapper:after,.ant-table-wrapper:before{display:table;content:""}.ant-table-wrapper:after{clear:both}.ant-table{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;clear:both}.ant-table-body{transition:opacity .3s}.ant-table-empty .ant-table-body{overflow-x:auto!important;overflow-y:hidden!important}.ant-table table{width:100%;text-align:left;border-radius:1rem 1rem 0 0;border-collapse:separate;border-spacing:0}.ant-table-layout-fixed table{table-layout:fixed}.ant-table-thead>tr>th{color:rgba(0,0,0,.85);font-weight:500;text-align:left;background:#fafafa;border-bottom:1px solid #e8e8e8;transition:background .3s ease}.ant-table-thead>tr>th[colspan]:not([colspan="1"]){text-align:center}.ant-table-thead>tr>th .ant-table-filter-icon,.ant-table-thead>tr>th .anticon-filter{position:absolute;top:0;right:0;width:28px;height:100%;color:#bfbfbf;font-size:12px;text-align:center;cursor:pointer;transition:all .3s}.ant-table-thead>tr>th .ant-table-filter-icon>svg,.ant-table-thead>tr>th .anticon-filter>svg{position:absolute;top:50%;left:50%;margin-top:-5px;margin-left:-6px}.ant-table-thead>tr>th .ant-table-filter-selected.anticon{color:#008771}.ant-table-thead>tr>th .ant-table-column-sorter{display:table-cell;vertical-align:middle}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner{height:1em;margin-top:.35em;margin-left:.57142857em;color:#bfbfbf;line-height:1em;text-align:center;transition:all .3s}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up{display:inline-block;font-size:12px;font-size:11px\9;transform:scale(.91666667) rotate(0deg);display:block;height:1em;line-height:1em;transition:all .3s}:root .ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down,:root .ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up{font-size:12px}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-down.on,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner .ant-table-column-sorter-up.on{color:#008771}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full{margin-top:-.15em}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-down,.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-up{height:.5em;line-height:.5em}.ant-table-thead>tr>th .ant-table-column-sorter .ant-table-column-sorter-inner-full .ant-table-column-sorter-down{margin-top:.125em}.ant-table-thead>tr>th.ant-table-column-has-actions{position:relative;background-clip:padding-box;-webkit-background-clip:border-box}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters{padding-right:30px!important}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters .ant-table-filter-icon.ant-table-filter-open,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters .anticon-filter.ant-table-filter-open,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:hover,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:hover{color:rgba(0,0,0,.45);background:#e5e5e5}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .ant-table-filter-icon:active,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-filters:hover .anticon-filter:active{color:rgba(0,0,0,.65)}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters{cursor:pointer}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .ant-table-filter-icon,.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover .anticon-filter{background:#f2f2f2}.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-down:not(.on),.ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:active .ant-table-column-sorter-up:not(.on){color:rgba(0,0,0,.45)}.ant-table-thead>tr>th .ant-table-header-column{display:inline-block;max-width:100%;vertical-align:top}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters{display:table}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters>.ant-table-column-title{display:table-cell;vertical-align:middle}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters>:not(.ant-table-column-sorter){position:relative}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters:before{position:absolute;top:0;right:0;bottom:0;left:0;background:transparent;transition:all .3s;content:""}.ant-table-thead>tr>th .ant-table-header-column .ant-table-column-sorters:hover:before{background:rgba(0,0,0,.04)}.ant-table-thead>tr>th.ant-table-column-has-sorters{-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-table-thead>tr:first-child>th:first-child{border-top-left-radius:1rem}.ant-table-thead>tr:first-child>th:last-child{border-top-right-radius:1rem}.ant-table-thead>tr:not(:last-child)>th[colspan]{border-bottom:0}.ant-table-tbody>tr>td{border-bottom:1px solid #e8e8e8;transition:background .3s}.ant-table-tbody>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr.ant-table-row-hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,.ant-table-thead>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td{background:#b3c7c0}.ant-table-tbody>tr.ant-table-row-selected>td.ant-table-column-sort,.ant-table-tbody>tr:hover.ant-table-row-selected>td,.ant-table-tbody>tr:hover.ant-table-row-selected>td.ant-table-column-sort,.ant-table-thead>tr.ant-table-row-selected>td.ant-table-column-sort,.ant-table-thead>tr:hover.ant-table-row-selected>td,.ant-table-thead>tr:hover.ant-table-row-selected>td.ant-table-column-sort{background:#fafafa}.ant-table-thead>tr:hover{background:none}.ant-table-footer{position:relative;padding:16px;color:rgba(0,0,0,.85);background:#fafafa;border-top:1px solid #e8e8e8;border-radius:0 0 1rem 1rem}.ant-table-footer:before{position:absolute;top:-1px;left:0;width:100%;height:1px;background:#fafafa;content:""}.ant-table.ant-table-bordered .ant-table-footer{border:1px solid #e8e8e8}.ant-table-title{position:relative;top:1px;padding:16px 0;border-radius:1rem 1rem 0 0}.ant-table.ant-table-bordered .ant-table-title{padding-right:16px;padding-left:16px;border:1px solid #e8e8e8}.ant-table-title+.ant-table-content{position:relative;border-radius:1rem 1rem 0 0}.ant-table-bordered .ant-table-title+.ant-table-content,.ant-table-bordered .ant-table-title+.ant-table-content .ant-table-thead>tr:first-child>th,.ant-table-bordered .ant-table-title+.ant-table-content table,.ant-table-without-column-header .ant-table-title+.ant-table-content,.ant-table-without-column-header table{border-radius:0}.ant-table-without-column-header.ant-table-bordered.ant-table-empty .ant-table-placeholder{border-top:1px solid #e8e8e8;border-radius:1rem}.ant-table-tbody>tr.ant-table-row-selected td{color:inherit;background:#fafafa}.ant-table-thead>tr>th.ant-table-column-sort{background:#f5f5f5}.ant-table-tbody>tr>td.ant-table-column-sort{background:rgba(0,0,0,.01)}.ant-table-tbody>tr>td,.ant-table-thead>tr>th{padding:16px;overflow-wrap:break-word}.ant-table-expand-icon-th,.ant-table-row-expand-icon-cell{width:50px;min-width:50px;text-align:center}.ant-table-header{overflow:hidden;background:#fafafa}.ant-table-header table{border-radius:1rem 1rem 0 0}.ant-table-loading{position:relative}.ant-table-loading .ant-table-body{background:#fff;opacity:.5}.ant-table-loading .ant-table-spin-holder{position:absolute;top:50%;left:50%;height:20px;margin-left:-30px;line-height:20px}.ant-table-loading .ant-table-with-pagination{margin-top:-20px}.ant-table-loading .ant-table-without-pagination{margin-top:10px}.ant-table-bordered .ant-table-body>table,.ant-table-bordered .ant-table-fixed-left table,.ant-table-bordered .ant-table-fixed-right table,.ant-table-bordered .ant-table-header>table{border:1px solid #e8e8e8;border-right:0;border-bottom:0}.ant-table-bordered.ant-table-empty .ant-table-placeholder{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8}.ant-table-bordered.ant-table-fixed-header .ant-table-header>table{border-bottom:0}.ant-table-bordered.ant-table-fixed-header .ant-table-body>table{border-top-left-radius:0;border-top-right-radius:0}.ant-table-bordered.ant-table-fixed-header .ant-table-body-inner>table,.ant-table-bordered.ant-table-fixed-header .ant-table-header+.ant-table-body>table{border-top:0}.ant-table-bordered .ant-table-thead>tr:not(:last-child)>th{border-bottom:1px solid #e8e8e8}.ant-table-bordered .ant-table-tbody>tr>td,.ant-table-bordered .ant-table-thead>tr>th{border-right:1px solid #e8e8e8}.ant-table-placeholder{position:relative;z-index:1;margin-top:-1px;padding:16px;color:rgba(0,0,0,.25);font-size:14px;text-align:center;background:#fff;border-top:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8;border-radius:0 0 1rem 1rem}.ant-table-pagination.ant-pagination{float:right;margin:16px 0}.ant-table-filter-dropdown{position:relative;min-width:96px;margin-left:-8px;background:#fff;border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-filter-dropdown .ant-dropdown-menu{max-height:calc(100vh - 130px);overflow-x:hidden;border:0;border-radius:1rem 1rem 0 0;box-shadow:none}.ant-table-filter-dropdown .ant-dropdown-menu-item>label+span{padding-right:0}.ant-table-filter-dropdown .ant-dropdown-menu-sub{border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-filter-dropdown .ant-dropdown-menu .ant-dropdown-submenu-contain-selected .ant-dropdown-menu-submenu-title:after{color:#008771;font-weight:700;text-shadow:0 0 2px #77baa6}.ant-table-filter-dropdown .ant-dropdown-menu-item{overflow:hidden}.ant-table-filter-dropdown>.ant-dropdown-menu>.ant-dropdown-menu-item:last-child,.ant-table-filter-dropdown>.ant-dropdown-menu>.ant-dropdown-menu-submenu:last-child .ant-dropdown-menu-submenu-title{border-radius:0}.ant-table-filter-dropdown-btns{padding:7px 8px;overflow:hidden;border-top:1px solid #e8e8e8}.ant-table-filter-dropdown-link{color:#008771}.ant-table-filter-dropdown-link:hover{color:#18947b}.ant-table-filter-dropdown-link:active{color:#006154}.ant-table-filter-dropdown-link.confirm{float:left}.ant-table-filter-dropdown-link.clear{float:right}.ant-table-selection{white-space:nowrap}.ant-table-selection-select-all-custom{margin-right:4px!important}.ant-table-selection .anticon-down{color:#bfbfbf;transition:all .3s}.ant-table-selection-menu{min-width:96px;margin-top:5px;margin-left:-30px;background:#fff;border-radius:1rem;box-shadow:0 2px 8px rgba(0,0,0,.15)}.ant-table-selection-menu .ant-action-down{color:#bfbfbf}.ant-table-selection-down{display:inline-block;padding:0;line-height:1;cursor:pointer}.ant-table-selection-down:hover .anticon-down{color:rgba(0,0,0,.6)}.ant-table-row-expand-icon{color:#008771;text-decoration:none;cursor:pointer;transition:color .3s;display:inline-block;width:17px;height:17px;color:inherit;line-height:13px;text-align:center;background:#fff;border:1px solid #e8e8e8;border-radius:2px;outline:none;transition:all .3s;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{color:#18947b}.ant-table-row-expand-icon:active{color:#006154}.ant-table-row-expand-icon:active,.ant-table-row-expand-icon:focus,.ant-table-row-expand-icon:hover{border-color:currentColor}.ant-table-row-expanded:after{content:"-"}.ant-table-row-collapsed:after{content:"+"}.ant-table-row-spaced{visibility:hidden}.ant-table-row-spaced:after{content:"."}.ant-table-row-cell-ellipsis,.ant-table-row-cell-ellipsis .ant-table-column-title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-table-row-cell-ellipsis .ant-table-column-title{display:block}.ant-table-row-cell-break-word{word-wrap:break-word;word-break:break-word}tr.ant-table-expanded-row,tr.ant-table-expanded-row:hover{background:#fbfbfb}tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-16px -16px -17px}.ant-table .ant-table-row-indent+.ant-table-row-expand-icon{margin-right:8px}.ant-table-scroll{overflow:auto;overflow-x:hidden}.ant-table-scroll table{min-width:100%}.ant-table-scroll table .ant-table-fixed-columns-in-body:not([colspan]){color:transparent}.ant-table-scroll table .ant-table-fixed-columns-in-body:not([colspan])>*{visibility:hidden}.ant-table-body-inner{height:100%}.ant-table-fixed-header>.ant-table-content>.ant-table-scroll>.ant-table-body{position:relative;background:#fff}.ant-table-fixed-header .ant-table-body-inner{overflow:scroll}.ant-table-fixed-header .ant-table-scroll .ant-table-header{margin-bottom:-20px;padding-bottom:20px;overflow:scroll;opacity:.9999}.ant-table-fixed-header .ant-table-scroll .ant-table-header::-webkit-scrollbar{border:solid #e8e8e8;border-width:0 0 1px}.ant-table-hide-scrollbar{scrollbar-color:transparent transparent;min-width:unset}.ant-table-hide-scrollbar::-webkit-scrollbar{min-width:inherit;background-color:transparent}.ant-table-bordered.ant-table-fixed-header .ant-table-scroll .ant-table-header::-webkit-scrollbar{border:1px solid #e8e8e8;border-left-width:0}.ant-table-bordered.ant-table-fixed-header .ant-table-scroll .ant-table-header.ant-table-hide-scrollbar .ant-table-thead>tr:only-child>th:last-child{border-right-color:transparent}.ant-table-fixed-left,.ant-table-fixed-right{position:absolute;top:0;z-index:1;overflow:hidden;border-radius:0;transition:box-shadow .3s ease}.ant-table-fixed-left table,.ant-table-fixed-right table{width:auto;background:#fff}.ant-table-fixed-header .ant-table-fixed-left .ant-table-body-outer .ant-table-fixed,.ant-table-fixed-header .ant-table-fixed-right .ant-table-body-outer .ant-table-fixed{border-radius:0}.ant-table-fixed-left{left:0;box-shadow:6px 0 6px -4px rgba(0,0,0,.15)}.ant-table-fixed-left .ant-table-header{overflow-y:hidden}.ant-table-fixed-left .ant-table-body-inner{margin-right:-20px;padding-right:20px}.ant-table-fixed-header .ant-table-fixed-left .ant-table-body-inner{padding-right:0}.ant-table-fixed-left,.ant-table-fixed-left table{border-radius:1rem 0 0 0}.ant-table-fixed-left .ant-table-thead>tr>th:last-child{border-top-right-radius:0}.ant-table-fixed-right{right:0;box-shadow:-6px 0 6px -4px rgba(0,0,0,.15)}.ant-table-fixed-right,.ant-table-fixed-right table{border-radius:0 1rem 0 0}.ant-table-fixed-right .ant-table-expanded-row{color:transparent;pointer-events:none}.ant-table-fixed-right .ant-table-thead>tr>th:first-child{border-top-left-radius:0}.ant-table.ant-table-scroll-position-left .ant-table-fixed-left,.ant-table.ant-table-scroll-position-right .ant-table-fixed-right{box-shadow:none}.ant-table colgroup>col.ant-table-selection-col{width:60px}.ant-table-thead>tr>th.ant-table-selection-column-custom .ant-table-selection{margin-right:-15px}.ant-table-tbody>tr>td.ant-table-selection-column,.ant-table-thead>tr>th.ant-table-selection-column{text-align:center}.ant-table-tbody>tr>td.ant-table-selection-column .ant-radio-wrapper,.ant-table-thead>tr>th.ant-table-selection-column .ant-radio-wrapper{margin-right:0}.ant-table-row[class*=ant-table-row-level-0] .ant-table-selection-column>span{display:inline-block}.ant-table-filter-dropdown-submenu .ant-checkbox-wrapper+span,.ant-table-filter-dropdown .ant-checkbox-wrapper+span{padding-left:8px}@supports (-moz-appearance:meterbar){.ant-table-thead>tr>th.ant-table-column-has-actions{background-clip:padding-box}}.ant-table-middle>.ant-table-content>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-footer,.ant-table-middle>.ant-table-content>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-middle>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-middle>.ant-table-title{padding:12px 8px}.ant-table-middle tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-12px -8px -13px}.ant-table-small{border:1px solid #e8e8e8;border-radius:1rem}.ant-table-small>.ant-table-content>.ant-table-footer,.ant-table-small>.ant-table-title{padding:8px}.ant-table-small>.ant-table-title{top:0;border-bottom:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-footer{background-color:transparent;border-top:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-footer:before{background-color:transparent}.ant-table-small>.ant-table-content>.ant-table-body{margin:0 8px}.ant-table-small>.ant-table-content>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table{border:0}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-tbody>tr>td,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th{padding:8px}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th{background-color:transparent}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr{border-bottom:1px solid #e8e8e8}.ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table>.ant-table-thead>tr>th.ant-table-column-sort,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table>.ant-table-thead>tr>th.ant-table-column-sort{background-color:rgba(0,0,0,.01)}.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-left>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-body-outer>.ant-table-body-inner>table,.ant-table-small>.ant-table-content>.ant-table-fixed-right>.ant-table-header>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-body>table,.ant-table-small>.ant-table-content>.ant-table-scroll>.ant-table-header>table{padding:0}.ant-table-small>.ant-table-content .ant-table-header{background-color:transparent;border-radius:1rem 1rem 0 0}.ant-table-small>.ant-table-content .ant-table-placeholder,.ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:0}.ant-table-small.ant-table-bordered{border-right:0}.ant-table-small.ant-table-bordered .ant-table-title{border:0;border-right:1px solid #e8e8e8;border-bottom:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-content{border-right:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-footer{border:0;border-top:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-footer:before{display:none}.ant-table-small.ant-table-bordered .ant-table-placeholder{border-right:0;border-bottom:0;border-left:0}.ant-table-small.ant-table-bordered .ant-table-tbody>tr>td:last-child,.ant-table-small.ant-table-bordered .ant-table-thead>tr>th.ant-table-row-cell-last{border-right:none}.ant-table-small.ant-table-bordered .ant-table-fixed-left .ant-table-tbody>tr>td:last-child,.ant-table-small.ant-table-bordered .ant-table-fixed-left .ant-table-thead>tr>th:last-child{border-right:1px solid #e8e8e8}.ant-table-small.ant-table-bordered .ant-table-fixed-right{border-right:1px solid #e8e8e8;border-left:1px solid #e8e8e8}.ant-table-small tr.ant-table-expanded-row td>.ant-table-wrapper{margin:-8px -8px -9px}.ant-table-small.ant-table-fixed-header>.ant-table-content>.ant-table-scroll>.ant-table-body{border-radius:0 0 1rem 1rem}.ant-timeline{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;font-feature-settings:"tnum";margin:0;padding:0;list-style:none}.ant-timeline-item{position:relative;margin:0;padding:0 0 20px;font-size:14px;list-style:none}.ant-timeline-item-tail{position:absolute;top:10px;left:4px;height:calc(100% - 10px);border-left:2px solid #e8e8e8}.ant-timeline-item-pending .ant-timeline-item-head{font-size:12px;background-color:transparent}.ant-timeline-item-pending .ant-timeline-item-tail{display:none}.ant-timeline-item-head{position:absolute;width:10px;height:10px;background-color:#fff;border:2px solid transparent;border-radius:100px}.ant-timeline-item-head-blue{color:#008771;border-color:#008771}.ant-timeline-item-head-red{color:#f5222d;border-color:#f5222d}.ant-timeline-item-head-green{color:#008771;border-color:#008771}.ant-timeline-item-head-gray{color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-timeline-item-head-custom{position:absolute;top:5.5px;left:5px;width:auto;height:auto;margin-top:0;padding:3px 1px;line-height:1;text-align:center;border:0;border-radius:0;transform:translate(-50%,-50%)}.ant-timeline-item-content{position:relative;top:-6px;margin:0 0 0 18px;word-break:break-word}.ant-timeline-item-last>.ant-timeline-item-tail{display:none}.ant-timeline-item-last>.ant-timeline-item-content{min-height:48px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-alternate .ant-timeline-item-tail,.ant-timeline.ant-timeline-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-tail{left:50%}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-head{margin-left:-4px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-head-custom{margin-left:1px}.ant-timeline.ant-timeline-alternate .ant-timeline-item-left .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-left .ant-timeline-item-content{left:calc(50% - 4px);width:calc(50% - 14px);text-align:left}.ant-timeline.ant-timeline-alternate .ant-timeline-item-right .ant-timeline-item-content,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(50% - 12px);margin:0;text-align:right}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-head-custom,.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-tail{left:calc(100% - 6px)}.ant-timeline.ant-timeline-right .ant-timeline-item-right .ant-timeline-item-content{width:calc(100% - 18px)}.ant-timeline.ant-timeline-pending .ant-timeline-item-last .ant-timeline-item-tail{display:block;height:calc(100% - 14px);border-left:2px dotted #e8e8e8}.ant-timeline.ant-timeline-reverse .ant-timeline-item-last .ant-timeline-item-tail{display:none}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-tail{top:15px;display:block;height:calc(100% - 15px);border-left:2px dotted #e8e8e8}.ant-timeline.ant-timeline-reverse .ant-timeline-item-pending .ant-timeline-item-content{min-height:48px}.ant-transfer-customize-list{display:flex}.ant-transfer-customize-list .ant-transfer-operation{flex:none;align-self:center}.ant-transfer-customize-list .ant-transfer-list{flex:auto;width:auto;height:auto;min-height:200px}.ant-transfer-customize-list .ant-transfer-list-body-with-search{padding-top:0}.ant-transfer-customize-list .ant-transfer-list-body-search-wrapper{position:relative;padding-bottom:0}.ant-transfer-customize-list .ant-transfer-list-body-customize-wrapper{padding:12px}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small{border:0;border-radius:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content>.ant-table-body>table>.ant-table-thead>tr>th{background:#fafafa}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small>.ant-table-content .ant-table-row:last-child td{border-bottom:1px solid #e8e8e8}.ant-transfer-customize-list .ant-table-wrapper .ant-table-small .ant-table-body{margin:0}.ant-transfer-customize-list .ant-table-wrapper .ant-table-pagination.ant-pagination{margin:16px 0 4px}.ant-transfer{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative}.ant-transfer-disabled .ant-transfer-list{background:#f5f5f5}.ant-transfer-list{position:relative;display:inline-block;width:180px;height:200px;padding-top:40px;vertical-align:middle;border:1px solid #d9d9d9;border-radius:1rem}.ant-transfer-list-with-footer{padding-bottom:34px}.ant-transfer-list-search{padding:0 24px 0 8px}.ant-transfer-list-search-action{position:absolute;top:12px;right:12px;bottom:12px;width:28px;color:rgba(0,0,0,.25);line-height:32px;text-align:center}.ant-transfer-list-search-action .anticon{color:rgba(0,0,0,.25);transition:all .3s}.ant-transfer-list-search-action .anticon:hover{color:rgba(0,0,0,.45)}span.ant-transfer-list-search-action{pointer-events:none}.ant-transfer-list-header{position:absolute;top:0;left:0;width:100%;padding:8px 12px 9px;overflow:hidden;color:rgba(0,0,0,.65);background:#fff;border-bottom:1px solid #e8e8e8;border-radius:1rem 1rem 0 0}.ant-transfer-list-header-title{position:absolute;right:12px}.ant-transfer-list-header .ant-checkbox-wrapper+span{padding-left:8px}.ant-transfer-list-body{position:relative;height:100%;font-size:14px}.ant-transfer-list-body-search-wrapper{position:absolute;top:0;left:0;width:100%;padding:12px}.ant-transfer-list-body-with-search{padding-top:56px}.ant-transfer-list-content{height:100%;margin:0;padding:0;overflow:auto;list-style:none}.ant-transfer-list-content>.LazyLoad{animation:transferHighlightIn 1s}.ant-transfer-list-content-item{min-height:32px;padding:6px 12px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-transfer-list-content-item>span{padding-right:0}.ant-transfer-list-content-item-text{padding-left:8px}.ant-transfer-list-content-item:not(.ant-transfer-list-content-item-disabled):hover{background-color:#b3c7c0;cursor:pointer}.ant-transfer-list-content-item-disabled{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-transfer-list-body-not-found{position:absolute;top:50%;width:100%;padding-top:0;color:rgba(0,0,0,.25);text-align:center;transform:translateY(-50%)}.ant-transfer-list-body-with-search .ant-transfer-list-body-not-found{margin-top:16px}.ant-transfer-list-footer{position:absolute;bottom:0;left:0;width:100%;border-top:1px solid #e8e8e8;border-radius:0 0 1rem 1rem}.ant-transfer-operation{display:inline-block;margin:0 8px;overflow:hidden;vertical-align:middle}.ant-transfer-operation .ant-btn{display:block}.ant-transfer-operation .ant-btn:first-child{margin-bottom:4px}.ant-transfer-operation .ant-btn .anticon{font-size:12px}@keyframes transferHighlightIn{0%{background:#77baa6}to{background:transparent}}.ant-select-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-select-tree-checkbox-input:focus+.ant-select-tree-checkbox-inner,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-inner,.ant-select-tree-checkbox:hover .ant-select-tree-checkbox-inner{border-color:#008771}.ant-select-tree-checkbox-checked:after{position:absolute;top:0;left:0;width:100%;height:100%;border:1px solid #008771;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox:after,.ant-select-tree-checkbox:hover:after{visibility:visible}.ant-select-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-select-tree-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-select-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner{background-color:#008771;border-color:#008771}.ant-select-tree-checkbox-disabled{cursor:not-allowed}.ant-select-tree-checkbox-disabled.ant-select-tree-checkbox-checked .ant-select-tree-checkbox-inner:after{border-color:rgba(0,0,0,.25);animation-name:none}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-input{cursor:not-allowed}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-select-tree-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-select-tree-checkbox-disabled:hover:after,.ant-select-tree-checkbox-wrapper:hover .ant-select-tree-checkbox-disabled:after{visibility:hidden}.ant-select-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block;line-height:unset;cursor:pointer}.ant-select-tree-checkbox-wrapper.ant-select-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-select-tree-checkbox-wrapper+.ant-select-tree-checkbox-wrapper{margin-left:8px}.ant-select-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-select-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-select-tree-checkbox-group-item{display:inline-block;margin-right:8px}.ant-select-tree-checkbox-group-item:last-child{margin-right:0}.ant-select-tree-checkbox-group-item+.ant-select-tree-checkbox-group-item{margin-left:0}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-select-tree-checkbox-indeterminate .ant-select-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#008771;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-select-tree-checkbox-indeterminate.ant-select-tree-checkbox-disabled .ant-select-tree-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-select-tree{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";margin:-4px 0 0;padding:0 4px}.ant-select-tree li{margin:8px 0;padding:0;white-space:nowrap;list-style:none;outline:0}.ant-select-tree li.filter-node>span{font-weight:500}.ant-select-tree li ul{margin:0;padding:0 0 0 18px}.ant-select-tree li .ant-select-tree-node-content-wrapper{display:inline-block;width:calc(100% - 24px);margin:0;padding:3px 5px;color:rgba(0,0,0,.65);text-decoration:none;border-radius:2px;cursor:pointer;transition:all .3s}.ant-select-tree li .ant-select-tree-node-content-wrapper:hover{background-color:#b3c7c0}.ant-select-tree li .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected{background-color:#77baa6}.ant-select-tree li span.ant-select-tree-checkbox{margin:0 4px 0 0}.ant-select-tree li span.ant-select-tree-checkbox+.ant-select-tree-node-content-wrapper{width:calc(100% - 46px)}.ant-select-tree li span.ant-select-tree-iconEle,.ant-select-tree li span.ant-select-tree-switcher{display:inline-block;width:24px;height:24px;margin:0;line-height:22px;text-align:center;vertical-align:middle;border:0;outline:none;cursor:pointer}.ant-select-tree li span.ant-select-icon_loading .ant-select-switcher-loading-icon{position:absolute;left:0;display:inline-block;color:#008771;font-size:14px;transform:none}.ant-select-tree li span.ant-select-icon_loading .ant-select-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-select-tree li span.ant-select-tree-switcher{position:relative}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher-noop{cursor:auto}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon{font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon,:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon{font-size:12px}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-tree-switcher-icon svg{transition:transform .3s}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon{font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon,:root .ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon{font-size:12px}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-tree-switcher-icon svg{transition:transform .3s}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-icon svg{transform:rotate(-90deg)}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-loading-icon,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-loading-icon{position:absolute;left:0;display:inline-block;width:24px;height:24px;color:#008771;font-size:14px;transform:none}.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_close .ant-select-switcher-loading-icon svg,.ant-select-tree li span.ant-select-tree-switcher.ant-select-tree-switcher_open .ant-select-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}.ant-select-tree-child-tree,.ant-select-tree .ant-select-tree-treenode-loading .ant-select-tree-iconEle{display:none}.ant-select-tree-child-tree-open{display:block}li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper,li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper span,li.ant-select-tree-treenode-disabled>span:not(.ant-select-tree-switcher){color:rgba(0,0,0,.25);cursor:not-allowed}li.ant-select-tree-treenode-disabled>.ant-select-tree-node-content-wrapper:hover{background:transparent}.ant-select-tree-icon__close,.ant-select-tree-icon__open{margin-right:2px;vertical-align:top}.ant-select-tree-dropdown{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum"}.ant-select-tree-dropdown .ant-select-dropdown-search{position:sticky;top:0;z-index:1;display:block;padding:4px;background:#fff}.ant-select-tree-dropdown .ant-select-dropdown-search .ant-select-search__field__wrap{width:100%}.ant-select-tree-dropdown .ant-select-dropdown-search .ant-select-search__field{box-sizing:border-box;width:100%;padding:4px 7px;border:1px solid #d9d9d9;border-radius:4px;outline:none}.ant-select-tree-dropdown .ant-select-dropdown-search.ant-select-search--hide{display:none}.ant-select-tree-dropdown .ant-select-not-found{display:block;padding:7px 16px;color:rgba(0,0,0,.25);cursor:not-allowed}@keyframes antCheckboxEffect{0%{transform:scale(1);opacity:.5}to{transform:scale(1.6);opacity:0}}.ant-tree.ant-tree-directory{position:relative}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-switcher,.ant-tree.ant-tree-directory>li span.ant-tree-switcher{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-switcher.ant-tree-switcher-noop,.ant-tree.ant-tree-directory>li span.ant-tree-switcher.ant-tree-switcher-noop{pointer-events:none}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-checkbox,.ant-tree.ant-tree-directory>li span.ant-tree-checkbox{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper{border-radius:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:hover,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:hover:before,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:hover:before{background:#b3c7c0}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper.ant-tree-node-selected,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper.ant-tree-node-selected{color:#fff;background:transparent}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper:before,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper:before{position:absolute;right:0;left:0;height:24px;transition:all .3s;content:""}.ant-tree.ant-tree-directory .ant-tree-child-tree>li span.ant-tree-node-content-wrapper>span,.ant-tree.ant-tree-directory>li span.ant-tree-node-content-wrapper>span{position:relative;z-index:1}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-switcher,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-switcher{color:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox .ant-tree-checkbox-inner,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox .ant-tree-checkbox-inner{border-color:#008771}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked:after,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked:after{border-color:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background:#fff}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-checkbox.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:#008771}.ant-tree.ant-tree-directory .ant-tree-child-tree>li.ant-tree-treenode-selected>span.ant-tree-node-content-wrapper:before,.ant-tree.ant-tree-directory>li.ant-tree-treenode-selected>span.ant-tree-node-content-wrapper:before{background:#008771}.ant-tree-checkbox{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";position:relative;top:-.09em;display:inline-block;line-height:1;white-space:nowrap;vertical-align:middle;outline:none;cursor:pointer}.ant-tree-checkbox-input:focus+.ant-tree-checkbox-inner,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-inner,.ant-tree-checkbox:hover .ant-tree-checkbox-inner{border-color:#008771}.ant-tree-checkbox-checked:after{top:0;height:100%;border:1px solid #008771;border-radius:2px;visibility:hidden;animation:antCheckboxEffect .36s ease-in-out;animation-fill-mode:backwards;content:""}.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox:after,.ant-tree-checkbox:hover:after{visibility:visible}.ant-tree-checkbox-inner{position:relative;top:0;left:0;display:block;width:16px;height:16px;background-color:#fff;border:1px solid #d9d9d9;border-radius:2px;border-collapse:separate;transition:all .3s}.ant-tree-checkbox-inner:after{position:absolute;top:50%;left:22%;display:table;width:5.71428571px;height:9.14285714px;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(0) translate(-50%,-50%);opacity:0;transition:all .1s cubic-bezier(.71,-.46,.88,.6),opacity .1s;content:" "}.ant-tree-checkbox-input{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;width:100%;height:100%;cursor:pointer;opacity:0}.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{position:absolute;display:table;border:2px solid #fff;border-top:0;border-left:0;transform:rotate(45deg) scale(1) translate(-50%,-50%);opacity:1;transition:all .2s cubic-bezier(.12,.4,.29,1.46) .1s;content:" "}.ant-tree-checkbox-checked .ant-tree-checkbox-inner{background-color:#008771;border-color:#008771}.ant-tree-checkbox-disabled{cursor:not-allowed}.ant-tree-checkbox-disabled.ant-tree-checkbox-checked .ant-tree-checkbox-inner:after{border-color:rgba(0,0,0,.25);animation-name:none}.ant-tree-checkbox-disabled .ant-tree-checkbox-input{cursor:not-allowed}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner{background-color:#f5f5f5;border-color:#d9d9d9!important}.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{border-color:#f5f5f5;border-collapse:separate;animation-name:none}.ant-tree-checkbox-disabled+span{color:rgba(0,0,0,.25);cursor:not-allowed}.ant-tree-checkbox-disabled:hover:after,.ant-tree-checkbox-wrapper:hover .ant-tree-checkbox-disabled:after{visibility:hidden}.ant-tree-checkbox-wrapper{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block;line-height:unset;cursor:pointer}.ant-tree-checkbox-wrapper.ant-tree-checkbox-wrapper-disabled{cursor:not-allowed}.ant-tree-checkbox-wrapper+.ant-tree-checkbox-wrapper{margin-left:8px}.ant-tree-checkbox+span{padding-right:8px;padding-left:8px}.ant-tree-checkbox-group{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";display:inline-block}.ant-tree-checkbox-group-item{display:inline-block;margin-right:8px}.ant-tree-checkbox-group-item:last-child{margin-right:0}.ant-tree-checkbox-group-item+.ant-tree-checkbox-group-item{margin-left:0}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner{background-color:#fff;border-color:#d9d9d9}.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner:after{top:50%;left:50%;width:8px;height:8px;background-color:#008771;border:0;transform:translate(-50%,-50%) scale(1);opacity:1;content:" "}.ant-tree-checkbox-indeterminate.ant-tree-checkbox-disabled .ant-tree-checkbox-inner:after{background-color:rgba(0,0,0,.25);border-color:rgba(0,0,0,.25)}.ant-tree{box-sizing:border-box;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";margin:0;padding:0}.ant-tree-checkbox-checked:after{position:absolute;top:16.67%;left:0;width:100%;height:66.67%}.ant-tree ol,.ant-tree ul{margin:0;padding:0;list-style:none}.ant-tree li{margin:0;padding:4px 0;white-space:nowrap;list-style:none;outline:0}.ant-tree li span[draggable=true],.ant-tree li span[draggable]{line-height:20px;border-top:2px solid transparent;border-bottom:2px solid transparent;-webkit-user-select:none;-moz-user-select:none;user-select:none;-khtml-user-drag:element;-webkit-user-drag:element}.ant-tree li.drag-over>span[draggable]{color:#fff;background-color:#008771;opacity:.8}.ant-tree li.drag-over-gap-top>span[draggable]{border-top-color:#008771}.ant-tree li.drag-over-gap-bottom>span[draggable]{border-bottom-color:#008771}.ant-tree li.filter-node>span{color:#f5222d!important;font-weight:500!important}.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-loading-icon,.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-loading-icon{position:absolute;left:0;display:inline-block;width:24px;height:24px;color:#008771;font-size:14px;transform:none}.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-loading-icon svg,.ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-loading-icon svg{position:absolute;top:0;right:0;bottom:0;left:0;margin:auto}:root .ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_close:after,:root .ant-tree li.ant-tree-treenode-loading span.ant-tree-switcher.ant-tree-switcher_open:after{opacity:0}.ant-tree li ul{margin:0;padding:0 0 0 18px}.ant-tree li .ant-tree-node-content-wrapper{display:inline-block;height:24px;margin:0;padding:0 5px;color:rgba(0,0,0,.65);line-height:24px;text-decoration:none;vertical-align:top;border-radius:2px;cursor:pointer;transition:all .3s}.ant-tree li .ant-tree-node-content-wrapper:hover{background-color:#b3c7c0}.ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected{background-color:#77baa6}.ant-tree li span.ant-tree-checkbox{top:auto;height:24px;margin:0 4px 0 2px;padding:4px 0}.ant-tree li span.ant-tree-iconEle,.ant-tree li span.ant-tree-switcher{display:inline-block;width:24px;height:24px;margin:0;line-height:24px;text-align:center;vertical-align:top;border:0;outline:none;cursor:pointer}.ant-tree li span.ant-tree-iconEle:empty{display:none}.ant-tree li span.ant-tree-switcher{position:relative}.ant-tree li span.ant-tree-switcher.ant-tree-switcher-noop{cursor:default}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{font-size:12px}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon svg,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon svg{transition:transform .3s}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);display:inline-block;font-weight:700}:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,:root .ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{font-size:12px}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon svg,.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{transition:transform .3s}.ant-tree li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{transform:rotate(-90deg)}.ant-tree li:last-child>span.ant-tree-iconEle:before,.ant-tree li:last-child>span.ant-tree-switcher:before{display:none}.ant-tree>li:first-child{padding-top:7px}.ant-tree>li:last-child{padding-bottom:7px}.ant-tree-child-tree>li:first-child{padding-top:8px}.ant-tree-child-tree>li:last-child{padding-bottom:0}li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper,li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper span,li.ant-tree-treenode-disabled>span:not(.ant-tree-switcher){color:rgba(0,0,0,.25);cursor:not-allowed}li.ant-tree-treenode-disabled>.ant-tree-node-content-wrapper:hover{background:transparent}.ant-tree-icon__close,.ant-tree-icon__open{margin-right:2px;vertical-align:top}.ant-tree.ant-tree-show-line li{position:relative}.ant-tree.ant-tree-show-line li span.ant-tree-switcher{color:rgba(0,0,0,.45);background:#fff}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher-noop .ant-tree-switcher-icon svg{transition:transform .3s}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_open .ant-tree-switcher-icon svg{transition:transform .3s}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon{display:inline-block;font-weight:400;font-size:12px}.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-select-switcher-icon svg,.ant-tree.ant-tree-show-line li span.ant-tree-switcher.ant-tree-switcher_close .ant-tree-switcher-icon svg{transition:transform .3s}.ant-tree.ant-tree-show-line li:not(:last-child):before{position:absolute;left:12px;width:1px;height:100%;height:calc(100% - 22px);margin:22px 0 0;border-left:1px solid #d9d9d9;content:" "}.ant-tree.ant-tree-icon-hide .ant-tree-treenode-loading .ant-tree-iconEle{display:none}.ant-tree.ant-tree-block-node li .ant-tree-node-content-wrapper{width:calc(100% - 24px)}.ant-tree.ant-tree-block-node li span.ant-tree-checkbox+.ant-tree-node-content-wrapper{width:calc(100% - 46px)}.ant-upload{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";outline:0}.ant-upload p{margin:0}.ant-upload-btn{display:block;width:100%;outline:none}.ant-upload input[type=file]{cursor:pointer}.ant-upload.ant-upload-select{display:inline-block}.ant-upload.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-select-picture-card{display:table;float:left;width:104px;height:104px;margin-right:8px;margin-bottom:8px;text-align:center;vertical-align:top;background-color:#fafafa;border:1px dashed #d9d9d9;border-radius:1rem;cursor:pointer;transition:border-color .3s ease}.ant-upload.ant-upload-select-picture-card>.ant-upload{display:table-cell;width:100%;height:100%;padding:8px;text-align:center;vertical-align:middle}.ant-upload.ant-upload-select-picture-card:hover{border-color:#008771}.ant-upload.ant-upload-drag{position:relative;width:100%;height:100%;text-align:center;background:#fafafa;border:1px dashed #d9d9d9;border-radius:1rem;cursor:pointer;transition:border-color .3s}.ant-upload.ant-upload-drag .ant-upload{padding:16px 0}.ant-upload.ant-upload-drag.ant-upload-drag-hover:not(.ant-upload-disabled){border-color:#006154}.ant-upload.ant-upload-drag.ant-upload-disabled{cursor:not-allowed}.ant-upload.ant-upload-drag .ant-upload-btn{display:table;height:100%}.ant-upload.ant-upload-drag .ant-upload-drag-container{display:table-cell;vertical-align:middle}.ant-upload.ant-upload-drag:not(.ant-upload-disabled):hover{border-color:#18947b}.ant-upload.ant-upload-drag p.ant-upload-drag-icon{margin-bottom:20px}.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon{color:#18947b;font-size:48px}.ant-upload.ant-upload-drag p.ant-upload-text{margin:0 0 4px;color:rgba(0,0,0,.85);font-size:16px}.ant-upload.ant-upload-drag p.ant-upload-hint{color:rgba(0,0,0,.45);font-size:14px}.ant-upload.ant-upload-drag .anticon-plus{color:rgba(0,0,0,.25);font-size:30px;transition:all .3s}.ant-upload.ant-upload-drag .anticon-plus:hover,.ant-upload.ant-upload-drag:hover .anticon-plus{color:rgba(0,0,0,.45)}.ant-upload-picture-card-wrapper{zoom:1;display:inline-block;width:100%}.ant-upload-picture-card-wrapper:after,.ant-upload-picture-card-wrapper:before{display:table;content:""}.ant-upload-picture-card-wrapper:after{clear:both}.ant-upload-list{box-sizing:border-box;margin:0;padding:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5;list-style:none;font-feature-settings:"tnum";zoom:1}.ant-upload-list:after,.ant-upload-list:before{display:table;content:""}.ant-upload-list:after{clear:both}.ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-1{padding-right:14px}.ant-upload-list-item-list-type-text:hover .ant-upload-list-item-name-icon-count-2{padding-right:28px}.ant-upload-list-item{position:relative;height:22px;margin-top:8px;font-size:14px}.ant-upload-list-item-name{display:inline-block;width:100%;padding-left:22px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ant-upload-list-item-name-icon-count-1{padding-right:14px}.ant-upload-list-item-card-actions{position:absolute;right:0;opacity:0}.ant-upload-list-item-card-actions.picture{top:25px;line-height:1;opacity:1}.ant-upload-list-item-card-actions .anticon{padding-right:6px;color:rgba(0,0,0,.45)}.ant-upload-list-item-info{height:100%;padding:0 12px 0 4px;transition:background-color .3s}.ant-upload-list-item-info>span{display:block;width:100%;height:100%}.ant-upload-list-item-info .anticon-loading,.ant-upload-list-item-info .anticon-paper-clip{position:absolute;top:5px;color:rgba(0,0,0,.45);font-size:14px}.ant-upload-list-item .anticon-close{display:inline-block;font-size:12px;font-size:10px\9;transform:scale(.83333333) rotate(0deg);position:absolute;top:6px;right:4px;color:rgba(0,0,0,.45);line-height:0;cursor:pointer;opacity:0;transition:all .3s}:root .ant-upload-list-item .anticon-close{font-size:12px}.ant-upload-list-item .anticon-close:hover{color:rgba(0,0,0,.65)}.ant-upload-list-item:hover .ant-upload-list-item-info{background-color:#b3c7c0}.ant-upload-list-item:hover .ant-upload-list-item-card-actions,.ant-upload-list-item:hover .anticon-close{opacity:1}.ant-upload-list-item-error,.ant-upload-list-item-error .ant-upload-list-item-name,.ant-upload-list-item-error .anticon-paper-clip{color:#f5222d}.ant-upload-list-item-error .ant-upload-list-item-card-actions{opacity:1}.ant-upload-list-item-error .ant-upload-list-item-card-actions .anticon{color:#f5222d}.ant-upload-list-item-progress{position:absolute;bottom:-12px;width:100%;padding-left:26px;font-size:14px;line-height:0}.ant-upload-list-picture-card .ant-upload-list-item,.ant-upload-list-picture .ant-upload-list-item{position:relative;height:66px;padding:8px;border:1px solid #d9d9d9;border-radius:1rem}.ant-upload-list-picture-card .ant-upload-list-item:hover,.ant-upload-list-picture .ant-upload-list-item:hover{background:transparent}.ant-upload-list-picture-card .ant-upload-list-item-error,.ant-upload-list-picture .ant-upload-list-item-error{border-color:#f5222d}.ant-upload-list-picture-card .ant-upload-list-item-info,.ant-upload-list-picture .ant-upload-list-item-info{padding:0}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info,.ant-upload-list-picture .ant-upload-list-item:hover .ant-upload-list-item-info{background:transparent}.ant-upload-list-picture-card .ant-upload-list-item-uploading,.ant-upload-list-picture .ant-upload-list-item-uploading{border-style:dashed}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture .ant-upload-list-item-thumbnail{position:absolute;top:8px;left:8px;width:48px;height:48px;font-size:26px;line-height:54px;text-align:center;opacity:.8}.ant-upload-list-picture-card .ant-upload-list-item-icon,.ant-upload-list-picture .ant-upload-list-item-icon{position:absolute;top:50%;left:50%;font-size:26px;transform:translate(-50%,-50%)}.ant-upload-list-picture-card .ant-upload-list-item-image,.ant-upload-list-picture .ant-upload-list-item-image{max-width:100%}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img,.ant-upload-list-picture .ant-upload-list-item-thumbnail img{display:block;width:48px;height:48px;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-name{display:inline-block;box-sizing:border-box;max-width:100%;margin:0 0 0 8px;padding-right:8px;padding-left:48px;overflow:hidden;line-height:44px;white-space:nowrap;text-overflow:ellipsis;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-1,.ant-upload-list-picture .ant-upload-list-item-name-icon-count-1{padding-right:18px}.ant-upload-list-picture-card .ant-upload-list-item-name-icon-count-2,.ant-upload-list-picture .ant-upload-list-item-name-icon-count-2{padding-right:36px}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-name,.ant-upload-list-picture .ant-upload-list-item-uploading .ant-upload-list-item-name{line-height:28px}.ant-upload-list-picture-card .ant-upload-list-item-progress,.ant-upload-list-picture .ant-upload-list-item-progress{bottom:14px;width:calc(100% - 24px);margin-top:0;padding-left:56px}.ant-upload-list-picture-card .anticon-close,.ant-upload-list-picture .anticon-close{position:absolute;top:8px;right:8px;line-height:1;opacity:1}.ant-upload-list-picture-card.ant-upload-list:after{display:none}.ant-upload-list-picture-card-container,.ant-upload-list-picture-card .ant-upload-list-item{float:left;width:104px;height:104px;margin:0 8px 8px 0}.ant-upload-list-picture-card .ant-upload-list-item-info{position:relative;height:100%;overflow:hidden}.ant-upload-list-picture-card .ant-upload-list-item-info:before{position:absolute;z-index:1;width:100%;height:100%;background-color:rgba(0,0,0,.5);opacity:0;transition:all .3s;content:" "}.ant-upload-list-picture-card .ant-upload-list-item:hover .ant-upload-list-item-info:before{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-actions{position:absolute;top:50%;left:50%;z-index:10;white-space:nowrap;transform:translate(-50%,-50%);opacity:0;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye-o{z-index:10;width:16px;margin:0 4px;color:hsla(0,0%,100%,.85);font-size:16px;cursor:pointer;transition:all .3s}.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-delete:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-download:hover,.ant-upload-list-picture-card .ant-upload-list-item-actions .anticon-eye-o:hover{color:#fff}.ant-upload-list-picture-card .ant-upload-list-item-actions:hover,.ant-upload-list-picture-card .ant-upload-list-item-info:hover+.ant-upload-list-item-actions{opacity:1}.ant-upload-list-picture-card .ant-upload-list-item-thumbnail,.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img{position:static;display:block;width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.ant-upload-list-picture-card .ant-upload-list-item-name{display:none;margin:8px 0 0;padding:0;line-height:1.5;text-align:center}.ant-upload-list-picture-card .anticon-picture+.ant-upload-list-item-name{position:absolute;bottom:10px;display:block}.ant-upload-list-picture-card .ant-upload-list-item-uploading.ant-upload-list-item{background-color:#fafafa}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info{height:auto}.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-delete,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info .anticon-eye-o,.ant-upload-list-picture-card .ant-upload-list-item-uploading .ant-upload-list-item-info:before{display:none}.ant-upload-list-picture-card .ant-upload-list-item-uploading-text{margin-top:18px;color:rgba(0,0,0,.45)}.ant-upload-list-picture-card .ant-upload-list-item-progress{bottom:32px;padding-left:0}.ant-upload-list .ant-upload-success-icon{color:#008771;font-weight:700}.ant-upload-list .ant-upload-animate-enter,.ant-upload-list .ant-upload-animate-inline-enter,.ant-upload-list .ant-upload-animate-inline-leave,.ant-upload-list .ant-upload-animate-leave{animation-duration:.3s;animation-fill-mode:cubic-bezier(.78,.14,.15,.86)}.ant-upload-list .ant-upload-animate-enter{animation-name:uploadAnimateIn}.ant-upload-list .ant-upload-animate-leave{animation-name:uploadAnimateOut}.ant-upload-list .ant-upload-animate-inline-enter{animation-name:uploadAnimateInlineIn}.ant-upload-list .ant-upload-animate-inline-leave{animation-name:uploadAnimateInlineOut}@keyframes uploadAnimateIn{0%{height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateOut{to{height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineIn{0%{width:0;height:0;margin:0;padding:0;opacity:0}}@keyframes uploadAnimateInlineOut{to{width:0;height:0;margin:0;padding:0;opacity:0}} \ No newline at end of file diff --git a/web/assets/ant-design-vue/antd.min.js b/web/assets/ant-design-vue/antd.min.js deleted file mode 100644 index af5586d5..00000000 --- a/web/assets/ant-design-vue/antd.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see antd.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("moment"),require("vue")):"function"==typeof define&&define.amd?define(["moment","vue"],t):"object"==typeof exports?exports.antd=t(require("moment"),require("vue")):e.antd=t(e.moment,e.Vue)}(window,(function(e,t){return function(e){var t={};function n(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(i,r,function(t){return e[t]}.bind(null,r));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=435)}([function(e,t,n){"use strict";var i=n(14),r=n.n(i),o=n(35),a=n.n(o),s=Object.prototype,c=s.toString,l=s.hasOwnProperty,u=/^\s*function (\w+)/,h=function(e){var t=null!=e?e.type?e.type:e:null,n=t&&t.toString().match(u);return n&&n[1]},d=function(e){if(null==e)return null;var t=e.constructor.toString().match(u);return t&&t[1]},f=Number.isInteger||function(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e},p=Array.isArray||function(e){return"[object Array]"===c.call(e)},v=function(e){return"[object Function]"===c.call(e)},m=function(e,t){var n;return Object.defineProperty(t,"_vueTypes_name",{enumerable:!1,writable:!1,value:e}),n=t,Object.defineProperty(n,"isRequired",{get:function(){return this.required=!0,this},enumerable:!1}),function(e){Object.defineProperty(e,"def",{value:function(e){return void 0===e&&void 0===this.default?(this.default=void 0,this):v(e)||g(this,e)?(this.default=p(e)||a()(e)?function(){return e}:e,this):(b(this._vueTypes_name+' - invalid default value: "'+e+'"',e),this)},enumerable:!1,writable:!1})}(t),v(t.validator)&&(t.validator=t.validator.bind(t)),t},g=function e(t,n){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=t,o=!0,s=void 0;a()(t)||(r={type:t});var c=r._vueTypes_name?r._vueTypes_name+" - ":"";return l.call(r,"type")&&null!==r.type&&(p(r.type)?(o=r.type.some((function(t){return e(t,n,!0)})),s=r.type.map((function(e){return h(e)})).join(" or ")):o="Array"===(s=h(r))?p(n):"Object"===s?a()(n):"String"===s||"Number"===s||"Boolean"===s||"Function"===s?d(n)===s:n instanceof r.type),o?l.call(r,"validator")&&v(r.validator)?((o=r.validator(n))||!1!==i||b(c+"custom validation failed"),o):o:(!1===i&&b(c+'value "'+n+'" should be of type "'+s+'"'),!1)},b=function(){},y={get any(){return m("any",{type:null})},get func(){return m("function",{type:Function}).def(C.func)},get bool(){return m("boolean",{type:Boolean}).def(C.bool)},get string(){return m("string",{type:String}).def(C.string)},get number(){return m("number",{type:Number}).def(C.number)},get array(){return m("array",{type:Array}).def(C.array)},get object(){return m("object",{type:Object}).def(C.object)},get integer(){return m("integer",{type:Number,validator:function(e){return f(e)}}).def(C.integer)},get symbol(){return m("symbol",{type:null,validator:function(e){return"symbol"===(void 0===e?"undefined":r()(e))}})},custom:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"custom validation failed";if("function"!=typeof e)throw new TypeError("[VueTypes error]: You must provide a function as argument");return m(e.name||"<>",{validator:function(){var n=e.apply(void 0,arguments);return n||b(this._vueTypes_name+" - "+t),n}})},oneOf:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t='oneOf - value should be one of "'+e.join('", "')+'"',n=e.reduce((function(e,t){return null!=t&&-1===e.indexOf(t.constructor)&&e.push(t.constructor),e}),[]);return m("oneOf",{type:n.length>0?n:null,validator:function(n){var i=-1!==e.indexOf(n);return i||b(t),i}})},instanceOf:function(e){return m("instanceOf",{type:e})},oneOfType:function(e){if(!p(e))throw new TypeError("[VueTypes error]: You must provide an array as argument");var t=!1,n=e.reduce((function(e,n){if(a()(n)){if("oneOf"===n._vueTypes_name)return e.concat(n.type||[]);if(n.type&&!v(n.validator)){if(p(n.type))return e.concat(n.type);e.push(n.type)}else v(n.validator)&&(t=!0);return e}return e.push(n),e}),[]);if(!t)return m("oneOfType",{type:n}).def(void 0);var i=e.map((function(e){return e&&p(e.type)?e.type.map(h):h(e)})).reduce((function(e,t){return e.concat(p(t)?t:[t])}),[]).join('", "');return this.custom((function(t){var n=e.some((function(e){return"oneOf"===e._vueTypes_name?!e.type||g(e.type,t,!0):g(e,t,!0)}));return n||b('oneOfType - value type should be one of "'+i+'"'),n})).def(void 0)},arrayOf:function(e){return m("arrayOf",{type:Array,validator:function(t){var n=t.every((function(t){return g(e,t)}));return n||b('arrayOf - value must be an array of "'+h(e)+'"'),n}})},objectOf:function(e){return m("objectOf",{type:Object,validator:function(t){var n=Object.keys(t).every((function(n){return g(e,t[n])}));return n||b('objectOf - value must be an object of "'+h(e)+'"'),n}})},shape:function(e){var t=Object.keys(e),n=t.filter((function(t){return e[t]&&!0===e[t].required})),i=m("shape",{type:Object,validator:function(i){var r=this;if(!a()(i))return!1;var o=Object.keys(i);return n.length>0&&n.some((function(e){return-1===o.indexOf(e)}))?(b('shape - at least one of required properties "'+n.join('", "')+'" is not present'),!1):o.every((function(n){if(-1===t.indexOf(n))return!0===r._vueTypes_isLoose||(b('shape - object is missing "'+n+'" property'),!1);var o=e[n];return g(o,i[n])}))}});return Object.defineProperty(i,"_vueTypes_isLoose",{enumerable:!1,writable:!0,value:!1}),Object.defineProperty(i,"loose",{get:function(){return this._vueTypes_isLoose=!0,this},enumerable:!1}),i}},C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0};Object.defineProperty(y,"sensibleDefaults",{enumerable:!1,set:function(e){!1===e?C={}:!0===e?C={func:void 0,bool:void 0,string:void 0,number:void 0,array:void 0,object:void 0,integer:void 0}:a()(e)&&(C=e)},get:function(){return C}});t.a=y},function(e,t,n){"use strict";n.d(t,"i",(function(){return V})),n.d(t,"h",(function(){return T})),n.d(t,"k",(function(){return H})),n.d(t,"f",(function(){return j})),n.d(t,"q",(function(){return P})),n.d(t,"u",(function(){return L})),n.d(t,"v",(function(){return _})),n.d(t,"c",(function(){return $})),n.d(t,"x",(function(){return A})),n.d(t,"s",(function(){return m})),n.d(t,"l",(function(){return z})),n.d(t,"g",(function(){return w})),n.d(t,"o",(function(){return x})),n.d(t,"m",(function(){return O})),n.d(t,"j",(function(){return k})),n.d(t,"e",(function(){return M})),n.d(t,"r",(function(){return S})),n.d(t,"y",(function(){return v})),n.d(t,"t",(function(){return E})),n.d(t,"w",(function(){return F})),n.d(t,"a",(function(){return p})),n.d(t,"p",(function(){return b})),n.d(t,"n",(function(){return y})),n.d(t,"d",(function(){return C}));var i=n(14),r=n.n(i),o=n(22),a=n.n(o),s=n(2),c=n.n(s),l=n(35),u=n.n(l),h=n(5),d=n.n(h);var f=/-(\w)/g,p=function(e){return e.replace(f,(function(e,t){return t?t.toUpperCase():""}))},v=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments[1],n={},i=/;(?![^(]*\))/g,r=/:(.+)/;return e.split(i).forEach((function(e){if(e){var i=e.split(r);if(i.length>1){var o=t?p(i[0].trim()):i[0].trim();n[o]=i[1].trim()}}})),n},m=function(e,t){return t in((e.$options||{}).propsData||{})},g=function(e){return e.data&&e.data.scopedSlots||{}},b=function(e){var t=e.componentOptions||{};e.$vnode&&(t=e.$vnode.componentOptions||{});var n=e.children||t.children||[],i={};return n.forEach((function(e){if(!L(e)){var t=e.data&&e.data.slot||"default";i[t]=i[t]||[],i[t].push(e)}})),c()({},i,g(e))},y=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"default",n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e.$scopedSlots&&e.$scopedSlots[t]&&e.$scopedSlots[t](n)||e.$slots[t]||[]},C=function(e){var t=e.componentOptions||{};return e.$vnode&&(t=e.$vnode.componentOptions||{}),e.children||t.children||[]},x=function(e){if(e.fnOptions)return e.fnOptions;var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.Ctor.options||{}},z=function(e){if(e.componentOptions){var t=e.componentOptions,n=t.propsData,i=void 0===n?{}:n,r=t.Ctor,o=((void 0===r?{}:r).options||{}).props||{},s={},l=!0,u=!1,h=void 0;try{for(var d,f=Object.entries(o)[Symbol.iterator]();!(l=(d=f.next()).done);l=!0){var p=d.value,v=a()(p,2),m=v[0],g=v[1],b=g.default;void 0!==b&&(s[m]="function"==typeof b&&"Function"!==(y=g.type,C=void 0,(C=y&&y.toString().match(/^\s*function (\w+)/))?C[1]:"")?b.call(e):b)}}catch(e){u=!0,h=e}finally{try{!l&&f.return&&f.return()}finally{if(u)throw h}}return c()({},s,i)}var y,C,x=e.$options,z=void 0===x?{}:x,w=e.$props;return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={};return Object.keys(e).forEach((function(i){(i in t||void 0!==e[i])&&(n[i]=e[i])})),n}(void 0===w?{}:w,z.propsData)},w=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:e,i=!(arguments.length>3&&void 0!==arguments[3])||arguments[3];if(e.$createElement){var r=e.$createElement,o=e[t];return void 0!==o?"function"==typeof o&&i?o(r,n):o:e.$scopedSlots[t]&&i&&e.$scopedSlots[t](n)||e.$scopedSlots[t]||e.$slots[t]||void 0}var a=e.context.$createElement,s=O(e)[t];if(void 0!==s)return"function"==typeof s&&i?s(a,n):s;var c=g(e)[t];if(void 0!==c)return"function"==typeof c&&i?c(a,n):c;var l=[],u=e.componentOptions||{};return(u.children||[]).forEach((function(e){e.data&&e.data.slot===t&&(e.data.attrs&&delete e.data.attrs.slot,"template"===e.tag?l.push(e.children):l.push(e))})),l.length?l:void 0},O=function(e){var t=e.componentOptions;return e.$vnode&&(t=e.$vnode.componentOptions),t&&t.propsData||{}},S=function(e,t){return O(e)[t]},M=function(e){var t=e.data;return e.$vnode&&(t=e.$vnode.data),t&&t.attrs||{}},k=function(e){var t=e.key;return e.$vnode&&(t=e.$vnode.key),t};function V(e){var t={};return e.componentOptions&&e.componentOptions.listeners?t=e.componentOptions.listeners:e.data&&e.data.on&&(t=e.data.on),c()({},t)}function T(e){var t={};return e.data&&e.data.on&&(t=e.data.on),c()({},t)}function H(e){return(e.$vnode?e.$vnode.componentOptions.listeners:e.$listeners)||{}}function j(e){var t={};e.data?t=e.data:e.$vnode&&e.$vnode.data&&(t=e.$vnode.data);var n=t.class||{},i=t.staticClass,r={};return i&&i.split(" ").forEach((function(e){r[e.trim()]=!0})),"string"==typeof n?n.split(" ").forEach((function(e){r[e.trim()]=!0})):Array.isArray(n)?d()(n).split(" ").forEach((function(e){r[e.trim()]=!0})):r=c()({},r,n),r}function P(e,t){var n={};e.data?n=e.data:e.$vnode&&e.$vnode.data&&(n=e.$vnode.data);var i=n.style||n.staticStyle;if("string"==typeof i)i=v(i,t);else if(t&&i){var r={};return Object.keys(i).forEach((function(e){return r[p(e)]=i[e]})),r}return i}function L(e){return!(e.tag||e.text&&""!==e.text.trim())}function _(e){return!e.tag}function $(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter((function(e){return!L(e)}))}var E=function(e,t){return Object.keys(t).forEach((function(n){if(!e[n])throw new Error("not have "+n+" prop");e[n].def&&(e[n]=e[n].def(t[n]))})),e};function A(){var e=[].slice.call(arguments,0),t={};return e.forEach((function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=!0,i=!1,r=void 0;try{for(var o,s=Object.entries(e)[Symbol.iterator]();!(n=(o=s.next()).done);n=!0){var l=o.value,h=a()(l,2),d=h[0],f=h[1];t[d]=t[d]||{},u()(f)?c()(t[d],f):t[d]=f}}catch(e){i=!0,r=e}finally{try{!n&&s.return&&s.return()}finally{if(i)throw r}}})),t}function F(e){return e&&"object"===(void 0===e?"undefined":r()(e))&&"componentOptions"in e&&"context"in e&&void 0!==e.tag}t.b=m},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(245),o=(i=r)&&i.__esModule?i:{default:i};t.default=o.default||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n="function"==typeof e?e(this.$data,this.$props):e;if(this.getDerivedStateFromProps){var i=this.getDerivedStateFromProps(Object(s.l)(this),a()({},this.$data,n));if(null===i)return;n=a()({},n,i||{})}a()(this.$data,n),this.$forceUpdate(),this.$nextTick((function(){t&&t()}))},__emit:function(){var e=[].slice.call(arguments,0),t=e[0],n=this.$listeners[t];if(e.length&&n)if(Array.isArray(n))for(var i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:v;if(e){var n=m.definitions.get(e);return n&&"function"==typeof n.icon&&(n=a()({},n,{icon:n.icon(t.primaryColor,t.secondaryColor)})),n}},setTwoToneColors:function(e){var t=e.primaryColor,n=e.secondaryColor;v.primaryColor=t,v.secondaryColor=n||Object(p.c)(t)},getTwoToneColors:function(){return a()({},v)},render:function(e){var t=this.$props,n=t.type,i=t.primaryColor,r=t.secondaryColor,o=void 0,s=v;if(i&&(s={primaryColor:i,secondaryColor:r||Object(p.c)(i)}),Object(p.d)(n))o=n;else if("string"==typeof n&&!(o=m.get(n,s)))return null;return o?(o&&"function"==typeof o.icon&&(o=a()({},o,{icon:o.icon(s.primaryColor,s.secondaryColor)})),Object(p.b)(e,o.icon,"svg-"+o.name,{attrs:{"data-icon":o.name,width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true"},on:this.$listeners})):(Object(p.e)("type should be string or icon definiton, but got "+n),null)},install:function(e){e.component(m.name,m)}},g=m,b=n(0),y=n(12),C=n.n(y),x=n(1),z=new Set;var w=n(13),O={width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true",focusable:"false"},S=/-fill$/,M=/-o$/,k=/-twotone$/;var V=n(26);function T(e){return g.setTwoToneColors({primaryColor:e})}var H=n(10);g.add.apply(g,u()(Object.keys(f).filter((function(e){return"default"!==e})).map((function(e){return f[e]})))),T("#1890ff");function j(e,t,n){var i,o=n.$props,s=n.$slots,l=Object(x.k)(n),u=o.type,h=o.component,f=o.viewBox,p=o.spin,v=o.theme,m=o.twoToneColor,b=o.rotate,y=o.tabIndex,C=Object(x.c)(s.default);C=0===C.length?void 0:C,Object(w.a)(Boolean(u||h||C),"Icon","Icon should have `type` prop or `component` prop or `children`.");var z=d()((i={},c()(i,"anticon",!0),c()(i,"anticon-"+u,!!u),i)),V=d()(c()({},"anticon-spin",!!p||"loading"===u)),T=b?{msTransform:"rotate("+b+"deg)",transform:"rotate("+b+"deg)"}:void 0,H={attrs:a()({},O,{viewBox:f}),class:V,style:T};f||delete H.attrs.viewBox;var j=y;void 0===j&&"click"in l&&(j=-1);var P={attrs:{"aria-label":u&&t.icon+": "+u,tabIndex:j},on:l,class:z,staticClass:""};return e("i",P,[function(){if(h)return e(h,H,[C]);if(C){Object(w.a)(Boolean(f)||1===C.length&&"use"===C[0].tag,"Icon","Make sure that you provide correct `viewBox` prop (default `0 0 1024 1024`) to the icon.");var t={attrs:a()({},O),class:V,style:T};return e("svg",r()([t,{attrs:{viewBox:f}}]),[C])}if("string"==typeof u){var n=u;if(v){var i=function(e){var t=null;return S.test(e)?t="filled":M.test(e)?t="outlined":k.test(e)&&(t="twoTone"),t}(u);Object(w.a)(!i||v===i,"Icon","The icon name '"+u+"' already specify a theme '"+i+"', the 'theme' prop '"+v+"' will be ignored.")}return n=function(e,t){var n=e;return"filled"===t?n+="-fill":"outlined"===t?n+="-o":"twoTone"===t?n+="-twotone":Object(w.a)(!1,"Icon","This icon '"+e+"' has unknown theme '"+t+"'"),n}(function(e){return e.replace(S,"").replace(M,"").replace(k,"")}(function(e){var t=e;switch(e){case"cross":t="close";break;case"interation":t="interaction";break;case"canlendar":t="calendar";break;case"colum-height":t="column-height"}return Object(w.a)(t===e,"Icon","Icon '"+e+"' was a typo and is now deprecated, please use '"+t+"' instead."),t}(n)),v||"outlined"),e(g,{attrs:{focusable:"false",type:n,primaryColor:m},class:V,style:T})}}()])}var P={name:"AIcon",props:{tabIndex:b.a.number,type:b.a.string,component:b.a.any,viewBox:b.a.any,spin:b.a.bool.def(!1),rotate:b.a.number,theme:b.a.oneOf(["filled","outlined","twoTone"]),twoToneColor:b.a.string,role:b.a.string},render:function(e){var t=this;return e(V.a,{attrs:{componentName:"Icon"},scopedSlots:{default:function(n){return j(e,n,t)}}})},createFromIconfontCN:function(e){var t=e.scriptUrl,n=e.extraCommonProps,i=void 0===n?{}:n;if("undefined"!=typeof document&&"undefined"!=typeof window&&"function"==typeof document.createElement&&"string"==typeof t&&t.length&&!z.has(t)){var r=document.createElement("script");r.setAttribute("src",t),r.setAttribute("data-namespace",t),z.add(t),document.body.appendChild(r)}return{functional:!0,name:"AIconfont",props:L.props,render:function(e,t){var n=t.props,r=t.slots,o=t.listeners,a=t.data,s=n.type,c=C()(n,["type"]),l=r().default,u=null;s&&(u=e("use",{attrs:{"xlink:href":"#"+s}})),l&&(u=l);var h=Object(x.x)(i,a,{props:c,on:o});return e(L,h,[u])}}},getTwoToneColor:function(){return g.getTwoToneColors().primaryColor}};P.setTwoToneColor=T,P.install=function(e){e.use(H.a),e.component(P.name,P)};var L=t.a=P},function(e,t,n){"use strict";n.d(t,"b",(function(){return h})),n.d(t,"a",(function(){return d}));var i=n(11),r=n.n(i),o=n(2),a=n.n(o),s=n(1),c=n(5),l=n.n(c);function u(e,t){var n=e.componentOptions,i=e.data,r={};n&&n.listeners&&(r=a()({},n.listeners));var o={};i&&i.on&&(o=a()({},i.on));var s=new e.constructor(e.tag,i?a()({},i,{on:o}):i,e.children,e.text,e.elm,e.context,n?a()({},n,{listeners:r}):n,e.asyncFactory);return s.ns=e.ns,s.isStatic=e.isStatic,s.key=e.key,s.isComment=e.isComment,s.fnContext=e.fnContext,s.fnOptions=e.fnOptions,s.fnScopeId=e.fnScopeId,s.isCloned=!0,t&&(e.children&&(s.children=h(e.children,!0)),n&&n.children&&(n.children=h(n.children,!0))),s}function h(e,t){for(var n=e.length,i=new Array(n),r=0;r1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],i=e;if(Array.isArray(e)&&(i=Object(s.c)(e)[0]),!i)return null;var o=u(i,n),c=t.props,h=void 0===c?{}:c,d=t.key,f=t.on,p=void 0===f?{}:f,v=t.nativeOn,m=void 0===v?{}:v,g=t.children,b=t.directives,y=void 0===b?[]:b,C=o.data||{},x={},z={},w=t.attrs,O=void 0===w?{}:w,S=t.ref,M=t.domProps,k=void 0===M?{}:M,V=t.style,T=void 0===V?{}:V,H=t.class,j=void 0===H?{}:H,P=t.scopedSlots,L=void 0===P?{}:P;return z="string"==typeof C.style?Object(s.y)(C.style):a()({},C.style,z),z="string"==typeof T?a()({},z,Object(s.y)(z)):a()({},z,T),"string"==typeof C.class&&""!==C.class.trim()?C.class.split(" ").forEach((function(e){x[e.trim()]=!0})):Array.isArray(C.class)?l()(C.class).split(" ").forEach((function(e){x[e.trim()]=!0})):x=a()({},C.class,x),"string"==typeof j&&""!==j.trim()?j.split(" ").forEach((function(e){x[e.trim()]=!0})):x=a()({},x,j),o.data=a()({},C,{style:z,attrs:a()({},C.attrs,O),class:x,domProps:a()({},C.domProps,k),scopedSlots:a()({},C.scopedSlots,L),directives:[].concat(r()(C.directives||[]),r()(y))}),o.componentOptions?(o.componentOptions.propsData=o.componentOptions.propsData||{},o.componentOptions.listeners=o.componentOptions.listeners||{},o.componentOptions.propsData=a()({},o.componentOptions.propsData,h),o.componentOptions.listeners=a()({},o.componentOptions.listeners,p),g&&(o.componentOptions.children=g)):(g&&(o.children=g),o.data.on=a()({},o.data.on||{},p)),o.data.on=a()({},o.data.on||{},m),void 0!==d&&(o.key=d,o.data.key=d),"string"==typeof S&&(o.data.ref=S),o}},function(e,t,n){"use strict";var i=n(25),r=n.n(i),o=n(112),a=n(77);function s(e){return e.directive("ant-portal",{inserted:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)},componentUpdated:function(e,t){var n=t.value,i="function"==typeof n?n(e):n;i!==e.parentNode&&i.appendChild(e)}})}var c={install:function(e){e.use(r.a,{name:"ant-ref"}),Object(o.a)(e),Object(a.a)(e),s(e)}},l={};l.install=function(e){l.Vue=e,e.use(c)};t.a=l},function(e,t,n){"use strict";t.__esModule=!0;var i,r=n(274),o=(i=r)&&i.__esModule?i:{default:i};t.default=function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t=0||Object.prototype.hasOwnProperty.call(e,i)&&(n[i]=e[i]);return n}},function(e,t,n){"use strict";var i={};function r(e,t){0}function o(e,t,n){t||i[n]||(e(!1,n),i[n]=!0)}var a=function(e,t){o(r,e,t)};t.a=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"";a(e,"[antdv: "+t+"] "+n)}},function(e,t,n){"use strict";t.__esModule=!0;var i=a(n(251)),r=a(n(261)),o="function"==typeof r.default&&"symbol"==typeof i.default?function(e){return typeof e}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":typeof e};function a(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof r.default&&"symbol"===o(i.default)?function(e){return void 0===e?"undefined":o(e)}:function(e){return e&&"function"==typeof r.default&&e.constructor===r.default&&e!==r.default.prototype?"symbol":void 0===e?"undefined":o(e)}},function(t,n){t.exports=e},function(e,t,n){"use strict";var i=n(2),r=n.n(i);t.a=function(e,t){for(var n=r()({},e),i=0;i=0&&n.splice(i,1),n}function y(e,t){var n=e.slice();return-1===n.indexOf(t)&&n.push(t),n}function C(e){return e.split("-")}function x(e,t){return e+"-"+t}function z(e){return Object(v.o)(e).isTreeNode}function w(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.filter(z)}function O(e){var t=Object(v.l)(e)||{},n=t.disabled,i=t.disableCheckbox,r=t.checkable;return!(!n&&!i)||!1===r}function S(e,t){!function n(i,r,o){var a=i?i.componentOptions.children:e,s=i?x(o.pos,r):0,c=w(a);if(i){var l=i.key;l||null!=l||(l=s);var u={node:i,index:r,pos:s,key:l,parentPos:o.node?o.pos:null};t(u)}c.forEach((function(e,t){n(e,t,{node:i,pos:s})}))}(null)}function M(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1],n=e.map(t);return 1===n.length?n[0]:n}function k(e,t){var n=Object(v.l)(t),i=n.eventKey,r=n.pos,o=[];return S(e,(function(e){var t=e.key;o.push(t)})),o.push(i||r),o}function V(e,t){var n=e.clientY,i=t.$refs.selectHandle.getBoundingClientRect(),r=i.top,o=i.bottom,a=i.height,s=Math.max(.25*a,2);return n<=r+s?-1:n>=o-s?1:0}function T(e,t){if(e)return t.multiple?e.slice():e.length?[e[0]]:e}var H=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{props:Object(f.a)(e,["on","key","class","className","style"]),on:e.on||{},class:e.class||e.className,style:e.style,key:e.key}};function j(e,t,n){if(!t)return[];var i=(n||{}).processProps,r=void 0===i?H:i;return(Array.isArray(t)?t:[t]).map((function(t){var i=t.children,o=u()(t,["children"]),a=j(e,i,n);return e(p.a,r(o),[a])}))}function P(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.initWrapper,i=t.processEntity,r=t.onProcessFinished,o=new Map,a=new Map,s={posEntities:o,keyEntities:a};return n&&(s=n(s)||s),S(e,(function(e){var t=e.node,n=e.index,r=e.pos,c=e.key,l=e.parentPos,u={node:t,index:n,key:c,pos:r};o.set(r,u),a.set(c,u),u.parent=o.get(l),u.parent&&(u.parent.children=u.parent.children||[],u.parent.children.push(u)),i&&i(u,s)})),r&&r(s),s}function L(e){if(!e)return null;var t=void 0;if(Array.isArray(e))t={checkedKeys:e,halfCheckedKeys:void 0};else{if("object"!==(void 0===e?"undefined":c()(e)))return d()(!1,"`checkedKeys` is not an array or an object"),null;t={checkedKeys:e.checked||void 0,halfCheckedKeys:e.halfChecked||void 0}}return t}function _(e,t,n){var i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},r=new Map,o=new Map;function s(e){if(r.get(e)!==t){var i=n.get(e);if(i){var a=i.children,c=i.parent;if(!O(i.node)){var l=!0,u=!1;(a||[]).filter((function(e){return!O(e.node)})).forEach((function(e){var t=e.key,n=r.get(t),i=o.get(t);(n||i)&&(u=!0),n||(l=!1)})),t?r.set(e,l):r.set(e,!1),o.set(e,u),c&&s(c.key)}}}}function c(e){if(r.get(e)!==t){var i=n.get(e);if(i){var o=i.children;O(i.node)||(r.set(e,t),(o||[]).forEach((function(e){c(e.key)})))}}}function l(e){var i=n.get(e);if(i){var o=i.children,a=i.parent,l=i.node;r.set(e,t),O(l)||((o||[]).filter((function(e){return!O(e.node)})).forEach((function(e){c(e.key)})),a&&s(a.key))}else d()(!1,"'"+e+"' does not exist in the tree.")}(i.checkedKeys||[]).forEach((function(e){r.set(e,!0)})),(i.halfCheckedKeys||[]).forEach((function(e){o.set(e,!0)})),(e||[]).forEach((function(e){l(e)}));var u=[],h=[],f=!0,p=!1,v=void 0;try{for(var m,g=r[Symbol.iterator]();!(f=(m=g.next()).done);f=!0){var b=m.value,y=a()(b,2),C=y[0],x=y[1];x&&u.push(C)}}catch(e){p=!0,v=e}finally{try{!f&&g.return&&g.return()}finally{if(p)throw v}}var z=!0,w=!1,S=void 0;try{for(var M,k=o[Symbol.iterator]();!(z=(M=k.next()).done);z=!0){var V=M.value,T=a()(V,2),H=T[0],j=T[1];!r.get(H)&&j&&h.push(H)}}catch(e){w=!0,S=e}finally{try{!z&&k.return&&k.return()}finally{if(w)throw S}}return{checkedKeys:u,halfCheckedKeys:h}}function $(e,t){var n=new Map;return(e||[]).forEach((function(e){!function e(i){if(!n.get(i)){var r=t.get(i);if(r){n.set(i,!0);var o=r.parent,a=r.node,s=Object(v.l)(a);s&&s.disabled||o&&e(o.key)}}}(e)})),[].concat(r()(n.keys()))}},function(e,t,n){(function(t){for(var i=n(287),r="undefined"==typeof window?t:window,o=["moz","webkit"],a="AnimationFrame",s=r["request"+a],c=r["cancel"+a]||r["cancelRequest"+a],l=0;!s&&l1&&void 0!==arguments[1]?arguments[1]:{},n=t.beforeEnter,o=t.enter,a=t.afterEnter,s=t.leave,c=t.afterLeave,l=t.appear,u=void 0===l||l,h=t.tag,d=t.nativeOn,f={props:{appear:u,css:!1},on:{beforeEnter:n||r,enter:o||function(t,n){Object(i.a)(t,e+"-enter",n)},afterEnter:a||r,leave:s||function(t,n){Object(i.a)(t,e+"-leave",n)},afterLeave:c||r},nativeOn:d};return h&&(f.tag=h),f}},function(e,t,n){"use strict";var i=function(){};e.exports=i},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default={install:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.name||"ref";e.directive(n,{bind:function(t,n,i){e.nextTick((function(){n.value(i.componentInstance||t,i.key)})),n.value(i.componentInstance||t,i.key)},update:function(e,t,i,r){if(r.data&&r.data.directives){var o=r.data.directives.find((function(e){return e.name===n}));if(o&&o.value!==t.value)return o&&o.value(null,r.key),void t.value(i.componentInstance||e,i.key)}i.componentInstance===r.componentInstance&&i.elm===r.elm||t.value(i.componentInstance||e,i.key)},unbind:function(e,t,n){t.value(null,n.key)}})}}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),o=n(0),a=n(36);t.a={name:"LocaleReceiver",props:{componentName:o.a.string.def("global"),defaultLocale:o.a.oneOfType([o.a.object,o.a.func]),children:o.a.func},inject:{localeData:{default:function(){return{}}}},methods:{getLocale:function(){var e=this.componentName,t=this.defaultLocale||a.a[e||"global"],n=this.localeData.antLocale,i=e&&n?n[e]:{};return r()({},"function"==typeof t?t():t,i||{})},getLocaleCode:function(){var e=this.localeData.antLocale,t=e&&e.locale;return e&&e.exist&&!t?a.a.locale:t}},render:function(){var e=this.$scopedSlots,t=this.children||e.default,n=this.localeData.antLocale;return t(this.getLocale(),this.getLocaleCode(),n)}}},function(e,t){e.exports=function(e,t,n,i){var r=n?n.call(i,e,t):void 0;if(void 0!==r)return!!r;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(var s=Object.prototype.hasOwnProperty.bind(t),c=0;c=0&&n.left>=0&&n.bottom>n.top&&n.right>n.left?n:null}function se(e){var t,n,i;if(ne.isWindow(e)||9===e.nodeType){var r=ne.getWindow(e);t={left:ne.getWindowScrollLeft(r),top:ne.getWindowScrollTop(r)},n=ne.viewportWidth(r),i=ne.viewportHeight(r)}else t=ne.offset(e),n=ne.outerWidth(e),i=ne.outerHeight(e);return t.width=n,t.height=i,t}function ce(e,t){var n=t.charAt(0),i=t.charAt(1),r=e.width,o=e.height,a=e.left,s=e.top;return"c"===n?s+=o/2:"b"===n&&(s+=o),"c"===i?a+=r/2:"r"===i&&(a+=r),{left:a,top:s}}function le(e,t,n,i,r){var o=ce(t,n[1]),a=ce(e,n[0]),s=[a.left-o.left,a.top-o.top];return{left:Math.round(e.left-s[0]+i[0]-r[0]),top:Math.round(e.top-s[1]+i[1]-r[1])}}function ue(e,t,n){return e.leftn.right}function he(e,t,n){return e.topn.bottom}function de(e,t,n){var i=[];return ne.each(e,(function(e){i.push(e.replace(t,(function(e){return n[e]})))})),i}function fe(e,t){return e[t]=-e[t],e}function pe(e,t){return(/%$/.test(e)?parseInt(e.substring(0,e.length-1),10)/100*t:parseInt(e,10))||0}function ve(e,t){e[0]=pe(e[0],t.width),e[1]=pe(e[1],t.height)}function me(e,t,n,i){var r=n.points,o=n.offset||[0,0],a=n.targetOffset||[0,0],s=n.overflow,c=n.source||e;o=[].concat(o),a=[].concat(a);var l={},u=0,h=ae(c,!(!(s=s||{})||!s.alwaysByViewport)),d=se(c);ve(o,d),ve(a,t);var f=le(d,t,r,o,a),p=ne.merge(d,f);if(h&&(s.adjustX||s.adjustY)&&i){if(s.adjustX&&ue(f,d,h)){var v=de(r,/[lr]/gi,{l:"r",r:"l"}),m=fe(o,0),g=fe(a,0);(function(e,t,n){return e.left>n.right||e.left+t.widthn.bottom||e.top+t.height=n.left&&r.left+o.width>n.right&&(o.width-=r.left+o.width-n.right),i.adjustX&&r.left+o.width>n.right&&(r.left=Math.max(n.right-o.width,n.left)),i.adjustY&&r.top=n.top&&r.top+o.height>n.bottom&&(o.height-=r.top+o.height-n.bottom),i.adjustY&&r.top+o.height>n.bottom&&(r.top=Math.max(n.bottom-o.height,n.top)),ne.mix(r,o)}(f,d,h,l))}return p.width!==d.width&&ne.css(c,"width",ne.width(c)+p.width-d.width),p.height!==d.height&&ne.css(c,"height",ne.height(c)+p.height-d.height),ne.offset(c,{left:p.left,top:p.top},{useCssRight:n.useCssRight,useCssBottom:n.useCssBottom,useCssTransform:n.useCssTransform,ignoreShake:n.ignoreShake}),{points:r,offset:o,targetOffset:a,overflow:l}}function ge(e,t,n){var i=n.target||t;return me(e,se(i),n,!function(e,t){var n=ae(e,t),i=se(e);return!n||i.left+i.width<=n.left||i.top+i.height<=n.top||i.left>=n.right||i.top>=n.bottom}(i,n.overflow&&n.overflow.alwaysByViewport))}ge.__getOffsetParent=re,ge.__getVisibleRectForElement=ae;function be(e){return e&&"object"===(void 0===e?"undefined":g()(e))&&e.window===e}function ye(e,t){var n=Math.floor(e),i=Math.floor(t);return Math.abs(n-i)<=1}var Ce=n(9),xe=n(114),ze=n.n(xe);function we(e){return"function"==typeof e&&e?e():null}function Oe(e){return"object"===(void 0===e?"undefined":g()(e))&&e?e:null}var Se={props:{childrenProps:u.a.object,align:u.a.object.isRequired,target:u.a.oneOfType([u.a.func,u.a.object]).def((function(){return window})),monitorBufferTime:u.a.number.def(50),monitorWindowResize:u.a.bool.def(!1),disabled:u.a.bool.def(!1)},data:function(){return this.aligned=!1,{}},mounted:function(){var e=this;this.$nextTick((function(){e.prevProps=o()({},e.$props);var t=e.$props;!e.aligned&&e.forceAlign(),!t.disabled&&t.monitorWindowResize&&e.startMonitorWindowResize()}))},updated:function(){var e=this;this.$nextTick((function(){var t,n,i=e.prevProps,r=e.$props,a=!1;if(!r.disabled){var s=e.$el,c=s?s.getBoundingClientRect():null;if(i.disabled)a=!0;else{var l=we(i.target),u=we(r.target),h=Oe(i.target),d=Oe(r.target);be(l)&&be(u)?a=!1:(l!==u||l&&!u&&d||h&&d&&u||d&&!((t=h)===(n=d)||t&&n&&("pageX"in n&&"pageY"in n?t.pageX===n.pageX&&t.pageY===n.pageY:"clientX"in n&&"clientY"in n&&t.clientX===n.clientX&&t.clientY===n.clientY)))&&(a=!0);var f=e.sourceRect||{};a||!s||ye(f.width,c.width)&&ye(f.height,c.height)||(a=!0)}e.sourceRect=c}a&&e.forceAlign(),r.monitorWindowResize&&!r.disabled?e.startMonitorWindowResize():e.stopMonitorWindowResize(),e.prevProps=o()({},e.$props,{align:ze()(e.$props.align)})}))},beforeDestroy:function(){this.stopMonitorWindowResize()},methods:{startMonitorWindowResize:function(){this.resizeHandler||(this.bufferMonitor=function(e,t){var n=void 0;function i(){n&&(clearTimeout(n),n=null)}function r(){i(),n=setTimeout(e,t)}return r.clear=i,r}(this.forceAlign,this.$props.monitorBufferTime),this.resizeHandler=Object(p.a)(window,"resize",this.bufferMonitor))},stopMonitorWindowResize:function(){this.resizeHandler&&(this.bufferMonitor.clear(),this.resizeHandler.remove(),this.resizeHandler=null)},forceAlign:function(){var e=this.$props,t=e.disabled,n=e.target,i=e.align;if(!t&&n){var r=this.$el,o=Object(d.k)(this),a=void 0,s=we(n),c=Oe(n),l=document.activeElement;s?a=ge(r,s,i):c&&(a=function(e,t,n){var i,r,o=ne.getDocument(e),a=o.defaultView||o.parentWindow,s=ne.getWindowScrollLeft(a),c=ne.getWindowScrollTop(a),l=ne.viewportWidth(a),u=ne.viewportHeight(a),h={left:i="pageX"in t?t.pageX:s+t.clientX,top:r="pageY"in t?t.pageY:c+t.clientY,width:0,height:0},d=i>=0&&i<=s+l&&r>=0&&r<=c+u,f=[n.points[0],"cc"];return me(e,h,y(y({},n),{},{points:f}),d)}(r,c,i)),function(e,t){e!==document.activeElement&&Object(h.a)(t,e)&&e.focus()}(l,r),this.aligned=!0,o.align&&o.align(r,a)}}},render:function(){var e=this.$props.childrenProps,t=Object(d.n)(this)[0];return t&&e?Object(Ce.a)(t,{props:e}):t}},Me=n(6),ke=n.n(Me),Ve={props:{visible:u.a.bool,hiddenClassName:u.a.string},render:function(){var e=arguments[0],t=this.$props,n=t.hiddenClassName,i=(t.visible,null);if(n||!this.$slots.default||this.$slots.default.length>1){var r="";i=e("div",{class:r},[this.$slots.default])}else i=this.$slots.default[0];return i}},Te={props:{hiddenClassName:u.a.string.def(""),prefixCls:u.a.string,visible:u.a.bool},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.visible,r=t.hiddenClassName,o={on:Object(d.k)(this)};return e("div",ke()([o,{class:i?"":r}]),[e(Ve,{class:n+"-content",attrs:{visible:i}},[this.$slots.default])])}},He=n(46),je=n(4),Pe={name:"VCTriggerPopup",mixins:[je.a],props:{visible:u.a.bool,getClassNameFromAlign:u.a.func,getRootDomNode:u.a.func,align:u.a.any,destroyPopupOnHide:u.a.bool,prefixCls:u.a.string,getContainer:u.a.func,transitionName:u.a.string,animation:u.a.any,maskAnimation:u.a.string,maskTransitionName:u.a.string,mask:u.a.bool,zIndex:u.a.number,popupClassName:u.a.any,popupStyle:u.a.object.def((function(){return{}})),stretch:u.a.string,point:u.a.shape({pageX:u.a.number,pageY:u.a.number})},data:function(){return this.domEl=null,{stretchChecked:!1,targetWidth:void 0,targetHeight:void 0}},mounted:function(){var e=this;this.$nextTick((function(){e.rootNode=e.getPopupDomNode(),e.setStretchSize()}))},updated:function(){var e=this;this.$nextTick((function(){e.setStretchSize()}))},beforeDestroy:function(){this.$el.parentNode?this.$el.parentNode.removeChild(this.$el):this.$el.remove&&this.$el.remove()},methods:{onAlign:function(e,t){var n=this.$props.getClassNameFromAlign(t);this.currentAlignClassName!==n&&(this.currentAlignClassName=n,e.className=this.getClassName(n));var i=Object(d.k)(this);i.align&&i.align(e,t)},setStretchSize:function(){var e=this.$props,t=e.stretch,n=e.getRootDomNode,i=e.visible,r=this.$data,o=r.stretchChecked,a=r.targetHeight,s=r.targetWidth;if(t&&i){var c=n();if(c){var l=c.offsetHeight,u=c.offsetWidth;a===l&&s===u&&o||this.setState({stretchChecked:!0,targetHeight:l,targetWidth:u})}}else o&&this.setState({stretchChecked:!1})},getPopupDomNode:function(){return this.$refs.popupInstance?this.$refs.popupInstance.$el:null},getTargetElement:function(){return this.$props.getRootDomNode()},getAlignTarget:function(){var e=this.$props.point;return e||this.getTargetElement},getMaskTransitionName:function(){var e=this.$props,t=e.maskTransitionName,n=e.maskAnimation;return!t&&n&&(t=e.prefixCls+"-"+n),t},getTransitionName:function(){var e=this.$props,t=e.transitionName,n=e.animation;return t||("string"==typeof n?t=""+n:n&&n.props&&n.props.name&&(t=n.props.name)),t},getClassName:function(e){return this.$props.prefixCls+" "+this.$props.popupClassName+" "+e},getPopupElement:function(){var e=this,t=this.$createElement,n=this.$props,i=this.$slots,r=this.getTransitionName,a=this.$data,s=a.stretchChecked,c=a.targetHeight,l=a.targetWidth,u=n.align,h=n.visible,f=n.prefixCls,p=n.animation,v=n.popupStyle,m=n.getClassNameFromAlign,b=n.destroyPopupOnHide,y=n.stretch,C=this.getClassName(this.currentAlignClassName||m(u));h||(this.currentAlignClassName=null);var x={};y&&(-1!==y.indexOf("height")?x.height="number"==typeof c?c+"px":c:-1!==y.indexOf("minHeight")&&(x.minHeight="number"==typeof c?c+"px":c),-1!==y.indexOf("width")?x.width="number"==typeof l?l+"px":l:-1!==y.indexOf("minWidth")&&(x.minWidth="number"==typeof l?l+"px":l),s||setTimeout((function(){e.$refs.alignInstance&&e.$refs.alignInstance.forceAlign()}),0));var z={props:{prefixCls:f,visible:h},class:C,on:Object(d.k)(this),ref:"popupInstance",style:o()({},x,v,this.getZIndexStyle())},w={props:{appear:!0,css:!1}},O=r(),S=!!O,M={beforeEnter:function(){},enter:function(t,n){e.$nextTick((function(){e.$refs.alignInstance?e.$refs.alignInstance.$nextTick((function(){e.domEl=t,Object(He.a)(t,O+"-enter",n)})):n()}))},beforeLeave:function(){e.domEl=null},leave:function(e,t){Object(He.a)(e,O+"-leave",t)}};if("object"===(void 0===p?"undefined":g()(p))){S=!0;var k=p.on,V=void 0===k?{}:k,T=p.props,H=void 0===T?{}:T;w.props=o()({},w.props,H),w.on=o()({},M,V)}else w.on=M;return S||(w={}),t("transition",w,b?[h?t(Se,{attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Te,z,[i.default])]):null]:[t(Se,{directives:[{name:"show",value:h}],attrs:{target:this.getAlignTarget(),monitorWindowResize:!0,disabled:!h,align:u},key:"popup",ref:"alignInstance",on:{align:this.onAlign}},[t(Te,z,[i.default])])])},getZIndexStyle:function(){var e={},t=this.$props;return void 0!==t.zIndex&&(e.zIndex=t.zIndex),e},getMaskElement:function(){var e=this.$createElement,t=this.$props,n=null;if(t.mask){var i=this.getMaskTransitionName();n=e(Ve,{directives:[{name:"show",value:t.visible}],style:this.getZIndexStyle(),key:"mask",class:t.prefixCls+"-mask",attrs:{visible:t.visible}}),i&&(n=e("transition",{attrs:{appear:!0,name:i}},[n]))}return n}},render:function(){var e=arguments[0],t=this.getMaskElement,n=this.getPopupElement;return e("div",[t(),n()])}};function Le(e,t,n){return n?e[0]===t[0]:e[0]===t[0]&&e[1]===t[1]}function _e(){}var $e={props:{autoMount:u.a.bool.def(!0),autoDestroy:u.a.bool.def(!0),visible:u.a.bool,forceRender:u.a.bool.def(!1),parent:u.a.any,getComponent:u.a.func.isRequired,getContainer:u.a.func.isRequired,children:u.a.func.isRequired},mounted:function(){this.autoMount&&this.renderComponent()},updated:function(){this.autoMount&&this.renderComponent()},beforeDestroy:function(){this.autoDestroy&&this.removeContainer()},methods:{removeContainer:function(){this.container&&(this._component&&this._component.$destroy(),this.container.parentNode.removeChild(this.container),this.container=null,this._component=null)},renderComponent:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1],n=this.visible,i=this.forceRender,r=this.getContainer,o=this.parent,a=this;if(n||o._component||o.$refs._component||i){var s=this.componentEl;this.container||(this.container=r(),s=document.createElement("div"),this.componentEl=s,this.container.appendChild(s));var c={component:a.getComponent(e)};this._component?this._component.setComponent(c):this._component=new this.$root.constructor({el:s,parent:a,data:{_com:c},mounted:function(){this.$nextTick((function(){t&&t.call(a)}))},updated:function(){this.$nextTick((function(){t&&t.call(a)}))},methods:{setComponent:function(e){this.$data._com=e}},render:function(){return this.$data._com.component}})}}},render:function(){return this.children({renderComponent:this.renderComponent,removeContainer:this.removeContainer})}};s.a.use(l.a,{name:"ant-ref"});var Ee=["click","mousedown","touchstart","mouseenter","mouseleave","focus","blur","contextmenu"],Ae={name:"Trigger",mixins:[je.a],props:{action:u.a.oneOfType([u.a.string,u.a.arrayOf(u.a.string)]).def([]),showAction:u.a.any.def([]),hideAction:u.a.any.def([]),getPopupClassNameFromAlign:u.a.any.def((function(){return""})),afterPopupVisibleChange:u.a.func.def(_e),popup:u.a.any,popupStyle:u.a.object.def((function(){return{}})),prefixCls:u.a.string.def("rc-trigger-popup"),popupClassName:u.a.string.def(""),popupPlacement:u.a.string,builtinPlacements:u.a.object,popupTransitionName:u.a.oneOfType([u.a.string,u.a.object]),popupAnimation:u.a.any,mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),zIndex:u.a.number,focusDelay:u.a.number.def(0),blurDelay:u.a.number.def(.15),getPopupContainer:u.a.func,getDocument:u.a.func.def((function(){return window.document})),forceRender:u.a.bool,destroyPopupOnHide:u.a.bool.def(!1),mask:u.a.bool.def(!1),maskClosable:u.a.bool.def(!0),popupAlign:u.a.object.def((function(){return{}})),popupVisible:u.a.bool,defaultPopupVisible:u.a.bool.def(!1),maskTransitionName:u.a.oneOfType([u.a.string,u.a.object]),maskAnimation:u.a.string,stretch:u.a.string,alignPoint:u.a.bool},provide:function(){return{vcTriggerContext:this}},inject:{vcTriggerContext:{default:function(){return{}}},savePopupRef:{default:function(){return _e}},dialogContext:{default:function(){return null}}},data:function(){var e=this,t=this.$props,n=void 0;return n=Object(d.s)(this,"popupVisible")?!!t.popupVisible:!!t.defaultPopupVisible,Ee.forEach((function(t){e["fire"+t]=function(n){e.fireEvents(t,n)}})),{prevPopupVisible:n,sPopupVisible:n,point:null}},watch:{popupVisible:function(e){void 0!==e&&(this.prevPopupVisible=this.sPopupVisible,this.sPopupVisible=e)}},deactivated:function(){this.setPopupVisible(!1)},mounted:function(){var e=this;this.$nextTick((function(){e.renderComponent(null),e.updatedCal()}))},updated:function(){var e=this;this.renderComponent(null,(function(){e.sPopupVisible!==e.prevPopupVisible&&e.afterPopupVisibleChange(e.sPopupVisible),e.prevPopupVisible=e.sPopupVisible})),this.$nextTick((function(){e.updatedCal()}))},beforeDestroy:function(){this.clearDelayTimer(),this.clearOutsideHandler(),clearTimeout(this.mouseDownTimeout)},methods:{updatedCal:function(){var e=this.$props;if(this.$data.sPopupVisible){var t=void 0;this.clickOutsideHandler||!this.isClickToHide()&&!this.isContextmenuToShow()||(t=e.getDocument(),this.clickOutsideHandler=Object(p.a)(t,"mousedown",this.onDocumentClick)),this.touchOutsideHandler||(t=t||e.getDocument(),this.touchOutsideHandler=Object(p.a)(t,"touchstart",this.onDocumentClick)),!this.contextmenuOutsideHandler1&&this.isContextmenuToShow()&&(t=t||e.getDocument(),this.contextmenuOutsideHandler1=Object(p.a)(t,"scroll",this.onContextmenuClose)),!this.contextmenuOutsideHandler2&&this.isContextmenuToShow()&&(this.contextmenuOutsideHandler2=Object(p.a)(window,"blur",this.onContextmenuClose))}else this.clearOutsideHandler()},onMouseenter:function(e){var t=this.$props.mouseEnterDelay;this.fireEvents("mouseenter",e),this.delaySetPopupVisible(!0,t,t?null:e)},onMouseMove:function(e){this.fireEvents("mousemove",e),this.setPoint(e)},onMouseleave:function(e){this.fireEvents("mouseleave",e),this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onPopupMouseenter:function(){this.clearDelayTimer()},onPopupMouseleave:function(e){e&&e.relatedTarget&&!e.relatedTarget.setTimeout&&this._component&&this._component.getPopupDomNode&&Object(h.a)(this._component.getPopupDomNode(),e.relatedTarget)||this.delaySetPopupVisible(!1,this.$props.mouseLeaveDelay)},onFocus:function(e){this.fireEvents("focus",e),this.clearDelayTimer(),this.isFocusToShow()&&(this.focusTime=Date.now(),this.delaySetPopupVisible(!0,this.$props.focusDelay))},onMousedown:function(e){this.fireEvents("mousedown",e),this.preClickTime=Date.now()},onTouchstart:function(e){this.fireEvents("touchstart",e),this.preTouchTime=Date.now()},onBlur:function(e){Object(h.a)(e.target,e.relatedTarget||document.activeElement)||(this.fireEvents("blur",e),this.clearDelayTimer(),this.isBlurToHide()&&this.delaySetPopupVisible(!1,this.$props.blurDelay))},onContextmenu:function(e){e.preventDefault(),this.fireEvents("contextmenu",e),this.setPopupVisible(!0,e)},onContextmenuClose:function(){this.isContextmenuToShow()&&this.close()},onClick:function(e){if(this.fireEvents("click",e),this.focusTime){var t=void 0;if(this.preClickTime&&this.preTouchTime?t=Math.min(this.preClickTime,this.preTouchTime):this.preClickTime?t=this.preClickTime:this.preTouchTime&&(t=this.preTouchTime),Math.abs(t-this.focusTime)<20)return;this.focusTime=0}this.preClickTime=0,this.preTouchTime=0,this.isClickToShow()&&(this.isClickToHide()||this.isBlurToHide())&&e&&e.preventDefault&&e.preventDefault(),e&&e.domEvent&&e.domEvent.preventDefault();var n=!this.$data.sPopupVisible;(this.isClickToHide()&&!n||n&&this.isClickToShow())&&this.setPopupVisible(!this.$data.sPopupVisible,e)},onPopupMouseDown:function(){var e=this,t=this.vcTriggerContext,n=void 0===t?{}:t;this.hasPopupMouseDown=!0,clearTimeout(this.mouseDownTimeout),this.mouseDownTimeout=setTimeout((function(){e.hasPopupMouseDown=!1}),0),n.onPopupMouseDown&&n.onPopupMouseDown.apply(n,arguments)},onDocumentClick:function(e){if(!this.$props.mask||this.$props.maskClosable){var t=e.target,n=this.$el;Object(h.a)(n,t)||this.hasPopupMouseDown||this.close()}},getPopupDomNode:function(){return this._component&&this._component.getPopupDomNode?this._component.getPopupDomNode():null},getRootDomNode:function(){return this.$el},handleGetPopupClassFromAlign:function(e){var t=[],n=this.$props,i=n.popupPlacement,r=n.builtinPlacements,o=n.prefixCls,a=n.alignPoint,s=n.getPopupClassNameFromAlign;return i&&r&&t.push(function(e,t,n,i){var r=n.points;for(var o in e)if(e.hasOwnProperty(o)&&Le(e[o].points,r,i))return t+"-placement-"+o;return""}(r,o,e,a)),s&&t.push(s(e)),t.join(" ")},getPopupAlign:function(){var e=this.$props,t=e.popupPlacement,n=e.popupAlign,i=e.builtinPlacements;return t&&i?function(e,t,n){var i=e[t]||{};return o()({},i,n)}(i,t,n):n},savePopup:function(e){this._component=e,this.savePopupRef(e)},getComponent:function(){var e=this.$createElement,t={};this.isMouseEnterToShow()&&(t.mouseenter=this.onPopupMouseenter),this.isMouseLeaveToHide()&&(t.mouseleave=this.onPopupMouseleave),t.mousedown=this.onPopupMouseDown,t.touchstart=this.onPopupMouseDown;var n=this.handleGetPopupClassFromAlign,i=this.getRootDomNode,r=this.getContainer,a=this.$props,s=a.prefixCls,c=a.destroyPopupOnHide,l=a.popupClassName,u=a.action,h=a.popupAnimation,f=a.popupTransitionName,p=a.popupStyle,v=a.mask,m=a.maskAnimation,g=a.maskTransitionName,b=a.zIndex,y=a.stretch,C=a.alignPoint,x=this.$data,z=x.sPopupVisible,w=x.point,O={props:{prefixCls:s,destroyPopupOnHide:c,visible:z,point:C&&w,action:u,align:this.getPopupAlign(),animation:h,getClassNameFromAlign:n,stretch:y,getRootDomNode:i,mask:v,zIndex:b,transitionName:f,maskAnimation:m,maskTransitionName:g,getContainer:r,popupClassName:l,popupStyle:p},on:o()({align:Object(d.k)(this).popupAlign||_e},t),directives:[{name:"ant-ref",value:this.savePopup}]};return e(Pe,O,[Object(d.g)(this,"popup")])},getContainer:function(){var e=this.$props,t=this.dialogContext,n=document.createElement("div");return n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",(e.getPopupContainer?e.getPopupContainer(this.$el,t):e.getDocument().body).appendChild(n),this.popupContainer=n,n},setPopupVisible:function(e,t){var n=this.alignPoint,i=this.sPopupVisible;if(this.clearDelayTimer(),i!==e){Object(d.s)(this,"popupVisible")||this.setState({sPopupVisible:e,prevPopupVisible:i});var r=Object(d.k)(this);r.popupVisibleChange&&r.popupVisibleChange(e)}n&&t&&this.setPoint(t)},setPoint:function(e){this.$props.alignPoint&&e&&this.setState({point:{pageX:e.pageX,pageY:e.pageY}})},delaySetPopupVisible:function(e,t,n){var i=this,r=1e3*t;if(this.clearDelayTimer(),r){var o=n?{pageX:n.pageX,pageY:n.pageY}:null;this.delayTimer=Object(f.b)((function(){i.setPopupVisible(e,o),i.clearDelayTimer()}),r)}else this.setPopupVisible(e,n)},clearDelayTimer:function(){this.delayTimer&&(Object(f.a)(this.delayTimer),this.delayTimer=null)},clearOutsideHandler:function(){this.clickOutsideHandler&&(this.clickOutsideHandler.remove(),this.clickOutsideHandler=null),this.contextmenuOutsideHandler1&&(this.contextmenuOutsideHandler1.remove(),this.contextmenuOutsideHandler1=null),this.contextmenuOutsideHandler2&&(this.contextmenuOutsideHandler2.remove(),this.contextmenuOutsideHandler2=null),this.touchOutsideHandler&&(this.touchOutsideHandler.remove(),this.touchOutsideHandler=null)},createTwoChains:function(e){var t=function(){},n=Object(d.k)(this);return this.childOriginEvents[e]&&n[e]?this["fire"+e]:t=this.childOriginEvents[e]||n[e]||t},isClickToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isContextmenuToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("contextmenu")||-1!==n.indexOf("contextmenu")},isClickToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("click")||-1!==n.indexOf("click")},isMouseEnterToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseenter")},isMouseLeaveToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("hover")||-1!==n.indexOf("mouseleave")},isFocusToShow:function(){var e=this.$props,t=e.action,n=e.showAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("focus")},isBlurToHide:function(){var e=this.$props,t=e.action,n=e.hideAction;return-1!==t.indexOf("focus")||-1!==n.indexOf("blur")},forcePopupAlign:function(){this.$data.sPopupVisible&&this._component&&this._component.$refs.alignInstance&&this._component.$refs.alignInstance.forceAlign()},fireEvents:function(e,t){this.childOriginEvents[e]&&this.childOriginEvents[e](t),this.__emit(e,t)},close:function(){this.setPopupVisible(!1)}},render:function(){var e=this,t=arguments[0],n=this.sPopupVisible,i=Object(d.c)(this.$slots.default),r=this.$props,o=r.forceRender,a=r.alignPoint;i.length>1&&Object(v.a)(!1,"Trigger $slots.default.length > 1, just support only one default",!0);var s=i[0];this.childOriginEvents=Object(d.h)(s);var c={props:{},nativeOn:{},key:"trigger"};return this.isContextmenuToShow()?c.nativeOn.contextmenu=this.onContextmenu:c.nativeOn.contextmenu=this.createTwoChains("contextmenu"),this.isClickToHide()||this.isClickToShow()?(c.nativeOn.click=this.onClick,c.nativeOn.mousedown=this.onMousedown,c.nativeOn.touchstart=this.onTouchstart):(c.nativeOn.click=this.createTwoChains("click"),c.nativeOn.mousedown=this.createTwoChains("mousedown"),c.nativeOn.touchstart=this.createTwoChains("onTouchstart")),this.isMouseEnterToShow()?(c.nativeOn.mouseenter=this.onMouseenter,a&&(c.nativeOn.mousemove=this.onMouseMove)):c.nativeOn.mouseenter=this.createTwoChains("mouseenter"),this.isMouseLeaveToHide()?c.nativeOn.mouseleave=this.onMouseleave:c.nativeOn.mouseleave=this.createTwoChains("mouseleave"),this.isFocusToShow()||this.isBlurToHide()?(c.nativeOn.focus=this.onFocus,c.nativeOn.blur=this.onBlur):(c.nativeOn.focus=this.createTwoChains("focus"),c.nativeOn.blur=function(t){!t||t.relatedTarget&&Object(h.a)(t.target,t.relatedTarget)||e.createTwoChains("blur")(t)}),this.trigger=Object(Ce.a)(s,c),t($e,{attrs:{parent:this,visible:n,autoMount:!1,forceRender:o,getComponent:this.getComponent,getContainer:this.getContainer,children:function(t){var n=t.renderComponent;return e.renderComponent=n,e.trigger}}})}};t.a=Ae},function(e,t,n){var i=n(33),r=n(347),o=n(196),a=Math.max,s=Math.min;e.exports=function(e,t,n){var c,l,u,h,d,f,p=0,v=!1,m=!1,g=!0;if("function"!=typeof e)throw new TypeError("Expected a function");function b(t){var n=c,i=l;return c=l=void 0,p=t,h=e.apply(i,n)}function y(e){return p=e,d=setTimeout(x,t),v?b(e):h}function C(e){var n=e-f;return void 0===f||n>=t||n<0||m&&e-p>=u}function x(){var e=r();if(C(e))return z(e);d=setTimeout(x,function(e){var n=t-(e-f);return m?s(n,u-(e-p)):n}(e))}function z(e){return d=void 0,g&&c?b(e):(c=l=void 0,h)}function w(){var e=r(),n=C(e);if(c=arguments,l=this,f=e,n){if(void 0===d)return y(f);if(m)return clearTimeout(d),d=setTimeout(x,t),b(f)}return void 0===d&&(d=setTimeout(x,t)),h}return t=o(t)||0,i(n)&&(v=!!n.leading,u=(m="maxWait"in n)?a(o(n.maxWait)||0,t):u,g="trailing"in n?!!n.trailing:g),w.cancel=function(){void 0!==d&&clearTimeout(d),p=0,c=f=l=d=void 0},w.flush=function(){return void 0===d?h:z(r())},w}},function(e,t,n){"use strict";var i=n(3),r=n.n(i),o=n(2),a=n.n(o),s=n(9),c=n(12),l=n.n(c),u=n(0),h=n(28),d={adjustX:1,adjustY:1},f=[0,0],p={left:{points:["cr","cl"],overflow:d,offset:[-4,0],targetOffset:f},right:{points:["cl","cr"],overflow:d,offset:[4,0],targetOffset:f},top:{points:["bc","tc"],overflow:d,offset:[0,-4],targetOffset:f},bottom:{points:["tc","bc"],overflow:d,offset:[0,4],targetOffset:f},topLeft:{points:["bl","tl"],overflow:d,offset:[0,-4],targetOffset:f},leftTop:{points:["tr","tl"],overflow:d,offset:[-4,0],targetOffset:f},topRight:{points:["br","tr"],overflow:d,offset:[0,-4],targetOffset:f},rightTop:{points:["tl","tr"],overflow:d,offset:[4,0],targetOffset:f},bottomRight:{points:["tr","br"],overflow:d,offset:[0,4],targetOffset:f},rightBottom:{points:["bl","br"],overflow:d,offset:[4,0],targetOffset:f},bottomLeft:{points:["tl","bl"],overflow:d,offset:[0,4],targetOffset:f},leftBottom:{points:["br","bl"],overflow:d,offset:[-4,0],targetOffset:f}},v={props:{prefixCls:u.a.string,overlay:u.a.any,trigger:u.a.any},updated:function(){var e=this.trigger;e&&e.forcePopupAlign()},render:function(){var e=arguments[0],t=this.overlay,n=this.prefixCls;return e("div",{class:n+"-inner",attrs:{role:"tooltip"}},["function"==typeof t?t():t])}},m=n(1);function g(){}var b={props:{trigger:u.a.any.def(["hover"]),defaultVisible:u.a.bool,visible:u.a.bool,placement:u.a.string.def("right"),transitionName:u.a.oneOfType([u.a.string,u.a.object]),animation:u.a.any,afterVisibleChange:u.a.func.def((function(){})),overlay:u.a.any,overlayStyle:u.a.object,overlayClassName:u.a.string,prefixCls:u.a.string.def("rc-tooltip"),mouseEnterDelay:u.a.number.def(0),mouseLeaveDelay:u.a.number.def(.1),getTooltipContainer:u.a.func,destroyTooltipOnHide:u.a.bool.def(!1),align:u.a.object.def((function(){return{}})),arrowContent:u.a.any.def(null),tipId:u.a.string,builtinPlacements:u.a.object},methods:{getPopupElement:function(){var e=this.$createElement,t=this.$props,n=t.prefixCls,i=t.tipId;return[e("div",{class:n+"-arrow",key:"arrow"},[Object(m.g)(this,"arrowContent")]),e(v,{key:"content",attrs:{trigger:this.$refs.trigger,prefixCls:n,id:i,overlay:Object(m.g)(this,"overlay")}})]},getPopupDomNode:function(){return this.$refs.trigger.getPopupDomNode()}},render:function(e){var t=Object(m.l)(this),n=t.overlayClassName,i=t.trigger,r=t.mouseEnterDelay,o=t.mouseLeaveDelay,s=t.overlayStyle,c=t.prefixCls,u=t.afterVisibleChange,d=t.transitionName,f=t.animation,v=t.placement,b=t.align,y=t.destroyTooltipOnHide,C=t.defaultVisible,x=t.getTooltipContainer,z=l()(t,["overlayClassName","trigger","mouseEnterDelay","mouseLeaveDelay","overlayStyle","prefixCls","afterVisibleChange","transitionName","animation","placement","align","destroyTooltipOnHide","defaultVisible","getTooltipContainer"]),w=a()({},z);Object(m.s)(this,"visible")&&(w.popupVisible=this.$props.visible);var O=Object(m.k)(this),S={props:a()({popupClassName:n,prefixCls:c,action:i,builtinPlacements:p,popupPlacement:v,popupAlign:b,getPopupContainer:x,afterPopupVisibleChange:u,popupTransitionName:d,popupAnimation:f,defaultPopupVisible:C,destroyPopupOnHide:y,mouseLeaveDelay:o,popupStyle:s,mouseEnterDelay:r},w),on:a()({},O,{popupVisibleChange:O.visibleChange||g,popupAlign:O.popupAlign||g}),ref:"trigger"};return e(h.a,S,[e("template",{slot:"popup"},[this.getPopupElement(e)]),this.$slots.default])}},y={adjustX:1,adjustY:1},C={adjustX:0,adjustY:0},x=[0,0];function z(e){return"boolean"==typeof e?e?y:C:a()({},C,e)}var w=n(7),O=n(53),S=Object(O.a)(),M={name:"ATooltip",model:{prop:"visible",event:"visibleChange"},props:a()({},S,{title:u.a.any}),inject:{configProvider:{default:function(){return w.a}}},data:function(){return{sVisible:!!this.$props.visible||!!this.$props.defaultVisible}},watch:{visible:function(e){this.sVisible=e}},methods:{onVisibleChange:function(e){Object(m.s)(this,"visible")||(this.sVisible=!this.isNoTitle()&&e),this.isNoTitle()||this.$emit("visibleChange",e)},getPopupDomNode:function(){return this.$refs.tooltip.getPopupDomNode()},getPlacements:function(){var e=this.$props,t=e.builtinPlacements,n=e.arrowPointAtCenter,i=e.autoAdjustOverflow;return t||function(e){var t=e.arrowWidth,n=void 0===t?5:t,i=e.horizontalArrowShift,r=void 0===i?16:i,o=e.verticalArrowShift,s=void 0===o?12:o,c=e.autoAdjustOverflow,l=void 0===c||c,u={left:{points:["cr","cl"],offset:[-4,0]},right:{points:["cl","cr"],offset:[4,0]},top:{points:["bc","tc"],offset:[0,-4]},bottom:{points:["tc","bc"],offset:[0,4]},topLeft:{points:["bl","tc"],offset:[-(r+n),-4]},leftTop:{points:["tr","cl"],offset:[-4,-(s+n)]},topRight:{points:["br","tc"],offset:[r+n,-4]},rightTop:{points:["tl","cr"],offset:[4,-(s+n)]},bottomRight:{points:["tr","bc"],offset:[r+n,4]},rightBottom:{points:["bl","cr"],offset:[4,s+n]},bottomLeft:{points:["tl","bc"],offset:[-(r+n),4]},leftBottom:{points:["br","cl"],offset:[-4,s+n]}};return Object.keys(u).forEach((function(t){u[t]=e.arrowPointAtCenter?a()({},u[t],{overflow:z(l),targetOffset:x}):a()({},p[t],{overflow:z(l)}),u[t].ignoreShake=!0})),u}({arrowPointAtCenter:n,verticalArrowShift:8,autoAdjustOverflow:i})},getDisabledCompatibleChildren:function(e){var t=this.$createElement,n=e.componentOptions&&e.componentOptions.Ctor.options||{};if((!0===n.__ANT_BUTTON||!0===n.__ANT_SWITCH||!0===n.__ANT_CHECKBOX)&&(e.componentOptions.propsData.disabled||""===e.componentOptions.propsData.disabled)||"button"===e.tag&&e.data&&e.data.attrs&&void 0!==e.data.attrs.disabled){var i=function(e,t){var n={},i=a()({},e);return t.forEach((function(t){e&&t in e&&(n[t]=e[t],delete i[t])})),{picked:n,omitted:i}}(Object(m.q)(e),["position","left","right","top","bottom","float","display","zIndex"]),r=i.picked,o=i.omitted,c=a()({display:"inline-block"},r,{cursor:"not-allowed",width:e.componentOptions.propsData.block?"100%":null}),l=a()({},o,{pointerEvents:"none"});return t("span",{style:c,class:Object(m.f)(e)},[Object(s.a)(e,{style:l,class:null})])}return e},isNoTitle:function(){var e=Object(m.g)(this,"title");return!e&&0!==e},getOverlay:function(){var e=Object(m.g)(this,"title");return 0===e?e:e||""},onPopupAlign:function(e,t){var n=this.getPlacements(),i=Object.keys(n).filter((function(e){return n[e].points[0]===t.points[0]&&n[e].points[1]===t.points[1]}))[0];if(i){var r=e.getBoundingClientRect(),o={top:"50%",left:"50%"};i.indexOf("top")>=0||i.indexOf("Bottom")>=0?o.top=r.height-t.offset[1]+"px":(i.indexOf("Top")>=0||i.indexOf("bottom")>=0)&&(o.top=-t.offset[1]+"px"),i.indexOf("left")>=0||i.indexOf("Right")>=0?o.left=r.width-t.offset[0]+"px":(i.indexOf("right")>=0||i.indexOf("Left")>=0)&&(o.left=-t.offset[0]+"px"),e.style.transformOrigin=o.left+" "+o.top}}},render:function(){var e=arguments[0],t=this.$props,n=this.$data,i=this.$slots,o=t.prefixCls,c=t.openClassName,l=t.getPopupContainer,u=this.configProvider.getPopupContainer,h=this.configProvider.getPrefixCls,d=h("tooltip",o),f=(i.default||[]).filter((function(e){return e.tag||""!==e.text.trim()}));f=1===f.length?f[0]:f;var p=n.sVisible;if(!Object(m.s)(this,"visible")&&this.isNoTitle()&&(p=!1),!f)return null;var v=this.getDisabledCompatibleChildren(Object(m.w)(f)?f:e("span",[f])),g=r()({},c||d+"-open",!0),y={props:a()({},t,{prefixCls:d,getTooltipContainer:l||u,builtinPlacements:this.getPlacements(),overlay:this.getOverlay(),visible:p}),ref:"tooltip",on:a()({},Object(m.k)(this),{visibleChange:this.onVisibleChange,popupAlign:this.onPopupAlign})};return e(b,y,[p?Object(s.a)(v,{class:g}):v])}},k=n(10);M.install=function(e){e.use(k.a),e.component(M.name,M)};t.a=M},function(e,t,n){"use strict";n.d(t,"a",(function(){return o})),n.d(t,"b",(function(){return a}));var i=["moz","ms","webkit"];var r=function(){if("undefined"==typeof window)return function(){};if(window.requestAnimationFrame)return window.requestAnimationFrame.bind(window);var e,t=i.filter((function(e){return e+"RequestAnimationFrame"in window}))[0];return t?window[t+"RequestAnimationFrame"]:(e=0,function(t){var n=(new Date).getTime(),i=Math.max(0,16-(n-e)),r=window.setTimeout((function(){t(n+i)}),i);return e=n+i,r})}(),o=function(e){return function(e){if("undefined"==typeof window)return null;if(window.cancelAnimationFrame)return window.cancelAnimationFrame(e);var t=i.filter((function(e){return e+"CancelAnimationFrame"in window||e+"CancelRequestAnimationFrame"in window}))[0];return t?(window[t+"CancelAnimationFrame"]||window[t+"CancelRequestAnimationFrame"]).call(this,e):clearTimeout(e)}(e.id)},a=function(e,t){var n=Date.now();var i={id:r((function o(){Date.now()-n>=t?e.call():i.id=r(o)}))};return i}},function(e,t,n){var i=n(215);e.exports=function(e,t,n){return null==e?e:i(e,t,n)}},function(e,t){e.exports=function(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}},function(e,t){var n=Array.isArray;e.exports=n},function(e,t,n){var i=n(61),r=n(135),o=n(44),a=Function.prototype,s=Object.prototype,c=a.toString,l=s.hasOwnProperty,u=c.call(Object);e.exports=function(e){if(!o(e)||"[object Object]"!=i(e))return!1;var t=r(e);if(null===t)return!0;var n=l.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==u}},function(e,t,n){"use strict";var i=n(81);t.a=i.a},function(e,t,n){"use strict";n.d(t,"a",(function(){return o})),n.d(t,"b",(function(){return a})),n.d(t,"c",(function(){return s})),n.d(t,"d",(function(){return c})),n.d(t,"g",(function(){return l})),n.d(t,"e",(function(){return h})),n.d(t,"f",(function(){return d}));var i=n(2),r=n.n(i);function o(){return!0}function a(e){return r()({},e,{lastModified:e.lastModified,lastModifiedDate:e.lastModifiedDate,name:e.name,size:e.size,type:e.type,uid:e.uid,percent:0,originFileObj:e})}function s(){var e=.1;return function(t){var n=t;return n>=.98||(n+=e,(e-=.01)<.001&&(e=.001)),n}}function c(e,t){var n=void 0!==e.uid?"uid":"name";return t.filter((function(t){return t[n]===e[n]}))[0]}function l(e,t){var n=void 0!==e.uid?"uid":"name",i=t.filter((function(t){return t[n]!==e[n]}));return i.length===t.length?null:i}var u=function(e){return!!e&&0===e.indexOf("image/")},h=function(e){if(u(e.type))return!0;var t=e.thumbUrl||e.url,n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=e.split("/"),n=t[t.length-1],i=n.split(/#|\?/)[0];return(/\.[^./\\]*$/.exec(i)||[""])[0]}(t);return!(!/^data:image\//.test(t)&&!/(webp|svg|png|gif|jpg|jpeg|jfif|bmp|dpg|ico)$/i.test(n))||!/^data:/.test(t)&&!n};function d(e){return new Promise((function(t){if(u(e.type)){var n=document.createElement("canvas");n.width=200,n.height=200,n.style.cssText="position: fixed; left: 0; top: 0; width: 200px; height: 200px; z-index: 9999; display: none;",document.body.appendChild(n);var i=n.getContext("2d"),r=new Image;r.onload=function(){var e=r.width,o=r.height,a=200,s=200,c=0,l=0;e0},e.prototype.connect_=function(){i&&!this.connected_&&(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),s?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){i&&this.connected_&&(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(e){var t=e.propertyName,n=void 0===t?"":t;a.some((function(e){return!!~n.indexOf(e)}))&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),l=function(e,t){for(var n=0,i=Object.keys(t);n0},e}(),x="undefined"!=typeof WeakMap?new WeakMap:new n,z=function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var n=c.getInstance(),i=new C(t,n,this);x.set(this,i)};["observe","unobserve","disconnect"].forEach((function(e){z.prototype[e]=function(){var t;return(t=x.get(this))[e].apply(t,arguments)}}));var w=void 0!==r.ResizeObserver?r.ResizeObserver:z;t.a=w}).call(this,n(134))},function(e,t,n){"use strict";t.__esModule=!0,t.default=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}},function(e,t,n){"use strict";var i=n(0),r=i.a.oneOf(["hover","focus","click","contextmenu"]);t.a=function(){return{trigger:i.a.oneOfType([r,i.a.arrayOf(r)]).def("hover"),visible:i.a.bool,defaultVisible:i.a.bool,placement:i.a.oneOf(["top","left","right","bottom","topLeft","topRight","bottomLeft","bottomRight","leftTop","leftBottom","rightTop","rightBottom"]).def("top"),transitionName:i.a.string.def("zoom-big-fast"),overlayStyle:i.a.object.def((function(){return{}})),overlayClassName:i.a.string,prefixCls:i.a.string,mouseEnterDelay:i.a.number.def(.1),mouseLeaveDelay:i.a.number.def(.1),getPopupContainer:i.a.func,arrowPointAtCenter:i.a.bool.def(!1),autoAdjustOverflow:i.a.oneOfType([i.a.bool,i.a.object]).def(!0),destroyTooltipOnHide:i.a.bool.def(!1),align:i.a.object.def((function(){return{}})),builtinPlacements:i.a.object}}},function(e,t,n){try{var i=n(180)}catch(e){i=n(180)}var r=/\s+/,o=Object.prototype.toString;function a(e){if(!e||!e.nodeType)throw new Error("A DOM element reference is required");this.el=e,this.list=e.classList}e.exports=function(e){return new a(e)},a.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array();return~i(t,e)||t.push(e),this.el.className=t.join(" "),this},a.prototype.remove=function(e){if("[object RegExp]"==o.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),n=i(t,e);return~n&&t.splice(n,1),this.el.className=t.join(" "),this},a.prototype.removeMatching=function(e){for(var t=this.array(),n=0;n0&&void 0!==arguments[0]?arguments[0]:{};return Object.keys(e).reduce((function(t,n){var i=e[n];switch(n){case"class":t.className=i,delete t.class;break;default:t[n]=i}return t}),{})}var f=function(){function e(){a()(this,e),this.collection={}}return c()(e,[{key:"clear",value:function(){this.collection={}}},{key:"delete",value:function(e){return delete this.collection[e]}},{key:"get",value:function(e){return this.collection[e]}},{key:"has",value:function(e){return Boolean(this.collection[e])}},{key:"set",value:function(e,t){return this.collection[e]=t,this}},{key:"size",get:function(){return Object.keys(this.collection).length}}]),e}();function p(e,t,n,i){return e(t.tag,i?r()({key:n},i,{attrs:r()({},d(t.attrs),i.attrs)}):{key:n,attrs:r()({},d(t.attrs))},(t.children||[]).map((function(i,r){return p(e,i,n+"-"+t.tag+"-"+r)})))}function v(e){return Object(l.generate)(e)[0]}function m(e,t){switch(t){case"fill":return e+"-fill";case"outline":return e+"-o";case"twotone":return e+"-twotone";default:throw new TypeError("Unknown theme type: "+t+", name: "+e)}}}).call(this,n(97))},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t,n){var i=n(71),r=n(272),o=n(273),a=i?i.toStringTag:void 0;e.exports=function(e){return null==e?void 0===e?"[object Undefined]":"[object Null]":a&&a in Object(e)?r(e):o(e)}},function(e,t,n){var i=n(304),r=n(307);e.exports=function(e,t){var n=r(e,t);return i(n)?n:void 0}},function(e,t){e.exports=function(e,t){return e===t||e!=e&&t!=t}},function(e,t,n){"use strict";var i=n(2),r=n.n(i),o=n(45),a=n(65),s={lang:r()({placeholder:"Select date",rangePlaceholder:["Start date","End date"]},o.a),timePickerLocale:r()({},a.a)};t.a=s},function(e,t,n){"use strict";t.a={placeholder:"Select time"}},function(e,t,n){e.exports=function(){"use strict";return function(e,t,n){(n=n||{}).childrenKeyName=n.childrenKeyName||"children";var i=e||[],r=[],o=0;do{var a=i.filter((function(e){return t(e,o)}))[0];if(!a)break;r.push(a),i=a[n.childrenKeyName]||[],o+=1}while(i.length>0);return r}}()},function(e,t,n){var i=n(49),r=n(86);e.exports=n(50)?function(e,t,n){return i.f(e,t,r(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){var i=n(84);e.exports=function(e){if(!i(e))throw TypeError(e+" is not an object!");return e}},function(e,t,n){var i=n(170),r=n(124);e.exports=function(e){return i(r(e))}},function(e,t){e.exports={}},function(e,t,n){var i=n(39).Symbol;e.exports=i},function(e,t,n){var i=n(139),r=n(140);e.exports=function(e,t,n,o){var a=!n;n||(n={});for(var s=-1,c=t.length;++s100?100:e}var b=function(e){var t=e.from,n=void 0===t?"#1890ff":t,i=e.to,r=void 0===i?"#1890ff":i,o=e.direction,a=void 0===o?"to right":o,s=p()(e,["from","to","direction"]);return 0!==Object.keys(s).length?{backgroundImage:"linear-gradient("+a+", "+function(e){var t=[],n=!0,i=!1,r=void 0;try{for(var o,a=Object.entries(e)[Symbol.iterator]();!(n=(o=a.next()).done);n=!0){var s=o.value,c=m()(s,2),l=c[0],u=c[1],h=parseFloat(l.replace(/%/g,""));if(isNaN(h))return{};t.push({key:h,value:u})}}catch(e){i=!0,r=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw r}}return(t=t.sort((function(e,t){return e.key-t.key}))).map((function(e){var t=e.key;return e.value+" "+t+"%"})).join(", ")}(s)+")"}:{backgroundImage:"linear-gradient("+a+", "+n+", "+r+")"}},y={functional:!0,render:function(e,t){var n=t.props,i=t.children,r=n.prefixCls,o=n.percent,s=n.successPercent,c=n.strokeWidth,l=n.size,u=n.strokeColor,h=n.strokeLinecap,d=void 0;d=u&&"string"!=typeof u?b(u):{background:u};var f=a()({width:g(o)+"%",height:(c||("small"===l?6:8))+"px",background:u,borderRadius:"square"===h?0:"100px"},d),p={width:g(s)+"%",height:(c||("small"===l?6:8))+"px",borderRadius:"square"===h?0:""},v=void 0!==s?e("div",{class:r+"-success-bg",style:p}):null;return e("div",[e("div",{class:r+"-outer"},[e("div",{class:r+"-inner"},[e("div",{class:r+"-bg",style:f}),v])]),i])}},C=n(6),x=n.n(C),z=n(17),w=n.n(z),O=n(25),S=n.n(O);var M=function(e){return{mixins:[e],updated:function(){var e=this,t=Date.now(),n=!1;Object.keys(this.paths).forEach((function(i){var r=e.paths[i];if(r){n=!0;var o=r.style;o.transitionDuration=".3s, .3s, .3s, .06s",e.prevTimeStamp&&t-e.prevTimeStamp<100&&(o.transitionDuration="0s, 0s")}})),n&&(this.prevTimeStamp=Date.now())}}},k=l.a.oneOfType([l.a.number,l.a.string]),V={percent:l.a.oneOfType([k,l.a.arrayOf(k)]),prefixCls:l.a.string,strokeColor:l.a.oneOfType([l.a.string,l.a.arrayOf(l.a.oneOfType([l.a.string,l.a.object])),l.a.object]),strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeWidth:k,trailColor:l.a.string,trailWidth:k},T=a()({},V,{gapPosition:l.a.oneOf(["top","bottom","left","right"]),gapDegree:l.a.oneOfType([l.a.number,l.a.string,l.a.bool])}),H=a()({},{percent:0,prefixCls:"rc-progress",strokeColor:"#2db7f5",strokeLinecap:"round",strokeWidth:1,trailColor:"#D9D9D9",trailWidth:1},{gapPosition:"top"});w.a.use(S.a,{name:"ant-ref"});var j=0;function P(e){return+e.replace("%","")}function L(e){return Array.isArray(e)?e:[e]}function _(e,t,n,i){var r=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0,o=arguments[5],a=50-i/2,s=0,c=-a,l=0,u=-2*a;switch(o){case"left":s=-a,c=0,l=2*a,u=0;break;case"right":s=a,c=0,l=-2*a,u=0;break;case"bottom":c=a,u=2*a}var h="M 50,50 m "+s+","+c+"\n a "+a+","+a+" 0 1 1 "+l+","+-u+"\n a "+a+","+a+" 0 1 1 "+-l+","+u,d=2*Math.PI*a,f={stroke:n,strokeDasharray:t/100*(d-r)+"px "+d+"px",strokeDashoffset:"-"+(r/2+e/100*(d-r))+"px",transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s"};return{pathString:h,pathStyle:f}}var $=M({props:Object(u.t)(T,H),created:function(){this.paths={},this.gradientId=j,j+=1},methods:{getStokeList:function(){var e=this,t=this.$createElement,n=this.$props,i=n.prefixCls,r=n.percent,o=n.strokeColor,a=n.strokeWidth,s=n.strokeLinecap,c=n.gapDegree,l=n.gapPosition,u=L(r),h=L(o),d=0;return u.map((function(n,r){var o=h[r]||h[h.length-1],u="[object Object]"===Object.prototype.toString.call(o)?"url(#"+i+"-gradient-"+e.gradientId+")":"",f=_(d,n,o,a,c,l),p=f.pathString,v=f.pathStyle;return d+=n,t("path",{key:r,attrs:{d:p,stroke:u,"stroke-linecap":s,"stroke-width":a,opacity:0===n?0:1,"fill-opacity":"0"},class:i+"-circle-path",style:v,directives:[{name:"ant-ref",value:function(t){e.paths[r]=t}}]})}))}},render:function(){var e=arguments[0],t=this.$props,n=t.prefixCls,i=t.strokeWidth,r=t.trailWidth,o=t.gapDegree,a=t.gapPosition,s=t.trailColor,c=t.strokeLinecap,l=t.strokeColor,u=p()(t,["prefixCls","strokeWidth","trailWidth","gapDegree","gapPosition","trailColor","strokeLinecap","strokeColor"]),h=_(0,100,s,i,o,a),d=h.pathString,f=h.pathStyle;delete u.percent;var v=L(l),m=v.find((function(e){return"[object Object]"===Object.prototype.toString.call(e)})),g={attrs:{d:d,stroke:s,"stroke-linecap":c,"stroke-width":r||i,"fill-opacity":"0"},class:n+"-circle-trail",style:f};return e("svg",x()([{class:n+"-circle",attrs:{viewBox:"0 0 100 100"}},u]),[m&&e("defs",[e("linearGradient",{attrs:{id:n+"-gradient-"+this.gradientId,x1:"100%",y1:"0%",x2:"0%",y2:"0%"}},[Object.keys(m).sort((function(e,t){return P(e)-P(t)})).map((function(t,n){return e("stop",{key:n,attrs:{offset:t,"stop-color":m[t]}})}))])]),e("path",g),this.getStokeList().reverse()])}}),E={normal:"#108ee9",exception:"#ff5500",success:"#87d068"};function A(e){var t=e.percent,n=e.successPercent,i=g(t);if(!n)return i;var r=g(n);return[n,g(i-r)]}var F={functional:!0,render:function(e,t){var n,i,o,a,s,c=t.props,l=t.children,u=c.prefixCls,h=c.width,d=c.strokeWidth,f=c.trailColor,p=c.strokeLinecap,v=c.gapPosition,m=c.gapDegree,g=c.type,b=h||120,y={width:"number"==typeof b?b+"px":b,height:"number"==typeof b?b+"px":b,fontSize:.15*b+6},C=d||6,x=v||"dashboard"===g&&"bottom"||"top",z=m||"dashboard"===g&&75,w=(o=(i=c).progressStatus,a=i.successPercent,s=i.strokeColor||E[o],a?[E.success,s]:s),O="[object Object]"===Object.prototype.toString.call(w);return e("div",{class:(n={},r()(n,u+"-inner",!0),r()(n,u+"-circle-gradient",O),n),style:y},[e($,{attrs:{percent:A(c),strokeWidth:C,trailWidth:C,strokeColor:w,strokeLinecap:p,trailColor:f,prefixCls:u,gapDegree:z,gapPosition:x}}),l])}},I=["normal","exception","active","success"],R=l.a.oneOf(["line","circle","dashboard"]),D=l.a.oneOf(["default","small"]),N={prefixCls:l.a.string,type:R,percent:l.a.number,successPercent:l.a.number,format:l.a.func,status:l.a.oneOf(I),showInfo:l.a.bool,strokeWidth:l.a.number,strokeLinecap:l.a.oneOf(["butt","round","square"]),strokeColor:l.a.oneOfType([l.a.string,l.a.object]),trailColor:l.a.string,width:l.a.number,gapDegree:l.a.number,gapPosition:l.a.oneOf(["top","bottom","left","right"]),size:D},K={name:"AProgress",props:Object(u.t)(N,{type:"line",percent:0,showInfo:!0,trailColor:"#f3f3f3",size:"default",gapDegree:0,strokeLinecap:"round"}),inject:{configProvider:{default:function(){return h.a}}},methods:{getPercentNumber:function(){var e=this.$props,t=e.successPercent,n=e.percent,i=void 0===n?0:n;return parseInt(void 0!==t?t.toString():i.toString(),10)},getProgressStatus:function(){var e=this.$props.status;return I.indexOf(e)<0&&this.getPercentNumber()>=100?"success":e||"normal"},renderProcessInfo:function(e,t){var n=this.$createElement,i=this.$props,r=i.showInfo,o=i.format,a=i.type,s=i.percent,c=i.successPercent;if(!r)return null;var l=void 0,u=o||this.$scopedSlots.format||function(e){return e+"%"},h="circle"===a||"dashboard"===a?"":"-circle";return o||this.$scopedSlots.format||"exception"!==t&&"success"!==t?l=u(g(s),g(c)):"exception"===t?l=n(d.a,{attrs:{type:"close"+h,theme:"line"===a?"filled":"outlined"}}):"success"===t&&(l=n(d.a,{attrs:{type:"check"+h,theme:"line"===a?"filled":"outlined"}})),n("span",{class:e+"-text",attrs:{title:"string"==typeof l?l:void 0}},[l])}},render:function(){var e,t=arguments[0],n=Object(u.l)(this),i=n.prefixCls,o=n.size,s=n.type,l=n.showInfo,h=this.configProvider.getPrefixCls,d=h("progress",i),f=this.getProgressStatus(),p=this.renderProcessInfo(d,f),v=void 0;if("line"===s){var m={props:a()({},n,{prefixCls:d})};v=t(y,m,[p])}else if("circle"===s||"dashboard"===s){var g={props:a()({},n,{prefixCls:d,progressStatus:f})};v=t(F,g,[p])}var b=c()(d,(e={},r()(e,d+"-"+("dashboard"===s?"circle":s),!0),r()(e,d+"-status-"+f,!0),r()(e,d+"-show-info",l),r()(e,d+"-"+o,o),e)),C={on:Object(u.k)(this),class:b};return t("div",C,[v])}},B=n(10);K.install=function(e){e.use(B.a),e.component(K.name,K)};t.a=K},function(e,t,n){"use strict";t.a={items_per_page:"/ page",jump_to:"Go to",jump_to_confirm:"confirm",page:"",prev_page:"Previous Page",next_page:"Next Page",prev_5:"Previous 5 Pages",next_5:"Next 5 Pages",prev_3:"Previous 3 Pages",next_3:"Next 3 Pages"}},function(e,t,n){"use strict";var i=n(64);t.a=i.a},function(e,t,n){var i=n(169),r=n(128);e.exports=Object.keys||function(e){return i(e,r)}},function(e,t){e.exports=!0},function(e,t){var n=0,i=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++n+i).toString(36))}},function(e,t){t.f={}.propertyIsEnumerable},function(e,t,n){var i=n(124);e.exports=function(e){return Object(i(e))}},function(e,t,n){"use strict";var i=n(253)(!0);n(172)(String,"String",(function(e){this._t=String(e),this._i=0}),(function(){var e,t=this._t,n=this._i;return n>=t.length?{value:void 0,done:!0}:(e=i(t,n),this._i+=e.length,{value:e,done:!1})}))},function(e,t){var n,i,r=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{i="function"==typeof clearTimeout?clearTimeout:a}catch(e){i=a}}();var c,l=[],u=!1,h=-1;function d(){u&&c&&(u=!1,c.length?l=c.concat(l):h=-1,l.length&&f())}function f(){if(!u){var e=s(d);u=!0;for(var t=l.length;t;){for(c=l,l=[];++h1)for(var n=1;n-1&&e%1==0&&e0;var o=function(e,t){for(var n=Object.create(null),i=e.split(","),r=0;r1),t})),s(e,u(e),n),l&&(n=r(n,7,c));for(var h=t.length;h--;)o(n,t[h]);return n}));e.exports=h},function(e,t,n){var i=n(368),r=n(106),o=n(107),a=o&&o.isRegExp,s=a?r(a):i;e.exports=s},function(e,t,n){"use strict";(function(e){function n(){return(n=Object.assign||function(e){for(var t=1;t=o)return e;switch(e){case"%s":return String(t[i++]);case"%d":return Number(t[i++]);case"%j":try{return JSON.stringify(t[i++])}catch(e){return"[Circular]"}break;default:return e}}));return a}return r}function d(e,t){return null==e||(!("array"!==t||!Array.isArray(e)||e.length)||!(!function(e){return"string"===e||"url"===e||"hex"===e||"email"===e||"date"===e||"pattern"===e}(t)||"string"!=typeof e||e))}function f(e,t,n){var i=0,r=e.length;!function o(a){if(a&&a.length)n(a);else{var s=i;i+=1,s()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,url:new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$","i"),hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},C={integer:function(e){return C.number(e)&&parseInt(e,10)===e},float:function(e){return C.number(e)&&!C.integer(e)},array:function(e){return Array.isArray(e)},regexp:function(e){if(e instanceof RegExp)return!0;try{return!!new RegExp(e)}catch(e){return!1}},date:function(e){return"function"==typeof e.getTime&&"function"==typeof e.getMonth&&"function"==typeof e.getYear&&!isNaN(e.getTime())},number:function(e){return!isNaN(e)&&"number"==typeof e},object:function(e){return"object"==typeof e&&!C.array(e)},method:function(e){return"function"==typeof e},email:function(e){return"string"==typeof e&&!!e.match(y.email)&&e.length<255},url:function(e){return"string"==typeof e&&!!e.match(y.url)},hex:function(e){return"string"==typeof e&&!!e.match(y.hex)}};var x={required:b,whitespace:function(e,t,n,i,r){(/^\s+$/.test(t)||""===t)&&i.push(h(r.messages.whitespace,e.fullField))},type:function(e,t,n,i,r){if(e.required&&void 0===t)b(e,t,n,i,r);else{var o=e.type;["integer","float","array","regexp","object","method","email","number","date","url","hex"].indexOf(o)>-1?C[o](t)||i.push(h(r.messages.types[o],e.fullField,e.type)):o&&typeof t!==e.type&&i.push(h(r.messages.types[o],e.fullField,e.type))}},range:function(e,t,n,i,r){var o="number"==typeof e.len,a="number"==typeof e.min,s="number"==typeof e.max,c=t,l=null,u="number"==typeof t,d="string"==typeof t,f=Array.isArray(t);if(u?l="number":d?l="string":f&&(l="array"),!l)return!1;f&&(c=t.length),d&&(c=t.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,"_").length),o?c!==e.len&&i.push(h(r.messages[l].len,e.fullField,e.len)):a&&!s&&ce.max?i.push(h(r.messages[l].max,e.fullField,e.max)):a&&s&&(ce.max)&&i.push(h(r.messages[l].range,e.fullField,e.min,e.max))},enum:function(e,t,n,i,r){e.enum=Array.isArray(e.enum)?e.enum:[],-1===e.enum.indexOf(t)&&i.push(h(r.messages.enum,e.fullField,e.enum.join(", ")))},pattern:function(e,t,n,i,r){if(e.pattern)if(e.pattern instanceof RegExp)e.pattern.lastIndex=0,e.pattern.test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern));else if("string"==typeof e.pattern){new RegExp(e.pattern).test(t)||i.push(h(r.messages.pattern.mismatch,e.fullField,t,e.pattern))}}};function z(e,t,n,i,r){var o=e.type,a=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,o)&&!e.required)return n();x.required(e,t,i,a,r,o),d(t,o)||x.type(e,t,i,a,r)}n(a)}var w={string:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,o,r,"string"),d(t,"string")||(x.type(e,t,i,o,r),x.range(e,t,i,o,r),x.pattern(e,t,i,o,r),!0===e.whitespace&&x.whitespace(e,t,i,o,r))}n(o)},method:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&x.type(e,t,i,o,r)}n(o)},number:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(""===t&&(t=void 0),d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&(x.type(e,t,i,o,r),x.range(e,t,i,o,r))}n(o)},boolean:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&x.type(e,t,i,o,r)}n(o)},regexp:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),d(t)||x.type(e,t,i,o,r)}n(o)},integer:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&(x.type(e,t,i,o,r),x.range(e,t,i,o,r))}n(o)},float:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&(x.type(e,t,i,o,r),x.range(e,t,i,o,r))}n(o)},array:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(null==t&&!e.required)return n();x.required(e,t,i,o,r,"array"),null!=t&&(x.type(e,t,i,o,r),x.range(e,t,i,o,r))}n(o)},object:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&x.type(e,t,i,o,r)}n(o)},enum:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r),void 0!==t&&x.enum(e,t,i,o,r)}n(o)},pattern:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"string")&&!e.required)return n();x.required(e,t,i,o,r),d(t,"string")||x.pattern(e,t,i,o,r)}n(o)},date:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t,"date")&&!e.required)return n();var a;if(x.required(e,t,i,o,r),!d(t,"date"))a=t instanceof Date?t:new Date(t),x.type(e,a,i,o,r),a&&x.range(e,a.getTime(),i,o,r)}n(o)},url:z,hex:z,email:z,required:function(e,t,n,i,r){var o=[],a=Array.isArray(t)?"array":typeof t;x.required(e,t,i,o,r,a),n(o)},any:function(e,t,n,i,r){var o=[];if(e.required||!e.required&&i.hasOwnProperty(e.field)){if(d(t)&&!e.required)return n();x.required(e,t,i,o,r)}n(o)}};function O(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var e=JSON.parse(JSON.stringify(this));return e.clone=this.clone,e}}}var S=O();function M(e){this.rules=null,this._messages=S,this.define(e)}M.prototype={messages:function(e){return e&&(this._messages=g(O(),e)),this._messages},define:function(e){if(!e)throw new Error("Cannot configure a schema with no rules");if("object"!=typeof e||Array.isArray(e))throw new Error("Rules must be an object");var t,n;for(t in this.rules={},e)e.hasOwnProperty(t)&&(n=e[t],this.rules[t]=Array.isArray(n)?n:[n])},validate:function(e,t,i){var r=this;void 0===t&&(t={}),void 0===i&&(i=function(){});var o,a,s=e,c=t,l=i;if("function"==typeof c&&(l=c,c={}),!this.rules||0===Object.keys(this.rules).length)return l&&l(),Promise.resolve();if(c.messages){var d=this.messages();d===S&&(d=O()),g(d,c.messages),c.messages=d}else c.messages=this.messages();var f={};(c.keys||Object.keys(this.rules)).forEach((function(t){o=r.rules[t],a=s[t],o.forEach((function(i){var o=i;"function"==typeof o.transform&&(s===e&&(s=n({},s)),a=s[t]=o.transform(a)),(o="function"==typeof o?{validator:o}:n({},o)).validator=r.getValidationMethod(o),o.field=t,o.fullField=o.fullField||t,o.type=r.getType(o),o.validator&&(f[t]=f[t]||[],f[t].push({rule:o,value:a,source:s,field:t}))}))}));var p={};return v(f,c,(function(e,t){var i,r=e.rule,o=!("object"!==r.type&&"array"!==r.type||"object"!=typeof r.fields&&"object"!=typeof r.defaultField);function a(e,t){return n({},t,{fullField:r.fullField+"."+e})}function s(i){void 0===i&&(i=[]);var s=i;if(Array.isArray(s)||(s=[s]),!c.suppressWarning&&s.length&&M.warning("async-validator:",s),s.length&&void 0!==r.message&&(s=[].concat(r.message)),s=s.map(m(r)),c.first&&s.length)return p[r.field]=1,t(s);if(o){if(r.required&&!e.value)return void 0!==r.message?s=[].concat(r.message).map(m(r)):c.error&&(s=[c.error(r,h(c.messages.required,r.field))]),t(s);var l={};if(r.defaultField)for(var u in e.value)e.value.hasOwnProperty(u)&&(l[u]=r.defaultField);for(var d in l=n({},l,e.rule.fields))if(l.hasOwnProperty(d)){var f=Array.isArray(l[d])?l[d]:[l[d]];l[d]=f.map(a.bind(null,d))}var v=new M(l);v.messages(c.messages),e.rule.options&&(e.rule.options.messages=c.messages,e.rule.options.error=c.error),v.validate(e.value,e.rule.options||c,(function(e){var n=[];s&&s.length&&n.push.apply(n,s),e&&e.length&&n.push.apply(n,e),t(n.length?n:null)}))}else t(s)}o=o&&(r.required||!r.required&&e.value),r.field=e.field,r.asyncValidator?i=r.asyncValidator(r,e.value,s,e.source,c):r.validator&&(!0===(i=r.validator(r,e.value,s,e.source,c))?s():!1===i?s(r.message||r.field+" fails"):i instanceof Array?s(i):i instanceof Error&&s(i.message)),i&&i.then&&i.then((function(){return s()}),(function(e){return s(e)}))}),(function(e){!function(e){var t,n,i,r=[],o={};for(t=0;t0?i:n)(e)}},function(e,t,n){var i=n(127)("keys"),r=n(93);e.exports=function(e){return i[e]||(i[e]=r(e))}},function(e,t,n){var i=n(43),r=n(48),o=r["__core-js_shared__"]||(r["__core-js_shared__"]={});(e.exports=function(e,t){return o[e]||(o[e]=void 0!==t?t:{})})("versions",[]).push({version:i.version,mode:n(92)?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){var i=n(49).f,r=n(60),o=n(38)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,o)&&i(e,o,{configurable:!0,value:t})}},function(e,t,n){n(258);for(var i=n(48),r=n(67),o=n(70),a=n(38)("toStringTag"),s="CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","),c=0;c-1&&e%1==0&&e<=9007199254740991}},function(e,t){var n=Object.prototype;e.exports=function(e){var t=e&&e.constructor;return e===("function"==typeof t&&t.prototype||n)}},function(e,t,n){var i=n(332),r=n(187),o=Object.prototype.propertyIsEnumerable,a=Object.getOwnPropertySymbols,s=a?function(e){return null==e?[]:(e=Object(e),i(a(e),(function(t){return o.call(e,t)})))}:r;e.exports=s},function(e,t){e.exports=function(e,t){for(var n=-1,i=t.length,r=e.length;++n0&&(t.percent=t.loaded/t.total*100),e.onProgress(t)});var n=new window.FormData;e.data&&Object.keys(e.data).forEach((function(t){var i=e.data[t];Array.isArray(i)?i.forEach((function(e){n.append(t+"[]",e)})):n.append(t,e.data[t])})),n.append(e.filename,e.file),t.onerror=function(t){e.onError(t)},t.onload=function(){if(t.status<200||t.status>=300)return e.onError(function(e,t){var n="cannot "+e.method+" "+e.action+" "+t.status+"'",i=new Error(n);return i.status=t.status,i.method=e.method,i.url=e.action,i}(e,t),p(t));e.onSuccess(p(t),t)},t.open(e.method,e.action,!0),e.withCredentials&&"withCredentials"in t&&(t.withCredentials=!0);var i=e.headers||{};for(var r in null!==i["X-Requested-With"]&&t.setRequestHeader("X-Requested-With","XMLHttpRequest"),i)i.hasOwnProperty(r)&&null!==i[r]&&t.setRequestHeader(r,i[r]);return t.send(n),{abort:function(){t.abort()}}}var m=+new Date,g=0;function b(){return"vc-upload-"+m+"-"+ ++g}var y=function(e,t){if(e&&t){var n=Array.isArray(t)?t:t.split(","),i=e.name||"",r=e.type||"",o=r.replace(/\/.*$/,"");return n.some((function(e){var t,n,a=e.trim();return"."===a.charAt(0)?(t=i.toLowerCase(),n=a.toLowerCase(),-1!==t.indexOf(n,t.length-n.length)):/\/\*$/.test(a)?o===a.replace(/\/.*$/,""):r===a}))}return!0};var C=function(e,t,n){var i=function e(i,r){r=r||"",i.isFile?i.file((function(e){n(e)&&(i.fullPath&&!e.webkitRelativePath&&(Object.defineProperties(e,{webkitRelativePath:{writable:!0}}),e.webkitRelativePath=i.fullPath.replace(/^\//,""),Object.defineProperties(e,{webkitRelativePath:{writable:!1}})),t([e]))})):i.isDirectory&&function(e,t){var n=e.createReader(),i=[];!function e(){n.readEntries((function(n){var r=Array.prototype.slice.apply(n);i=i.concat(r),!r.length?t(i):e()}))}()}(i,(function(t){t.forEach((function(t){e(t,""+r+i.name+"/")}))}))},r=!0,o=!1,a=void 0;try{for(var s,c=e[Symbol.iterator]();!(r=(s=c.next()).done);r=!0){i(s.value.webkitGetAsEntry())}}catch(e){o=!0,a=e}finally{try{!r&&c.return&&c.return()}finally{if(o)throw a}}},x={componentTag:o.a.string,prefixCls:o.a.string,name:o.a.string,multiple:o.a.bool,directory:o.a.bool,disabled:o.a.bool,accept:o.a.string,data:o.a.oneOfType([o.a.object,o.a.func]),action:o.a.oneOfType([o.a.string,o.a.func]),headers:o.a.object,beforeUpload:o.a.func,customRequest:o.a.func,withCredentials:o.a.bool,openFileDialogOnClick:o.a.bool,transformFile:o.a.func,method:o.a.string},z={inheritAttrs:!1,name:"ajaxUploader",mixins:[s.a],props:x,data:function(){return this.reqs={},{uid:b()}},mounted:function(){this._isMounted=!0},beforeDestroy:function(){this._isMounted=!1,this.abort()},methods:{onChange:function(e){var t=e.target.files;this.uploadFiles(t),this.reset()},onClick:function(){var e=this.$refs.fileInputRef;e&&e.click()},onKeyDown:function(e){"Enter"===e.key&&this.onClick()},onFileDrop:function(e){var t=this,n=this.$props.multiple;if(e.preventDefault(),"dragover"!==e.type)if(this.directory)C(e.dataTransfer.items,this.uploadFiles,(function(e){return y(e,t.accept)}));else{var i=h()(Array.prototype.slice.call(e.dataTransfer.files),(function(e){return y(e,t.accept)})),r=i[0],o=i[1];!1===n&&(r=r.slice(0,1)),this.uploadFiles(r),o.length&&this.$emit("reject",o)}},uploadFiles:function(e){var t=this,n=Array.prototype.slice.call(e);n.map((function(e){return e.uid=b(),e})).forEach((function(e){t.upload(e,n)}))},upload:function(e,t){var n=this;if(!this.beforeUpload)return setTimeout((function(){return n.post(e)}),0);var i=this.beforeUpload(e,t);i&&i.then?i.then((function(t){var i=Object.prototype.toString.call(t);return"[object File]"===i||"[object Blob]"===i?n.post(t):n.post(e)})).catch((function(e){console&&console.log(e)})):!1!==i&&setTimeout((function(){return n.post(e)}),0)},post:function(e){var t=this;if(this._isMounted){var n=this.$props,i=n.data,r=n.transformFile,o=void 0===r?function(e){return e}:r;new Promise((function(n){var i=t.action;if("function"==typeof i)return n(i(e));n(i)})).then((function(r){var a=e.uid,s=t.customRequest||v;Promise.resolve(o(e)).catch((function(e){console.error(e)})).then((function(o){"function"==typeof i&&(i=i(e));var c={action:r,filename:t.name,data:i,file:o,headers:t.headers,withCredentials:t.withCredentials,method:n.method||"post",onProgress:function(n){t.$emit("progress",n,e)},onSuccess:function(n,i){delete t.reqs[a],t.$emit("success",n,e,i)},onError:function(n,i){delete t.reqs[a],t.$emit("error",n,i,e)}};t.reqs[a]=s(c),t.$emit("start",e)}))}))}},reset:function(){this.setState({uid:b()})},abort:function(e){var t=this.reqs;if(e){var n=e;e&&e.uid&&(n=e.uid),t[n]&&t[n].abort&&t[n].abort(),delete t[n]}else Object.keys(t).forEach((function(e){t[e]&&t[e].abort&&t[e].abort(),delete t[e]}))}},render:function(){var e,t=arguments[0],n=this.$props,i=this.$attrs,o=n.componentTag,s=n.prefixCls,c=n.disabled,u=n.multiple,h=n.accept,d=n.directory,p=n.openFileDialogOnClick,v=f()((e={},l()(e,s,!0),l()(e,s+"-disabled",c),e)),m=c?{}:{click:p?this.onClick:function(){},keydown:p?this.onKeyDown:function(){},drop:this.onFileDrop,dragover:this.onFileDrop},g={on:r()({},Object(a.k)(this),m),attrs:{role:"button",tabIndex:c?null:"0"},class:v};return t(o,g,[t("input",{attrs:{id:i.id,type:"file",accept:h,directory:d?"directory":null,webkitdirectory:d?"webkitdirectory":null,multiple:u},ref:"fileInputRef",on:{click:function(e){return e.stopPropagation()},change:this.onChange},key:this.uid,style:{display:"none"}}),this.$slots.default])}},w=n(13),O={position:"absolute",top:0,opacity:0,filter:"alpha(opacity=0)",left:0,zIndex:9999},S={mixins:[s.a],props:{componentTag:o.a.string,disabled:o.a.bool,prefixCls:o.a.string,accept:o.a.string,multiple:o.a.bool,data:o.a.oneOfType([o.a.object,o.a.func]),action:o.a.oneOfType([o.a.string,o.a.func]),name:o.a.string},data:function(){return this.file={},{uploading:!1}},methods:{onLoad:function(){if(this.uploading){var e=this.file,t=void 0;try{var n=this.getIframeDocument(),i=n.getElementsByTagName("script")[0];i&&i.parentNode===n.body&&n.body.removeChild(i),t=n.body.innerHTML,this.$emit("success",t,e)}catch(n){Object(w.a)(!1,"cross domain error for Upload. Maybe server should return document.domain script. see Note from https://github.com/react-component/upload"),t="cross-domain",this.$emit("error",n,null,e)}this.endUpload()}},onChange:function(){var e=this,t=this.getFormInputNode(),n=this.file={uid:b(),name:t.value&&t.value.substring(t.value.lastIndexOf("\\")+1,t.value.length)};this.startUpload();var i=this.$props;if(!i.beforeUpload)return this.post(n);var r=i.beforeUpload(n);r&&r.then?r.then((function(){e.post(n)}),(function(){e.endUpload()})):!1!==r?this.post(n):this.endUpload()},getIframeNode:function(){return this.$refs.iframeRef},getIframeDocument:function(){return this.getIframeNode().contentDocument},getFormNode:function(){return this.getIframeDocument().getElementById("form")},getFormInputNode:function(){return this.getIframeDocument().getElementById("input")},getFormDataNode:function(){return this.getIframeDocument().getElementById("data")},getFileForMultiple:function(e){return this.multiple?[e]:e},getIframeHTML:function(e){var t="",n="";if(e){t='` is injected +// just before so the AppSidebar's link generator sees the +// right prefix. +// 2. Absolute Vite-emitted asset URLs (`/assets/...`) are rewritten +// to include the panel's basePath, so installs running under a +// custom URL prefix (e.g. `/myprefix/`) load the bundle from +// `/myprefix/assets/...` where the static handler actually lives. +// +// The HTML responses are served with no-cache so a panel update +// reaches users on the next reload; the long-hashed JS/CSS files +// under /assets/ stay cacheable indefinitely. +func serveDistPage(c *gin.Context, name string) { + body, err := distFS.ReadFile("dist/" + name) + if err != nil { + c.String(http.StatusInternalServerError, "missing embedded page: %s", name) + return + } + + basePath := c.GetString("base_path") + if basePath == "" { + basePath = "/" + } + + // Rewrite asset URLs only when basePath isn't the root — for the + // default `/` install, Vite's `/assets/...` already resolves + // correctly and we save the byte churn. + if basePath != "/" { + // Vite emits these three attribute shapes for every entry's + // JS / CSS / modulepreload reference. Anchoring the search to + // the leading attribute name avoids matching unrelated /assets + // substrings inside any inlined script. + body = bytes.ReplaceAll(body, []byte(`src="/assets/`), []byte(`src="`+basePath+`assets/`)) + body = bytes.ReplaceAll(body, []byte(`href="/assets/`), []byte(`href="`+basePath+`assets/`)) + } + + // Escape just enough that a hostile basePath setting can't break + // out of the JS string literal. The setting is admin-controlled + // but defense-in-depth costs nothing here. + jsEscape := strings.NewReplacer( + `\`, `\\`, + `"`, `\"`, + "\n", `\n`, + "\r", `\r`, + "<", `<`, + ">", `>`, + "&", `&`, + ) + escapedBase := jsEscape.Replace(basePath) + escapedVer := jsEscape.Replace(config.GetVersion()) + + // Embed a CSRF token in the served HTML the same way the legacy + // templates did via ``. Without this the + // SPA login page has no way to acquire a token (the existing + // /panel/csrf-token endpoint sits behind checkLogin), and POST + // /login is rejected by CSRFMiddleware. EnsureCSRFToken creates + // a session token on first call even for anonymous visitors. + csrfToken, err := session.EnsureCSRFToken(c) + if err != nil { + logger.Warning("Unable to mint CSRF token for", name+":", err) + csrfToken = "" + } + csrfMeta := []byte(``) + + inject := []byte(``) + inject = append(inject, csrfMeta...) + inject = append(inject, []byte(``)...) + out := bytes.Replace(body, []byte(""), inject, 1) + + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + c.Header("Pragma", "no-cache") + c.Header("Expires", "0") + c.Header("Last-Modified", distPageBuildTime.UTC().Format(http.TimeFormat)) + c.Data(http.StatusOK, "text/html; charset=utf-8", out) +} diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 5b8ad38b..477d46cc 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -148,10 +148,23 @@ func (a *InboundController) addInbound(c *gin.Context) { } user := session.GetLoginUser(c) inbound.UserId = user.Id - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) + // Treat NodeID=0 as "no node" — gin's *int form binding can land on + // 0 when the field is absent or empty, and 0 is never a valid Node + // row id. Without this normalization the runtime layer would try to + // load Node id=0 and surface "record not found". + if inbound.NodeID != nil && *inbound.NodeID == 0 { + inbound.NodeID = nil + } + // When the central panel deploys an inbound to a remote node, it sends + // the Tag pre-computed (so both DBs agree on the identifier). Local + // UI submits don't include a Tag — we compute one from listen+port + // using the original collision-avoiding scheme. + if inbound.Tag == "" { + if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { + inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) + } else { + inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) + } } inbound, needRestart, err := a.inboundService.AddInbound(inbound) @@ -201,6 +214,13 @@ func (a *InboundController) updateInbound(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err) return } + // Same NodeID=0 → nil normalisation as addInbound. UpdateInbound + // loads the existing row's NodeID from DB anyway (Phase 1 doesn't + // support migrating an inbound between nodes), but normalising here + // keeps the wire shape consistent. + if inbound.NodeID != nil && *inbound.NodeID == 0 { + inbound.NodeID = nil + } inbound, needRestart, err := a.inboundService.UpdateInbound(inbound) if err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) @@ -458,10 +478,12 @@ func (a *InboundController) importInbound(c *gin.Context) { user := session.GetLoginUser(c) inbound.Id = 0 inbound.UserId = user.Id - if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { - inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - } else { - inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) + if inbound.Tag == "" { + if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" { + inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) + } else { + inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port) + } } for index := range inbound.ClientStats { diff --git a/web/controller/index.go b/web/controller/index.go index d3c58da8..1a719a98 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -40,6 +40,12 @@ func NewIndexController(g *gin.RouterGroup) *IndexController { func (a *IndexController) initRouter(g *gin.RouterGroup) { g.GET("/", a.index) g.GET("/logout", a.logout) + // Public CSRF endpoint — the SPA login page (served by Vite in + // dev or by serveDistPage in prod) needs a token to POST /login, + // but the panel-side /panel/csrf-token sits behind checkLogin. + // EnsureCSRFToken creates a session token even for anonymous + // callers, so any pre-login flow can bootstrap from here. + g.GET("/csrf-token", a.csrfToken) g.POST("/login", middleware.CSRFMiddleware(), a.login) g.POST("/getTwoFactorEnable", middleware.CSRFMiddleware(), a.getTwoFactorEnable) @@ -51,7 +57,7 @@ func (a *IndexController) index(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, "panel/") return } - html(c, "login.html", "pages.login.title", nil) + serveDistPage(c, "login.html") } // login handles user authentication and session creation. @@ -145,6 +151,17 @@ func (a *IndexController) logout(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) } +// csrfToken returns the session CSRF token. Public — the login page +// needs a token before authenticating. +func (a *IndexController) csrfToken(c *gin.Context) { + token, err := session.EnsureCSRFToken(c) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"success": false, "msg": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"success": true, "obj": token}) +} + // getTwoFactorEnable retrieves the current status of two-factor authentication. func (a *IndexController) getTwoFactorEnable(c *gin.Context) { status, err := a.settingService.GetTwoFactorEnable() diff --git a/web/controller/node.go b/web/controller/node.go new file mode 100644 index 00000000..715f1a27 --- /dev/null +++ b/web/controller/node.go @@ -0,0 +1,211 @@ +package controller + +import ( + "context" + "fmt" + "slices" + "strconv" + "time" + + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/web/service" + + "github.com/gin-gonic/gin" +) + +// NodeController exposes CRUD + probe endpoints for managing remote +// 3x-ui panels registered as nodes. All routes mount under +// /panel/api/nodes/ via APIController.initRouter and inherit its +// session-or-bearer auth from checkAPIAuth. +type NodeController struct { + nodeService service.NodeService +} + +// NewNodeController creates the controller and wires its routes onto g. +func NewNodeController(g *gin.RouterGroup) *NodeController { + a := &NodeController{} + a.initRouter(g) + return a +} + +func (a *NodeController) initRouter(g *gin.RouterGroup) { + g.GET("/list", a.list) + g.GET("/get/:id", a.get) + + g.POST("/add", a.add) + g.POST("/update/:id", a.update) + g.POST("/del/:id", a.del) + g.POST("/setEnable/:id", a.setEnable) + + // /test takes a transient payload (no DB write) so the user can + // validate connectivity before saving the node. + g.POST("/test", a.test) + // /probe/:id triggers a synchronous probe of an already-saved node + // without waiting for the next 10s heartbeat tick. + g.POST("/probe/:id", a.probe) + // /history/:id/:metric/:bucket returns up to 60 averaged buckets of + // the per-node CPU or Mem time series collected by the heartbeat job. + g.GET("/history/:id/:metric/:bucket", a.history) +} + +func (a *NodeController) list(c *gin.Context) { + nodes, err := a.nodeService.GetAll() + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.list"), err) + return + } + jsonObj(c, nodes, nil) +} + +func (a *NodeController) get(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + n, err := a.nodeService.GetById(id) + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.obtain"), err) + return + } + jsonObj(c, n, nil) +} + +func (a *NodeController) add(c *gin.Context) { + n := &model.Node{} + if err := c.ShouldBind(n); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.add"), err) + return + } + if err := a.nodeService.Create(n); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.add"), err) + return + } + jsonMsgObj(c, I18nWeb(c, "pages.nodes.toasts.add"), n, nil) +} + +func (a *NodeController) update(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + n := &model.Node{} + if err := c.ShouldBind(n); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), err) + return + } + if err := a.nodeService.Update(id, n); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), err) + return + } + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), nil) +} + +func (a *NodeController) del(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + if err := a.nodeService.Delete(id); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.delete"), err) + return + } + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.delete"), nil) +} + +// setEnable accepts a JSON body { "enable": bool } so the toggle +// switch can flip a node without sending the whole record back. +func (a *NodeController) setEnable(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + body := struct { + Enable bool `json:"enable" form:"enable"` + }{} + if err := c.ShouldBind(&body); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), err) + return + } + if err := a.nodeService.SetEnable(id, body.Enable); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), err) + return + } + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.update"), nil) +} + +// test runs Probe against a transient Node payload without writing to +// the DB. Used by the form modal to validate connectivity before save. +func (a *NodeController) test(c *gin.Context) { + n := &model.Node{} + if err := c.ShouldBind(n); err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.test"), err) + return + } + // Reuse normalize-style defaults so the form can leave scheme/basePath + // blank and still get a sensible probe URL. We do this by round-tripping + // through Create's validator without the DB write — a tiny duplication + // here vs. exposing normalize publicly. + if n.Scheme == "" { + n.Scheme = "https" + } + if n.BasePath == "" { + n.BasePath = "/" + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 6*time.Second) + defer cancel() + patch, err := a.nodeService.Probe(ctx, n) + jsonObj(c, patch.ToUI(err == nil), nil) +} + +// probe triggers a one-off probe against a saved node and persists +// the result so the dashboard updates immediately, without waiting +// for the next heartbeat tick. +func (a *NodeController) probe(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + n, err := a.nodeService.GetById(id) + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.obtain"), err) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 6*time.Second) + defer cancel() + patch, probeErr := a.nodeService.Probe(ctx, n) + if probeErr != nil { + patch.Status = "offline" + } else { + patch.Status = "online" + } + _ = a.nodeService.UpdateHeartbeat(id, patch) + jsonObj(c, patch.ToUI(probeErr == nil), nil) +} + +// history returns averaged buckets of the per-node CPU/Mem time-series. +// Mirrors the system-level /panel/api/server/history/:metric/:bucket +// endpoint so the frontend can reuse the same fetch logic. +func (a *NodeController) history(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "get"), err) + return + } + metric := c.Param("metric") + if !slices.Contains(service.NodeMetricKeys, metric) { + jsonMsg(c, "invalid metric", fmt.Errorf("unknown metric")) + return + } + bucket, err := strconv.Atoi(c.Param("bucket")) + if err != nil || bucket <= 0 || !allowedHistoryBuckets[bucket] { + jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket")) + return + } + jsonObj(c, a.nodeService.AggregateNodeMetric(id, metric, bucket, 60), nil) +} diff --git a/web/controller/server.go b/web/controller/server.go index fc3dbec5..031c5318 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "regexp" + "slices" "strconv" "time" @@ -45,6 +46,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.GET("/status", a.status) g.GET("/cpuHistory/:bucket", a.getCpuHistoryBucket) + g.GET("/history/:metric/:bucket", a.getMetricHistoryBucket) g.GET("/getXrayVersion", a.getXrayVersion) g.GET("/getPanelUpdateInfo", a.getPanelUpdateInfo) g.GET("/getConfigJson", a.getConfigJson) @@ -67,12 +69,13 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/getNewEchCert", a.getNewEchCert) } -// refreshStatus updates the cached server status and collects CPU history. +// refreshStatus updates the cached server status and collects time-series +// metrics. CPU/Mem/Net/Online/Load are all written in one call so the +// SystemHistoryModal's tabs share an identical x-axis. func (a *ServerController) refreshStatus() { a.lastStatus = a.serverService.GetStatus(a.lastStatus) - // collect cpu history when status is fresh if a.lastStatus != nil { - a.serverService.AppendCpuSample(time.Now(), a.lastStatus.Cpu) + a.serverService.AppendStatusSample(time.Now(), a.lastStatus) // Broadcast status update via WebSocket websocket.BroadcastStatus(a.lastStatus) } @@ -92,7 +95,22 @@ func (a *ServerController) startTask() { // status returns the current server status information. func (a *ServerController) status(c *gin.Context) { jsonObj(c, a.lastStatus, nil) } +// allowedHistoryBuckets is the bucket-second whitelist shared by both +// /cpuHistory/:bucket and /history/:metric/:bucket. Restricting it +// prevents callers from triggering arbitrary aggregation work and keeps +// the front-end's bucket selector self-documenting. +var allowedHistoryBuckets = map[int]bool{ + 2: true, // Real-time view + 30: true, // 30s intervals + 60: true, // 1m intervals + 120: true, // 2m intervals + 180: true, // 3m intervals + 300: true, // 5m intervals +} + // getCpuHistoryBucket retrieves aggregated CPU usage history based on the specified time bucket. +// Kept for back-compat; new callers should use /history/cpu/:bucket which +// returns {"t","v"} (uniform across all metrics) instead of {"t","cpu"}. func (a *ServerController) getCpuHistoryBucket(c *gin.Context) { bucketStr := c.Param("bucket") bucket, err := strconv.Atoi(bucketStr) @@ -100,15 +118,7 @@ func (a *ServerController) getCpuHistoryBucket(c *gin.Context) { jsonMsg(c, "invalid bucket", fmt.Errorf("bad bucket")) return } - allowed := map[int]bool{ - 2: true, // Real-time view - 30: true, // 30s intervals - 60: true, // 1m intervals - 120: true, // 2m intervals - 180: true, // 3m intervals - 300: true, // 5m intervals - } - if !allowed[bucket] { + if !allowedHistoryBuckets[bucket] { jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket")) return } @@ -116,6 +126,23 @@ func (a *ServerController) getCpuHistoryBucket(c *gin.Context) { jsonObj(c, points, nil) } +// getMetricHistoryBucket returns up to 60 buckets of history for a single +// system metric (cpu, mem, netUp, netDown, online, load1/5/15). The +// SystemHistoryModal calls one endpoint per active tab. +func (a *ServerController) getMetricHistoryBucket(c *gin.Context) { + metric := c.Param("metric") + if !slices.Contains(service.SystemMetricKeys, metric) { + jsonMsg(c, "invalid metric", fmt.Errorf("unknown metric")) + return + } + bucket, err := strconv.Atoi(c.Param("bucket")) + if err != nil || bucket <= 0 || !allowedHistoryBuckets[bucket] { + jsonMsg(c, "invalid bucket", fmt.Errorf("unsupported bucket")) + return + } + jsonObj(c, a.serverService.AggregateSystemMetric(metric, bucket, 60), nil) +} + // getXrayVersion retrieves available Xray versions, with caching for 1 minute. func (a *ServerController) getXrayVersion(c *gin.Context) { now := time.Now().Unix() diff --git a/web/controller/setting.go b/web/controller/setting.go index 263dbe32..e35a7bde 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -44,6 +44,8 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) { g.POST("/updateUser", a.updateUser) g.POST("/restartPanel", a.restartPanel) g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig) + g.GET("/getApiToken", a.getApiToken) + g.POST("/regenerateApiToken", a.regenerateApiToken) } // getAllSetting retrieves all current settings. @@ -121,3 +123,27 @@ func (a *SettingController) getDefaultXrayConfig(c *gin.Context) { } jsonObj(c, defaultJsonConfig, nil) } + +// getApiToken returns the panel's API token used by remote central +// panels to authenticate as Bearer tokens. The token is auto-generated +// on first read so existing installs upgrade transparently. +func (a *SettingController) getApiToken(c *gin.Context) { + tok, err := a.settingService.GetApiToken() + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err) + return + } + jsonObj(c, tok, nil) +} + +// regenerateApiToken rotates the API token. Any central panel that had +// the old value cached will start failing heartbeats until it is updated +// with the new token — that's intentional, it's the whole point of rotation. +func (a *SettingController) regenerateApiToken(c *gin.Context) { + tok, err := a.settingService.RegenerateApiToken() + if err != nil { + jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) + return + } + jsonObj(c, tok, nil) +} diff --git a/web/controller/util.go b/web/controller/util.go index 070d2c70..5cc18250 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -7,10 +7,8 @@ import ( "net/netip" "strings" - "github.com/mhsanaei/3x-ui/v2/config" "github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/web/entity" - "github.com/mhsanaei/3x-ui/v2/web/session" "github.com/gin-gonic/gin" ) @@ -116,46 +114,6 @@ func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) { }) } -// html renders an HTML template with the provided data and title. -func html(c *gin.Context, name string, title string, data gin.H) { - if data == nil { - data = gin.H{} - } - data["title"] = title - csrfToken, err := session.EnsureCSRFToken(c) - if err != nil { - logger.Warning("Unable to create CSRF token:", err) - } else { - data["csrf_token"] = csrfToken - } - host := c.GetHeader("X-Forwarded-Host") - if host == "" { - host = c.GetHeader("X-Real-IP") - } - if host == "" { - var err error - host, _, err = net.SplitHostPort(c.Request.Host) - if err != nil { - host = c.Request.Host - } - } - data["host"] = host - data["request_uri"] = c.Request.RequestURI - data["base_path"] = c.GetString("base_path") - c.HTML(http.StatusOK, name, getContext(data)) -} - -// getContext adds version and other context data to the provided gin.H. -func getContext(h gin.H) gin.H { - a := gin.H{ - "cur_ver": config.GetVersion(), - } - for key, value := range h { - a[key] = value - } - return a -} - // isAjax checks if the request is an AJAX request. func isAjax(c *gin.Context) bool { return c.GetHeader("X-Requested-With") == "XMLHttpRequest" diff --git a/web/controller/xui.go b/web/controller/xui.go index afbbeb71..fd633e7b 100644 --- a/web/controller/xui.go +++ b/web/controller/xui.go @@ -1,7 +1,11 @@ package controller import ( + "net/http" + + "github.com/mhsanaei/3x-ui/v2/web/entity" "github.com/mhsanaei/3x-ui/v2/web/middleware" + "github.com/mhsanaei/3x-ui/v2/web/session" "github.com/gin-gonic/gin" ) @@ -29,29 +33,57 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) { g.GET("/", a.index) g.GET("/inbounds", a.inbounds) + g.GET("/nodes", a.nodes) g.GET("/settings", a.settings) g.GET("/xray", a.xraySettings) + // SPA pages built by Vite don't have a server-rendered , + // so they fetch the session token via this endpoint at startup and replay it + // on subsequent unsafe requests through axios. + g.GET("/csrf-token", a.csrfToken) + a.settingController = NewSettingController(g) a.xraySettingController = NewXraySettingController(g) } +// All four panel pages now serve the Vue 3 builds from web/dist/ +// instead of rendering the legacy Go templates. Each handler is a +// thin wrapper around serveDistPage so the basePath injection + +// no-cache headers stay centralised. + // index renders the main panel index page. func (a *XUIController) index(c *gin.Context) { - html(c, "index.html", "pages.index.title", nil) + serveDistPage(c, "index.html") } // inbounds renders the inbounds management page. func (a *XUIController) inbounds(c *gin.Context) { - html(c, "inbounds.html", "pages.inbounds.title", nil) + serveDistPage(c, "inbounds.html") +} + +// nodes renders the multi-panel nodes management page. +func (a *XUIController) nodes(c *gin.Context) { + serveDistPage(c, "nodes.html") } // settings renders the settings management page. func (a *XUIController) settings(c *gin.Context) { - html(c, "settings.html", "pages.settings.title", nil) + serveDistPage(c, "settings.html") } // xraySettings renders the Xray settings page. func (a *XUIController) xraySettings(c *gin.Context) { - html(c, "xray.html", "pages.xray.title", nil) + serveDistPage(c, "xray.html") +} + +// csrfToken returns the session CSRF token to authenticated SPA clients. +// The endpoint is GET (a safe method) so it bypasses CSRFMiddleware itself, +// but checkLogin still gates the response — anonymous callers get 401/redirect. +func (a *XUIController) csrfToken(c *gin.Context) { + token, err := session.EnsureCSRFToken(c) + if err != nil { + c.JSON(http.StatusInternalServerError, entity.Msg{Success: false, Msg: err.Error()}) + return + } + c.JSON(http.StatusOK, entity.Msg{Success: true, Obj: token}) } diff --git a/web/html/common/page.html b/web/html/common/page.html deleted file mode 100644 index 13f5d64c..00000000 --- a/web/html/common/page.html +++ /dev/null @@ -1,106 +0,0 @@ -{{ define "page/head_start" }} - - - - - - - - - {{ if .csrf_token }}{{ end }} - - - - {{ .host }} – {{ i18n .title}} - {{ end }} - - {{ define "page/head_end" }} - -{{ end }} - -{{ define "page/body_start" }} - - -
- {{ end }} - - {{ define "page/body_scripts" }} - - - - - - - - - - {{ end }} - - {{ define "page/body_end" }} - - - -{{ end }} diff --git a/web/html/component/aClientTable.html b/web/html/component/aClientTable.html deleted file mode 100644 index 1c24e94d..00000000 --- a/web/html/component/aClientTable.html +++ /dev/null @@ -1,301 +0,0 @@ -{{define "component/aClientTable"}} - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/component/aCustomStatistic.html b/web/html/component/aCustomStatistic.html deleted file mode 100644 index be20a39a..00000000 --- a/web/html/component/aCustomStatistic.html +++ /dev/null @@ -1,44 +0,0 @@ -{{define "component/customStatistic"}} - -{{end}} - -{{define "component/aCustomStatistic"}} - - - -{{end}} \ No newline at end of file diff --git a/web/html/component/aPersianDatepicker.html b/web/html/component/aPersianDatepicker.html deleted file mode 100644 index cb4c2918..00000000 --- a/web/html/component/aPersianDatepicker.html +++ /dev/null @@ -1,73 +0,0 @@ -{{define "component/persianDatepickerTemplate"}} - -{{end}} - -{{define "component/aPersianDatepicker"}} - - - - -{{end}} \ No newline at end of file diff --git a/web/html/component/aSettingListItem.html b/web/html/component/aSettingListItem.html deleted file mode 100644 index 7ad13456..00000000 --- a/web/html/component/aSettingListItem.html +++ /dev/null @@ -1,49 +0,0 @@ -{{define "component/settingListItem"}} - - - - - - - - - - - - - -{{end}} - -{{define "component/aSettingListItem"}} - -{{end}} \ No newline at end of file diff --git a/web/html/component/aSidebar.html b/web/html/component/aSidebar.html deleted file mode 100644 index 08b39dc3..00000000 --- a/web/html/component/aSidebar.html +++ /dev/null @@ -1,102 +0,0 @@ -{{define "component/sidebar/content"}} - -{{end}} - -{{define "component/aSidebar"}} - - - -{{end}} \ No newline at end of file diff --git a/web/html/component/aTableSortable.html b/web/html/component/aTableSortable.html deleted file mode 100644 index fdbc247e..00000000 --- a/web/html/component/aTableSortable.html +++ /dev/null @@ -1,302 +0,0 @@ -{{define "component/sortableTableTrigger"}} - -{{end}} - -{{define "component/aTableSortable"}} - - - -{{end}} diff --git a/web/html/component/aThemeSwitch.html b/web/html/component/aThemeSwitch.html deleted file mode 100644 index 2712b1f7..00000000 --- a/web/html/component/aThemeSwitch.html +++ /dev/null @@ -1,122 +0,0 @@ -{{define "component/themeSwitchTemplate"}} - -{{end}} - -{{define "component/themeSwitchTemplateLogin"}} - -{{end}} - -{{define "component/aThemeSwitch"}} - -{{end}} \ No newline at end of file diff --git a/web/html/form/client.html b/web/html/form/client.html deleted file mode 100644 index 737c102e..00000000 --- a/web/html/form/client.html +++ /dev/null @@ -1,199 +0,0 @@ -{{define "form/client"}} - - - - - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "none" }} - [[ key ]] - - - - - - - - - - - - - [[ SizeFormatter.sizeFormat(clientStats.up) ]] / [[ - SizeFormatter.sizeFormat(clientStats.down) ]] ([[ - SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]]) - - - - - - - - - - - - - - - - - Expired - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/fallbacks.html b/web/html/form/fallbacks.html deleted file mode 100644 index 703bfc8e..00000000 --- a/web/html/form/fallbacks.html +++ /dev/null @@ -1,85 +0,0 @@ -{{define "form/fallbacks"}} -
- - - Fallbacks ([[ inbound.settings.fallbacks.length ]]) - - - - - Add -
- - - - Fallback [[ index + 1 ]] - - - - - - - - - - - - any - h2 - http/1.1 - - - - - - - - - - - - - - - - - Off - v1 - v2 - - - -{{end}} diff --git a/web/html/form/inbound.html b/web/html/form/inbound.html deleted file mode 100644 index 61d7bc57..00000000 --- a/web/html/form/inbound.html +++ /dev/null @@ -1,171 +0,0 @@ -{{define "form/inbound"}} - - - - - - - - - - - - [[ p - ]] - - - - - - - - - - - - - - - - - - - - - {{ i18n - "pages.inbounds.periodicTrafficReset.never" }} - {{ i18n - "pages.inbounds.periodicTrafficReset.hourly" - }} - {{ i18n - "pages.inbounds.periodicTrafficReset.daily" }} - {{ i18n - "pages.inbounds.periodicTrafficReset.weekly" - }} - {{ i18n - "pages.inbounds.periodicTrafficReset.monthly" - }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{template "form/sniffing" .}} - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/outbound.html b/web/html/form/outbound.html deleted file mode 100644 index b171a099..00000000 --- a/web/html/form/outbound.html +++ /dev/null @@ -1,1284 +0,0 @@ -{{define "form/outbound"}} - - - - - - - [[ y ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/dokodemo.html b/web/html/form/protocol/dokodemo.html deleted file mode 100644 index 1dbace29..00000000 --- a/web/html/form/protocol/dokodemo.html +++ /dev/null @@ -1,37 +0,0 @@ -{{define "form/tunnel"}} - - - - - - - - - + - - - - - - - - - - - - - - - TCP,UDP - TCP - UDP - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/http.html b/web/html/form/protocol/http.html deleted file mode 100644 index dd0512f5..00000000 --- a/web/html/form/protocol/http.html +++ /dev/null @@ -1,27 +0,0 @@ -{{define "form/http"}} - - - - - - - -
{{ i18n "username" }}{{ i18n "password" }} - -
- - - - - - - - - - - -
-{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/hysteria.html b/web/html/form/protocol/hysteria.html deleted file mode 100644 index 5613dfc5..00000000 --- a/web/html/form/protocol/hysteria.html +++ /dev/null @@ -1,28 +0,0 @@ -{{define "form/hysteria"}} - - - {{template "form/client" .}} - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}Auth
[[ client.email ]][[ client.auth ]]
-
-
- - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/shadowsocks.html b/web/html/form/protocol/shadowsocks.html deleted file mode 100644 index 12371399..00000000 --- a/web/html/form/protocol/shadowsocks.html +++ /dev/null @@ -1,56 +0,0 @@ -{{define "form/shadowsocks"}} - - - - - [[ method_name ]] - - - - - - - - - TCP,UDP - TCP - UDP - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/socks.html b/web/html/form/protocol/socks.html deleted file mode 100644 index 8a968453..00000000 --- a/web/html/form/protocol/socks.html +++ /dev/null @@ -1,36 +0,0 @@ -{{define "form/mixed"}} - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/trojan.html b/web/html/form/protocol/trojan.html deleted file mode 100644 index 4c04e6e7..00000000 --- a/web/html/form/protocol/trojan.html +++ /dev/null @@ -1,25 +0,0 @@ -{{define "form/trojan"}} - - - {{template "form/client" .}} - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}Password
[[ client.email ]][[ client.password ]]
-
-
- -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/tun.html b/web/html/form/protocol/tun.html deleted file mode 100644 index 5541d327..00000000 --- a/web/html/form/protocol/tun.html +++ /dev/null @@ -1,55 +0,0 @@ -{{define "form/tun"}} - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/vless.html b/web/html/form/protocol/vless.html deleted file mode 100644 index 749e5b0c..00000000 --- a/web/html/form/protocol/vless.html +++ /dev/null @@ -1,101 +0,0 @@ -{{define "form/vless"}} - - - {{template "form/client" .}} - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}ID
[[ client.email ]][[ client.id ]]
-
-
- - - -{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/vmess.html b/web/html/form/protocol/vmess.html deleted file mode 100644 index 1beb4259..00000000 --- a/web/html/form/protocol/vmess.html +++ /dev/null @@ -1,24 +0,0 @@ -{{define "form/vmess"}} - - - {{template "form/client" .}} - - - - - - - - - - - - - - - -
{{ i18n "pages.inbounds.email" }}ID{{ i18n "security" }}
[[ client.email ]][[ client.id ]][[ client.security ]]
-
-
-{{end}} \ No newline at end of file diff --git a/web/html/form/protocol/wireguard.html b/web/html/form/protocol/wireguard.html deleted file mode 100644 index 6a5ff541..00000000 --- a/web/html/form/protocol/wireguard.html +++ /dev/null @@ -1,82 +0,0 @@ -{{define "form/wireguard"}} - - - - - - - - - - - - - - - - - - - Peer [[ index + 1 ]] - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/reality_settings.html b/web/html/form/reality_settings.html deleted file mode 100644 index 0ae79252..00000000 --- a/web/html/form/reality_settings.html +++ /dev/null @@ -1,83 +0,0 @@ -{{define "form/realitySettings"}} - -{{end}} \ No newline at end of file diff --git a/web/html/form/sniffing.html b/web/html/form/sniffing.html deleted file mode 100644 index 7d13a099..00000000 --- a/web/html/form/sniffing.html +++ /dev/null @@ -1,37 +0,0 @@ -{{define "form/sniffing"}} - - - - {{ i18n "enabled" }} - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/external_proxy.html b/web/html/form/stream/external_proxy.html deleted file mode 100644 index 5c13df1b..00000000 --- a/web/html/form/stream/external_proxy.html +++ /dev/null @@ -1,31 +0,0 @@ -{{define "form/externalProxy"}} - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_finalmask.html b/web/html/form/stream/stream_finalmask.html deleted file mode 100644 index 0e28b476..00000000 --- a/web/html/form/stream/stream_finalmask.html +++ /dev/null @@ -1,408 +0,0 @@ -{{define "form/streamFinalMask"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_grpc.html b/web/html/form/stream/stream_grpc.html deleted file mode 100644 index 1d814126..00000000 --- a/web/html/form/stream/stream_grpc.html +++ /dev/null @@ -1,13 +0,0 @@ -{{define "form/streamGRPC"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_httpupgrade.html b/web/html/form/stream/stream_httpupgrade.html deleted file mode 100644 index 3b2398b6..00000000 --- a/web/html/form/stream/stream_httpupgrade.html +++ /dev/null @@ -1,29 +0,0 @@ -{{define "form/streamHTTPUpgrade"}} - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_hysteria.html b/web/html/form/stream/stream_hysteria.html deleted file mode 100644 index 67226fa3..00000000 --- a/web/html/form/stream/stream_hysteria.html +++ /dev/null @@ -1,58 +0,0 @@ -{{define "form/streamHysteria"}} - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_kcp.html b/web/html/form/stream/stream_kcp.html deleted file mode 100644 index 3d89387f..00000000 --- a/web/html/form/stream/stream_kcp.html +++ /dev/null @@ -1,22 +0,0 @@ -{{define "form/streamKCP"}} - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_settings.html b/web/html/form/stream/stream_settings.html deleted file mode 100644 index 2a918305..00000000 --- a/web/html/form/stream/stream_settings.html +++ /dev/null @@ -1,58 +0,0 @@ -{{define "form/streamSettings"}} - - - - - TCP (RAW) - mKCP - WebSocket - gRPC - HTTPUpgrade - XHTTP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_sockopt.html b/web/html/form/stream/stream_sockopt.html deleted file mode 100644 index dd7acbd2..00000000 --- a/web/html/form/stream/stream_sockopt.html +++ /dev/null @@ -1,78 +0,0 @@ -{{define "form/streamSockopt"}} - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_tcp.html b/web/html/form/stream/stream_tcp.html deleted file mode 100644 index 2fb69cb0..00000000 --- a/web/html/form/stream/stream_tcp.html +++ /dev/null @@ -1,81 +0,0 @@ -{{define "form/streamTCP"}} - - - - - - - - - - - - - {{ i18n "pages.inbounds.stream.general.request" }} - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.inbounds.stream.general.response" }} - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_ws.html b/web/html/form/stream/stream_ws.html deleted file mode 100644 index bedd1681..00000000 --- a/web/html/form/stream/stream_ws.html +++ /dev/null @@ -1,31 +0,0 @@ -{{define "form/streamWS"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/form/stream/stream_xhttp.html b/web/html/form/stream/stream_xhttp.html deleted file mode 100644 index c275026c..00000000 --- a/web/html/form/stream/stream_xhttp.html +++ /dev/null @@ -1,117 +0,0 @@ -{{define "form/streamXHTTP"}} - - - - - - - - - - - - - - - - - - - - - - - [[ key ]] - - - - - - - - - - - - - - - - - - - - - - - - Default (path) - path - header - cookie - query - - - - - - - - Default (path) - path - header - cookie - query - - - - - - - - Default (body) - body - header - cookie - query - - - - - - - - - -{{end}} diff --git a/web/html/form/tls_settings.html b/web/html/form/tls_settings.html deleted file mode 100644 index c34a6b2b..00000000 --- a/web/html/form/tls_settings.html +++ /dev/null @@ -1,138 +0,0 @@ -{{define "form/tlsSettings"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/inbounds.html b/web/html/inbounds.html deleted file mode 100644 index c84dc551..00000000 --- a/web/html/inbounds.html +++ /dev/null @@ -1,2356 +0,0 @@ -{{ template "page/head_start" .}} -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - {{ i18n "none" }} - {{ i18n "subscription.active" - }} - {{ i18n "disabled" - }} - {{ i18n "depleted" - }} - {{ i18n "depletingSoon" - }} - {{ i18n "online" - }} - -
- - - - - - - - - - - -
-
-
-
-
-
-
-
-
-{{template "page/body_scripts" .}} - - - - - -{{template "component/aSidebar" .}} -{{template "component/aThemeSwitch" .}} -{{template "component/aCustomStatistic" .}} -{{template "component/aPersianDatepicker" .}} -{{template "modals/inboundModal" .}} -{{template "modals/promptModal" .}} -{{template "modals/qrcodeModal" .}} -{{template "modals/textModal" .}} -{{template "modals/inboundInfoModal" .}} -{{template "modals/clientsModal" .}} -{{template "modals/clientsBulkModal" .}} - - -
-
{{ i18n "pages.client.copySource" }}
- - - [[ item.label ]] - - -
-
- - {{ i18n "pages.client.selectAll" }} - {{ i18n "pages.client.clearAll" }} - - - - -
-
-
{{ i18n "pages.client.copyFlowLabel" }}
- - {{ i18n "none" }} - xtls-rprx-vision - xtls-rprx-vision-udp443 - -
- {{ i18n "pages.client.copyFlowHint" }} -
-
-
-
{{ i18n "pages.client.copyEmailPreview" }}
-
- - [[ preview ]] - -
-
-
-
- - - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/html/index.html b/web/html/index.html deleted file mode 100644 index 83b1a795..00000000 --- a/web/html/index.html +++ /dev/null @@ -1,1806 +0,0 @@ -{{ template "page/head_start" .}} -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.index.currentPanelVersion" }} - v[[ panelUpdateModal.info.currentVersion || '{{ .cur_ver }}' ]] - - - {{ i18n "pages.index.latestPanelVersion" }} - - [[ panelUpdateModal.info.latestVersion || '-' ]] - - - - {{ i18n "pages.index.panelUpToDate" }} - {{ i18n "pages.index.upToDate" }} - - -
- - {{ i18n "pages.index.updatePanel" }} - -
-
- - - - - - - [[ version ]] - - - - - - - - [[ file ]] - - - -
{{ i18n - "pages.index.geofilesUpdateAll" }}
-
- -
- -
- - {{ i18n "pages.index.customGeoAdd" }} - - {{ i18n "pages.index.geofilesUpdateAll" }} - [[ customGeoList.length ]] -
- - - - - - - - -
-
-
-
- - - - - geosite - geoip - - - - - - - - - - - - - - - - - 10 - 20 - 50 - 100 - 500 - - - Debug - Info - Notice - Warning - Error - - - - - SysLog - - - - - -
-
- - - - - - - 10 - 20 - 50 - 100 - 500 - - - - - - - - Direct - Blocked - Proxy - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - -
- -
Timeframe: [[ cpuHistoryModal.bucket ]] sec per point - (total [[ cpuHistoryLong.length ]] points)
-
-
-
-{{template "page/body_scripts" .}} -{{template "component/aSidebar" .}} -{{template "component/aThemeSwitch" .}} -{{template "component/aCustomStatistic" .}} -{{template "modals/textModal" .}} - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/html/login.html b/web/html/login.html deleted file mode 100644 index 2e03a4c5..00000000 --- a/web/html/login.html +++ /dev/null @@ -1,261 +0,0 @@ -{{ template "page/head_start" .}} -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - -
-
- - - - - - - - - - - -
- - - - - - -
-
-
-{{template "page/body_scripts" .}} -{{template "component/aThemeSwitch" .}} - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/html/modals/client_bulk_modal.html b/web/html/modals/client_bulk_modal.html deleted file mode 100644 index 81f711e7..00000000 --- a/web/html/modals/client_bulk_modal.html +++ /dev/null @@ -1,262 +0,0 @@ -{{define "modals/clientsBulkModal"}} - - - - - Random - Random+Prefix - Random+Prefix+Num - Random+Prefix+Num+Postfix - Prefix+Num+Postfix - - - - - - - - - - - - - - - - - - - - [[ - key ]] - - - - - {{ i18n "none" - }} - [[ - key ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/client_modal.html b/web/html/modals/client_modal.html deleted file mode 100644 index 65a481f6..00000000 --- a/web/html/modals/client_modal.html +++ /dev/null @@ -1,193 +0,0 @@ -{{define "modals/clientsModal"}} - - - {{template "form/client" .}} - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/dns_presets_modal.html b/web/html/modals/dns_presets_modal.html deleted file mode 100644 index 0bc3126b..00000000 --- a/web/html/modals/dns_presets_modal.html +++ /dev/null @@ -1,122 +0,0 @@ -{{define "modals/dnsPresetsModal"}} - - - -
- - [[ dns.family ? '{{ i18n "pages.xray.dns.dnsPresetFamily" }}' - : 'DNS' ]] - [[ dns.name ]] - - {{ i18n "install" }} -
-
-
-
- - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/inbound_info_modal.html b/web/html/modals/inbound_info_modal.html deleted file mode 100644 index cc2b734d..00000000 --- a/web/html/modals/inbound_info_modal.html +++ /dev/null @@ -1,799 +0,0 @@ -{{define "modals/inboundInfoModal"}} - - - - - - - - - - - - - - - - -
{{ i18n "protocol" }} - [[ dbInbound.protocol ]] -
{{ i18n "pages.inbounds.address" }} - - [[ dbInbound.address ]] - -
{{ i18n "pages.inbounds.port" }} - [[ dbInbound.port ]] -
-
- - - - - - - - - - - - - - - - - -
{{ i18n "encryption" }} - [[ inbound.settings.method ]] -
{{ i18n "password" }} - - [[ inbound.settings.password - ]] - -
{{ i18n "pages.inbounds.network" }} - [[ inbound.settings.network ]] -
- - - -
- -{{end}} \ No newline at end of file diff --git a/web/html/modals/inbound_modal.html b/web/html/modals/inbound_modal.html deleted file mode 100644 index df2588db..00000000 --- a/web/html/modals/inbound_modal.html +++ /dev/null @@ -1,368 +0,0 @@ -{{define "modals/inboundModal"}} - - {{template "form/inbound" .}} - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/nord_modal.html b/web/html/modals/nord_modal.html deleted file mode 100644 index 75d9778e..00000000 --- a/web/html/modals/nord_modal.html +++ /dev/null @@ -1,327 +0,0 @@ -{{define "modals/nordModal"}} - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/prompt_modal.html b/web/html/modals/prompt_modal.html deleted file mode 100644 index 7d4c19de..00000000 --- a/web/html/modals/prompt_modal.html +++ /dev/null @@ -1,66 +0,0 @@ -{{define "modals/promptModal"}} - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/qrcode_modal.html b/web/html/modals/qrcode_modal.html deleted file mode 100644 index d3443c34..00000000 --- a/web/html/modals/qrcode_modal.html +++ /dev/null @@ -1,311 +0,0 @@ -{{define "modals/qrcodeModal"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/text_modal.html b/web/html/modals/text_modal.html deleted file mode 100644 index 76a4b159..00000000 --- a/web/html/modals/text_modal.html +++ /dev/null @@ -1,51 +0,0 @@ -{{define "modals/textModal"}} - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/two_factor_modal.html b/web/html/modals/two_factor_modal.html deleted file mode 100644 index a6decd76..00000000 --- a/web/html/modals/two_factor_modal.html +++ /dev/null @@ -1,125 +0,0 @@ -{{define "modals/twoFactorModal"}} - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/warp_modal.html b/web/html/modals/warp_modal.html deleted file mode 100644 index 667e4ed2..00000000 --- a/web/html/modals/warp_modal.html +++ /dev/null @@ -1,250 +0,0 @@ -{{define "modals/warpModal"}} - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/xray_balancer_modal.html b/web/html/modals/xray_balancer_modal.html deleted file mode 100644 index 194ed036..00000000 --- a/web/html/modals/xray_balancer_modal.html +++ /dev/null @@ -1,122 +0,0 @@ -{{define "modals/balancerModal"}} - - - - - - - - Random - Round Robin - Least Load - Least Ping - - - - - [[ tag ]] - - - - - [[ tag - ]] - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/xray_dns_modal.html b/web/html/modals/xray_dns_modal.html deleted file mode 100644 index 0d02a71d..00000000 --- a/web/html/modals/xray_dns_modal.html +++ /dev/null @@ -1,136 +0,0 @@ -{{define "modals/dnsModal"}} - - - - - - - - - - - [[ l ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/xray_fakedns_modal.html b/web/html/modals/xray_fakedns_modal.html deleted file mode 100644 index 20768f0b..00000000 --- a/web/html/modals/xray_fakedns_modal.html +++ /dev/null @@ -1,65 +0,0 @@ -{{define "modals/fakednsModal"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/xray_outbound_modal.html b/web/html/modals/xray_outbound_modal.html deleted file mode 100644 index 16b6417a..00000000 --- a/web/html/modals/xray_outbound_modal.html +++ /dev/null @@ -1,144 +0,0 @@ -{{define "modals/outModal"}} - - {{template "form/outbound" .}} - - -{{end}} \ No newline at end of file diff --git a/web/html/modals/xray_rule_modal.html b/web/html/modals/xray_rule_modal.html deleted file mode 100644 index b64e6182..00000000 --- a/web/html/modals/xray_rule_modal.html +++ /dev/null @@ -1,263 +0,0 @@ -{{define "modals/ruleModal"}} - - - - - - - - - - - - - - - - - [[ x ]] - - - - - [[ x ]] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [[ tag ]] - - - - - [[ tag ]] - - - - - - [[ tag ]] - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings.html b/web/html/settings.html deleted file mode 100644 index 22645b3a..00000000 --- a/web/html/settings.html +++ /dev/null @@ -1,721 +0,0 @@ -{{ template "page/head_start" .}} -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - - - - - - - - - - - - - - - -{{template "page/body_scripts" .}} - - - -{{template "component/aSidebar" .}} -{{template "component/aThemeSwitch" .}} -{{template "component/aSettingListItem" .}} -{{template "modals/twoFactorModal" .}} - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/html/settings/panel/general.html b/web/html/settings/panel/general.html deleted file mode 100644 index 718c431c..00000000 --- a/web/html/settings/panel/general.html +++ /dev/null @@ -1,294 +0,0 @@ -{{define "settings/panel/general"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/panel/security.html b/web/html/settings/panel/security.html deleted file mode 100644 index 2a570faa..00000000 --- a/web/html/settings/panel/security.html +++ /dev/null @@ -1,44 +0,0 @@ -{{define "settings/panel/security"}} - - - - - - - - - - - - - - - - - - - - - {{ i18n "confirm" }} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/panel/subscription/general.html b/web/html/settings/panel/subscription/general.html deleted file mode 100644 index a7b58e46..00000000 --- a/web/html/settings/panel/subscription/general.html +++ /dev/null @@ -1,173 +0,0 @@ -{{define "settings/panel/subscription/general"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.xray.basicTemplate"}} - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.xray.advancedTemplate"}} (Happ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/panel/subscription/json.html b/web/html/settings/panel/subscription/json.html deleted file mode 100644 index 9b83571a..00000000 --- a/web/html/settings/panel/subscription/json.html +++ /dev/null @@ -1,220 +0,0 @@ -{{define "settings/panel/subscription/json"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Remove - - - - Add Noise - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/panel/subscription/subpage.html b/web/html/settings/panel/subscription/subpage.html deleted file mode 100644 index adfbea93..00000000 --- a/web/html/settings/panel/subscription/subpage.html +++ /dev/null @@ -1,288 +0,0 @@ -{{ template "page/head_start" .}} - - - - - - - -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - - - - - - - - - - - - - - {{ i18n - "pages.settings.subSettings"}} - - - - - - - - - - - - {{ i18n - "pages.settings.subSettings"}} - Json - - - - - - - - - - - - Clash / Mihomo - - - - - - - - - - - - - - - [[ - app.sId - ]] - - - - - - [[ - app.download - ]] - [[ - app.upload - ]] - [[ app.used - ]] - [[ - app.total - ]] - [[ - app.remained ]] - - - - - - - - - - - - -
-
-
- - [[ linkName(link, idx) ]] - - -
-
-
- - - - - - - - - Android - - - V2Box - V2RayNG - Sing-box - V2RayTun - NPV - Tunnel - Happ - - - - - - - - iOS - - - Shadowrocket - V2Box - Streisand - V2RayTun - NPV - Tunnel - - Happ - - - - - - -
-
-
-
-
- - - - - -{{template "component/aThemeSwitch" .}} - - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/html/settings/panel/telegram.html b/web/html/settings/panel/telegram.html deleted file mode 100644 index 248831fd..00000000 --- a/web/html/settings/panel/telegram.html +++ /dev/null @@ -1,87 +0,0 @@ -{{define "settings/panel/telegram"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/xray/advanced.html b/web/html/settings/xray/advanced.html deleted file mode 100644 index 28e31871..00000000 --- a/web/html/settings/xray/advanced.html +++ /dev/null @@ -1,14 +0,0 @@ -{{define "settings/xray/advanced"}} - - - - {{ i18n "pages.xray.completeTemplate"}} - {{ i18n "pages.xray.Inbounds" }} - {{ i18n "pages.xray.Outbounds" }} - {{ i18n "pages.xray.Routings" }} - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/xray/balancers.html b/web/html/settings/xray/balancers.html deleted file mode 100644 index 5a193cdf..00000000 --- a/web/html/settings/xray/balancers.html +++ /dev/null @@ -1,55 +0,0 @@ -{{define "settings/xray/balancers"}} - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/xray/basics.html b/web/html/settings/xray/basics.html deleted file mode 100644 index 3ac2349a..00000000 --- a/web/html/settings/xray/basics.html +++ /dev/null @@ -1,310 +0,0 @@ -{{define "settings/xray/basics"}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ i18n "pages.settings.resetDefaultConfig" }} - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/xray/dns.html b/web/html/settings/xray/dns.html deleted file mode 100644 index 7935be52..00000000 --- a/web/html/settings/xray/dns.html +++ /dev/null @@ -1,170 +0,0 @@ -{{define "settings/xray/dns"}} - - - - - - - - - - - -{{end}} \ No newline at end of file diff --git a/web/html/settings/xray/outbounds.html b/web/html/settings/xray/outbounds.html deleted file mode 100644 index 83c25df4..00000000 --- a/web/html/settings/xray/outbounds.html +++ /dev/null @@ -1,263 +0,0 @@ -{{define "settings/xray/outbounds"}} - - - - - - {{ i18n "pages.xray.outbound.addOutbound" }} - - WARP - NordVPN - - - - - - - - - - - - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/settings/xray/routing.html b/web/html/settings/xray/routing.html deleted file mode 100644 index 866fa7f0..00000000 --- a/web/html/settings/xray/routing.html +++ /dev/null @@ -1,193 +0,0 @@ -{{define "settings/xray/routing"}} - - {{ i18n - "pages.xray.rules.add" }} - - - - - - - - - - - - - - - -{{end}} diff --git a/web/html/xray.html b/web/html/xray.html deleted file mode 100644 index 03fd4232..00000000 --- a/web/html/xray.html +++ /dev/null @@ -1,2361 +0,0 @@ -{{ template "page/head_start" .}} - - - - -{{ template "page/head_end" .}} - -{{ template "page/body_start" .}} - - - - - - - - - - - -
-
- - - - - - - - {{ i18n "pages.xray.save" }} - - - {{ i18n "pages.xray.restart" }} - - - {{ i18n - "pages.index.xrayErrorPopoverTitle" }} - - - - - - - - - - - - - - - - {{ template "settings/xray/basics" . }} - - - - {{ template "settings/xray/routing" . }} - - - - {{ template "settings/xray/outbounds" . }} - - - - {{ template "settings/xray/balancers" . }} - - - - {{ template "settings/xray/dns" . }} - - - - {{ template "settings/xray/advanced" . }} - - - - -
-
-
-
-
-{{template "page/body_scripts" .}} - - - - - - - - - - - -{{template "component/aSidebar" .}} -{{template "component/aThemeSwitch" .}} -{{template "component/aTableSortable" .}} -{{template "component/aSettingListItem" .}} -{{template "modals/ruleModal" .}} -{{template "modals/outModal" .}} -{{template "modals/balancerModal" .}} -{{template "modals/dnsModal" .}} -{{template "modals/dnsPresetsModal" .}} -{{template "modals/fakednsModal" .}} -{{template "modals/warpModal" .}} -{{template "modals/nordModal" .}} - - -{{ template "page/body_end" .}} \ No newline at end of file diff --git a/web/job/node_heartbeat_job.go b/web/job/node_heartbeat_job.go new file mode 100644 index 00000000..1fa04169 --- /dev/null +++ b/web/job/node_heartbeat_job.go @@ -0,0 +1,107 @@ +package job + +import ( + "context" + "sync" + "time" + + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/logger" + "github.com/mhsanaei/3x-ui/v2/web/service" + "github.com/mhsanaei/3x-ui/v2/web/websocket" +) + +// nodeHeartbeatConcurrency caps how many remote panels we probe at once. +// Plenty of headroom for typical deployments (tens of nodes) without +// letting a misconfigured run open thousands of sockets at once. +const nodeHeartbeatConcurrency = 32 + +// nodeHeartbeatRequestTimeout bounds a single probe. The cron is @every 10s, +// so this needs to stay well under that to avoid run pile-up. +const nodeHeartbeatRequestTimeout = 6 * time.Second + +// NodeHeartbeatJob probes every enabled remote node once per cron tick +// and persists the result. Disabled nodes are skipped entirely so a +// long-broken node can be parked without burning sockets every 10s. +type NodeHeartbeatJob struct { + nodeService service.NodeService + + // Coarse mutex prevents two ticks running concurrently if probes + // pile up under network failure. The next tick simply skips when + // the previous one is still draining. + running sync.Mutex +} + +// NewNodeHeartbeatJob constructs a heartbeat job. The robfig/cron +// scheduler will hand the same instance to every tick, so the +// running mutex carries across runs as intended. +func NewNodeHeartbeatJob() *NodeHeartbeatJob { + return &NodeHeartbeatJob{} +} + +func (j *NodeHeartbeatJob) Run() { + if !j.running.TryLock() { + // Previous tick still in flight — skip this one. + return + } + defer j.running.Unlock() + + nodes, err := j.nodeService.GetAll() + if err != nil { + logger.Warning("node heartbeat: load nodes failed:", err) + return + } + if len(nodes) == 0 { + return + } + + sem := make(chan struct{}, nodeHeartbeatConcurrency) + var wg sync.WaitGroup + for _, n := range nodes { + if !n.Enable { + continue + } + wg.Add(1) + sem <- struct{}{} + go func(n *model.Node) { + defer wg.Done() + defer func() { <-sem }() + j.probeOne(n) + }(n) + } + wg.Wait() + + // Push the fresh list to any open Nodes page over WebSocket so the + // status / latency / cpu / mem cells update without the user clicking + // refresh. Skip the DB read entirely when no browser is connected — + // matches the gating pattern in xray_traffic_job. + if !websocket.HasClients() { + return + } + updated, err := j.nodeService.GetAll() + if err != nil { + logger.Warning("node heartbeat: load nodes for broadcast failed:", err) + return + } + websocket.BroadcastNodes(updated) +} + +// probeOne runs a single probe and persists the result. We deliberately +// don't return errors — partial failures across the node set should not +// abort other probes, and the LastError column carries the message for +// the UI to surface. +func (j *NodeHeartbeatJob) probeOne(n *model.Node) { + ctx, cancel := context.WithTimeout(context.Background(), nodeHeartbeatRequestTimeout) + defer cancel() + patch, err := j.nodeService.Probe(ctx, n) + if err != nil { + patch.Status = "offline" + } else { + patch.Status = "online" + } + if updErr := j.nodeService.UpdateHeartbeat(n.Id, patch); updErr != nil { + // A row deleted mid-tick produces "rows affected = 0", which + // gorm reports as nil — so any error we get here is real. + logger.Warning("node heartbeat: update node", n.Id, "failed:", updErr) + } +} diff --git a/web/job/node_traffic_sync_job.go b/web/job/node_traffic_sync_job.go new file mode 100644 index 00000000..15881072 --- /dev/null +++ b/web/job/node_traffic_sync_job.go @@ -0,0 +1,137 @@ +package job + +import ( + "context" + "sync" + "time" + + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/logger" + "github.com/mhsanaei/3x-ui/v2/web/runtime" + "github.com/mhsanaei/3x-ui/v2/web/service" + "github.com/mhsanaei/3x-ui/v2/web/websocket" +) + +// nodeTrafficSyncConcurrency caps how many nodes we sync simultaneously. +// Each sync does three HTTP calls in series, so the wall-clock budget +// per node is the request timeout below — keeping the cap modest avoids +// flooding the network while still getting through dozens of nodes +// inside a 10s tick. +const nodeTrafficSyncConcurrency = 8 + +// nodeTrafficSyncRequestTimeout bounds the per-node sync. Three probes +// in series at 8s each would blow past the cron interval, so the budget +// here covers the whole snapshot — FetchTrafficSnapshot internally caps +// each HTTP call at the runtime's own 10s ceiling but uses ctx for the +// outer total. +const nodeTrafficSyncRequestTimeout = 8 * time.Second + +// NodeTrafficSyncJob pulls absolute traffic + online stats from every +// enabled, currently-online remote node and merges them into the central +// DB. Mirrors NodeHeartbeatJob's structure: TryLock to skip pile-ups, +// errgroup-style fan-out with a concurrency cap, per-node ctx timeout. +// +// Offline nodes are skipped entirely — the heartbeat job already owns +// status tracking, and we'd just waste sockets retrying a node we know +// is unreachable. As soon as heartbeat marks a node online again, the +// next traffic tick picks it up. +type NodeTrafficSyncJob struct { + nodeService service.NodeService + inboundService service.InboundService + + // Coarse mutex prevents two ticks running concurrently if a single + // sync stalls past the 10s cron interval (rare but possible when + // many nodes are slow simultaneously). + running sync.Mutex +} + +// NewNodeTrafficSyncJob builds a singleton sync job. Cron hands the same +// instance to every tick so the running mutex is preserved across runs. +func NewNodeTrafficSyncJob() *NodeTrafficSyncJob { + return &NodeTrafficSyncJob{} +} + +func (j *NodeTrafficSyncJob) Run() { + if !j.running.TryLock() { + return + } + defer j.running.Unlock() + + mgr := runtime.GetManager() + if mgr == nil { + // Server still booting — pre-Manager runs are normal during + // the first few seconds of startup. + return + } + + nodes, err := j.nodeService.GetAll() + if err != nil { + logger.Warning("node traffic sync: load nodes failed:", err) + return + } + if len(nodes) == 0 { + return + } + + sem := make(chan struct{}, nodeTrafficSyncConcurrency) + var wg sync.WaitGroup + for _, n := range nodes { + if !n.Enable || n.Status != "online" { + continue + } + wg.Add(1) + sem <- struct{}{} + go func(n *model.Node) { + defer wg.Done() + defer func() { <-sem }() + j.syncOne(mgr, n) + }(n) + } + wg.Wait() + + // One broadcast per tick, batched across all nodes — frontend code + // is invariant to whether the rows came from local xray or a node, + // so we reuse the same WebSocket envelope XrayTrafficJob uses. + if websocket.HasClients() { + online := j.inboundService.GetOnlineClients() + if online == nil { + online = []string{} + } + lastOnline, err := j.inboundService.GetClientsLastOnline() + if err != nil { + logger.Warning("node traffic sync: get last-online failed:", err) + } + if lastOnline == nil { + lastOnline = map[string]int64{} + } + websocket.BroadcastTraffic(map[string]any{ + "onlineClients": online, + "lastOnlineMap": lastOnline, + }) + } +} + +// syncOne fetches and merges one node's snapshot. Errors are logged +// per-node and don't propagate; one slow node shouldn't keep the rest +// from running. +func (j *NodeTrafficSyncJob) syncOne(mgr *runtime.Manager, n *model.Node) { + ctx, cancel := context.WithTimeout(context.Background(), nodeTrafficSyncRequestTimeout) + defer cancel() + + rt, err := mgr.RemoteFor(n) + if err != nil { + logger.Warning("node traffic sync: remote lookup failed for", n.Name, ":", err) + return + } + snap, err := rt.FetchTrafficSnapshot(ctx) + if err != nil { + logger.Warning("node traffic sync: fetch from", n.Name, "failed:", err) + // Drop node-online contribution so a hiccup doesn't leave the + // online filter showing stale clients indefinitely. + j.inboundService.ClearNodeOnlineClients(n.Id) + return + } + if err := j.inboundService.SetRemoteTraffic(n.Id, snap); err != nil { + logger.Warning("node traffic sync: merge for", n.Name, "failed:", err) + } +} diff --git a/web/locale/locale.go b/web/locale/locale.go index 73da75b4..69aeabff 100644 --- a/web/locale/locale.go +++ b/web/locale/locale.go @@ -4,6 +4,7 @@ package locale import ( "embed" + "encoding/json" "io/fs" "os" "strings" @@ -12,7 +13,6 @@ import ( "github.com/gin-gonic/gin" "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/pelletier/go-toml/v2" "golang.org/x/text/language" ) @@ -39,7 +39,7 @@ type SettingService interface { func InitLocalizer(i18nFS embed.FS, settingService SettingService) error { // set default bundle to English i18nBundle = i18n.NewBundle(language.MustParse("en-US")) - i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + i18nBundle.RegisterUnmarshalFunc("json", json.Unmarshal) // parse files if err := parseTranslationFiles(i18nFS, i18nBundle); err != nil { @@ -125,7 +125,7 @@ func LocalizerMiddleware() gin.HandlerFunc { // Ensure bundle is initialized so creating a Localizer won't panic if i18nBundle == nil { i18nBundle = i18n.NewBundle(language.MustParse("en-US")) - i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + i18nBundle.RegisterUnmarshalFunc("json", json.Unmarshal) // Try lazy-load from disk when running sub server without InitLocalizer if err := loadTranslationsFromDisk(i18nBundle); err != nil { logger.Warning("i18n lazy load failed:", err) diff --git a/web/middleware/security.go b/web/middleware/security.go index 478d1a62..ec2fa013 100644 --- a/web/middleware/security.go +++ b/web/middleware/security.go @@ -23,8 +23,15 @@ func SecurityHeadersMiddleware(directHTTPS bool) gin.HandlerFunc { } // CSRFMiddleware rejects unsafe requests that do not include the session CSRF token. +// Bearer-token-authenticated callers (api_authed flag set by APIController.checkAPIAuth) +// short-circuit the CSRF check — they are not browser sessions, so the +// cross-site request forgery threat model doesn't apply to them. func CSRFMiddleware() gin.HandlerFunc { return func(c *gin.Context) { + if c.GetBool("api_authed") { + c.Next() + return + } if isSafeMethod(c.Request.Method) { c.Next() return diff --git a/web/runtime/local.go b/web/runtime/local.go new file mode 100644 index 00000000..bd88e46a --- /dev/null +++ b/web/runtime/local.go @@ -0,0 +1,137 @@ +package runtime + +import ( + "context" + "encoding/json" + "errors" + "sync" + + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/xray" +) + +// LocalDeps wires the runtime to the panel's xray process and the +// service.XrayService restart trigger via callbacks. We use callbacks +// (not an interface to *service.XrayService) because the runtime +// package would otherwise cycle-import service. +type LocalDeps struct { + // APIPort returns the xray gRPC API port the local engine is + // currently listening on. Returns 0 when xray isn't running yet — + // callers should treat that as a transient error. + APIPort func() int + // SetNeedRestart trips the panel's "restart xray on next cron tick" + // flag. Mirrors how InboundController.addInbound calls + // xrayService.SetToNeedRestart() today. + SetNeedRestart func() +} + +// Local implements Runtime against the panel's own xray process. Each +// call follows the existing inbound.go pattern: open a gRPC client, +// run one operation, close. Per-call init keeps the connection state +// scoped so a stuck call can't leak across operations. +type Local struct { + deps LocalDeps + + // Serialise gRPC operations — xray's HandlerService isn't documented + // as concurrent-safe and the existing InboundService implicitly + // runs one op at a time per request. This matches that. + mu sync.Mutex +} + +// NewLocal builds a Local runtime. deps.APIPort and deps.SetNeedRestart +// are required; callers that want a no-op restart can pass `func(){}`. +func NewLocal(deps LocalDeps) *Local { + return &Local{deps: deps} +} + +func (l *Local) Name() string { return "local" } + +// withAPI runs fn against a freshly-initialised XrayAPI client and +// guarantees Close() afterwards. Returns an error if the gRPC port +// isn't available yet (xray still starting / stopped). +func (l *Local) withAPI(fn func(api *xray.XrayAPI) error) error { + l.mu.Lock() + defer l.mu.Unlock() + + port := l.deps.APIPort() + if port <= 0 { + return errors.New("local xray is not running") + } + var api xray.XrayAPI + if err := api.Init(port); err != nil { + return err + } + defer api.Close() + return fn(&api) +} + +func (l *Local) AddInbound(_ context.Context, ib *model.Inbound) error { + body, err := json.MarshalIndent(ib.GenXrayInboundConfig(), "", " ") + if err != nil { + return err + } + return l.withAPI(func(api *xray.XrayAPI) error { + return api.AddInbound(body) + }) +} + +func (l *Local) DelInbound(_ context.Context, ib *model.Inbound) error { + return l.withAPI(func(api *xray.XrayAPI) error { + return api.DelInbound(ib.Tag) + }) +} + +func (l *Local) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound) error { + // xray-core has no in-place inbound update — drop and re-add. + // Matches what InboundService.UpdateInbound did inline. + if err := l.DelInbound(ctx, oldIb); err != nil { + // Best-effort: continue to AddInbound so a transient remove + // failure (e.g. inbound already gone) doesn't strand us. The + // caller's needRestart fallback will reconcile from config. + _ = err + } + if !newIb.Enable { + // Disabled inbounds aren't pushed to xray; we already removed + // the old one above. + return nil + } + return l.AddInbound(ctx, newIb) +} + +func (l *Local) AddUser(_ context.Context, ib *model.Inbound, userMap map[string]any) error { + return l.withAPI(func(api *xray.XrayAPI) error { + return api.AddUser(string(ib.Protocol), ib.Tag, userMap) + }) +} + +func (l *Local) RemoveUser(_ context.Context, ib *model.Inbound, email string) error { + return l.withAPI(func(api *xray.XrayAPI) error { + return api.RemoveUser(ib.Tag, email) + }) +} + +func (l *Local) RestartXray(_ context.Context) error { + if l.deps.SetNeedRestart != nil { + l.deps.SetNeedRestart() + } + return nil +} + +// Reset methods are intentional no-ops for Local. The central DB UPDATE +// that runs in InboundService.Reset* before this call has already zeroed +// the counters that xray reads; on the next stats poll the gRPC service +// will pick up matching values. Pre-Phase-1 the panel never issued an +// xrayApi reset call here either — keeping the same shape avoids a +// behaviour change for single-panel users. + +func (l *Local) ResetClientTraffic(_ context.Context, _ *model.Inbound, _ string) error { + return nil +} + +func (l *Local) ResetInboundClientTraffics(_ context.Context, _ *model.Inbound) error { + return nil +} + +func (l *Local) ResetAllTraffics(_ context.Context) error { + return nil +} diff --git a/web/runtime/manager.go b/web/runtime/manager.go new file mode 100644 index 00000000..c89499ec --- /dev/null +++ b/web/runtime/manager.go @@ -0,0 +1,145 @@ +package runtime + +import ( + "errors" + "sync" + + "github.com/mhsanaei/3x-ui/v2/database" + "github.com/mhsanaei/3x-ui/v2/database/model" +) + +// Manager is the entry point for service code that needs a Runtime. +// One singleton lives in the package-level `manager` var, set at +// server bootstrap (web.go calls SetManager once). InboundService and +// friends read it via GetManager(). +// +// Local runs forever; Remotes are built lazily per nodeID and cached. +// Cache invalidation runs on node Update/Delete (NodeService hooks +// InvalidateNode) so a token rotation surfaces the next call. +type Manager struct { + local Runtime + + mu sync.RWMutex + remotes map[int]*Remote +} + +// NewManager wires the singleton with the deps Local needs. The runtime +// package can't import service so the caller (web.go) supplies the +// callbacks that bridge into XrayService. +func NewManager(localDeps LocalDeps) *Manager { + return &Manager{ + local: NewLocal(localDeps), + remotes: make(map[int]*Remote), + } +} + +// RuntimeFor picks the right adapter for an inbound based on NodeID. +// Returns local when nodeID is nil; otherwise looks up the node row +// (or returns the cached Remote for it). The caller does not need to +// know which kind they got — that's the point of the abstraction. +func (m *Manager) RuntimeFor(nodeID *int) (Runtime, error) { + if nodeID == nil { + return m.local, nil + } + m.mu.RLock() + if rt, ok := m.remotes[*nodeID]; ok { + m.mu.RUnlock() + return rt, nil + } + m.mu.RUnlock() + + // Cache miss — load the node row and build a Remote. We re-check + // under the write lock to avoid duplicate construction under load. + m.mu.Lock() + defer m.mu.Unlock() + if rt, ok := m.remotes[*nodeID]; ok { + return rt, nil + } + n, err := loadNode(*nodeID) + if err != nil { + return nil, err + } + if !n.Enable { + return nil, errors.New("node " + n.Name + " is disabled") + } + rt := NewRemote(n) + m.remotes[*nodeID] = rt + return rt, nil +} + +// Local returns the singleton local runtime. Used by code that needs +// to operate on the panel's own xray regardless of which inbound it +// came from (e.g. on-demand restart from the UI). +func (m *Manager) Local() Runtime { return m.local } + +// RemoteFor returns the Remote adapter for an already-loaded node row. +// Differs from RuntimeFor in two ways: it skips the DB lookup (caller +// hands in the node), and it returns the concrete *Remote so callers +// like NodeTrafficSyncJob can reach FetchTrafficSnapshot, which the +// Runtime interface doesn't expose. +func (m *Manager) RemoteFor(node *model.Node) (*Remote, error) { + if node == nil { + return nil, errors.New("node is nil") + } + m.mu.RLock() + if rt, ok := m.remotes[node.Id]; ok { + m.mu.RUnlock() + return rt, nil + } + m.mu.RUnlock() + + m.mu.Lock() + defer m.mu.Unlock() + if rt, ok := m.remotes[node.Id]; ok { + return rt, nil + } + rt := NewRemote(node) + m.remotes[node.Id] = rt + return rt, nil +} + +// InvalidateNode drops the cached Remote for nodeID so the next +// RuntimeFor call rebuilds it from the (possibly updated) node row. +// Called from NodeService.Update / Delete. +func (m *Manager) InvalidateNode(nodeID int) { + m.mu.Lock() + defer m.mu.Unlock() + delete(m.remotes, nodeID) +} + +// loadNode reads a node row directly from the DB. Kept package-local +// to avoid pulling NodeService into the runtime — service depends on +// runtime, not the other way around. +func loadNode(id int) (*model.Node, error) { + db := database.GetDB() + n := &model.Node{} + if err := db.Model(model.Node{}).Where("id = ?", id).First(n).Error; err != nil { + return nil, err + } + return n, nil +} + +// Singleton wiring ------------------------------------------------------- + +var ( + managerMu sync.RWMutex + manager *Manager +) + +// SetManager installs the process-wide Manager. web.go calls this once +// during NewServer. Tests can call it again with a stub. +func SetManager(m *Manager) { + managerMu.Lock() + defer managerMu.Unlock() + manager = m +} + +// GetManager returns the installed Manager, or nil before SetManager +// has run. Callers should treat nil as "still booting" — the existing +// behaviour for code paths that only run on the local engine continues +// to work via a pre-wired fallback set up in init() below. +func GetManager() *Manager { + managerMu.RLock() + defer managerMu.RUnlock() + return manager +} diff --git a/web/runtime/remote.go b/web/runtime/remote.go new file mode 100644 index 00000000..2195528a --- /dev/null +++ b/web/runtime/remote.go @@ -0,0 +1,407 @@ +package runtime + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/logger" +) + +// remoteHTTPTimeout bounds a single remote API call. Generous enough for +// a slow node under load, short enough that a wedged remote doesn't +// block the central panel's UI thread for the user. +const remoteHTTPTimeout = 10 * time.Second + +// remoteHTTPClient is shared so repeated calls to the same node reuse +// connections. Per-request timeouts are set via context. +var remoteHTTPClient = &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 64, + MaxIdleConnsPerHost: 4, + IdleConnTimeout: 60 * time.Second, + }, +} + +// envelope mirrors entity.Msg without depending on the entity package +// (avoids a cycle on the controller side that pulls in this runtime). +type envelope struct { + Success bool `json:"success"` + Msg string `json:"msg"` + Obj json.RawMessage `json:"obj"` +} + +// Remote implements Runtime by calling the existing /panel/api/inbounds/* +// endpoints on a remote 3x-ui panel. The remote is authenticated as +// the central panel via its per-node Bearer token. +// +// remoteIDByTag caches the {tag → remote inbound id} mapping so the +// hot path (update/delete/addClient) avoids /list lookups. The cache +// is in-memory and rebuilt lazily on first miss after a process restart +// or InvalidateNode call. +type Remote struct { + node *model.Node + + mu sync.RWMutex + remoteIDByTag map[string]int +} + +// NewRemote constructs a Remote runtime for one node. The node pointer +// is cached; callers that mutate node config (via NodeService.Update) +// must drop the runtime through Manager.InvalidateNode so a fresh one +// picks up the new fields. +func NewRemote(n *model.Node) *Remote { + return &Remote{ + node: n, + remoteIDByTag: make(map[string]int), + } +} + +func (r *Remote) Name() string { return "node:" + r.node.Name } + +// baseURL composes the panel root for r.node, e.g. https://1.2.3.4:2053/ +// Always ends in '/' so callers can append "panel/api/...". +func (r *Remote) baseURL() string { + bp := r.node.BasePath + if bp == "" { + bp = "/" + } + if !strings.HasSuffix(bp, "/") { + bp += "/" + } + return fmt.Sprintf("%s://%s:%d%s", r.node.Scheme, r.node.Address, r.node.Port, bp) +} + +// do issues an HTTP request against the remote panel and decodes the +// entity.Msg envelope. Returns an error for transport failures, non-2xx +// responses, or {success:false} bodies. +// +// body may be nil. For application/x-www-form-urlencoded calls (the +// existing controllers bind via c.ShouldBind which prefers form-encoded) +// pass url.Values; for JSON pass any other type and we'll marshal it. +func (r *Remote) do(ctx context.Context, method, path string, body any) (*envelope, error) { + if r.node.ApiToken == "" { + return nil, errors.New("node has no API token configured") + } + + target := r.baseURL() + strings.TrimPrefix(path, "/") + + var ( + reqBody io.Reader + contentType string + ) + switch b := body.(type) { + case nil: + // nothing + case url.Values: + reqBody = strings.NewReader(b.Encode()) + contentType = "application/x-www-form-urlencoded" + default: + buf, err := json.Marshal(b) + if err != nil { + return nil, fmt.Errorf("marshal body: %w", err) + } + reqBody = bytes.NewReader(buf) + contentType = "application/json" + } + + cctx, cancel := context.WithTimeout(ctx, remoteHTTPTimeout) + defer cancel() + req, err := http.NewRequestWithContext(cctx, method, target, reqBody) + if err != nil { + return nil, err + } + req.Header.Set("Authorization", "Bearer "+r.node.ApiToken) + req.Header.Set("Accept", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + + resp, err := remoteHTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("%s %s: %w", method, path, err) + } + defer resp.Body.Close() + + raw, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read body: %w", err) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%s %s: HTTP %d", method, path, resp.StatusCode) + } + + var env envelope + if err := json.Unmarshal(raw, &env); err != nil { + return nil, fmt.Errorf("decode envelope: %w", err) + } + if !env.Success { + return &env, fmt.Errorf("remote: %s", env.Msg) + } + return &env, nil +} + +// resolveRemoteID returns the remote panel's local inbound ID for the +// given tag. Cache-backed; on miss it hits /panel/api/inbounds/list and +// repopulates the whole map (one-shot list is cheaper than per-tag +// lookups when several inbounds need resolving in sequence). +func (r *Remote) resolveRemoteID(ctx context.Context, tag string) (int, error) { + if id, ok := r.cacheGet(tag); ok { + return id, nil + } + if err := r.refreshRemoteIDs(ctx); err != nil { + return 0, err + } + if id, ok := r.cacheGet(tag); ok { + return id, nil + } + return 0, fmt.Errorf("remote inbound with tag %q not found on node %s", tag, r.node.Name) +} + +func (r *Remote) cacheGet(tag string) (int, bool) { + r.mu.RLock() + defer r.mu.RUnlock() + id, ok := r.remoteIDByTag[tag] + return id, ok +} + +func (r *Remote) cacheSet(tag string, id int) { + r.mu.Lock() + defer r.mu.Unlock() + r.remoteIDByTag[tag] = id +} + +func (r *Remote) cacheDel(tag string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.remoteIDByTag, tag) +} + +// refreshRemoteIDs replaces the in-memory tag→id map with whatever the +// node currently has. Called on cache miss; also a useful recovery path +// when the remote panel is rebuilt or we get a "not found" on update. +func (r *Remote) refreshRemoteIDs(ctx context.Context) error { + env, err := r.do(ctx, http.MethodGet, "panel/api/inbounds/list", nil) + if err != nil { + return err + } + var list []struct { + Id int `json:"id"` + Tag string `json:"tag"` + } + if err := json.Unmarshal(env.Obj, &list); err != nil { + return fmt.Errorf("decode inbound list: %w", err) + } + next := make(map[string]int, len(list)) + for _, ib := range list { + if ib.Tag == "" { + continue + } + next[ib.Tag] = ib.Id + } + r.mu.Lock() + r.remoteIDByTag = next + r.mu.Unlock() + return nil +} + +func (r *Remote) AddInbound(ctx context.Context, ib *model.Inbound) error { + // Strip NodeID from the wire payload so the remote stores a "local" + // row from its own perspective. We also ship the full model.Inbound + // minus runtime metadata. Tag is preserved so central + remote agree + // on the identifier — relies on InboundController being patched to + // not overwrite a non-empty Tag. + payload := wireInbound(ib) + env, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/add", payload) + if err != nil { + return err + } + // Response body contains the saved inbound (with the remote's Id). + var created struct { + Id int `json:"id"` + Tag string `json:"tag"` + } + if len(env.Obj) > 0 { + if err := json.Unmarshal(env.Obj, &created); err == nil && created.Id > 0 && created.Tag != "" { + r.cacheSet(created.Tag, created.Id) + } + } + return nil +} + +func (r *Remote) DelInbound(ctx context.Context, ib *model.Inbound) error { + id, err := r.resolveRemoteID(ctx, ib.Tag) + if err != nil { + // Already gone on remote — treat as success so a sync after a + // remote panel reset doesn't strand the central panel. + logger.Warning("remote DelInbound: tag", ib.Tag, "not found on", r.node.Name, "— treating as success") + return nil + } + if _, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/del/"+strconv.Itoa(id), nil); err != nil { + return err + } + r.cacheDel(ib.Tag) + return nil +} + +func (r *Remote) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound) error { + // The remote's old row is keyed by oldIb.Tag (tags can change on + // edit if listen/port changed). We update by remote-id so the row + // keeps its identity even when its tag flips. + id, err := r.resolveRemoteID(ctx, oldIb.Tag) + if err != nil { + // Remote lost the row — fall back to add. This can happen if + // the node panel was reset; we'd rather end up with the inbound + // existing than fail the user's update. + return r.AddInbound(ctx, newIb) + } + payload := wireInbound(newIb) + if _, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/update/"+strconv.Itoa(id), payload); err != nil { + return err + } + // Tag may have changed — remap the cache. + if oldIb.Tag != newIb.Tag { + r.cacheDel(oldIb.Tag) + } + r.cacheSet(newIb.Tag, id) + return nil +} + +// AddUser pushes a single client into the remote inbound's settings JSON. +// We can't reuse the central panel's xrayApi.AddUser shape directly +// because the remote's HTTP endpoint expects {id, settings} where +// settings is a JSON string with a "clients":[...] array. The central +// panel's InboundService has already updated its own settings JSON +// before calling us, so we just ship the new full settings to the +// remote via /update — simpler than reconstructing the partial AddUser +// payload remote-side. +// +// Caller passes the full updated *model.Inbound on the same code path +// AddUser is called from in InboundService. To avoid changing the +// Runtime interface for that, AddUser/RemoveUser delegate to UpdateInbound. +func (r *Remote) AddUser(ctx context.Context, ib *model.Inbound, _ map[string]any) error { + return r.UpdateInbound(ctx, ib, ib) +} + +func (r *Remote) RemoveUser(ctx context.Context, ib *model.Inbound, _ string) error { + return r.UpdateInbound(ctx, ib, ib) +} + +func (r *Remote) RestartXray(ctx context.Context) error { + _, err := r.do(ctx, http.MethodPost, "panel/api/server/restartXrayService", nil) + return err +} + +func (r *Remote) ResetClientTraffic(ctx context.Context, ib *model.Inbound, email string) error { + id, err := r.resolveRemoteID(ctx, ib.Tag) + if err != nil { + // Already gone on remote — central reset is enough. + logger.Warning("remote ResetClientTraffic: tag", ib.Tag, "not found on", r.node.Name, "— treating as success") + return nil + } + _, err = r.do(ctx, http.MethodPost, + fmt.Sprintf("panel/api/inbounds/%d/resetClientTraffic/%s", id, url.PathEscape(email)), + nil) + return err +} + +func (r *Remote) ResetInboundClientTraffics(ctx context.Context, ib *model.Inbound) error { + id, err := r.resolveRemoteID(ctx, ib.Tag) + if err != nil { + logger.Warning("remote ResetInboundClientTraffics: tag", ib.Tag, "not found on", r.node.Name, "— treating as success") + return nil + } + _, err = r.do(ctx, http.MethodPost, + fmt.Sprintf("panel/api/inbounds/resetAllClientTraffics/%d", id), nil) + return err +} + +func (r *Remote) ResetAllTraffics(ctx context.Context) error { + _, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/resetAllTraffics", nil) + return err +} + +// TrafficSnapshot is what NodeTrafficSyncJob pulls from a remote node +// every cron tick. Inbounds carry absolute up/down/all_time + ClientStats +// (the same shape /panel/api/inbounds/list returns); the two map fields +// come from the dedicated /onlines and /lastOnline endpoints. +type TrafficSnapshot struct { + Inbounds []*model.Inbound + OnlineEmails []string + LastOnlineMap map[string]int64 +} + +// FetchTrafficSnapshot pulls the three pieces in series. Sequential is +// fine because the cron job already fans out across nodes — adding +// per-node parallelism on top would just thrash the remote. +// +// Not on the Runtime interface: only the sync job needs it, and Local +// has no equivalent (XrayTrafficJob already covers the local engine). +func (r *Remote) FetchTrafficSnapshot(ctx context.Context) (*TrafficSnapshot, error) { + snap := &TrafficSnapshot{LastOnlineMap: map[string]int64{}} + + envList, err := r.do(ctx, http.MethodGet, "panel/api/inbounds/list", nil) + if err != nil { + return nil, err + } + if err := json.Unmarshal(envList.Obj, &snap.Inbounds); err != nil { + return nil, fmt.Errorf("decode inbound list: %w", err) + } + + envOnlines, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/onlines", nil) + if err != nil { + // Onlines/lastOnline are nice-to-have. A failure here shouldn't + // invalidate the inbound counter merge — log and continue with + // empty values, the next tick may succeed. + logger.Warning("remote", r.node.Name, "onlines fetch failed:", err) + } else if len(envOnlines.Obj) > 0 { + _ = json.Unmarshal(envOnlines.Obj, &snap.OnlineEmails) + } + + envLastOnline, err := r.do(ctx, http.MethodPost, "panel/api/inbounds/lastOnline", nil) + if err != nil { + logger.Warning("remote", r.node.Name, "lastOnline fetch failed:", err) + } else if len(envLastOnline.Obj) > 0 { + _ = json.Unmarshal(envLastOnline.Obj, &snap.LastOnlineMap) + } + + return snap, nil +} + +// wireInbound builds the request body for /panel/api/inbounds/add and +// /update. Mirrors the form fields the existing InboundController +// expects via c.ShouldBind — we use form-encoded to match exactly. +// +// We deliberately omit Id (remote assigns its own), UserId (remote's +// fallback user takes over), NodeID (the remote sees itself as local), +// and ClientStats (those are joined-table data the remote rebuilds). +func wireInbound(ib *model.Inbound) url.Values { + v := url.Values{} + v.Set("up", strconv.FormatInt(ib.Up, 10)) + v.Set("down", strconv.FormatInt(ib.Down, 10)) + v.Set("total", strconv.FormatInt(ib.Total, 10)) + v.Set("remark", ib.Remark) + v.Set("enable", strconv.FormatBool(ib.Enable)) + v.Set("expiryTime", strconv.FormatInt(ib.ExpiryTime, 10)) + v.Set("listen", ib.Listen) + v.Set("port", strconv.Itoa(ib.Port)) + v.Set("protocol", string(ib.Protocol)) + v.Set("settings", ib.Settings) + v.Set("streamSettings", ib.StreamSettings) + v.Set("tag", ib.Tag) + v.Set("sniffing", ib.Sniffing) + if ib.TrafficReset != "" { + v.Set("trafficReset", ib.TrafficReset) + } + return v +} diff --git a/web/runtime/runtime.go b/web/runtime/runtime.go new file mode 100644 index 00000000..dd86d4c1 --- /dev/null +++ b/web/runtime/runtime.go @@ -0,0 +1,66 @@ +// Package runtime abstracts the live xray engine that an inbound's +// configuration is shipped to. Two implementations exist: Local talks +// to the panel's own xray via gRPC (the original behaviour); Remote +// talks to another 3x-ui panel's HTTP API as a managed Node. +// +// InboundService picks a Runtime per-inbound based on Inbound.NodeID. +// The point of the abstraction is to keep `if node != nil` checks out +// of the service code as Phase 2/3 features (traffic sync, subscription +// per-node) build on top. +package runtime + +import ( + "context" + + "github.com/mhsanaei/3x-ui/v2/database/model" +) + +// Runtime is the live-engine adapter for one inbound's worth of +// operations. Implementations must be safe for concurrent use — the +// service layer does not synchronise calls. +type Runtime interface { + // Name identifies the adapter in logs ("local", "node:"). + Name() string + + // AddInbound deploys an inbound to the engine. The Tag field on ib + // is treated as the source of truth for identifying the inbound on + // the remote side; Local ignores it. + AddInbound(ctx context.Context, ib *model.Inbound) error + + // DelInbound removes the inbound identified by ib.Tag. + DelInbound(ctx context.Context, ib *model.Inbound) error + + // UpdateInbound replaces the existing inbound with newIb. oldIb + // carries the previous config so the adapter can compute a minimal + // diff (Local: drop+add by tag; Remote: HTTP update by remote-id). + UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound) error + + // AddUser hot-adds a client to the inbound identified by ib.Tag. + // userMap matches the shape that xray.XrayAPI.AddUser already takes + // — keys: email, id, password, auth, security, flow, cipher. + AddUser(ctx context.Context, ib *model.Inbound, userMap map[string]any) error + + // RemoveUser hot-removes the client by email from ib's inbound. + RemoveUser(ctx context.Context, ib *model.Inbound, email string) error + + // RestartXray asks the engine to fully restart. For Local this just + // flips the SetToNeedRestart flag and lets the cron pick it up; for + // Remote it issues an HTTP POST to /panel/api/server/restartXrayService. + RestartXray(ctx context.Context) error + + // ResetClientTraffic zeros the up/down counters for one client on the + // engine. Local: no-op — the central DB UPDATE that runs before this + // call is sufficient, and xray's gRPC stats counter resets on the next + // poll. Remote: HTTP POST so the next traffic sync doesn't pull the + // pre-reset absolute back from the node. + ResetClientTraffic(ctx context.Context, ib *model.Inbound, email string) error + + // ResetInboundClientTraffics zeros every client of one inbound. Same + // Local/Remote split as ResetClientTraffic. + ResetInboundClientTraffics(ctx context.Context, ib *model.Inbound) error + + // ResetAllTraffics zeros every inbound counter on the engine. Used by + // the panel-wide "reset all traffic" action; called once per affected + // node so that nodes with no inbounds for the current panel are skipped. + ResetAllTraffics(ctx context.Context) error +} diff --git a/web/service/inbound.go b/web/service/inbound.go index c99843b3..9c44e840 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -3,6 +3,7 @@ package service import ( + "context" "encoding/json" "fmt" "sort" @@ -15,26 +16,126 @@ import ( "github.com/mhsanaei/3x-ui/v2/database/model" "github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/util/common" + "github.com/mhsanaei/3x-ui/v2/web/runtime" "github.com/mhsanaei/3x-ui/v2/xray" "gorm.io/gorm" + "gorm.io/gorm/clause" ) // InboundService provides business logic for managing Xray inbound configurations. // It handles CRUD operations for inbounds, client management, traffic monitoring, // and integration with the Xray API for real-time updates. type InboundService struct { + // xrayApi is retained for backwards compatibility with bulk paths + // that still talk to the local engine directly (e.g. traffic-reset + // jobs that scope to NodeID IS NULL inbounds anyway). New code paths + // route through runtimeFor() instead so they can target remote nodes. xrayApi xray.XrayAPI } +// runtimeFor returns the Runtime adapter for an inbound's destination +// engine. Returns the local runtime when the inbound has no NodeID +// (legacy/local inbounds); otherwise the cached Remote for that node. +// +// nil is returned only when the runtime Manager hasn't been wired yet +// (extremely early bootstrap). Callers treat nil as a transient error +// and either fall back to needRestart=true or surface "panel still +// starting" upstream. +func (s *InboundService) runtimeFor(ib *model.Inbound) (runtime.Runtime, error) { + mgr := runtime.GetManager() + if mgr == nil { + return nil, fmt.Errorf("runtime manager not initialised") + } + return mgr.RuntimeFor(ib.NodeID) +} + type CopyClientsResult struct { Added []string `json:"added"` Skipped []string `json:"skipped"` Errors []string `json:"errors"` } -// GetInbounds retrieves all inbounds for a specific user. -// Returns a slice of inbound models with their associated client statistics. +// enrichClientStats parses each inbound's clients once, fills in the +// UUID/SubId fields on the preloaded ClientStats, and tops up rows owned by +// a sibling inbound (shared-email mode — the row is keyed on email so it +// only preloads on its owning inbound). +func (s *InboundService) enrichClientStats(db *gorm.DB, inbounds []*model.Inbound) { + if len(inbounds) == 0 { + return + } + clientsByInbound := make([][]model.Client, len(inbounds)) + seenByInbound := make([]map[string]struct{}, len(inbounds)) + missing := make(map[string]struct{}) + for i, inbound := range inbounds { + clients, _ := s.GetClients(inbound) + clientsByInbound[i] = clients + seen := make(map[string]struct{}, len(inbound.ClientStats)) + for _, st := range inbound.ClientStats { + if st.Email != "" { + seen[strings.ToLower(st.Email)] = struct{}{} + } + } + seenByInbound[i] = seen + for _, c := range clients { + if c.Email == "" { + continue + } + if _, ok := seen[strings.ToLower(c.Email)]; !ok { + missing[c.Email] = struct{}{} + } + } + } + if len(missing) > 0 { + emails := make([]string, 0, len(missing)) + for e := range missing { + emails = append(emails, e) + } + var extra []xray.ClientTraffic + if err := db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&extra).Error; err != nil { + logger.Warning("enrichClientStats:", err) + } else { + byEmail := make(map[string]xray.ClientTraffic, len(extra)) + for _, st := range extra { + byEmail[strings.ToLower(st.Email)] = st + } + for i, inbound := range inbounds { + for _, c := range clientsByInbound[i] { + if c.Email == "" { + continue + } + key := strings.ToLower(c.Email) + if _, ok := seenByInbound[i][key]; ok { + continue + } + if st, ok := byEmail[key]; ok { + inbound.ClientStats = append(inbound.ClientStats, st) + seenByInbound[i][key] = struct{}{} + } + } + } + } + } + for i, inbound := range inbounds { + clients := clientsByInbound[i] + if len(clients) == 0 || len(inbound.ClientStats) == 0 { + continue + } + cMap := make(map[string]model.Client, len(clients)) + for _, c := range clients { + cMap[strings.ToLower(c.Email)] = c + } + for j := range inbound.ClientStats { + email := strings.ToLower(inbound.ClientStats[j].Email) + if c, ok := cMap[email]; ok { + inbound.ClientStats[j].UUID = c.ID + inbound.ClientStats[j].SubId = c.SubID + } + } + } +} + +// GetInbounds retrieves all inbounds for a specific user with client stats. func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound @@ -42,30 +143,11 @@ func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { if err != nil && err != gorm.ErrRecordNotFound { return nil, err } - // Enrich client stats with UUID/SubId from inbound settings - for _, inbound := range inbounds { - clients, _ := s.GetClients(inbound) - if len(clients) == 0 || len(inbound.ClientStats) == 0 { - continue - } - // Build a map email -> client - cMap := make(map[string]model.Client, len(clients)) - for _, c := range clients { - cMap[strings.ToLower(c.Email)] = c - } - for i := range inbound.ClientStats { - email := strings.ToLower(inbound.ClientStats[i].Email) - if c, ok := cMap[email]; ok { - inbound.ClientStats[i].UUID = c.ID - inbound.ClientStats[i].SubId = c.SubID - } - } - } + s.enrichClientStats(db, inbounds) return inbounds, nil } -// GetAllInbounds retrieves all inbounds from the database. -// Returns a slice of all inbound models with their associated client statistics. +// GetAllInbounds retrieves all inbounds with client stats. func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound @@ -73,24 +155,7 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { if err != nil && err != gorm.ErrRecordNotFound { return nil, err } - // Enrich client stats with UUID/SubId from inbound settings - for _, inbound := range inbounds { - clients, _ := s.GetClients(inbound) - if len(clients) == 0 || len(inbound.ClientStats) == 0 { - continue - } - cMap := make(map[string]model.Client, len(clients)) - for _, c := range clients { - cMap[strings.ToLower(c.Email)] = c - } - for i := range inbound.ClientStats { - email := strings.ToLower(inbound.ClientStats[i].Email) - if c, ok := cMap[email]; ok { - inbound.ClientStats[i].UUID = c.ID - inbound.ClientStats[i].SubId = c.SubID - } - } - } + s.enrichClientStats(db, inbounds) return inbounds, nil } @@ -122,7 +187,7 @@ func (s *InboundService) getAllEmails() ([]string, error) { db := database.GetDB() var emails []string err := db.Raw(` - SELECT JSON_EXTRACT(client.value, '$.email') + SELECT DISTINCT JSON_EXTRACT(client.value, '$.email') FROM inbounds, JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client `).Scan(&emails).Error @@ -132,55 +197,97 @@ func (s *InboundService) getAllEmails() ([]string, error) { return emails, nil } -func (s *InboundService) contains(slice []string, str string) bool { - lowerStr := strings.ToLower(str) - for _, s := range slice { - if strings.ToLower(s) == lowerStr { - return true - } +// getAllEmailSubIDs returns email→subId. An email seen with two different +// non-empty subIds is locked (mapped to "") so neither identity can claim it. +func (s *InboundService) getAllEmailSubIDs() (map[string]string, error) { + db := database.GetDB() + var rows []struct { + Email string + SubID string } - return false + err := db.Raw(` + SELECT JSON_EXTRACT(client.value, '$.email') AS email, + JSON_EXTRACT(client.value, '$.subId') AS sub_id + FROM inbounds, + JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client + `).Scan(&rows).Error + if err != nil { + return nil, err + } + result := make(map[string]string, len(rows)) + for _, r := range rows { + email := strings.ToLower(strings.Trim(r.Email, "\"")) + if email == "" { + continue + } + subID := strings.Trim(r.SubID, "\"") + if existing, ok := result[email]; ok { + if existing != subID { + result[email] = "" + } + continue + } + result[email] = subID + } + return result, nil } +func lowerAll(in []string) []string { + out := make([]string, len(in)) + for i, s := range in { + out[i] = strings.ToLower(s) + } + return out +} + +// emailUsedByOtherInbounds reports whether email lives in any inbound other +// than exceptInboundId. Empty email returns false. +func (s *InboundService) emailUsedByOtherInbounds(email string, exceptInboundId int) (bool, error) { + if email == "" { + return false, nil + } + db := database.GetDB() + var count int64 + err := db.Raw(` + SELECT COUNT(*) + FROM inbounds, + JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client + WHERE inbounds.id != ? + AND LOWER(JSON_EXTRACT(client.value, '$.email')) = LOWER(?) + `, exceptInboundId, email).Scan(&count).Error + if err != nil { + return false, err + } + return count > 0, nil +} + +// checkEmailsExistForClients validates a batch of incoming clients. An email +// collides only when the existing holder has a different (or empty) subId — +// matching non-empty subIds let multiple inbounds share one identity. func (s *InboundService) checkEmailsExistForClients(clients []model.Client) (string, error) { - allEmails, err := s.getAllEmails() + emailSubIDs, err := s.getAllEmailSubIDs() if err != nil { return "", err } - var emails []string + seen := make(map[string]string, len(clients)) for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { - return client.Email, nil - } - if s.contains(allEmails, client.Email) { - return client.Email, nil - } - emails = append(emails, client.Email) + if client.Email == "" { + continue } - } - return "", nil -} - -func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (string, error) { - clients, err := s.GetClients(inbound) - if err != nil { - return "", err - } - allEmails, err := s.getAllEmails() - if err != nil { - return "", err - } - var emails []string - for _, client := range clients { - if client.Email != "" { - if s.contains(emails, client.Email) { + key := strings.ToLower(client.Email) + // Within the same payload, the same email must carry the same subId; + // otherwise we would silently merge two distinct identities. + if prev, ok := seen[key]; ok { + if prev != client.SubID || client.SubID == "" { return client.Email, nil } - if s.contains(allEmails, client.Email) { + continue + } + seen[key] = client.SubID + if existingSub, ok := emailSubIDs[key]; ok { + if client.SubID == "" || existingSub == "" || existingSub != client.SubID { return client.Email, nil } - emails = append(emails, client.Email) } } return "", nil @@ -209,7 +316,11 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo return inbound, false, err } - existEmail, err := s.checkEmailExistForInbound(inbound) + clients, err := s.GetClients(inbound) + if err != nil { + return inbound, false, err + } + existEmail, err := s.checkEmailsExistForClients(clients) if err != nil { return inbound, false, err } @@ -217,11 +328,6 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo return inbound, false, common.NewError("Duplicate email:", existEmail) } - clients, err := s.GetClients(inbound) - if err != nil { - return inbound, false, err - } - // Ensure created_at and updated_at on clients in settings if len(clients) > 0 { var settings map[string]any @@ -291,20 +397,28 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo needRestart := false if inbound.Enable { - s.xrayApi.Init(p.GetAPIPort()) - inboundJson, err1 := json.MarshalIndent(inbound.GenXrayInboundConfig(), "", " ") - if err1 != nil { - logger.Debug("Unable to marshal inbound config:", err1) + rt, rterr := s.runtimeFor(inbound) + if rterr != nil { + // Fail-fast on remote routing errors. Assign to the named + // `err` so the deferred tx handler rolls back the central + // DB row that tx.Save just inserted — otherwise we'd leave + // an orphan that the user sees succeed despite the toast. + err = rterr + return inbound, false, err } - - err1 = s.xrayApi.AddInbound(inboundJson) - if err1 == nil { - logger.Debug("New inbound added by api:", inbound.Tag) + if err1 := rt.AddInbound(context.Background(), inbound); err1 == nil { + logger.Debug("New inbound added on", rt.Name(), ":", inbound.Tag) } else { - logger.Debug("Unable to add inbound by api:", err1) + logger.Debug("Unable to add inbound on", rt.Name(), ":", err1) + if inbound.NodeID != nil { + // Remote add failed — roll back so central + node stay + // in sync (no row on either side). + err = err1 + return inbound, false, err + } + // Local: keep the existing fall-through-to-restart behaviour. needRestart = true } - s.xrayApi.Close() } return inbound, needRestart, err @@ -316,21 +430,35 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo func (s *InboundService) DelInbound(id int) (bool, error) { db := database.GetDB() - var tag string needRestart := false - result := db.Model(model.Inbound{}).Select("tag").Where("id = ? and enable = ?", id, true).First(&tag) - if result.Error == nil { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.DelInbound(tag) - if err1 == nil { - logger.Debug("Inbound deleted by api:", tag) + // Load the full inbound (not just the tag) so we know its NodeID and + // can route the runtime call to the right engine. Skip-on-not-found + // preserves the old "no-op when DB row doesn't exist" behaviour. + var ib model.Inbound + loadErr := db.Model(model.Inbound{}).Where("id = ? and enable = ?", id, true).First(&ib).Error + if loadErr == nil { + // Delete is best-effort on the runtime side: the user's intent is + // to get rid of the inbound, so a missing node row, an offline + // node, or a remote-side "already gone" should NEVER block the + // central DB cleanup. Worst case the remote keeps an orphan that + // the user can clean up manually — far less painful than the row + // being stuck on central. + rt, rterr := s.runtimeFor(&ib) + if rterr != nil { + logger.Warning("DelInbound: runtime lookup failed, deleting central row anyway:", rterr) + if ib.NodeID == nil { + needRestart = true + } + } else if err1 := rt.DelInbound(context.Background(), &ib); err1 == nil { + logger.Debug("Inbound deleted on", rt.Name(), ":", ib.Tag) } else { - logger.Debug("Unable to delete inbound by api:", err1) - needRestart = true + logger.Warning("DelInbound on", rt.Name(), "failed, deleting central row anyway:", err1) + if ib.NodeID == nil { + needRestart = true + } } - s.xrayApi.Close() } else { - logger.Debug("No enabled inbound founded to removing by api", tag) + logger.Debug("No enabled inbound found to remove by api, id:", id) } // Delete client traffics of inbounds @@ -403,36 +531,43 @@ func (s *InboundService) SetInboundEnable(id int, enable bool) (bool, error) { } inbound.Enable = enable - // Sync xray runtime: drop the live inbound, add it back if we're enabling. - // "User not found"-style errors from DelInbound mean the inbound was - // already absent from the live config — that's fine. Any other error - // means the live config and DB diverged, so we ask the caller to - // schedule a restart. + // Sync xray runtime via the Runtime adapter. For local inbounds we + // also rebuild the runtime config (drops clients flagged as disabled + // in ClientTraffic) so the live xray sees the same filtered view it + // did pre-refactor. Remote runtimes ship the unfiltered inbound — + // the remote panel does its own filtering before pushing to its xray. needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - defer s.xrayApi.Close() + rt, rterr := s.runtimeFor(inbound) + if rterr != nil { + if inbound.NodeID != nil { + return false, rterr + } + return true, nil + } - if err := s.xrayApi.DelInbound(inbound.Tag); err != nil && + if err := rt.DelInbound(context.Background(), inbound); err != nil && !strings.Contains(err.Error(), "not found") { - logger.Debug("SetInboundEnable: DelInbound via api failed:", err) + logger.Debug("SetInboundEnable: DelInbound on", rt.Name(), "failed:", err) needRestart = true } if !enable { return needRestart, nil } - runtimeInbound, err := s.buildRuntimeInboundForAPI(db, inbound) - if err != nil { - logger.Debug("SetInboundEnable: build runtime config failed:", err) - return true, nil + addTarget := inbound + if inbound.NodeID == nil { + runtimeInbound, err := s.buildRuntimeInboundForAPI(db, inbound) + if err != nil { + logger.Debug("SetInboundEnable: build runtime config failed:", err) + return true, nil + } + addTarget = runtimeInbound } - inboundJson, err := json.MarshalIndent(runtimeInbound.GenXrayInboundConfig(), "", " ") - if err != nil { - logger.Debug("SetInboundEnable: marshal runtime config failed:", err) - return true, nil - } - if err := s.xrayApi.AddInbound(inboundJson); err != nil { - logger.Debug("SetInboundEnable: AddInbound via api failed:", err) + if err := rt.AddInbound(context.Background(), addTarget); err != nil { + logger.Debug("SetInboundEnable: AddInbound on", rt.Name(), "failed:", err) + if inbound.NodeID != nil { + return false, err + } needRestart = true } return needRestart, nil @@ -553,32 +688,52 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, } needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - if s.xrayApi.DelInbound(tag) == nil { - logger.Debug("Old inbound deleted by api:", tag) - } - if inbound.Enable { - runtimeInbound, err2 := s.buildRuntimeInboundForAPI(tx, oldInbound) - if err2 != nil { - logger.Debug("Unable to prepare runtime inbound config:", err2) - needRestart = true - } else { - inboundJson, err2 := json.MarshalIndent(runtimeInbound.GenXrayInboundConfig(), "", " ") - if err2 != nil { - logger.Debug("Unable to marshal updated inbound config:", err2) - needRestart = true - } else { - err2 = s.xrayApi.AddInbound(inboundJson) - if err2 == nil { - logger.Debug("Updated inbound added by api:", oldInbound.Tag) + rt, rterr := s.runtimeFor(oldInbound) + if rterr != nil { + if oldInbound.NodeID != nil { + err = rterr + return inbound, false, err + } + needRestart = true + } else { + // Use a snapshot of the OLD tag so the remote can resolve its + // remote-id even when the new tag has changed (port/listen edit). + oldSnapshot := *oldInbound + oldSnapshot.Tag = tag + if oldInbound.NodeID == nil { + // Local: keep the old del-then-add-filtered behaviour to + // preserve runtime client filtering. + if err2 := rt.DelInbound(context.Background(), &oldSnapshot); err2 == nil { + logger.Debug("Old inbound deleted on", rt.Name(), ":", tag) + } + if inbound.Enable { + runtimeInbound, err2 := s.buildRuntimeInboundForAPI(tx, oldInbound) + if err2 != nil { + logger.Debug("Unable to prepare runtime inbound config:", err2) + needRestart = true + } else if err2 := rt.AddInbound(context.Background(), runtimeInbound); err2 == nil { + logger.Debug("Updated inbound added on", rt.Name(), ":", oldInbound.Tag) } else { - logger.Debug("Unable to update inbound by api:", err2) + logger.Debug("Unable to update inbound on", rt.Name(), ":", err2) needRestart = true } } + } else { + // Remote: a single UpdateInbound call (the Remote adapter + // resolves remote-id by old tag, then POSTs /update/{id}). + // Assign to the outer `err` on failure so the deferred tx + // handler rolls back the central DB write. + if !inbound.Enable { + if err2 := rt.DelInbound(context.Background(), &oldSnapshot); err2 != nil { + err = err2 + return inbound, false, err + } + } else if err2 := rt.UpdateInbound(context.Background(), &oldSnapshot, oldInbound); err2 != nil { + err = err2 + return inbound, false, err + } } } - s.xrayApi.Close() return inbound, needRestart, tx.Save(oldInbound).Error } @@ -675,7 +830,8 @@ func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inb newEmails[newClients[i].Email] = struct{}{} } - // Removed clients — drop their stats rows. + // Drop stats rows for removed emails — but not when a sibling inbound + // still references the email, since the row is the shared accumulator. for i := range oldClients { email := oldClients[i].Email if email == "" { @@ -684,6 +840,13 @@ func (s *InboundService) updateClientTraffics(tx *gorm.DB, oldInbound *model.Inb if _, kept := newEmails[email]; kept { continue } + stillUsed, err := s.emailUsedByOtherInbounds(email, oldInbound.Id) + if err != nil { + return err + } + if stillUsed { + continue + } if err := s.DelClientStat(tx, email); err != nil { return err } @@ -793,36 +956,62 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) { }() needRestart := false - s.xrayApi.Init(p.GetAPIPort()) - for _, client := range clients { - if len(client.Email) > 0 { - s.AddClientStat(tx, data.Id, &client) - if client.Enable { - cipher := "" - if oldInbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) - } - err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{ - "email": client.Email, - "id": client.ID, - "auth": client.Auth, - "security": client.Security, - "flow": client.Flow, - "password": client.Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client added by api:", client.Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true - } + rt, rterr := s.runtimeFor(oldInbound) + if rterr != nil { + if oldInbound.NodeID != nil { + err = rterr + return false, err + } + needRestart = true + } else if oldInbound.NodeID == nil { + // Local: per-client AddUser keeps existing connections alive + // (incremental hot-add). Walk every new client; on any failure + // fall back to needRestart so cron rebuilds from scratch. + for _, client := range clients { + if len(client.Email) == 0 { + needRestart = true + continue } - } else { - needRestart = true + s.AddClientStat(tx, data.Id, &client) + if !client.Enable { + continue + } + cipher := "" + if oldInbound.Protocol == "shadowsocks" { + cipher = oldSettings["method"].(string) + } + err1 := rt.AddUser(context.Background(), oldInbound, map[string]any{ + "email": client.Email, + "id": client.ID, + "auth": client.Auth, + "security": client.Security, + "flow": client.Flow, + "password": client.Password, + "cipher": cipher, + }) + if err1 == nil { + logger.Debug("Client added on", rt.Name(), ":", client.Email) + } else { + logger.Debug("Error in adding client on", rt.Name(), ":", err1) + needRestart = true + } + } + } else { + // Remote: a single UpdateInbound ships the new clients in one + // HTTP round-trip rather than N. Settings are already mutated + // in-memory (oldInbound.Settings) so the remote sees the final + // state. Per-client ClientStat rows still need the central DB + // update so the loop runs that branch first. + for _, client := range clients { + if len(client.Email) > 0 { + s.AddClientStat(tx, data.Id, &client) + } + } + if err1 := rt.UpdateInbound(context.Background(), oldInbound, oldInbound); err1 != nil { + err = err1 + return false, err } } - s.xrayApi.Close() return needRestart, tx.Save(oldInbound).Error } @@ -1080,11 +1269,20 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, db := database.GetDB() - err = s.DelClientIPs(db, email) + // Keep the client_traffics row and IPs alive when another inbound still + // references this email — siblings depend on the shared accounting state. + emailShared, err := s.emailUsedByOtherInbounds(email, inboundId) if err != nil { - logger.Error("Error in delete client IPs") return false, err } + + if !emailShared { + err = s.DelClientIPs(db, email) + if err != nil { + logger.Error("Error in delete client IPs") + return false, err + } + } needRestart := false if len(email) > 0 { @@ -1094,26 +1292,38 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, logger.Error("Get stats error") return false, err } - err = s.DelClientStat(db, email) - if err != nil { - logger.Error("Delete stats Data Error") - return false, err + if !emailShared { + err = s.DelClientStat(db, email) + if err != nil { + logger.Error("Delete stats Data Error") + return false, err + } } if needApiDel && notDepleted { - s.xrayApi.Init(p.GetAPIPort()) - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email) - if err1 == nil { - logger.Debug("Client deleted by api:", email) - needRestart = false - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { + rt, rterr := s.runtimeFor(oldInbound) + if rterr != nil { + if oldInbound.NodeID != nil { + return false, rterr + } + needRestart = true + } else if oldInbound.NodeID == nil { + err1 := rt.RemoveUser(context.Background(), oldInbound, email) + if err1 == nil { + logger.Debug("Client deleted on", rt.Name(), ":", email) + needRestart = false + } else if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { logger.Debug("User is already deleted. Nothing to do more...") } else { - logger.Debug("Error in deleting client by api:", err1) + logger.Debug("Error in deleting client on", rt.Name(), ":", err1) needRestart = true } + } else { + // Remote: settings already mutated above; one UpdateInbound + // ships the post-deletion state to the node. + if err1 := rt.UpdateInbound(context.Background(), oldInbound, oldInbound); err1 != nil { + return false, err1 + } } - s.xrayApi.Close() } } return needRestart, db.Save(oldInbound).Error @@ -1254,65 +1464,113 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin if len(clients[0].Email) > 0 { if len(oldEmail) > 0 { - err = s.UpdateClientStat(tx, oldEmail, &clients[0]) - if err != nil { - return false, err + // Repointing onto an email that already has a row would collide on + // the unique constraint, so retire the donor and let the surviving + // row carry the merged identity. + emailUnchanged := strings.EqualFold(oldEmail, clients[0].Email) + targetExists := int64(0) + if !emailUnchanged { + if err = tx.Model(xray.ClientTraffic{}).Where("email = ?", clients[0].Email).Count(&targetExists).Error; err != nil { + return false, err + } } - err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) - if err != nil { - return false, err + if emailUnchanged || targetExists == 0 { + err = s.UpdateClientStat(tx, oldEmail, &clients[0]) + if err != nil { + return false, err + } + err = s.UpdateClientIPs(tx, oldEmail, clients[0].Email) + if err != nil { + return false, err + } + } else { + stillUsed, sErr := s.emailUsedByOtherInbounds(oldEmail, data.Id) + if sErr != nil { + return false, sErr + } + if !stillUsed { + if err = s.DelClientStat(tx, oldEmail); err != nil { + return false, err + } + if err = s.DelClientIPs(tx, oldEmail); err != nil { + return false, err + } + } + // Refresh the surviving row with the new client's limits/expiry. + if err = s.UpdateClientStat(tx, clients[0].Email, &clients[0]); err != nil { + return false, err + } } } else { s.AddClientStat(tx, data.Id, &clients[0]) } } else { - err = s.DelClientStat(tx, oldEmail) + stillUsed, err := s.emailUsedByOtherInbounds(oldEmail, data.Id) if err != nil { return false, err } - err = s.DelClientIPs(tx, oldEmail) - if err != nil { - return false, err + if !stillUsed { + err = s.DelClientStat(tx, oldEmail) + if err != nil { + return false, err + } + err = s.DelClientIPs(tx, oldEmail) + if err != nil { + return false, err + } } } needRestart := false if len(oldEmail) > 0 { - s.xrayApi.Init(p.GetAPIPort()) - if oldClients[clientIndex].Enable { - err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail) - if err1 == nil { - logger.Debug("Old client deleted by api:", oldEmail) - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) { + rt, rterr := s.runtimeFor(oldInbound) + if rterr != nil { + if oldInbound.NodeID != nil { + err = rterr + return false, err + } + needRestart = true + } else if oldInbound.NodeID == nil { + // Local: paired Remove+Add on the live xray, keeping other + // clients online (full-restart fallback on partial failure). + if oldClients[clientIndex].Enable { + err1 := rt.RemoveUser(context.Background(), oldInbound, oldEmail) + if err1 == nil { + logger.Debug("Old client deleted on", rt.Name(), ":", oldEmail) + } else if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) { logger.Debug("User is already deleted. Nothing to do more...") } else { - logger.Debug("Error in deleting client by api:", err1) + logger.Debug("Error in deleting client on", rt.Name(), ":", err1) needRestart = true } } - } - if clients[0].Enable { - cipher := "" - if oldInbound.Protocol == "shadowsocks" { - cipher = oldSettings["method"].(string) + if clients[0].Enable { + cipher := "" + if oldInbound.Protocol == "shadowsocks" { + cipher = oldSettings["method"].(string) + } + err1 := rt.AddUser(context.Background(), oldInbound, map[string]any{ + "email": clients[0].Email, + "id": clients[0].ID, + "security": clients[0].Security, + "flow": clients[0].Flow, + "auth": clients[0].Auth, + "password": clients[0].Password, + "cipher": cipher, + }) + if err1 == nil { + logger.Debug("Client edited on", rt.Name(), ":", clients[0].Email) + } else { + logger.Debug("Error in adding client on", rt.Name(), ":", err1) + needRestart = true + } } - err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{ - "email": clients[0].Email, - "id": clients[0].ID, - "security": clients[0].Security, - "flow": clients[0].Flow, - "auth": clients[0].Auth, - "password": clients[0].Password, - "cipher": cipher, - }) - if err1 == nil { - logger.Debug("Client edited by api:", clients[0].Email) - } else { - logger.Debug("Error in adding client by api:", err1) - needRestart = true + } else { + // Remote: settings already mutated; one UpdateInbound suffices. + if err1 := rt.UpdateInbound(context.Background(), oldInbound, oldInbound); err1 != nil { + err = err1 + return false, err } } - s.xrayApi.Close() } else { logger.Debug("Client old email not found") needRestart = true @@ -1320,6 +1578,140 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin return needRestart, tx.Save(oldInbound).Error } +// resetGracePeriodMs is the window after a reset during which incoming +// traffic snapshots from the node are ignored if they would resurrect +// non-zero counters. Three sync ticks (10s each) is enough headroom for +// the central → node reset HTTP call to land before the next pull. +const resetGracePeriodMs int64 = 30000 + +// SetRemoteTraffic merges absolute counters from a remote node into the +// central DB. Unlike AddTraffic, which adds deltas pulled from the local +// xray gRPC stats endpoint, this SETs the values — the node already has +// the canonical absolute value and we just mirror it. +// +// Rows in the post-reset grace window are skipped if the snapshot would +// regress them, so a user-initiated reset survives until the propagation +// HTTP call has completed on the node. After the grace window expires +// the snapshot wins regardless (the node is authoritative for the +// inbounds it hosts). +func (s *InboundService) SetRemoteTraffic(nodeID int, snap *runtime.TrafficSnapshot) error { + if snap == nil || nodeID <= 0 { + return nil + } + db := database.GetDB() + now := time.Now().UnixMilli() + + // Load central inbounds for this node so we can resolve tag→id and + // honour the per-inbound grace window. One query covers every row + // touched in this tick. + var central []model.Inbound + if err := db.Model(model.Inbound{}). + Where("node_id = ?", nodeID). + Find(¢ral).Error; err != nil { + return err + } + tagToCentral := make(map[string]*model.Inbound, len(central)) + for i := range central { + tagToCentral[central[i].Tag] = ¢ral[i] + } + + tx := db.Begin() + committed := false + defer func() { + if !committed { + tx.Rollback() + } + }() + + // Per-inbound counter merge. Skip rows whose central allTime is + // suspiciously lower than the snapshot AND we're inside the grace + // window — that's the "reset hit central but not the node yet" + // pattern we want to defer until next tick. + for _, snapIb := range snap.Inbounds { + if snapIb == nil { + continue + } + c, ok := tagToCentral[snapIb.Tag] + if !ok { + continue // node has an inbound the central doesn't know about — ignore + } + snapAllTime := snapIb.AllTime + if snapAllTime == 0 { + snapAllTime = snapIb.Up + snapIb.Down + } + inGrace := c.LastTrafficResetTime > 0 && now-c.LastTrafficResetTime < resetGracePeriodMs + if inGrace && snapAllTime > c.AllTime { + logger.Debug("SetRemoteTraffic: skipping inbound", c.Id, "in reset grace window") + continue + } + if err := tx.Model(model.Inbound{}). + Where("id = ?", c.Id). + Updates(map[string]any{ + "up": snapIb.Up, + "down": snapIb.Down, + "all_time": snapAllTime, + }).Error; err != nil { + return err + } + } + + // Per-client merge. The snapshot's ClientStats are nested under + // each Inbound, so flatten before walking. Each client_traffics row + // is keyed by (inbound_id, email) — we resolve inbound_id from the + // central inbound row matched above. + for _, snapIb := range snap.Inbounds { + if snapIb == nil { + continue + } + c, ok := tagToCentral[snapIb.Tag] + if !ok { + continue + } + // Honour the same grace window for client rows: if the parent + // inbound was just reset, leave its clients alone too. + inGrace := c.LastTrafficResetTime > 0 && now-c.LastTrafficResetTime < resetGracePeriodMs + for _, cs := range snapIb.ClientStats { + snapAllTime := cs.AllTime + if snapAllTime == 0 { + snapAllTime = cs.Up + cs.Down + } + if inGrace { + // Skip client rows whose snapshot would push counters + // back up; allow rows that are zero on the node side + // (those are normal — node was reset alongside central). + if snapAllTime > 0 { + continue + } + } + // MAX(last_online, ?) so a momentary clock skew on the node + // can't regress the central row's last-seen timestamp. + if err := tx.Exec( + `UPDATE client_traffics + SET up = ?, down = ?, all_time = ?, last_online = MAX(last_online, ?) + WHERE inbound_id = ? AND email = ?`, + cs.Up, cs.Down, snapAllTime, cs.LastOnline, c.Id, cs.Email, + ).Error; err != nil { + return err + } + } + } + + if err := tx.Commit().Error; err != nil { + return err + } + committed = true + + // Push the node's online-clients contribution into xray.Process so + // GetOnlineClients returns the union of local + every node. Empty + // list still calls Set so a node that just had everyone disconnect + // updates promptly. + if p != nil { + p.SetNodeOnlineClients(nodeID, snap.OnlineEmails) + } + + return nil +} + func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (bool, bool, error) { var err error db := database.GetDB() @@ -1655,34 +2047,104 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) now := time.Now().Unix() * 1000 needRestart := false - var clientsToDisable []struct { + var depletedRows []xray.ClientTraffic + err := tx.Model(xray.ClientTraffic{}). + Where("((total > 0 AND up + down >= total) OR (expiry_time > 0 AND expiry_time <= ?)) AND enable = ?", now, true). + Find(&depletedRows).Error + if err != nil { + return false, 0, err + } + if len(depletedRows) == 0 { + return false, 0, nil + } + + rowByEmail := make(map[string]*xray.ClientTraffic, len(depletedRows)) + depletedEmails := make([]string, 0, len(depletedRows)) + for i := range depletedRows { + if depletedRows[i].Email == "" { + continue + } + rowByEmail[strings.ToLower(depletedRows[i].Email)] = &depletedRows[i] + depletedEmails = append(depletedEmails, depletedRows[i].Email) + } + + // Resolve inbound membership only for the depleted emails — pushing the + // filter into SQLite avoids dragging every panel client through Go for + // the common case where most clients are healthy. + var memberships []struct { + InboundId int + Tag string + Email string + SubID string `gorm:"column:sub_id"` + } + if len(depletedEmails) > 0 { + err = tx.Raw(` + SELECT inbounds.id AS inbound_id, + inbounds.tag AS tag, + JSON_EXTRACT(client.value, '$.email') AS email, + JSON_EXTRACT(client.value, '$.subId') AS sub_id + FROM inbounds, + JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client + WHERE LOWER(JSON_EXTRACT(client.value, '$.email')) IN ? + `, lowerAll(depletedEmails)).Scan(&memberships).Error + if err != nil { + return false, 0, err + } + } + + // Discover the row holder's subId per email. Only siblings sharing it + // get cascaded; legacy data where two identities reuse the same email + // stays isolated to the row owner. + holderSub := make(map[string]string, len(rowByEmail)) + for _, m := range memberships { + email := strings.ToLower(strings.Trim(m.Email, "\"")) + row, ok := rowByEmail[email] + if !ok || m.InboundId != row.InboundId { + continue + } + holderSub[email] = strings.Trim(m.SubID, "\"") + } + + type target struct { InboundId int Tag string Email string } - - err := tx.Table("inbounds"). - Select("inbounds.id as inbound_id, inbounds.tag, client_traffics.email"). - Joins("JOIN client_traffics ON inbounds.id = client_traffics.inbound_id"). - Where("((client_traffics.total > 0 AND client_traffics.up + client_traffics.down >= client_traffics.total) OR (client_traffics.expiry_time > 0 AND client_traffics.expiry_time <= ?)) AND client_traffics.enable = ?", now, true). - Scan(&clientsToDisable).Error - if err != nil { - return false, 0, err + var targets []target + for _, m := range memberships { + email := strings.ToLower(strings.Trim(m.Email, "\"")) + row, ok := rowByEmail[email] + if !ok { + continue + } + expected, hasSub := holderSub[email] + mSub := strings.Trim(m.SubID, "\"") + switch { + case !hasSub || expected == "": + if m.InboundId != row.InboundId { + continue + } + case mSub != expected: + continue + } + targets = append(targets, target{ + InboundId: m.InboundId, + Tag: m.Tag, + Email: strings.Trim(m.Email, "\""), + }) } - if p != nil { + if p != nil && len(targets) > 0 { s.xrayApi.Init(p.GetAPIPort()) - for _, client := range clientsToDisable { - err1 := s.xrayApi.RemoveUser(client.Tag, client.Email) + for _, t := range targets { + err1 := s.xrayApi.RemoveUser(t.Tag, t.Email) if err1 == nil { - logger.Debug("Client disabled by api:", client.Email) + logger.Debug("Client disabled by api:", t.Email) + } else if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", t.Email)) { + logger.Debug("User is already disabled. Nothing to do more...") } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", client.Email)) { - logger.Debug("User is already disabled. Nothing to do more...") - } else { - logger.Debug("Error in disabling client by api:", err1) - needRestart = true - } + logger.Debug("Error in disabling client by api:", err1) + needRestart = true } } s.xrayApi.Close() @@ -1697,58 +2159,71 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error) return needRestart, count, err } - // Also set enable=false in inbounds.settings JSON so clients are visibly disabled - if len(clientsToDisable) > 0 { - inboundEmailMap := make(map[int]map[string]struct{}) - for _, c := range clientsToDisable { - if inboundEmailMap[c.InboundId] == nil { - inboundEmailMap[c.InboundId] = make(map[string]struct{}) - } - inboundEmailMap[c.InboundId][c.Email] = struct{}{} + if len(targets) == 0 { + return needRestart, count, nil + } + + // Mirror enable=false + the row's authoritative quota/expiry into every + // (inbound, email) we just removed via the API. + inboundEmailMap := make(map[int]map[string]struct{}) + for _, t := range targets { + if inboundEmailMap[t.InboundId] == nil { + inboundEmailMap[t.InboundId] = make(map[string]struct{}) } - inboundIds := make([]int, 0, len(inboundEmailMap)) - for id := range inboundEmailMap { - inboundIds = append(inboundIds, id) + inboundEmailMap[t.InboundId][t.Email] = struct{}{} + } + inboundIds := make([]int, 0, len(inboundEmailMap)) + for id := range inboundEmailMap { + inboundIds = append(inboundIds, id) + } + var inbounds []*model.Inbound + if err = tx.Model(model.Inbound{}).Where("id IN ?", inboundIds).Find(&inbounds).Error; err != nil { + logger.Warning("disableInvalidClients fetch inbounds:", err) + return needRestart, count, nil + } + dirty := make([]*model.Inbound, 0, len(inbounds)) + for _, inbound := range inbounds { + settings := map[string]any{} + if jsonErr := json.Unmarshal([]byte(inbound.Settings), &settings); jsonErr != nil { + continue } - var inbounds []*model.Inbound - if err = tx.Model(model.Inbound{}).Where("id IN ?", inboundIds).Find(&inbounds).Error; err != nil { - logger.Warning("disableInvalidClients fetch inbounds:", err) - return needRestart, count, nil + clientsRaw, ok := settings["clients"].([]any) + if !ok { + continue } - for _, inbound := range inbounds { - settings := map[string]any{} - if jsonErr := json.Unmarshal([]byte(inbound.Settings), &settings); jsonErr != nil { - continue - } - clients, ok := settings["clients"].([]any) + emailSet := inboundEmailMap[inbound.Id] + changed := false + for i := range clientsRaw { + c, ok := clientsRaw[i].(map[string]any) if !ok { continue } - emailSet := inboundEmailMap[inbound.Id] - changed := false - for i := range clients { - c, ok := clients[i].(map[string]any) - if !ok { - continue - } - email, _ := c["email"].(string) - if _, shouldDisable := emailSet[email]; shouldDisable { - c["enable"] = false - c["updated_at"] = time.Now().Unix() * 1000 - clients[i] = c - changed = true - } + email, _ := c["email"].(string) + if _, shouldDisable := emailSet[email]; !shouldDisable { + continue } - if changed { - settings["clients"] = clients - modifiedSettings, jsonErr := json.MarshalIndent(settings, "", " ") - if jsonErr != nil { - continue - } - inbound.Settings = string(modifiedSettings) + c["enable"] = false + if row, ok := rowByEmail[strings.ToLower(email)]; ok { + c["totalGB"] = row.Total + c["expiryTime"] = row.ExpiryTime } + c["updated_at"] = now + clientsRaw[i] = c + changed = true } - if err = tx.Save(inbounds).Error; err != nil { + if !changed { + continue + } + settings["clients"] = clientsRaw + modifiedSettings, jsonErr := json.MarshalIndent(settings, "", " ") + if jsonErr != nil { + continue + } + inbound.Settings = string(modifiedSettings) + dirty = append(dirty, inbound) + } + if len(dirty) > 0 { + if err = tx.Save(dirty).Error; err != nil { logger.Warning("disableInvalidClients update inbound settings:", err) } } @@ -1824,19 +2299,20 @@ func (s *InboundService) MigrationRemoveOrphanedTraffics() { `) } +// AddClientStat inserts a per-client accounting row, no-op on email +// conflict. Xray reports traffic per email, so the surviving row acts as +// the shared accumulator for inbounds that re-use the same identity. func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model.Client) error { - clientTraffic := xray.ClientTraffic{} - clientTraffic.InboundId = inboundId - clientTraffic.Email = client.Email - clientTraffic.Total = client.TotalGB - clientTraffic.ExpiryTime = client.ExpiryTime - clientTraffic.Enable = client.Enable - clientTraffic.Up = 0 - clientTraffic.Down = 0 - clientTraffic.Reset = client.Reset - result := tx.Create(&clientTraffic) - err := result.Error - return err + clientTraffic := xray.ClientTraffic{ + InboundId: inboundId, + Email: client.Email, + Total: client.TotalGB, + ExpiryTime: client.ExpiryTime, + Enable: client.Enable, + Reset: client.Reset, + } + return tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "email"}}, DoNothing: true}). + Create(&clientTraffic).Error } func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error { @@ -2302,7 +2778,14 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e } for _, client := range clients { if client.Email == clientEmail && client.Enable { - s.xrayApi.Init(p.GetAPIPort()) + rt, rterr := s.runtimeFor(inbound) + if rterr != nil { + if inbound.NodeID != nil { + return false, rterr + } + needRestart = true + break + } cipher := "" if string(inbound.Protocol) == "shadowsocks" { var oldSettings map[string]any @@ -2312,7 +2795,7 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e } cipher = oldSettings["method"].(string) } - err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]any{ + err1 := rt.AddUser(context.Background(), inbound, map[string]any{ "email": client.Email, "id": client.ID, "auth": client.Auth, @@ -2322,12 +2805,11 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e "cipher": cipher, }) if err1 == nil { - logger.Debug("Client enabled due to reset traffic:", clientEmail) + logger.Debug("Client enabled on", rt.Name(), "due to reset traffic:", clientEmail) } else { - logger.Debug("Error in enabling client by api:", err1) + logger.Debug("Error in enabling client on", rt.Name(), ":", err1) needRestart = true } - s.xrayApi.Close() break } } @@ -2343,6 +2825,29 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e return false, err } + // Stamp last_traffic_reset_time on the parent inbound so the next + // NodeTrafficSyncJob tick honours the grace window and doesn't pull + // the pre-reset absolute back from the node. + now := time.Now().UnixMilli() + _ = db.Model(model.Inbound{}). + Where("id = ?", id). + Update("last_traffic_reset_time", now).Error + + // Propagate to the remote node if this inbound is node-managed. + // Best-effort: an offline node shouldn't block a user-driven reset + // — the central DB is already zeroed and the next successful sync + // (within the grace window) will re-pull whatever the node has. + inbound, err := s.GetInbound(id) + if err == nil && inbound != nil && inbound.NodeID != nil { + if rt, rterr := s.runtimeFor(inbound); rterr == nil { + if e := rt.ResetClientTraffic(context.Background(), inbound, clientEmail); e != nil { + logger.Warning("ResetClientTraffic: remote propagation to", rt.Name(), "failed:", e) + } + } else { + logger.Warning("ResetClientTraffic: runtime lookup failed:", rterr) + } + } + return needRestart, nil } @@ -2350,7 +2855,7 @@ func (s *InboundService) ResetAllClientTraffics(id int) error { db := database.GetDB() now := time.Now().Unix() * 1000 - return db.Transaction(func(tx *gorm.DB) error { + if err := db.Transaction(func(tx *gorm.DB) error { whereText := "inbound_id " if id == -1 { whereText += " > ?" @@ -2380,18 +2885,77 @@ func (s *InboundService) ResetAllClientTraffics(id int) error { Update("last_traffic_reset_time", now) return result.Error - }) + }); err != nil { + return err + } + + // Propagate to remote nodes after the central DB is settled. Single + // inbound: one rt.ResetInboundClientTraffics call. id == -1 (all + // inbounds across panel): walk every node-managed inbound and call + // the per-inbound endpoint — there's no panel-wide endpoint that + // only resets clients without zeroing inbound counters. + var inbounds []model.Inbound + q := db.Model(model.Inbound{}).Where("node_id IS NOT NULL") + if id != -1 { + q = q.Where("id = ?", id) + } + if err := q.Find(&inbounds).Error; err != nil { + // Failed to discover which inbounds to propagate to — central + // DB is already correct, log and move on. + logger.Warning("ResetAllClientTraffics: discover node inbounds failed:", err) + return nil + } + for i := range inbounds { + ib := &inbounds[i] + rt, rterr := s.runtimeFor(ib) + if rterr != nil { + logger.Warning("ResetAllClientTraffics: runtime lookup for inbound", ib.Id, "failed:", rterr) + continue + } + if e := rt.ResetInboundClientTraffics(context.Background(), ib); e != nil { + logger.Warning("ResetAllClientTraffics: remote propagation to", rt.Name(), "failed:", e) + } + } + return nil } func (s *InboundService) ResetAllTraffics() error { db := database.GetDB() + now := time.Now().UnixMilli() - result := db.Model(model.Inbound{}). + if err := db.Model(model.Inbound{}). Where("user_id > ?", 0). - Updates(map[string]any{"up": 0, "down": 0}) + Updates(map[string]any{ + "up": 0, + "down": 0, + "last_traffic_reset_time": now, + }).Error; err != nil { + return err + } - err := result.Error - return err + // Propagate to every node that has at least one inbound on this + // panel. We can't blanket-call rt.ResetAllTraffics because that + // would also zero traffic for inbounds the node hosts but the + // central panel doesn't know about — instead reset per inbound. + var inbounds []model.Inbound + if err := db.Model(model.Inbound{}). + Where("node_id IS NOT NULL"). + Find(&inbounds).Error; err != nil { + logger.Warning("ResetAllTraffics: discover node inbounds failed:", err) + return nil + } + for i := range inbounds { + ib := &inbounds[i] + rt, rterr := s.runtimeFor(ib) + if rterr != nil { + logger.Warning("ResetAllTraffics: runtime lookup for inbound", ib.Id, "failed:", rterr) + continue + } + if e := rt.ResetInboundClientTraffics(context.Background(), ib); e != nil { + logger.Warning("ResetAllTraffics: remote propagation to", rt.Name(), "failed:", e) + } + } + return nil } func (s *InboundService) ResetInboundTraffic(id int) error { @@ -2415,77 +2979,117 @@ func (s *InboundService) DelDepletedClients(id int) (err error) { } }() - whereText := "reset = 0 and inbound_id " - if id < 0 { - whereText += "> ?" - } else { - whereText += "= ?" - } - - // Only consider truly depleted clients: expired OR traffic exhausted + // Collect depleted emails globally — a shared-email row owned by one + // inbound depletes every sibling that lists the email. now := time.Now().Unix() * 1000 - depletedClients := []xray.ClientTraffic{} + depletedClause := "reset = 0 and ((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?))" + var depletedRows []xray.ClientTraffic err = db.Model(xray.ClientTraffic{}). - Where(whereText+" and ((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?))", id, now). - Select("inbound_id, GROUP_CONCAT(email) as email"). - Group("inbound_id"). - Find(&depletedClients).Error + Where(depletedClause, now). + Find(&depletedRows).Error if err != nil { return err } + if len(depletedRows) == 0 { + return nil + } - for _, depletedClient := range depletedClients { - emails := strings.Split(depletedClient.Email, ",") - oldInbound, err := s.GetInbound(depletedClient.InboundId) - if err != nil { + depletedEmails := make(map[string]struct{}, len(depletedRows)) + for _, r := range depletedRows { + if r.Email == "" { + continue + } + depletedEmails[strings.ToLower(r.Email)] = struct{}{} + } + if len(depletedEmails) == 0 { + return nil + } + + var inbounds []*model.Inbound + inboundQuery := db.Model(model.Inbound{}) + if id >= 0 { + inboundQuery = inboundQuery.Where("id = ?", id) + } + if err = inboundQuery.Find(&inbounds).Error; err != nil { + return err + } + + for _, inbound := range inbounds { + var settings map[string]any + if err = json.Unmarshal([]byte(inbound.Settings), &settings); err != nil { return err } - var oldSettings map[string]any - err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings) - if err != nil { - return err + rawClients, ok := settings["clients"].([]any) + if !ok { + continue } - - oldClients := oldSettings["clients"].([]any) - var newClients []any - for _, client := range oldClients { - deplete := false - c := client.(map[string]any) - for _, email := range emails { - if email == c["email"].(string) { - deplete = true - break - } - } - if !deplete { + newClients := make([]any, 0, len(rawClients)) + removed := 0 + for _, client := range rawClients { + c, ok := client.(map[string]any) + if !ok { newClients = append(newClients, client) + continue } + email, _ := c["email"].(string) + if _, isDepleted := depletedEmails[strings.ToLower(email)]; isDepleted { + removed++ + continue + } + newClients = append(newClients, client) } - if len(newClients) > 0 { - oldSettings["clients"] = newClients - - newSettings, err := json.MarshalIndent(oldSettings, "", " ") - if err != nil { - return err - } - - oldInbound.Settings = string(newSettings) - err = tx.Save(oldInbound).Error - if err != nil { - return err - } - } else { - // Delete inbound if no client remains - s.DelInbound(depletedClient.InboundId) + if removed == 0 { + continue + } + if len(newClients) == 0 { + s.DelInbound(inbound.Id) + continue + } + settings["clients"] = newClients + ns, mErr := json.MarshalIndent(settings, "", " ") + if mErr != nil { + return mErr + } + inbound.Settings = string(ns) + if err = tx.Save(inbound).Error; err != nil { + return err } } - // Delete stats only for truly depleted clients - err = tx.Where(whereText+" and ((total > 0 and up + down >= total) or (expiry_time > 0 and expiry_time <= ?))", id, now).Delete(xray.ClientTraffic{}).Error - if err != nil { + // Drop now-orphaned rows. With id >= 0, a row is safe to drop only when + // no out-of-scope inbound still references the email. + if id < 0 { + err = tx.Where(depletedClause, now).Delete(xray.ClientTraffic{}).Error return err } - + emails := make([]string, 0, len(depletedEmails)) + for e := range depletedEmails { + emails = append(emails, e) + } + var stillReferenced []string + if err = tx.Raw(` + SELECT DISTINCT LOWER(JSON_EXTRACT(client.value, '$.email')) + FROM inbounds, + JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client + WHERE LOWER(JSON_EXTRACT(client.value, '$.email')) IN ? + `, emails).Scan(&stillReferenced).Error; err != nil { + return err + } + stillSet := make(map[string]struct{}, len(stillReferenced)) + for _, e := range stillReferenced { + stillSet[e] = struct{}{} + } + toDelete := make([]string, 0, len(emails)) + for _, e := range emails { + if _, kept := stillSet[e]; !kept { + toDelete = append(toDelete, e) + } + } + if len(toDelete) > 0 { + if err = tx.Where("LOWER(email) IN ?", toDelete).Delete(xray.ClientTraffic{}).Error; err != nil { + return err + } + } return nil } @@ -3038,6 +3642,24 @@ func (s *InboundService) GetOnlineClients() []string { return p.GetOnlineClients() } +// SetNodeOnlineClients records a remote node's online-clients list on +// the panel-wide xray.Process so GetOnlineClients returns the union of +// local + every node's contribution. Called by NodeTrafficSyncJob. +func (s *InboundService) SetNodeOnlineClients(nodeID int, emails []string) { + if p != nil { + p.SetNodeOnlineClients(nodeID, emails) + } +} + +// ClearNodeOnlineClients drops one node's contribution to the online +// set. Used when the per-node sync probe fails so a downed node +// doesn't keep its clients listed as online forever. +func (s *InboundService) ClearNodeOnlineClients(nodeID int) { + if p != nil { + p.ClearNodeOnlineClients(nodeID) + } +} + func (s *InboundService) GetClientsLastOnline() (map[string]int64, error) { db := database.GetDB() var rows []xray.ClientTraffic @@ -3142,16 +3764,24 @@ func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (b db := database.GetDB() - // remove IP bindings - if err := s.DelClientIPs(db, email); err != nil { - logger.Error("Error in delete client IPs") + // Drop the row and IPs only when this was the last inbound referencing + // the email — siblings still need the shared accounting state. + emailShared, err := s.emailUsedByOtherInbounds(email, inboundId) + if err != nil { return false, err } + if !emailShared { + if err := s.DelClientIPs(db, email); err != nil { + logger.Error("Error in delete client IPs") + return false, err + } + } + needRestart := false // remove stats too - if len(email) > 0 { + if len(email) > 0 && !emailShared { traffic, err := s.GetClientTrafficByEmail(email) if err != nil { return false, err @@ -3164,19 +3794,27 @@ func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (b } if needApiDel { - s.xrayApi.Init(p.GetAPIPort()) - if err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email); err1 == nil { - logger.Debug("Client deleted by api:", email) - needRestart = false - } else { - if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { + rt, rterr := s.runtimeFor(oldInbound) + if rterr != nil { + if oldInbound.NodeID != nil { + return false, rterr + } + needRestart = true + } else if oldInbound.NodeID == nil { + if err1 := rt.RemoveUser(context.Background(), oldInbound, email); err1 == nil { + logger.Debug("Client deleted on", rt.Name(), ":", email) + needRestart = false + } else if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) { logger.Debug("User is already deleted. Nothing to do more...") } else { - logger.Debug("Error in deleting client by api:", err1) + logger.Debug("Error in deleting client on", rt.Name(), ":", err1) needRestart = true } + } else { + if err1 := rt.UpdateInbound(context.Background(), oldInbound, oldInbound); err1 != nil { + return false, err1 + } } - s.xrayApi.Close() } } diff --git a/web/service/metric_history.go b/web/service/metric_history.go new file mode 100644 index 00000000..f180d3b9 --- /dev/null +++ b/web/service/metric_history.go @@ -0,0 +1,143 @@ +package service + +import ( + "sync" + "time" +) + +// MetricSample is one point of any time-series we keep in memory. +// The frontend deserializes both keys, so they must stay short. +type MetricSample struct { + T int64 `json:"t"` + V float64 `json:"v"` +} + +// metricCapacityDefault caps each ring buffer at ~5h worth of @2s samples +// or ~25h worth of @10s samples. Plenty for the bucketed aggregation +// view and small enough that the working set per metric stays under +// ~150 KiB. +const metricCapacityDefault = 9000 + +// metricHistory is a thread-safe, in-memory ring buffer keyed by +// arbitrary strings. Two singletons live below: one for system-wide +// host metrics, one for per-node metrics. Keeping them in this file +// (rather than scattered across services) makes the storage model +// easy to reason about and avoids double-locking. +type metricHistory struct { + mu sync.Mutex + metrics map[string][]MetricSample +} + +func newMetricHistory() *metricHistory { + return &metricHistory{metrics: map[string][]MetricSample{}} +} + +// append stores a single sample for the given metric, deduping when +// two appends happen within the same wall-clock second (which can +// happen if the cron tick is faster than the metric's natural rate). +func (h *metricHistory) append(metric string, t time.Time, v float64) { + h.mu.Lock() + defer h.mu.Unlock() + buf := h.metrics[metric] + p := MetricSample{T: t.Unix(), V: v} + if n := len(buf); n > 0 && buf[n-1].T == p.T { + buf[n-1] = p + } else { + buf = append(buf, p) + } + if len(buf) > metricCapacityDefault { + buf = buf[len(buf)-metricCapacityDefault:] + } + h.metrics[metric] = buf +} + +// drop removes the entire history for one metric. Used when a node is +// deleted so its old samples don't linger forever in the singleton. +func (h *metricHistory) drop(metric string) { + h.mu.Lock() + delete(h.metrics, metric) + h.mu.Unlock() +} + +// aggregate returns up to maxPoints buckets of size bucketSeconds, +// each bucket carrying the arithmetic mean of the underlying samples. +// Bucket alignment is to absolute Unix-second boundaries so two +// concurrent calls (e.g. two browser tabs) see identical x-axes. +func (h *metricHistory) aggregate(metric string, bucketSeconds int, maxPoints int) []map[string]any { + if bucketSeconds <= 0 || maxPoints <= 0 { + return []map[string]any{} + } + cutoff := time.Now().Add(-time.Duration(bucketSeconds*maxPoints) * time.Second).Unix() + + h.mu.Lock() + hist := h.metrics[metric] + startIdx := 0 + for i := len(hist) - 1; i >= 0; i-- { + if hist[i].T < cutoff { + startIdx = i + 1 + break + } + } + if startIdx >= len(hist) { + h.mu.Unlock() + return []map[string]any{} + } + tmp := make([]MetricSample, len(hist)-startIdx) + copy(tmp, hist[startIdx:]) + h.mu.Unlock() + + if len(tmp) == 0 { + return []map[string]any{} + } + + bSize := int64(bucketSeconds) + curBucket := (tmp[0].T / bSize) * bSize + var out []map[string]any + var acc []float64 + flush := func(ts int64) { + if len(acc) == 0 { + return + } + sum := 0.0 + for _, v := range acc { + sum += v + } + out = append(out, map[string]any{"t": ts, "v": sum / float64(len(acc))}) + acc = acc[:0] + } + for _, p := range tmp { + b := (p.T / bSize) * bSize + if b != curBucket { + flush(curBucket) + curBucket = b + } + acc = append(acc, p.V) + } + flush(curBucket) + if len(out) > maxPoints { + out = out[len(out)-maxPoints:] + } + if out == nil { + return []map[string]any{} + } + return out +} + +// systemMetrics holds whole-host time series (cpu, mem, netUp, etc.) +// fed by ServerController.refreshStatus every 2s. nodeMetrics holds +// per-node CPU/Mem fed by NodeHeartbeatJob every 10s. Both are +// process-local — survival across panel restart is not required. +var ( + systemMetrics = newMetricHistory() + nodeMetrics = newMetricHistory() +) + +// SystemMetricKeys lists the metric names ServerService writes on every +// status sample. Exposed for documentation/test purposes; the +// controller validates incoming names against an allow-list. +var SystemMetricKeys = []string{ + "cpu", "mem", "netUp", "netDown", "online", "load1", "load5", "load15", +} + +// NodeMetricKeys lists the per-node metric names NodeHeartbeatJob writes. +var NodeMetricKeys = []string{"cpu", "mem"} diff --git a/web/service/node.go b/web/service/node.go new file mode 100644 index 00000000..3fd77f67 --- /dev/null +++ b/web/service/node.go @@ -0,0 +1,302 @@ +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/mhsanaei/3x-ui/v2/database" + "github.com/mhsanaei/3x-ui/v2/database/model" + "github.com/mhsanaei/3x-ui/v2/util/common" + "github.com/mhsanaei/3x-ui/v2/web/runtime" +) + +// HeartbeatPatch is the slice of fields a single Probe() result writes +// back to a Node row. We pass it as a struct (not a *model.Node) so the +// heartbeat path can't accidentally clobber configuration columns the +// user just edited. +type HeartbeatPatch struct { + Status string + LastHeartbeat int64 + LatencyMs int + XrayVersion string + CpuPct float64 + MemPct float64 + UptimeSecs uint64 + LastError string +} + +// NodeService manages remote 3x-ui nodes registered with this panel. +// It owns CRUD for the Node model and the HTTP probe used by both the +// heartbeat job and the on-demand "test connection" UI action. +type NodeService struct{} + +// httpClient is shared so repeated probes reuse TCP/TLS connections. +// Timeout is per-request, set on each Do() via context. +var nodeHTTPClient = &http.Client{ + Transport: &http.Transport{ + MaxIdleConns: 64, + MaxIdleConnsPerHost: 4, + IdleConnTimeout: 60 * time.Second, + }, +} + +func (s *NodeService) GetAll() ([]*model.Node, error) { + db := database.GetDB() + var nodes []*model.Node + err := db.Model(model.Node{}).Order("id asc").Find(&nodes).Error + return nodes, err +} + +func (s *NodeService) GetById(id int) (*model.Node, error) { + db := database.GetDB() + n := &model.Node{} + if err := db.Model(model.Node{}).Where("id = ?", id).First(n).Error; err != nil { + return nil, err + } + return n, nil +} + +// normalize fills in defaults and trims accidental whitespace before save. +// Pulled out so Create and Update share the same rules. +func (s *NodeService) normalize(n *model.Node) error { + n.Name = strings.TrimSpace(n.Name) + n.Address = strings.TrimSpace(n.Address) + n.ApiToken = strings.TrimSpace(n.ApiToken) + if n.Name == "" { + return common.NewError("node name is required") + } + if n.Address == "" { + return common.NewError("node address is required") + } + if n.Port <= 0 || n.Port > 65535 { + return common.NewError("node port must be 1-65535") + } + if n.Scheme != "http" && n.Scheme != "https" { + n.Scheme = "https" + } + if n.BasePath == "" { + n.BasePath = "/" + } + if !strings.HasPrefix(n.BasePath, "/") { + n.BasePath = "/" + n.BasePath + } + if !strings.HasSuffix(n.BasePath, "/") { + n.BasePath = n.BasePath + "/" + } + return nil +} + +func (s *NodeService) Create(n *model.Node) error { + if err := s.normalize(n); err != nil { + return err + } + db := database.GetDB() + return db.Create(n).Error +} + +func (s *NodeService) Update(id int, in *model.Node) error { + if err := s.normalize(in); err != nil { + return err + } + db := database.GetDB() + existing := &model.Node{} + if err := db.Where("id = ?", id).First(existing).Error; err != nil { + return err + } + // Only persist user-controlled columns. Heartbeat fields stay where + // the heartbeat job last wrote them so a no-op edit doesn't blank + // the dashboard out for ten seconds. + updates := map[string]any{ + "name": in.Name, + "remark": in.Remark, + "scheme": in.Scheme, + "address": in.Address, + "port": in.Port, + "base_path": in.BasePath, + "api_token": in.ApiToken, + "enable": in.Enable, + } + if err := db.Model(model.Node{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return err + } + // Drop any cached Remote so the next inbound op picks up the fresh + // address/token. Cheap to do unconditionally — the next miss rebuilds. + if mgr := runtime.GetManager(); mgr != nil { + mgr.InvalidateNode(id) + } + return nil +} + +func (s *NodeService) Delete(id int) error { + db := database.GetDB() + if err := db.Where("id = ?", id).Delete(model.Node{}).Error; err != nil { + return err + } + if mgr := runtime.GetManager(); mgr != nil { + mgr.InvalidateNode(id) + } + // Drop in-memory series so a freshly created node with the same id + // doesn't inherit stale points (sqlite reuses ids freely). + nodeMetrics.drop(nodeMetricKey(id, "cpu")) + nodeMetrics.drop(nodeMetricKey(id, "mem")) + return nil +} + +func (s *NodeService) SetEnable(id int, enable bool) error { + db := database.GetDB() + return db.Model(model.Node{}).Where("id = ?", id).Update("enable", enable).Error +} + +// UpdateHeartbeat persists the slice of fields written by a probe. We +// don't touch updated_at via gorm autoUpdateTime here — that field is +// reserved for user-driven config edits. +func (s *NodeService) UpdateHeartbeat(id int, p HeartbeatPatch) error { + db := database.GetDB() + updates := map[string]any{ + "status": p.Status, + "last_heartbeat": p.LastHeartbeat, + "latency_ms": p.LatencyMs, + "xray_version": p.XrayVersion, + "cpu_pct": p.CpuPct, + "mem_pct": p.MemPct, + "uptime_secs": p.UptimeSecs, + "last_error": p.LastError, + } + if err := db.Model(model.Node{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return err + } + // Only record online ticks. Offline probes carry zeroed cpu/mem and + // would draw a misleading dip on the chart; the gap on the x-axis is + // the truthful representation of "we couldn't reach the node". + if p.Status == "online" { + now := time.Unix(p.LastHeartbeat, 0) + nodeMetrics.append(nodeMetricKey(id, "cpu"), now, p.CpuPct) + nodeMetrics.append(nodeMetricKey(id, "mem"), now, p.MemPct) + } + return nil +} + +// nodeMetricKey is the namespacing used inside the singleton ring buffer +// so per-node metrics don't collide with each other or with the system +// metrics in the sibling singleton. +func nodeMetricKey(id int, metric string) string { + return "node:" + strconv.Itoa(id) + ":" + metric +} + +// AggregateNodeMetric returns up to maxPoints averaged buckets for one +// node's metric (currently "cpu" or "mem"). Output shape matches +// AggregateSystemMetric: {"t": unixSec, "v": value}. +func (s *NodeService) AggregateNodeMetric(id int, metric string, bucketSeconds int, maxPoints int) []map[string]any { + return nodeMetrics.aggregate(nodeMetricKey(id, metric), bucketSeconds, maxPoints) +} + +// Probe issues a single GET to the node's /panel/api/server/status and +// returns a HeartbeatPatch. On error the patch is zero-valued except +// for LastError; the caller is responsible for setting Status="offline". +// +// The remote endpoint requires authentication: we send the per-node +// ApiToken as a Bearer token, which the remote APIController.checkAPIAuth +// validates. Calls without a token would just get a 404, which masks +// the existence of the API entirely. +func (s *NodeService) Probe(ctx context.Context, n *model.Node) (HeartbeatPatch, error) { + patch := HeartbeatPatch{LastHeartbeat: time.Now().Unix()} + url := fmt.Sprintf("%s://%s:%d%spanel/api/server/status", + n.Scheme, n.Address, n.Port, n.BasePath) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + patch.LastError = err.Error() + return patch, err + } + if n.ApiToken != "" { + req.Header.Set("Authorization", "Bearer "+n.ApiToken) + } + req.Header.Set("Accept", "application/json") + + start := time.Now() + resp, err := nodeHTTPClient.Do(req) + if err != nil { + patch.LastError = err.Error() + return patch, err + } + defer resp.Body.Close() + patch.LatencyMs = int(time.Since(start) / time.Millisecond) + + if resp.StatusCode != http.StatusOK { + patch.LastError = fmt.Sprintf("HTTP %d from remote panel", resp.StatusCode) + return patch, errors.New(patch.LastError) + } + + // The remote wraps Status in entity.Msg. We decode into a typed + // envelope rather than map[string]any so a schema change on the + // remote shows up as a Go error instead of a silent zero-fill. + var envelope struct { + Success bool `json:"success"` + Msg string `json:"msg"` + Obj *struct { + Cpu uint64 `json:"-"` + // Status fields we care about. Decode CPU/Mem nested + // structs minimally — anything else gets discarded. + CpuPct float64 `json:"cpu"` + Mem struct { + Current uint64 `json:"current"` + Total uint64 `json:"total"` + } `json:"mem"` + Xray struct { + Version string `json:"version"` + } `json:"xray"` + Uptime uint64 `json:"uptime"` + } `json:"obj"` + } + if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil { + patch.LastError = "decode response: " + err.Error() + return patch, err + } + if !envelope.Success || envelope.Obj == nil { + patch.LastError = "remote returned success=false: " + envelope.Msg + return patch, errors.New(patch.LastError) + } + o := envelope.Obj + patch.CpuPct = o.CpuPct + if o.Mem.Total > 0 { + patch.MemPct = float64(o.Mem.Current) * 100.0 / float64(o.Mem.Total) + } + patch.XrayVersion = o.Xray.Version + patch.UptimeSecs = o.Uptime + return patch, nil +} + +// EnvelopeForUI is the shape a frontend test-connection action expects. +// Pulling it out keeps the controller dumb. +type ProbeResultUI struct { + Status string `json:"status"` + LatencyMs int `json:"latencyMs"` + XrayVersion string `json:"xrayVersion"` + CpuPct float64 `json:"cpuPct"` + MemPct float64 `json:"memPct"` + UptimeSecs uint64 `json:"uptimeSecs"` + Error string `json:"error"` +} + +func (p HeartbeatPatch) ToUI(ok bool) ProbeResultUI { + r := ProbeResultUI{ + LatencyMs: p.LatencyMs, + XrayVersion: p.XrayVersion, + CpuPct: p.CpuPct, + MemPct: p.MemPct, + UptimeSecs: p.UptimeSecs, + Error: p.LastError, + } + if ok { + r.Status = "online" + } else { + r.Status = "offline" + } + return r +} diff --git a/web/service/server.go b/web/service/server.go index 3bb93028..d3caf3aa 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -112,75 +112,27 @@ type ServerService struct { hasLastCPUSample bool hasNativeCPUSample bool emaCPU float64 - cpuHistory []CPUSample cachedCpuSpeedMhz float64 lastCpuInfoAttempt time.Time } -// AggregateCpuHistory returns up to maxPoints averaged buckets of size bucketSeconds over recent data. +// AggregateCpuHistory returns up to maxPoints averaged buckets of size bucketSeconds. +// Kept for back-compat with the original /panel/api/server/cpuHistory/:bucket route; +// the response key is "cpu" (not "v") so legacy consumers parse unchanged. func (s *ServerService) AggregateCpuHistory(bucketSeconds int, maxPoints int) []map[string]any { - if bucketSeconds <= 0 || maxPoints <= 0 { - return nil - } - cutoff := time.Now().Add(-time.Duration(bucketSeconds*maxPoints) * time.Second).Unix() - s.mu.Lock() - // find start index (history sorted ascending) - hist := s.cpuHistory - // binary-ish scan (simple linear from end since size capped ~10800 is fine) - startIdx := 0 - for i := len(hist) - 1; i >= 0; i-- { - if hist[i].T < cutoff { - startIdx = i + 1 - break - } - } - if startIdx >= len(hist) { - s.mu.Unlock() - return []map[string]any{} - } - slice := hist[startIdx:] - // copy for unlock - tmp := make([]CPUSample, len(slice)) - copy(tmp, slice) - s.mu.Unlock() - if len(tmp) == 0 { - return []map[string]any{} - } - var out []map[string]any - var acc []float64 - bSize := int64(bucketSeconds) - curBucket := (tmp[0].T / bSize) * bSize - flush := func(ts int64) { - if len(acc) == 0 { - return - } - sum := 0.0 - for _, v := range acc { - sum += v - } - avg := sum / float64(len(acc)) - out = append(out, map[string]any{"t": ts, "cpu": avg}) - acc = acc[:0] - } - for _, p := range tmp { - b := (p.T / bSize) * bSize - if b != curBucket { - flush(curBucket) - curBucket = b - } - acc = append(acc, p.Cpu) - } - flush(curBucket) - if len(out) > maxPoints { - out = out[len(out)-maxPoints:] + out := systemMetrics.aggregate("cpu", bucketSeconds, maxPoints) + for _, p := range out { + p["cpu"] = p["v"] + delete(p, "v") } return out } -// CPUSample single CPU utilization sample -type CPUSample struct { - T int64 `json:"t"` // unix seconds - Cpu float64 `json:"cpu"` // percent 0..100 +// AggregateSystemMetric returns up to maxPoints averaged buckets for any +// known system metric (see SystemMetricKeys). Output points have keys +// {"t": unixSec, "v": value}; the caller decides how to format the value. +func (s *ServerService) AggregateSystemMetric(metric string, bucketSeconds int, maxPoints int) []map[string]any { + return systemMetrics.aggregate(metric, bucketSeconds, maxPoints) } type LogEntry struct { @@ -423,18 +375,35 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { return status } +// AppendCpuSample is preserved for callers that only have the CPU number. +// New callers should prefer AppendStatusSample which writes the full set. func (s *ServerService) AppendCpuSample(t time.Time, v float64) { - const capacity = 9000 // ~5 hours @ 2s interval - s.mu.Lock() - defer s.mu.Unlock() - p := CPUSample{T: t.Unix(), Cpu: v} - if n := len(s.cpuHistory); n > 0 && s.cpuHistory[n-1].T == p.T { - s.cpuHistory[n-1] = p - } else { - s.cpuHistory = append(s.cpuHistory, p) + systemMetrics.append("cpu", t, v) +} + +// AppendStatusSample writes one tick of every metric we keep — CPU, memory +// percent, network throughput (bytes/s), online client count, and the three +// load averages. Called by ServerController.refreshStatus on the same @2s +// cadence as AppendCpuSample, so all series stay aligned. +func (s *ServerService) AppendStatusSample(t time.Time, status *Status) { + if status == nil { + return } - if len(s.cpuHistory) > capacity { - s.cpuHistory = s.cpuHistory[len(s.cpuHistory)-capacity:] + systemMetrics.append("cpu", t, status.Cpu) + if status.Mem.Total > 0 { + systemMetrics.append("mem", t, float64(status.Mem.Current)*100.0/float64(status.Mem.Total)) + } + systemMetrics.append("netUp", t, float64(status.NetIO.Up)) + systemMetrics.append("netDown", t, float64(status.NetIO.Down)) + online := 0 + if p != nil && p.IsRunning() { + online = len(p.GetOnlineClients()) + } + systemMetrics.append("online", t, float64(online)) + if len(status.Loads) >= 3 { + systemMetrics.append("load1", t, status.Loads[0]) + systemMetrics.append("load5", t, status.Loads[1]) + systemMetrics.append("load15", t, status.Loads[2]) } } diff --git a/web/service/setting.go b/web/service/setting.go index aca5f9a4..7c88efc1 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -1,6 +1,7 @@ package service import ( + "crypto/subtle" _ "embed" "encoding/json" "errors" @@ -32,6 +33,7 @@ var defaultValueMap = map[string]string{ "webCertFile": "", "webKeyFile": "", "secret": random.Seq(32), + "apiToken": "", "webBasePath": "/", "sessionMaxAge": "360", "pageSize": "25", @@ -430,6 +432,48 @@ func (s *SettingService) GetSecret() ([]byte, error) { return []byte(secret), err } +// GetApiToken returns the panel's API token, lazily generating one on +// first read so existing installs upgrade transparently. The token is +// stored plaintext to match how the existing tg/ldap secrets are kept. +func (s *SettingService) GetApiToken() (string, error) { + tok, err := s.getString("apiToken") + if err != nil { + return "", err + } + if tok == "" { + tok = random.Seq(48) + if saveErr := s.saveSetting("apiToken", tok); saveErr != nil { + logger.Warning("save apiToken failed:", saveErr) + return "", saveErr + } + } + return tok, nil +} + +// RegenerateApiToken rotates the API token, invalidating any central +// panel that has the old value cached. +func (s *SettingService) RegenerateApiToken() (string, error) { + tok := random.Seq(48) + if err := s.saveSetting("apiToken", tok); err != nil { + return "", err + } + return tok, nil +} + +// MatchApiToken returns true when the supplied bearer token matches the +// stored API token. Uses constant-time compare so a remote attacker +// can't time-attack the token byte-by-byte. +func (s *SettingService) MatchApiToken(presented string) bool { + if presented == "" { + return false + } + stored, err := s.getString("apiToken") + if err != nil || stored == "" { + return false + } + return subtle.ConstantTimeCompare([]byte(stored), []byte(presented)) == 1 +} + func (s *SettingService) SetBasePath(basePath string) error { if !strings.HasPrefix(basePath, "/") { basePath = "/" + basePath diff --git a/web/service/xray.go b/web/service/xray.go index b2d39646..4601483a 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -267,6 +267,18 @@ func (s *XrayService) SetToNeedRestart() { isNeedXrayRestart.Store(true) } +// GetXrayAPIPort returns the port the local xray process is listening on +// for its gRPC HandlerService, or 0 when xray isn't currently running. +// Exposed for the runtime package's LocalRuntime adapter — runtime can't +// reach into the package-level `p` directly without a service-package +// import cycle. +func (s *XrayService) GetXrayAPIPort() int { + if p == nil || !p.IsRunning() { + return 0 + } + return p.GetAPIPort() +} + // IsNeedRestartAndSetFalse checks if restart is needed and resets the flag to false. func (s *XrayService) IsNeedRestartAndSetFalse() bool { return isNeedXrayRestart.CompareAndSwap(true, false) diff --git a/web/session/session.go b/web/session/session.go index 7fc05be1..6267679a 100644 --- a/web/session/session.go +++ b/web/session/session.go @@ -15,6 +15,14 @@ import ( const ( loginUserKey = "LOGIN_USER" + // apiAuthUserKey is the gin-context key under which checkAPIAuth + // stashes a fallback user for Bearer-token-authenticated callers. + // Bearer requests don't carry a session cookie, so handlers that + // scope writes by user.Id (e.g. InboundController.addInbound) would + // otherwise nil-deref. Keeping the override in the gin context + // (not the cookie session) means the fallback never leaks into a + // browser request. + apiAuthUserKey = "api_auth_user" ) func init() { @@ -33,9 +41,25 @@ func SetLoginUser(c *gin.Context, user *model.User) error { return s.Save() } +// SetAPIAuthUser stashes a fallback user on the gin context for the +// lifetime of a single bearer-authed request. checkAPIAuth calls this +// after a successful token match so downstream handlers that read +// GetLoginUser don't see nil. +func SetAPIAuthUser(c *gin.Context, user *model.User) { + if user == nil { + return + } + c.Set(apiAuthUserKey, user) +} + // GetLoginUser retrieves the authenticated user from the session. // Returns nil if no user is logged in or if the session data is invalid. func GetLoginUser(c *gin.Context) *model.User { + if v, ok := c.Get(apiAuthUserKey); ok { + if u, ok2 := v.(*model.User); ok2 { + return u + } + } s := sessions.Default(c) obj := s.Get(loginUserKey) if obj == nil { diff --git a/web/translation/ar-EG.json b/web/translation/ar-EG.json new file mode 100644 index 00000000..7c50c527 --- /dev/null +++ b/web/translation/ar-EG.json @@ -0,0 +1,941 @@ +{ + "username": "اسم المستخدم", + "password": "الباسورد", + "login": "تسجيل الدخول", + "confirm": "تأكيد", + "cancel": "إلغاء", + "close": "إغلاق", + "save": "حفظ", + "logout": "تسجيل خروج", + "create": "إنشاء", + "update": "تحديث", + "copy": "نسخ", + "copied": "اتنسخ", + "download": "تحميل", + "remark": "ملاحظة", + "enable": "مفعل", + "protocol": "بروتوكول", + "search": "بحث", + "filter": "فلترة", + "loading": "جاري التحميل...", + "second": "ثانية", + "minute": "دقيقة", + "hour": "ساعة", + "day": "يوم", + "check": "شيك", + "indefinite": "غير محدد", + "unlimited": "غير محدود", + "none": "مفيش", + "qrCode": "كود QR", + "info": "معلومات أكتر", + "edit": "تعديل", + "delete": "مسح", + "reset": "إعادة ضبط", + "noData": "لا توجد بيانات.", + "copySuccess": "اتنسخ بنجاح", + "sure": "متأكد؟", + "encryption": "تشفير", + "useIPv4ForHost": "استخدم IPv4 للمضيف", + "transmission": "نقل", + "host": "المستضيف", + "path": "مسار", + "camouflage": "تمويه", + "status": "الحالة", + "enabled": "مفعل", + "disabled": "معطل", + "depleted": "خلص", + "depletingSoon": "هينتهي قريب", + "offline": "أوفلاين", + "online": "أونلاين", + "domainName": "اسم الدومين", + "monitor": "المسمع IP", + "certificate": "شهادة رقمية", + "fail": "فشل", + "comment": "تعليق", + "success": "تم بنجاح", + "lastOnline": "آخر متصل", + "getVersion": "جيب النسخة", + "install": "تثبيت", + "clients": "عملاء", + "usage": "استخدام", + "twoFactorCode": "الكود", + "remained": "المتبقي", + "security": "أمان", + "secAlertTitle": "تنبيه أمني", + "secAlertSsl": "الاتصال ده مش آمن. ابعد عن إدخال معلومات حساسة لغاية ما تشغل TLS لحماية البيانات.", + "secAlertConf": "بعض الإعدادات معرضة لهجمات. ينصح بتعزيز بروتوكولات الأمان عشان تمنع الاختراقات المحتملة.", + "secAlertSSL": "البانل مش مؤمن. حمّل شهادة TLS لحماية البيانات.", + "secAlertPanelPort": "بورت البانل الافتراضي معرض للخطر. ياريت تغير لبورت عشوائي أو محدد.", + "secAlertPanelURI": "مسار URI الافتراضي للبانل مش آمن. ياريت تضبط مسار URI معقد.", + "secAlertSubURI": "مسار URI الافتراضي للاشتراك مش آمن. ياريت تضبط مسار URI معقد.", + "secAlertSubJsonURI": "مسار URI الافتراضي لاشتراك JSON مش آمن. ياريت تضبط مسار URI معقد.", + "emptyDnsDesc": "مفيش سيرفر DNS مضاف.", + "emptyFakeDnsDesc": "مفيش سيرفر Fake DNS مضاف.", + "emptyBalancersDesc": "مفيش موازن تحميل مضاف.", + "emptyReverseDesc": "مفيش بروكسي عكسي مضاف.", + "somethingWentWrong": "حدث خطأ ما", + "subscription": { + "title": "معلومات الاشتراك", + "subId": "معرّف الاشتراك", + "status": "الحالة", + "downloaded": "التنزيل", + "uploaded": "الرفع", + "expiry": "تاريخ الانتهاء", + "totalQuota": "الحصة الإجمالية", + "individualLinks": "روابط فردية", + "active": "نشط", + "inactive": "غير نشط", + "unlimited": "غير محدود", + "noExpiry": "بدون انتهاء" + }, + "menu": { + "theme": "الثيم", + "dark": "داكن", + "ultraDark": "داكن جدًا", + "dashboard": "نظرة عامة", + "inbounds": "الإدخالات", + "settings": "إعدادات البانل", + "xray": "إعدادات Xray", + "logout": "تسجيل خروج", + "link": "إدارة" + }, + "pages": { + "login": { + "hello": "أهلا", + "title": "أهلاً وسهلاً", + "loginAgain": "انتهت صلاحية الجلسة، سجل دخول تاني", + "toasts": { + "invalidFormData": "تنسيق البيانات المدخلة مش صحيح.", + "emptyUsername": "اسم المستخدم مطلوب", + "emptyPassword": "الباسورد مطلوب", + "wrongUsernameOrPassword": "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح.", + "successLogin": "لقد تم تسجيل الدخول إلى حسابك بنجاح." + } + }, + "index": { + "title": "نظرة عامة", + "cpu": "المعالج", + "logicalProcessors": "المعالجات المنطقية", + "frequency": "التردد", + "swap": "Swap", + "storage": "تخزين", + "memory": "رام", + "threads": "خيوط المعالجة", + "xrayStatus": "Xray", + "stopXray": "إيقاف", + "restartXray": "إعادة تشغيل", + "xraySwitch": "النسخة", + "xraySwitchClick": "اختار النسخة اللي عايز تتحول لها.", + "xraySwitchClickDesk": "اختار بحذر، النسخ القديمة ممكن ما تتوافقش مع الإعدادات الحالية.", + "xrayUpdates": "تحديثات Xray", + "updatePanel": "تحديث البانل", + "panelUpdateDesc": "ده هيحدث 3X-UI لآخر إصدار وهيعيد تشغيل خدمة البانل.", + "currentPanelVersion": "إصدار البانل الحالي", + "latestPanelVersion": "أحدث إصدار للبانل", + "panelUpToDate": "البانل محدث لآخر إصدار", + "upToDate": "محدث", + "xrayStatusUnknown": "مش معروف", + "xrayStatusRunning": "شغالة", + "xrayStatusStop": "متوقفة", + "xrayStatusError": "فيها غلطة", + "xrayErrorPopoverTitle": "حصل خطأ أثناء تشغيل Xray", + "operationHours": "مدة التشغيل", + "systemHistoryTitle": "تاريخ النظام", + "trendLast2Min": "آخر دقيقتين", + "systemLoad": "تحميل النظام", + "systemLoadDesc": "متوسط تحميل النظام في الدقائق 1, 5, و15", + "connectionCount": "إحصائيات الاتصال", + "ipAddresses": "عناوين IP", + "toggleIpVisibility": "بدل إظهار IP", + "overallSpeed": "السرعة الكلية", + "upload": "رفع", + "download": "تنزيل", + "totalData": "إجمالي البيانات", + "sent": "مرسل", + "received": "مستقبل", + "documentation": "التوثيق", + "xraySwitchVersionDialog": "هل تريد حقًا تغيير إصدار Xray؟", + "xraySwitchVersionDialogDesc": "سيؤدي هذا إلى تغيير إصدار Xray إلى #version#.", + "xraySwitchVersionPopover": "تم تحديث Xray بنجاح", + "panelUpdateDialog": "هل فعلاً عايز تحدث البانل؟", + "panelUpdateDialogDesc": "ده هيحدث 3X-UI للإصدار #version# وهيعيد تشغيل البانل.", + "panelUpdateCheckPopover": "فشل التحقق من تحديث البانل", + "panelUpdateStartedPopover": "بدأ تحديث البانل", + "geofileUpdateDialog": "هل تريد حقًا تحديث ملف الجغرافيا؟", + "geofileUpdateDialogDesc": "سيؤدي هذا إلى تحديث ملف #filename#.", + "geofilesUpdateDialogDesc": "سيؤدي هذا إلى تحديث كافة الملفات.", + "geofilesUpdateAll": "تحديث الكل", + "geofileUpdatePopover": "تم تحديث ملف الجغرافيا بنجاح", + "dontRefresh": "التثبيت شغال، متعملش Refresh للصفحة", + "logs": "السجلات", + "config": "الإعدادات", + "backup": "نسخة احتياطية", + "backupTitle": "نسخ احتياطي واستعادة", + "exportDatabase": "اخزن نسخة", + "exportDatabaseDesc": "اضغط عشان تحمل ملف .db يحتوي على نسخة احتياطية لقاعدة البيانات الحالية على جهازك.", + "importDatabase": "استرجاع", + "importDatabaseDesc": "اضغط عشان تختار وتحمل ملف .db من جهازك لاسترجاع قاعدة البيانات من نسخة احتياطية.", + "importDatabaseSuccess": "تم استيراد قاعدة البيانات بنجاح", + "importDatabaseError": "حدث خطأ أثناء استيراد قاعدة البيانات", + "readDatabaseError": "حدث خطأ أثناء قراءة قاعدة البيانات", + "getDatabaseError": "حدث خطأ أثناء استرجاع قاعدة البيانات", + "getConfigError": "حدث خطأ أثناء استرجاع ملف الإعدادات", + "customGeoTitle": "GeoSite / GeoIP مخصص", + "customGeoAdd": "إضافة", + "customGeoType": "النوع", + "customGeoAlias": "الاسم المستعار", + "customGeoUrl": "URL", + "customGeoEnabled": "مفعّل", + "customGeoLastUpdated": "آخر تحديث", + "customGeoExtColumn": "التوجيه (ext:…)", + "customGeoToastUpdateAll": "تم تحديث جميع المصادر المخصصة", + "customGeoActions": "إجراءات", + "customGeoEdit": "تعديل", + "customGeoDelete": "حذف", + "customGeoDownload": "تحديث الآن", + "customGeoModalAdd": "إضافة geo مخصص", + "customGeoModalEdit": "تعديل geo مخصص", + "customGeoModalSave": "حفظ", + "customGeoDeleteConfirm": "حذف مصدر geo المخصص هذا؟", + "customGeoRoutingHint": "في قواعد التوجيه استخدم العمود كـ ext:file.dat:tag (استبدل tag).", + "customGeoInvalidId": "معرّف المورد غير صالح", + "customGeoAliasesError": "تعذّر تحميل أسماء geo المخصصة", + "customGeoValidationAlias": "الاسم المستعار: أحرف صغيرة وأرقام و - و _ فقط", + "customGeoValidationUrl": "يجب أن يبدأ الرابط بـ http:// أو https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (مخصص)", + "customGeoToastList": "قائمة geo المخصص", + "customGeoToastAdd": "إضافة geo مخصص", + "customGeoToastUpdate": "تحديث geo مخصص", + "customGeoToastDelete": "تم حذف geofile «{{ .fileName }}» المخصص", + "customGeoToastDownload": "تم تحديث geofile «{{ .fileName }}»", + "customGeoErrInvalidType": "يجب أن يكون النوع geosite أو geoip", + "customGeoErrAliasRequired": "الاسم المستعار مطلوب", + "customGeoErrAliasPattern": "الاسم المستعار يحتوي على أحرف غير مسموحة", + "customGeoErrAliasReserved": "هذا الاسم محجوز", + "customGeoErrUrlRequired": "الرابط مطلوب", + "customGeoErrInvalidUrl": "الرابط غير صالح", + "customGeoErrUrlScheme": "يجب أن يستخدم الرابط http أو https", + "customGeoErrUrlHost": "مضيف الرابط غير صالح", + "customGeoErrDuplicateAlias": "هذا الاسم مستخدم مسبقاً لهذا النوع", + "customGeoErrNotFound": "مصدر geo المخصص غير موجود", + "customGeoErrDownload": "فشل التنزيل", + "customGeoErrUpdateAllIncomplete": "تعذر تحديث مصدر واحد أو أكثر من مصادر geo المخصصة", + "customGeoEmpty": "لا توجد مصادر geo مخصصة بعد — انقر على «إضافة» لإنشاء واحد" + }, + "inbounds": { + "allTimeTraffic": "إجمالي حركة المرور", + "allTimeTrafficUsage": "إجمالي الاستخدام طوال الوقت", + "title": "الإدخالات", + "totalDownUp": "إجمالي المرسل/المستقبل", + "totalUsage": "إجمالي الاستخدام", + "inboundCount": "عدد الإدخالات", + "operate": "القائمة", + "enable": "مفعل", + "remark": "ملاحظة", + "node": "نود", + "deployTo": "نشر على", + "localPanel": "بانل محلي", + "protocol": "بروتوكول", + "port": "بورت", + "portMap": "خريطة البورت", + "traffic": "الترافيك", + "details": "تفاصيل", + "transportConfig": "نقل", + "expireDate": "المدة", + "createdAt": "تاريخ الإنشاء", + "updatedAt": "تاريخ التحديث", + "resetTraffic": "إعادة ضبط الترافيك", + "addInbound": "أضف إدخال", + "generalActions": "إجراءات عامة", + "modifyInbound": "تعديل الإدخال", + "deleteInbound": "حذف الإدخال", + "deleteInboundContent": "متأكد إنك عايز تحذف الإدخال؟", + "deleteClient": "حذف العميل", + "deleteClientContent": "متأكد إنك عايز تحذف العميل؟", + "resetTrafficContent": "متأكد إنك عايز تعيد ضبط الترافيك؟", + "copyLink": "انسخ الرابط", + "address": "العنوان", + "network": "الشبكة", + "destinationPort": "بورت الوجهة", + "targetAddress": "عنوان الهدف", + "monitorDesc": "سيبها فاضية لو عايز تستمع على كل الـ IPs", + "meansNoLimit": "= غير محدود. (الوحدة: جيجابايت)", + "totalFlow": "إجمالي التدفق", + "leaveBlankToNeverExpire": "سيبها فاضية عشان ماتنتهيش", + "noRecommendKeepDefault": "ننصح باستخدام الافتراضي", + "certificatePath": "مسار الملف", + "certificateContent": "محتوى الملف", + "publicKey": "المفتاح العام", + "privatekey": "المفتاح الخاص", + "clickOnQRcode": "اضغط على كود QR للنسخ", + "client": "عميل", + "export": "تصدير كل الروابط", + "clone": "استنساخ", + "cloneInbound": "استنساخ الإدخال", + "cloneInboundContent": "كل إعدادات الإدخال ده، غير البورت، IP الاستماع، والعملاء، هتتطبق على الاستنساخ.", + "cloneInboundOk": "استنساخ", + "resetAllTraffic": "إعادة ضبط ترافيك كل الإدخالات", + "resetAllTrafficTitle": "إعادة ضبط ترافيك كل الإدخالات", + "resetAllTrafficContent": "متأكد إنك عايز تعيد ضبط الترافيك لكل الإدخالات؟", + "resetInboundClientTraffics": "إعادة ضبط ترافيك العملاء", + "resetInboundClientTrafficTitle": "إعادة ضبط ترافيك العملاء", + "resetInboundClientTrafficContent": "متأكد إنك عايز تعيد ضبط ترافيك عملاء الإدخال ده؟", + "resetAllClientTraffics": "إعادة ضبط ترافيك كل العملاء", + "resetAllClientTrafficTitle": "إعادة ضبط ترافيك كل العملاء", + "resetAllClientTrafficContent": "متأكد إنك عايز تعيد ضبط ترافيك كل العملاء؟", + "delDepletedClients": "حذف العملاء اللي خلصت", + "delDepletedClientsTitle": "حذف العملاء اللي خلصت", + "delDepletedClientsContent": "متأكد إنك عايز تحذف كل العملاء اللي خلصت؟", + "email": "الإيميل", + "emailDesc": "ادخل إيميل فريد.", + "IPLimit": "تحديد IP", + "IPLimitDesc": "بيعطل الإدخال لو العدد زاد عن القيمة المحددة. (0 = تعطيل)", + "IPLimitlog": "سجل IP", + "IPLimitlogDesc": "سجل تاريخ الـ IPs. (عشان تفعل الإدخال بعد التعطيل، امسح السجل)", + "IPLimitlogclear": "امسح السجل", + "setDefaultCert": "استخدم شهادة البانل", + "telegramDesc": "ادخل ID شات Telegram. (استخدم '/id' في البوت) أو ({'@'}userinfobot)", + "subscriptionDesc": "عشان تلاقي رابط الاشتراك، ادخل على 'التفاصيل'. وكمان ممكن تستخدم نفس الاسم لعدة عملاء.", + "info": "معلومات", + "same": "نفسه", + "inboundData": "بيانات الإدخال", + "exportInbound": "تصدير الإدخال", + "import": "استيراد", + "importInbound": "استيراد إدخال", + "periodicTrafficResetTitle": "إعادة تعيين حركة المرور", + "periodicTrafficResetDesc": "إعادة تعيين عداد حركة المرور تلقائيًا في فترات محددة", + "lastReset": "آخر إعادة تعيين", + "periodicTrafficReset": { + "never": "أبداً", + "daily": "يومياً", + "weekly": "أسبوعياً", + "monthly": "شهرياً", + "hourly": "كل ساعة" + }, + "toasts": { + "obtain": "تم الحصول عليه", + "updateSuccess": "تم التحديث بنجاح", + "logCleanSuccess": "تم مسح السجل", + "inboundsUpdateSuccess": "تم تحديث الواردات بنجاح", + "inboundUpdateSuccess": "تم تحديث الوارد بنجاح", + "inboundCreateSuccess": "تم إنشاء الوارد بنجاح", + "inboundDeleteSuccess": "تم حذف الوارد بنجاح", + "inboundClientAddSuccess": "تمت إضافة عميل(عملاء) وارد", + "inboundClientDeleteSuccess": "تم حذف عميل وارد", + "inboundClientUpdateSuccess": "تم تحديث عميل وارد", + "delDepletedClientsSuccess": "تم حذف جميع العملاء المستنفذين", + "resetAllClientTrafficSuccess": "تم إعادة تعيين كل حركة المرور من العميل", + "resetAllTrafficSuccess": "تم إعادة تعيين كل حركة المرور", + "resetInboundClientTrafficSuccess": "تم إعادة تعيين حركة المرور", + "trafficGetError": "خطأ في الحصول على حركات المرور", + "getNewX25519CertError": "حدث خطأ أثناء الحصول على شهادة X25519.", + "getNewmldsa65Error": "حدث خطاء في الحصول على mldsa65.", + "getNewVlessEncError": "حدث خطأ أثناء الحصول على VlessEnc." + }, + "stream": { + "general": { + "request": "طلب", + "response": "رد", + "name": "اسم", + "value": "قيمة" + }, + "tcp": { + "version": "نسخة", + "method": "طريقة", + "path": "مسار", + "status": "الحالة", + "statusDescription": "وصف الحالة", + "requestHeader": "رأس الطلب", + "responseHeader": "رأس الرد" + } + } + }, + "client": { + "add": "أضف عميل", + "edit": "تعديل عميل", + "submitAdd": "أضف العميل", + "submitEdit": "احفظ التعديلات", + "clientCount": "عدد العملاء", + "bulk": "إضافة بالجملة", + "copyFromInbound": "نسخ العملاء من الـ Inbound", + "copyToInbound": "نسخ العملاء إلى", + "copySelected": "نسخ المحدد", + "copySource": "المصدر", + "copyEmailPreview": "معاينة البريد الإلكتروني الناتج", + "copySelectSourceFirst": "الرجاء اختيار الـ Inbound المصدر أولاً.", + "copyResult": "نتيجة النسخ", + "copyResultSuccess": "تم النسخ بنجاح", + "copyResultNone": "لا يوجد شيء للنسخ: لم يتم اختيار أي عميل أو أن المصدر فارغ", + "copyResultErrors": "أخطاء النسخ", + "copyFlowLabel": "Flow للعملاء الجدد (VLESS)", + "copyFlowHint": "يُطبَّق على جميع العملاء المنسوخين. اتركه فارغاً لتخطيه.", + "selectAll": "تحديد الكل", + "clearAll": "مسح الكل", + "method": "طريقة", + "first": "أول واحد", + "last": "آخر واحد", + "prefix": "بادئة", + "postfix": "لاحقة", + "delayedStart": "ابدأ بعد أول استخدام", + "expireDays": "المدة", + "days": "يوم/أيام", + "renew": "تجديد تلقائي", + "renewDesc": "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)" + }, + "nodes": { + "title": "النودز", + "addNode": "إضافة نود", + "editNode": "تعديل نود", + "totalNodes": "إجمالي النودز", + "onlineNodes": "أونلاين", + "offlineNodes": "أوفلاين", + "avgLatency": "متوسط الكمون", + "name": "الاسم", + "namePlaceholder": "مثال: de-frankfurt-1", + "addressPlaceholder": "panel.example.com أو 1.2.3.4", + "remark": "ملاحظة", + "scheme": "البروتوكول", + "address": "العنوان", + "port": "البورت", + "basePath": "المسار الأساسي", + "apiToken": "توكن API", + "apiTokenPlaceholder": "التوكن من صفحة إعدادات البانل البعيد", + "apiTokenHint": "البانل البعيد بيعرض توكن API بتاعه في الإعدادات → توكن API.", + "regenerate": "تجديد التوكن", + "regenerateConfirm": "تجديد التوكن هيلغي التوكن الحالي. أي بانل مركزي بيستخدمه هيفقد الصلاحية لحد ما تحدّث التوكن. تكمّل؟", + "enable": "مفعل", + "status": "الحالة", + "cpu": "المعالج", + "mem": "الذاكرة", + "uptime": "مدة التشغيل", + "latency": "الكمون", + "lastHeartbeat": "آخر نبضة", + "xrayVersion": "إصدار Xray", + "actions": "العمليات", + "probe": "فحص فوري", + "testConnection": "اختبار الاتصال", + "connectionOk": "الاتصال شغال ({ms} ms)", + "connectionFailed": "فشل الاتصال", + "never": "أبدًا", + "justNow": "دلوقتي", + "deleteConfirmTitle": "تحذف النود \"{name}\"؟", + "deleteConfirmContent": "ده هيوقّف مراقبة النود. البانل البعيد نفسه مش هيتأثر.", + "statusValues": { + "online": "أونلاين", + "offline": "أوفلاين", + "unknown": "غير معروف" + }, + "toasts": { + "list": "فشل تحميل النودز", + "obtain": "فشل تحميل النود", + "add": "إضافة نود", + "update": "تحديث النود", + "delete": "حذف النود", + "deleted": "اتمسح النود", + "test": "اختبار الاتصال", + "fillRequired": "الاسم والعنوان والبورت وتوكن API كلهم مطلوبين", + "probeFailed": "فشل الفحص" + } + }, + "settings": { + "title": "إعدادات البانل", + "save": "حفظ", + "infoDesc": "كل تغيير هتعمله هنا لازم يتخزن. ياريت تعيد تشغيل البانل عشان التعديلات تتفعل.", + "restartPanel": "إعادة تشغيل البانل", + "restartPanelDesc": "متأكد إنك عايز تعيد تشغيل البانل؟ لو ماقدرتش تدخل بعد إعادة التشغيل، شوف سجل البانل على السيرفر.", + "restartPanelSuccess": "تم إعادة تشغيل اللوحة بنجاح", + "actions": "إجراءات", + "resetDefaultConfig": "استرجاع الافتراضي", + "panelSettings": "عام", + "securitySettings": "المصادقة", + "TGBotSettings": "بوت Telegram", + "panelListeningIP": "IP الاستماع", + "panelListeningIPDesc": "عنوان IP للبانل. (سيبه فاضي عشان يستمع على كل الـ IPs)", + "panelListeningDomain": "دومين الاستماع", + "panelListeningDomainDesc": "اسم الدومين للبانل. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)", + "panelPort": "بورت الاستماع", + "panelPortDesc": "رقم البورت للبانل. (لازم يكون بورت فاضي)", + "publicKeyPath": "مسار المفتاح العام", + "publicKeyPathDesc": "مسار ملف المفتاح العام للبانل. (يبدأ بـ '/')", + "privateKeyPath": "مسار المفتاح الخاص", + "privateKeyPathDesc": "مسار ملف المفتاح الخاص للبانل. (يبدأ بـ '/')", + "panelUrlPath": "مسار URI", + "panelUrlPathDesc": "مسار URI للبانل. (يبدأ بـ '/' وبينتهي بـ '/')", + "pageSize": "حجم الصفحة", + "pageSizeDesc": "حدد حجم الصفحة لجدول الإدخالات. (0 = تعطيل)", + "remarkModel": "نموذج الملاحظة وحرف الفصل", + "datepicker": "نوع التقويم", + "datepickerPlaceholder": "اختار التاريخ", + "datepickerDescription": "المهام المجدولة هتشتغل بناءً على التقويم ده.", + "sampleRemark": "مثال للملاحظة", + "oldUsername": "اسم المستخدم الحالي", + "currentPassword": "الباسورد الحالي", + "newUsername": "اسم المستخدم الجديد", + "newPassword": "الباسورد الجديد", + "telegramBotEnable": "تفعيل بوت Telegram", + "telegramBotEnableDesc": "يفعل بوت Telegram.", + "telegramToken": "توكن Telegram", + "telegramTokenDesc": "توكن البوت اللي جبت من '{'@'}BotFather'.", + "telegramProxy": "بروكسي SOCKS", + "telegramProxyDesc": "يفعل بروكسي SOCKS5 للاتصال بـ Telegram. (اضبط الإعدادات حسب الدليل)", + "telegramAPIServer": "سيرفر Telegram API", + "telegramAPIServerDesc": "سيرفر Telegram API المستخدم. سيبه فاضي لاستخدام الافتراضي.", + "telegramChatId": "ID شات الأدمن", + "telegramChatIdDesc": "ID شات الأدمن في Telegram. (مفصول بفواصل)(تقدر تجيبه من {'@'}userinfobot) أو (استخدم '/id' في البوت)", + "telegramNotifyTime": "وقت الإشعار", + "telegramNotifyTimeDesc": "وقت إشعار البوت للتقارير الدورية. (استخدم صيغة وقت crontab)", + "tgNotifyBackup": "نسخة احتياطية لقاعدة البيانات", + "tgNotifyBackupDesc": "ابعت ملف النسخة الاحتياطية لقاعدة البيانات مع التقرير.", + "tgNotifyLogin": "إشعار بتسجيل الدخول", + "tgNotifyLoginDesc": "استقبل إشعار بكل محاولة تسجيل دخول للبانل مع اسم المستخدم، الـ IP، والوقت.", + "sessionMaxAge": "مدة الجلسة", + "sessionMaxAgeDesc": "المدة اللي تفضل فيها مسجل دخول. (الوحدة: دقيقة)", + "expireTimeDiff": "تنبيه بتاريخ الانتهاء", + "expireTimeDiffDesc": "استقبل تنبيه قبل ما توصل لتاريخ الانتهاء بالمدة المحددة. (الوحدة: يوم)", + "trafficDiff": "تنبيه حد الترافيك", + "trafficDiffDesc": "استقبل تنبيه عند وصول الترافيك للحد المحدد. (الوحدة: جيجابايت)", + "tgNotifyCpu": "تنبيه حمل المعالج", + "tgNotifyCpuDesc": "استقبل تنبيه لو حمل المعالج عدى الحد المحدد. (الوحدة: %)", + "timeZone": "المنطقة الزمنية", + "timeZoneDesc": "المهام المجدولة هتشتغل بناءً على المنطقة الزمنية دي.", + "subSettings": "الاشتراك", + "subEnable": "تفعيل خدمة الاشتراك", + "subEnableDesc": "يفعل خدمة الاشتراك.", + "subJsonEnable": "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل.", + "subTitle": "عنوان الاشتراك", + "subTitleDesc": "العنوان اللي هيظهر في عميل VPN", + "subSupportUrl": "رابط الدعم", + "subSupportUrlDesc": "رابط الدعم الفني المعروض في عميل VPN", + "subProfileUrl": "رابط الملف الشخصي", + "subProfileUrlDesc": "رابط لموقعك الإلكتروني يظهر في عميل VPN", + "subAnnounce": "إعلان", + "subAnnounceDesc": "نص الإعلان المعروض في عميل VPN", + "subEnableRouting": "تفعيل التوجيه", + "subEnableRoutingDesc": "إعداد عام لتمكين التوجيه (Routing) في عميل VPN. (فقط لـ Happ)", + "subRoutingRules": "قواعد التوجيه", + "subRoutingRulesDesc": "قواعد التوجيه العامة لعميل VPN. (فقط لـ Happ)", + "subListen": "IP الاستماع", + "subListenDesc": "عنوان IP لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الـ IPs)", + "subPort": "بورت الاستماع", + "subPortDesc": "رقم البورت لخدمة الاشتراك. (لازم يكون بورت فاضي)", + "subCertPath": "مسار المفتاح العام", + "subCertPathDesc": "مسار ملف المفتاح العام لخدمة الاشتراك. (يبدأ بـ '/')", + "subKeyPath": "مسار المفتاح الخاص", + "subKeyPathDesc": "مسار ملف المفتاح الخاص لخدمة الاشتراك. (يبدأ بـ '/')", + "subPath": "مسار URI", + "subPathDesc": "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')", + "subDomain": "دومين الاستماع", + "subDomainDesc": "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)", + "subUpdates": "فترات التحديث", + "subUpdatesDesc": "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)", + "subEncrypt": "تشفير", + "subEncryptDesc": "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64.", + "subShowInfo": "اظهر معلومات الاستخدام", + "subShowInfoDesc": "هيظهر الترافيك المتبقي والتاريخ في تطبيقات العملاء.", + "subURI": "مسار البروكسي العكسي", + "subURIDesc": "مسار URI لرابط الاشتراك عشان تستخدمه ورا البروكسي.", + "externalTrafficInformEnable": "تنبيه الترافيك الخارجي", + "externalTrafficInformEnableDesc": "يبعت تنبيه لـ API خارجي مع كل تحديث للترافيك.", + "externalTrafficInformURI": "مسار تنبيه الترافيك الخارجي", + "externalTrafficInformURIDesc": "تحديثات الترافيك هتتبعت للمسار ده.", + "restartXrayOnClientDisable": "إعادة تشغيل Xray بعد التعطيل التلقائي", + "restartXrayOnClientDisableDesc": "عند تعطيل العميل تلقائيا بسبب انتهاء الصلاحية أو حد حركة المرور، أعد تشغيل Xray.", + "fragment": "تجزئة", + "fragmentDesc": "يفعل تجزئة لحزمة TLS hello.", + "fragmentSett": "إعدادات التجزئة", + "noisesDesc": "يفعل التشويش.", + "noisesSett": "إعدادات التشويش", + "mux": "MUX", + "muxDesc": "ينقل أكثر من تيار بيانات مستقل خلال تيار بيانات واحد قائم.", + "muxSett": "إعدادات MUX", + "direct": "اتصال مباشر", + "directDesc": "ينشئ اتصال مباشر مع الدومينات أو نطاقات IP لدولة معينة.", + "notifications": "الإشعارات", + "certs": "الشهادات", + "externalTraffic": "الترافيك الخارجي", + "dateAndTime": "التاريخ والوقت", + "proxyAndServer": "البروكسي والسيرفر", + "intervals": "الفترات", + "information": "المعلومات", + "language": "اللغة", + "telegramBotLanguage": "لغة بوت Telegram", + "security": { + "admin": "بيانات الأدمن", + "twoFactor": "المصادقة الثنائية", + "twoFactorEnable": "تفعيل المصادقة الثنائية", + "twoFactorEnableDesc": "يضيف طبقة إضافية من المصادقة لتعزيز الأمان.", + "twoFactorModalSetTitle": "تفعيل المصادقة الثنائية", + "twoFactorModalDeleteTitle": "تعطيل المصادقة الثنائية", + "twoFactorModalSteps": "لإعداد المصادقة الثنائية، قم ببعض الخطوات:", + "twoFactorModalFirstStep": "1. امسح رمز QR هذا في تطبيق المصادقة أو انسخ الرمز الموجود بجانب رمز QR والصقه في التطبيق", + "twoFactorModalSecondStep": "2. أدخل الرمز من التطبيق", + "twoFactorModalRemoveStep": "أدخل الرمز من التطبيق لإزالة المصادقة الثنائية.", + "twoFactorModalChangeCredentialsTitle": "تغيير بيانات الاعتماد", + "twoFactorModalChangeCredentialsStep": "أدخل الرمز من التطبيق لتغيير بيانات اعتماد المسؤول.", + "twoFactorModalSetSuccess": "تم إنشاء المصادقة الثنائية بنجاح", + "twoFactorModalDeleteSuccess": "تم حذف المصادقة الثنائية بنجاح", + "twoFactorModalError": "رمز خاطئ" + }, + "toasts": { + "modifySettings": "تم تغيير المعلمات.", + "getSettings": "حدث خطأ أثناء استرداد المعلمات.", + "modifyUserError": "حدث خطأ أثناء تغيير بيانات اعتماد المسؤول.", + "modifyUser": "لقد قمت بتغيير بيانات اعتماد المسؤول بنجاح.", + "originalUserPassIncorrect": "اسم المستخدم أو الباسورد الحالي غير صحيح", + "userPassMustBeNotEmpty": "اسم المستخدم والباسورد الجديدين فاضيين", + "getOutboundTrafficError": "خطأ في الحصول على حركات المرور الصادرة", + "resetOutboundTrafficError": "خطأ في إعادة تعيين حركات المرور الصادرة" + } + }, + "xray": { + "title": "إعدادات Xray", + "save": "احفظ", + "restart": "أعد تشغيل Xray", + "restartSuccess": "تم إعادة تشغيل Xray بنجاح", + "stopSuccess": "تم إيقاف Xray بنجاح", + "restartError": "حدث خطأ أثناء إعادة تشغيل Xray.", + "stopError": "حدث خطأ أثناء إيقاف Xray.", + "basicTemplate": "أساسي", + "advancedTemplate": "متقدم", + "generalConfigs": "إعدادات عامة", + "generalConfigsDesc": "الخيارات دي هتحدد التعديلات العامة.", + "logConfigs": "السجلات", + "logConfigsDesc": "السجلات ممكن تأثر على كفاءة السيرفر. ننصح بتفعيلها بحكمة لما تكون محتاجها.", + "blockConfigsDesc": "الخيارات دي هتحجب الترافيك بناءً على بروتوكولات ومواقع محددة.", + "basicRouting": "توجيه أساسي", + "blockConnectionsConfigsDesc": "الخيارات دي هتحجب الترافيك بناءً على الدولة المطلوبة.", + "directConnectionsConfigsDesc": "الاتصال المباشر بيضمن إن الترافيك المعين مايمرش من سيرفر تاني.", + "blockips": "حظر IPs", + "blockdomains": "حظر دومينات", + "directips": "اتصالات مباشرة لـ IPs", + "directdomains": "اتصالات مباشرة للدومينات", + "ipv4Routing": "توجيه IPv4", + "ipv4RoutingDesc": "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر IPv4.", + "warpRouting": "توجيه WARP", + "warpRoutingDesc": "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر WARP.", + "nordRouting": "توجيه NordVPN", + "nordRoutingDesc": "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر NordVPN.", + "Template": "قالب إعدادات Xray المتقدم", + "TemplateDesc": "ملف إعدادات Xray النهائي هيتولد بناءً على القالب ده.", + "FreedomStrategy": "استراتيجية بروتوكول الحرية", + "FreedomStrategyDesc": "اختار استراتيجية المخرجات للشبكة في بروتوكول الحرية.", + "RoutingStrategy": "استراتيجية التوجيه العامة", + "RoutingStrategyDesc": "حدد استراتيجية التوجيه الإجمالية لحل كل الطلبات.", + "outboundTestUrl": "رابط اختبار المخرج", + "outboundTestUrlDesc": "الرابط المستخدم عند اختبار اتصال المخرج", + "Torrent": "حظر بروتوكول التورنت", + "Inbounds": "الإدخالات", + "InboundsDesc": "قبول العملاء المعينين.", + "Outbounds": "المخرجات", + "Balancers": "موازنات التحميل", + "OutboundsDesc": "حدد مسار الترافيك الصادر.", + "Routings": "قواعد التوجيه", + "RoutingsDesc": "أولوية كل قاعدة مهمة جداً!", + "completeTemplate": "الكل", + "logLevel": "مستوى السجلات", + "logLevelDesc": "مستوى السجل الخاص بالأخطاء، اللي بيوضح المعلومات المطلوبة للتسجيل.", + "accessLog": "سجل الوصول", + "accessLogDesc": "مسار ملف سجل الوصول. القيمة الخاصة 'none' بتعطل سجل الوصول.", + "errorLog": "سجل الأخطاء", + "errorLogDesc": "مسار ملف سجل الأخطاء. القيمة الخاصة 'none' بتعطل سجل الأخطاء.", + "dnsLog": "سجل DNS", + "dnsLogDesc": "لو هتسجل استعلامات DNS.", + "maskAddress": "إخفاء العنوان", + "maskAddressDesc": "إخفاء عنوان الـ IP؛ لو مفعل، هيستبدل تلقائياً عنوان IP اللي بيظهر في السجل.", + "statistics": "إحصائيات", + "statsInboundUplink": "إحصائيات رفع الإدخال", + "statsInboundUplinkDesc": "تفعيل جمع الإحصائيات لترافيك الرفع لكل بروكسي من الإدخالات.", + "statsInboundDownlink": "إحصائيات تنزيل الإدخال", + "statsInboundDownlinkDesc": "تفعيل جمع الإحصائيات لترافيك التنزيل لكل بروكسي من الإدخالات.", + "statsOutboundUplink": "إحصائيات رفع المخرجات", + "statsOutboundUplinkDesc": "تفعيل جمع الإحصائيات لترافيك الرفع لكل بروكسي من المخرجات.", + "statsOutboundDownlink": "إحصائيات تنزيل المخرجات", + "statsOutboundDownlinkDesc": "تفعيل جمع الإحصائيات لترافيك التنزيل لكل بروكسي من المخرجات.", + "rules": { + "first": "أول", + "last": "آخر", + "up": "فوق", + "down": "تحت", + "source": "المصدر", + "dest": "الوجهة", + "inbound": "إدخال", + "outbound": "مخرج", + "balancer": "موازن", + "info": "معلومات", + "add": "أضف قاعدة", + "edit": "عدل القاعدة", + "useComma": "عناصر مفصولة بفواصل" + }, + "outbound": { + "addOutbound": "أضف مخرج", + "addReverse": "أضف عكسي", + "editOutbound": "عدل المخرج", + "editReverse": "عدل العكسي", + "reverseTag": "وسم العكسي", + "reverseTagDesc": "وسم الخروج لبروكسي VLESS العكسي البسيط. اتركه فارغاً لتعطيله.", + "reverseTagPlaceholder": "وسم الخروج (اتركه فارغاً للتعطيل)", + "tag": "تاج", + "tagDesc": "تاج فريد", + "address": "العنوان", + "reverse": "عكسي", + "domain": "دومين", + "type": "النوع", + "bridge": "جسر", + "portal": "بوابة", + "link": "رابط", + "intercon": "تواصل", + "settings": "إعدادات", + "accountInfo": "معلومات الحساب", + "outboundStatus": "حالة المخرج", + "sendThrough": "أرسل من خلال", + "test": "اختبار", + "testResult": "نتيجة الاختبار", + "testing": "جاري اختبار الاتصال...", + "testSuccess": "الاختبار ناجح", + "testFailed": "فشل الاختبار", + "testError": "فشل اختبار المخرج", + "nordvpn": "NordVPN", + "accessToken": "رمز الوصول", + "country": "الدولة", + "server": "الخادم", + "city": "المدينة", + "allCities": "كل المدن", + "privateKey": "المفتاح الخاص", + "load": "الحمل" + }, + "balancer": { + "addBalancer": "أضف موازن تحميل", + "editBalancer": "عدل موازن التحميل", + "balancerStrategy": "استراتيجية الموازن", + "balancerSelectors": "المحددات", + "tag": "تاج", + "tagDesc": "تاج فريد", + "balancerDesc": "ماينفعش تستخدم balancerTag و outboundTag مع بعض. لو اتستخدموا مع بعض، outboundTag هو اللي هيشتغل." + }, + "wireguard": { + "secretKey": "المفتاح السري", + "publicKey": "المفتاح العام", + "allowedIPs": "عناوين IP المسموح بها", + "endpoint": "النهاية", + "psk": "المفتاح المشترك", + "domainStrategy": "استراتيجية الدومين" + }, + "tun": { + "nameDesc": "اسم واجهة TUN. القيمة الافتراضية هي 'xray0'", + "mtuDesc": "وحدة النقل الأقصى. الحد الأقصى لحجم حزم البيانات. القيمة الافتراضية هي 1500", + "userLevel": "مستوى المستخدم", + "userLevelDesc": "ستستخدم جميع الاتصالات المُرسلة عبر هذا الإدخال مستوى المستخدم هذا. القيمة الافتراضية هي 0" + }, + "dns": { + "enable": "فعل DNS", + "enableDesc": "فعل سيرفر DNS المدمج", + "tag": "تاج إدخال DNS", + "tagDesc": "التاج ده هيبقى متاح كإدخال في قواعد التوجيه.", + "clientIp": "IP العميل", + "clientIpDesc": "بيحدد موقع العميل خلال استعلامات DNS", + "disableCache": "تعطيل الكاش", + "disableCacheDesc": "بيعطل تخزين نتائج DNS مؤقتاً", + "disableFallback": "تعطيل النسخ الاحتياطي", + "disableFallbackDesc": "بيعطل استعلامات DNS الاحتياطية", + "disableFallbackIfMatch": "تعطيل النسخ الاحتياطي عند التطابق", + "disableFallbackIfMatchDesc": "بيعطل استعلامات DNS الاحتياطية لما يتحقق تطابق مع قائمة الدومينات", + "enableParallelQuery": "تفعيل الاستعلام المتوازي", + "enableParallelQueryDesc": "تفعيل استعلامات DNS المتوازية لعدة خوادم لحل أسرع", + "strategy": "استراتيجية الاستعلام", + "strategyDesc": "الاستراتيجية العامة لحل أسماء الدومين", + "add": "أضف سيرفر", + "edit": "عدل السيرفر", + "domains": "الدومينات", + "expectIPs": "العناوين المتوقعة", + "unexpectIPs": "عناوين IP غير متوقعة", + "useSystemHosts": "استخدام ملف Hosts الخاص بالنظام", + "useSystemHostsDesc": "استخدام ملف hosts من نظام مثبت", + "usePreset": "استخدام النموذج", + "dnsPresetTitle": "قوالب DNS", + "dnsPresetFamily": "العائلي" + }, + "fakedns": { + "add": "أضف Fake DNS", + "edit": "عدل Fake DNS", + "ipPool": "نطاق IP Pool", + "poolSize": "حجم المجموعة" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ لوحة المفاتيح مغلقة!", + "noResult": "❗ لا يوجد نتائج!", + "noQuery": "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!", + "wentWrong": "❌ حدث خطأ ما!", + "noIpRecord": "❗ لا يوجد سجل IP!", + "noInbounds": "❗ لم يتم العثور على أي وارد!", + "unlimited": "♾ غير محدود (إعادة تعيين)", + "add": "إضافة", + "month": "شهر", + "months": "أشهر", + "day": "يوم", + "days": "أيام", + "hours": "ساعات", + "minutes": "دقائق", + "unknown": "غير معروف", + "inbounds": "الواردات", + "clients": "العملاء", + "offline": "🔴 غير متصل", + "online": "🟢 متصل", + "commands": { + "unknown": "❗ أمر مش معروف.", + "pleaseChoose": "👇 من فضلك اختار:\r\n", + "help": "🤖 أهلا بيك في البوت! البوت ده معمول عشان يديك بيانات معينة من البانل ويسمحلك بالتعديلات.", + "start": "👋 أهلا {{ .Firstname }}.\r\n", + "welcome": "🤖 أهلا بيك في بوت إدارة {{ .Hostname }}.\r\n", + "status": "✅ البوت شغال!", + "usage": "❗ من فضلك ادخل نص للتبحث عنه!", + "getID": "🆔 الـ ID بتاعك: {{ .ID }}", + "helpAdminCommands": "عشان تعيد تشغيل Xray Core:\r\n/restart\r\n\r\nعشان تدور على إيميل عميل:\r\n/usage [Email]\r\n\r\nعشان تدور على إدخالات (مع إحصائيات العملاء):\r\n/inbound [Remark]\r\n\r\nID شات Telegram:\r\n/id", + "helpClientCommands": "عشان تدور على الإحصائيات، استخدم الأمر ده:\r\n\r\n/usage [Email]\r\n\r\nID شات Telegram:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ العملية نجحت!", + "restartFailed": "❗ حصل خطأ في العملية.\r\n\r\nError: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core مش شغال.", + "startDesc": "عرض القائمة الرئيسية", + "helpDesc": "مساعدة البوت", + "statusDesc": "التحقق من حالة البوت", + "idDesc": "عرض معرف Telegram الخاص بك" + }, + "messages": { + "cpuThreshold": "🔴 حمل المعالج {{ .Percent }}% عدى الحد المسموح ({{ .Threshold }}%)", + "selectUserFailed": "❌ حصل خطأ في اختيار المستخدم!", + "userSaved": "✅ حفظت بيانات مستخدم Telegram.", + "loginSuccess": "✅ تسجيل الدخول للبانل تم بنجاح.\r\n", + "loginFailed": "❗️فشل محاولة تسجيل الدخول للبانل.\r\n", + "2faFailed": "فشل 2FA", + "report": "🕰 التقارير المجدولة: {{ .RunTime }}\r\n", + "datetime": "⏰ التاريخ والوقت: {{ .DateTime }}\r\n", + "hostname": "💻 السيرفر: {{ .Hostname }}\r\n", + "version": "🚀 نسخة 3X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 نسخة Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 عناوين IP:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ وقت التشغيل: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 تحميل النظام: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 الرام: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 الترافيك: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ الحالة: {{ .State }}\r\n", + "username": "👤 اسم المستخدم: {{ .Username }}\r\n", + "reason": "❗️ السبب: {{ .Reason }}\r\n", + "time": "⏰ الوقت: {{ .Time }}\r\n", + "inbound": "📍 الإدخال: {{ .Remark }}\r\n", + "port": "🔌 البورت: {{ .Port }}\r\n", + "expire": "📅 تاريخ الانتهاء: {{ .Time }}\r\n", + "expireIn": "📅 هيخلص بعد: {{ .Time }}\r\n", + "active": "💡 مفعل: {{ .Enable }}\r\n", + "enabled": "🚨 مفعل: {{ .Enable }}\r\n", + "online": "🌐 حالة الاتصال: {{ .Status }}\r\n", + "lastOnline": "🔙 آخر متصل: {{ .Time }}\r\n", + "email": "📧 الإيميل: {{ .Email }}\r\n", + "upload": "🔼 رفع: ↑{{ .Upload }}\r\n", + "download": "🔽 تنزيل: ↓{{ .Download }}\r\n", + "total": "📊 الإجمالي: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 مستخدم Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 نفذ {{ .Type }}:\r\n", + "exhaustedCount": "🚨 عدد النفاذ لـ {{ .Type }}:\r\n", + "onlinesCount": "🌐 العملاء الأونلاين: {{ .Count }}\r\n", + "disabled": "🛑 معطل: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 هينتهي قريب: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 وقت النسخة الاحتياطية: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 اتحدّث في: {{ .Time }}\r\n\r\n", + "yes": "✅ أيوه", + "no": "❌ لأ", + "received_id": "🔑📥 الـ ID اتحدث.", + "received_password": "🔑📥 الباسورد اتحدث.", + "received_email": "📧📥 الإيميل اتحدث.", + "received_comment": "💬📥 التعليق اتحدث.", + "id_prompt": "🔑 الـ ID الافتراضي: {{ .ClientId }}\n\nادخل الـ ID بتاعك.", + "pass_prompt": "🔑 الباسورد الافتراضي: {{ .ClientPassword }}\n\nادخل الباسورد بتاعك.", + "email_prompt": "📧 الإيميل الافتراضي: {{ .ClientEmail }}\n\nادخل الإيميل بتاعك.", + "comment_prompt": "💬 التعليق الافتراضي: {{ .ClientComment }}\n\nادخل تعليقك.", + "inbound_client_data_id": "🔄 الدخول: {{ .InboundRemark }}\n\n🔑 المعرف: {{ .ClientId }}\n📧 البريد الإلكتروني: {{ .ClientEmail }}\n📊 الترافيك: {{ .ClientTraffic }}\n📅 تاريخ الانتهاء: {{ .ClientExp }}\n🌐 حدّ IP: {{ .IpLimit }}\n💬 تعليق: {{ .ClientComment }}\n\nدلوقتي تقدر تضيف العميل على الدخول!", + "inbound_client_data_pass": "🔄 الدخول: {{ .InboundRemark }}\n\n🔑 كلمة المرور: {{ .ClientPass }}\n📧 البريد الإلكتروني: {{ .ClientEmail }}\n📊 الترافيك: {{ .ClientTraffic }}\n📅 تاريخ الانتهاء: {{ .ClientExp }}\n🌐 حدّ IP: {{ .IpLimit }}\n💬 تعليق: {{ .ClientComment }}\n\nدلوقتي تقدر تضيف العميل على الدخول!", + "cancel": "❌ العملية اتلغت! \n\nممكن تبدأ من /start في أي وقت. 🔄", + "error_add_client": "⚠️ حصل خطأ:\n\n {{ .error }}", + "using_default_value": "تمام، هشيل على القيمة الافتراضية. 😊", + "incorrect_input": "المدخلات مش صحيحة.\nالكلمات لازم تكون متصلة من غير فراغات.\nمثال صحيح: aaaaaa\nمثال غلط: aaa aaa 🚫", + "AreYouSure": "إنت متأكد؟ 🤔", + "SuccessResetTraffic": "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ✅ تم بنجاح", + "FailedResetTraffic": "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ❌ فشل \n\n🛠️ الخطأ: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 عملية إعادة ضبط الترافيك خلصت لكل العملاء." + }, + "buttons": { + "closeKeyboard": "❌ اقفل الكيبورد", + "cancel": "❌ إلغاء", + "cancelReset": "❌ إلغاء إعادة الضبط", + "cancelIpLimit": "❌ إلغاء حد الـ IP", + "confirmResetTraffic": "✅ تأكيد إعادة ضبط الترافيك؟", + "confirmClearIps": "✅ تأكيد مسح الـ IPs؟", + "confirmRemoveTGUser": "✅ تأكيد حذف مستخدم Telegram؟", + "confirmToggle": "✅ تأكيد تفعيل/تعطيل المستخدم؟", + "dbBackup": "احصل على نسخة DB", + "serverUsage": "استخدام السيرفر", + "getInbounds": "احصل على الإدخالات", + "depleteSoon": "هينتهي قريب", + "clientUsage": "استخدام العميل", + "onlines": "العملاء الأونلاين", + "commands": "الأوامر", + "refresh": "🔄 تجديد", + "clearIPs": "❌ مسح الـ IPs", + "removeTGUser": "❌ حذف مستخدم Telegram", + "selectTGUser": "👤 اختار مستخدم Telegram", + "selectOneTGUser": "👤 اختار مستخدم Telegram:", + "resetTraffic": "📈 إعادة ضبط الترافيك", + "resetExpire": "📅 تغيير تاريخ الانتهاء", + "ipLog": "🔢 سجل الـ IP", + "ipLimit": "🔢 حد الـ IP", + "setTGUser": "👤 ضبط مستخدم Telegram", + "toggle": "🔘 تفعيل / تعطيل", + "custom": "🔢 مخصص", + "confirmNumber": "✅ تأكيد: {{ .Num }}", + "confirmNumberAdd": "✅ تأكيد إضافة: {{ .Num }}", + "limitTraffic": "🚧 حد الترافيك", + "getBanLogs": "احصل على سجلات الحظر", + "allClients": "كل العملاء", + "addClient": "إضافة عميل", + "submitDisable": "إرسال كمعطّل ☑️", + "submitEnable": "إرسال كمفعّل ✅", + "use_default": "🏷️ استخدام الإعدادات الافتراضية", + "change_id": "⚙️🔑 المعرّف", + "change_password": "⚙️🔑 كلمة السر", + "change_email": "⚙️📧 البريد الإلكتروني", + "change_comment": "⚙️💬 تعليق", + "ResetAllTraffics": "إعادة ضبط جميع الترافيك", + "SortedTrafficUsageReport": "تقرير استخدام الترافيك المرتب" + }, + "answers": { + "successfulOperation": "✅ العملية نجحت!", + "errorOperation": "❗ حصل خطأ في العملية.", + "getInboundsFailed": "❌ فشل الحصول على الإدخالات.", + "getClientsFailed": "❌ فشل الحصول على العملاء.", + "canceled": "❌ {{ .Email }}: العملية اتلغت.", + "clientRefreshSuccess": "✅ {{ .Email }}: العميل اتحدث بنجاح.", + "IpRefreshSuccess": "✅ {{ .Email }}: الـ IPs اتحدثت بنجاح.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: مستخدم Telegram اتحدث بنجاح.", + "resetTrafficSuccess": "✅ {{ .Email }}: الترافيك اتظبط بنجاح.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: حد الترافيك اتسجل بنجاح.", + "expireResetSuccess": "✅ {{ .Email }}: أيام الانتهاء اتظبطت بنجاح.", + "resetIpSuccess": "✅ {{ .Email }}: حد الـ IP ({{ .Count }}) اتسجل بنجاح.", + "clearIpSuccess": "✅ {{ .Email }}: الـ IPs اتمسحت بنجاح.", + "getIpLog": "✅ {{ .Email }}: سجل الـ IP اتجاب.", + "getUserInfo": "✅ {{ .Email }}: بيانات مستخدم Telegram اتجاب.", + "removedTGUserSuccess": "✅ {{ .Email }}: مستخدم Telegram اتحذف بنجاح.", + "enableSuccess": "✅ {{ .Email }}: اتفعل بنجاح.", + "disableSuccess": "✅ {{ .Email }}: اتعطل بنجاح.", + "askToAddUserId": "مافيش إعدادات ليك!\r\nاطلب من الأدمن يضيف الـ Telegram ChatID الخاص بيك في إعداداتك.\r\n\r\nالـ ChatID بتاعك: {{ .TgUserID }}", + "chooseClient": "اختار عميل للإدخال {{ .Inbound }}", + "chooseInbound": "اختار الإدخال" + } + } +} diff --git a/web/translation/en-US.json b/web/translation/en-US.json new file mode 100644 index 00000000..40bfa750 --- /dev/null +++ b/web/translation/en-US.json @@ -0,0 +1,942 @@ +{ + "username": "Username", + "password": "Password", + "login": "Log In", + "confirm": "Confirm", + "cancel": "Cancel", + "close": "Close", + "save": "Save", + "logout": "Log Out", + "create": "Create", + "update": "Update", + "copy": "Copy", + "copied": "Copied", + "download": "Download", + "remark": "Remark", + "enable": "Enabled", + "protocol": "Protocol", + "search": "Search", + "filter": "Filter", + "loading": "Loading...", + "second": "Second", + "minute": "Minute", + "hour": "Hour", + "day": "Day", + "check": "Check", + "indefinite": "Indefinite", + "unlimited": "Unlimited", + "none": "None", + "qrCode": "QR Code", + "info": "More Information", + "edit": "Edit", + "delete": "Delete", + "reset": "Reset", + "noData": "No data.", + "copySuccess": "Copied Successful", + "sure": "Sure", + "encryption": "Encryption", + "useIPv4ForHost": "Use IPv4 for host", + "transmission": "Transmission", + "host": "Host", + "path": "Path", + "camouflage": "Obfuscation", + "status": "Status", + "enabled": "Enabled", + "disabled": "Disabled", + "depleted": "Ended", + "depletingSoon": "Depleting", + "offline": "Offline", + "online": "Online", + "domainName": "Domain Name", + "monitor": "Listen IP", + "certificate": "Digital Certificate", + "fail": "Failed", + "comment": "Comment", + "success": "Successfully", + "lastOnline": "Last Online", + "getVersion": "Get Version", + "install": "Install", + "clients": "Clients", + "usage": "Usage", + "twoFactorCode": "Code", + "remained": "Remained", + "security": "Security", + "secAlertTitle": "Security Alert", + "secAlertSsl": "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection.", + "secAlertConf": "Certain settings are vulnerable to attacks. It is recommended to reinforce security protocols to prevent potential breaches.", + "secAlertSSL": "Panel lacks secure connection. Please install TLS certificate for data protection.", + "secAlertPanelPort": "Panel default port is vulnerable. Please configure a random or specific port.", + "secAlertPanelURI": "Panel default URI path is insecure. Please configure a complex URI path.", + "secAlertSubURI": "Subscription default URI path is insecure. Please configure a complex URI path.", + "secAlertSubJsonURI": "Subscription JSON default URI path is insecure. Please configure a complex URI path.", + "emptyDnsDesc": "No added DNS servers.", + "emptyFakeDnsDesc": "No added Fake DNS servers.", + "emptyBalancersDesc": "No added balancers.", + "emptyReverseDesc": "No added reverse proxies.", + "somethingWentWrong": "Something went wrong", + "subscription": { + "title": "Subscription info", + "subId": "Subscription ID", + "status": "Status", + "downloaded": "Downloaded", + "uploaded": "Uploaded", + "expiry": "Expiry", + "totalQuota": "Total quota", + "individualLinks": "Individual links", + "active": "Active", + "inactive": "Inactive", + "unlimited": "Unlimited", + "noExpiry": "No expiry" + }, + "menu": { + "theme": "Theme", + "dark": "Dark", + "ultraDark": "Ultra Dark", + "dashboard": "Overview", + "inbounds": "Inbounds", + "nodes": "Nodes", + "settings": "Panel Settings", + "xray": "Xray Configs", + "logout": "Log Out", + "link": "Manage" + }, + "pages": { + "login": { + "hello": "Hello", + "title": "Welcome", + "loginAgain": "Your session has expired, please log in again", + "toasts": { + "invalidFormData": "The Input data format is invalid.", + "emptyUsername": "Username is required", + "emptyPassword": "Password is required", + "wrongUsernameOrPassword": "Invalid username or password or two-factor code.", + "successLogin": " You have successfully logged into your account." + } + }, + "index": { + "title": "Overview", + "cpu": "CPU", + "logicalProcessors": "Logical Processors", + "frequency": "Frequency", + "swap": "Swap", + "storage": "Storage", + "memory": "RAM", + "threads": "Threads", + "xrayStatus": "Xray", + "stopXray": "Stop", + "restartXray": "Restart", + "xraySwitch": "Version", + "xrayUpdates": "Xray Updates", + "xraySwitchClick": "Choose the version you want to switch to.", + "xraySwitchClickDesk": "Choose carefully, as older versions may not be compatible with current configurations.", + "updatePanel": "Update Panel", + "panelUpdateDesc": "This will update 3X-UI itself to the latest release and restart the panel service.", + "currentPanelVersion": "Current panel version", + "latestPanelVersion": "Latest panel version", + "panelUpToDate": "Panel is up to date", + "upToDate": "Up to date", + "xrayStatusUnknown": "Unknown", + "xrayStatusRunning": "Running", + "xrayStatusStop": "Stop", + "xrayStatusError": "Error", + "xrayErrorPopoverTitle": "An error occurred while running Xray", + "operationHours": "Uptime", + "systemHistoryTitle": "System History", + "trendLast2Min": "Last 2 minutes", + "systemLoad": "System Load", + "systemLoadDesc": "System load average for the past 1, 5, and 15 minutes", + "connectionCount": "Connection Stats", + "ipAddresses": "IP Addresses", + "toggleIpVisibility": "Toggle visibility of the IP", + "overallSpeed": "Overall Speed", + "upload": "Upload", + "download": "Download", + "totalData": "Total Data", + "sent": "Sent", + "received": "Received", + "documentation": "Documentation", + "xraySwitchVersionDialog": "Do you really want to change the Xray version?", + "xraySwitchVersionDialogDesc": "This will change the Xray version to #version#.", + "xraySwitchVersionPopover": "Xray updated successfully", + "panelUpdateDialog": "Do you really want to update the panel?", + "panelUpdateDialogDesc": "This will update 3X-UI to #version# and restart the panel service.", + "panelUpdateCheckPopover": "Panel update check failed", + "panelUpdateStartedPopover": "Panel update started", + "geofileUpdateDialog": "Do you really want to update the geofile?", + "geofileUpdateDialogDesc": "This will update the #filename# file.", + "geofilesUpdateDialogDesc": "This will update all geofiles.", + "geofilesUpdateAll": "Update all", + "geofileUpdatePopover": "Geofile updated successfully", + "customGeoTitle": "Custom GeoSite / GeoIP", + "customGeoAdd": "Add", + "customGeoType": "Type", + "customGeoAlias": "Alias", + "customGeoUrl": "URL", + "customGeoEnabled": "Enabled", + "customGeoLastUpdated": "Last updated", + "customGeoExtColumn": "Routing (ext:…)", + "customGeoToastUpdateAll": "All custom geo sources updated", + "customGeoActions": "Actions", + "customGeoEdit": "Edit", + "customGeoDelete": "Delete", + "customGeoDownload": "Update now", + "customGeoModalAdd": "Add custom geo", + "customGeoModalEdit": "Edit custom geo", + "customGeoModalSave": "Save", + "customGeoDeleteConfirm": "Delete this custom geo source?", + "customGeoRoutingHint": "In routing rules use the value column as ext:file.dat:tag (replace tag).", + "customGeoInvalidId": "Invalid resource id", + "customGeoAliasesError": "Failed to load custom geo aliases", + "customGeoValidationAlias": "Alias may only contain lowercase letters, digits, - and _", + "customGeoValidationUrl": "URL must start with http:// or https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (custom)", + "customGeoToastList": "Custom geo list", + "customGeoToastAdd": "Add custom geo", + "customGeoToastUpdate": "Update custom geo", + "customGeoToastDelete": "Custom geo file “{{ .fileName }}” deleted", + "customGeoToastDownload": "Geofile “{{ .fileName }}” updated", + "customGeoErrInvalidType": "Type must be geosite or geoip", + "customGeoErrAliasRequired": "Alias is required", + "customGeoErrAliasPattern": "Alias must match allowed characters", + "customGeoErrAliasReserved": "This alias is reserved", + "customGeoErrUrlRequired": "URL is required", + "customGeoErrInvalidUrl": "URL is invalid", + "customGeoErrUrlScheme": "URL must use http or https", + "customGeoErrUrlHost": "URL host is invalid", + "customGeoErrDuplicateAlias": "This alias is already used for this type", + "customGeoErrNotFound": "Custom geo source not found", + "customGeoErrDownload": "Download failed", + "customGeoErrUpdateAllIncomplete": "One or more custom geo sources failed to update", + "customGeoEmpty": "No custom geo sources yet — click Add to create one", + "dontRefresh": "Installation is in progress, please do not refresh this page", + "logs": "Logs", + "config": "Config", + "backup": "Backup", + "backupTitle": "Backup & Restore", + "exportDatabase": "Back Up", + "exportDatabaseDesc": "Click to download a .db file containing a backup of your current database to your device.", + "importDatabase": "Restore", + "importDatabaseDesc": "Click to select and upload a .db file from your device to restore your database from a backup.", + "importDatabaseSuccess": "The database has been successfully imported.", + "importDatabaseError": "An error occurred while importing the database.", + "readDatabaseError": "An error occurred while reading the database.", + "getDatabaseError": "An error occurred while retrieving the database.", + "getConfigError": "An error occurred while retrieving the config file." + }, + "inbounds": { + "allTimeTraffic": "All-time Traffic", + "allTimeTrafficUsage": "All Time Total Usage", + "title": "Inbounds", + "totalDownUp": "Total Sent/Received", + "totalUsage": "Total Usage", + "inboundCount": "Total Inbounds", + "operate": "Menu", + "enable": "Enabled", + "remark": "Remark", + "node": "Node", + "deployTo": "Deploy to", + "localPanel": "Local panel", + "protocol": "Protocol", + "port": "Port", + "portMap": "Port Mapping", + "traffic": "Traffic", + "details": "Details", + "transportConfig": "Transport", + "expireDate": "Duration", + "createdAt": "Created", + "updatedAt": "Updated", + "resetTraffic": "Reset Traffic", + "addInbound": "Add Inbound", + "generalActions": "General Actions", + "modifyInbound": "Modify Inbound", + "deleteInbound": "Delete Inbound", + "deleteInboundContent": "Are you sure you want to delete inbound?", + "deleteClient": "Delete Client", + "deleteClientContent": "Are you sure you want to delete client?", + "resetTrafficContent": "Are you sure you want to reset traffic?", + "copyLink": "Copy URL", + "address": "Address", + "network": "Network", + "destinationPort": "Destination Port", + "targetAddress": "Target Address", + "monitorDesc": "Leave blank to listen on all IPs", + "meansNoLimit": "= Unlimited. (unit: GB)", + "totalFlow": "Total Flow", + "leaveBlankToNeverExpire": "Leave blank to never expire", + "noRecommendKeepDefault": "It is recommended to keep the default", + "certificatePath": "File Path", + "certificateContent": "File Content", + "publicKey": "Public Key", + "privatekey": "Private Key", + "clickOnQRcode": "Click on QR Code to Copy", + "client": "Client", + "export": "Export All URLs", + "clone": "Clone", + "cloneInbound": "Clone", + "cloneInboundContent": "All settings of this inbound, except Port, Listening IP, and Clients, will be applied to the clone.", + "cloneInboundOk": "Clone", + "resetAllTraffic": "Reset All Inbounds Traffic", + "resetAllTrafficTitle": "Reset All Inbounds Traffic", + "resetAllTrafficContent": "Are you sure you want to reset the traffic of all inbounds?", + "resetInboundClientTraffics": "Reset Clients Traffic", + "resetInboundClientTrafficTitle": "Reset Clients Traffic", + "resetInboundClientTrafficContent": "Are you sure you want to reset the traffic of this inbound's clients?", + "resetAllClientTraffics": "Reset All Clients Traffic", + "resetAllClientTrafficTitle": "Reset All Clients Traffic", + "resetAllClientTrafficContent": "Are you sure you want to reset the traffic of all clients?", + "delDepletedClients": "Delete Depleted Clients", + "delDepletedClientsTitle": "Delete Depleted Clients", + "delDepletedClientsContent": "Are you sure you want to delete all the depleted clients?", + "email": "Email", + "emailDesc": "Please provide a unique email address.", + "IPLimit": "IP Limit", + "IPLimitDesc": "Disables inbound if the count exceeds the set value. (0 = disable)", + "IPLimitlog": "IP Log", + "IPLimitlogDesc": "The IPs history log. (to enable inbound after disabling, clear the log)", + "IPLimitlogclear": "Clear The Log", + "setDefaultCert": "Set Cert from Panel", + "telegramDesc": "Please provide Telegram Chat ID. (use '/id' command in the bot) or ({'@'}userinfobot)", + "subscriptionDesc": "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients.", + "info": "Info", + "same": "Same", + "inboundData": "Inbound's Data", + "exportInbound": "Export Inbound", + "import": "Import", + "importInbound": "Import an Inbound", + "periodicTrafficResetTitle": "Traffic Reset", + "periodicTrafficResetDesc": "Automatically reset traffic counter at specified intervals", + "lastReset": "Last Reset", + "periodicTrafficReset": { + "never": "Never", + "daily": "Daily", + "weekly": "Weekly", + "monthly": "Monthly", + "hourly": "Hourly" + }, + "toasts": { + "obtain": "Obtain", + "updateSuccess": "The update was successful.", + "logCleanSuccess": "The log has been cleared.", + "inboundsUpdateSuccess": "Inbounds have been successfully updated.", + "inboundUpdateSuccess": "Inbound has been successfully updated.", + "inboundCreateSuccess": "Inbound has been successfully created.", + "inboundDeleteSuccess": "Inbound has been successfully deleted.", + "inboundClientAddSuccess": "Inbound client(s) have been added.", + "inboundClientDeleteSuccess": "Inbound client has been deleted.", + "inboundClientUpdateSuccess": "Inbound client has been updated.", + "delDepletedClientsSuccess": "All depleted clients are deleted.", + "resetAllClientTrafficSuccess": "All traffic from the client has been reset.", + "resetAllTrafficSuccess": "All traffic has been reset.", + "resetInboundClientTrafficSuccess": "Traffic has been reset.", + "trafficGetError": "Error getting traffics.", + "getNewX25519CertError": "Error while obtaining the X25519 certificate.", + "getNewmldsa65Error": "Error while obtaining mldsa65.", + "getNewVlessEncError": "Error while obtaining VlessEnc." + }, + "stream": { + "general": { + "request": "Request", + "response": "Response", + "name": "Name", + "value": "Value" + }, + "tcp": { + "version": "Version", + "method": "Method", + "path": "Path", + "status": "Status", + "statusDescription": "Status Desc", + "requestHeader": "Request Header", + "responseHeader": "Response Header" + } + } + }, + "client": { + "add": "Add Client", + "edit": "Edit Client", + "submitAdd": "Add Client", + "submitEdit": "Save Changes", + "clientCount": "Number of Clients", + "bulk": "Add Bulk", + "copyFromInbound": "Copy Clients from Inbound", + "copyToInbound": "Copy clients to", + "copySelected": "Copy Selected", + "copySource": "Source", + "copyEmailPreview": "Resulting email preview", + "copySelectSourceFirst": "Please select a source inbound first.", + "copyResult": "Copy result", + "copyResultSuccess": "Copied successfully", + "copyResultNone": "Nothing to copy: no clients selected or source is empty", + "copyResultErrors": "Copy errors", + "copyFlowLabel": "Flow for new clients (VLESS)", + "copyFlowHint": "Applied to all copied clients. Leave empty to skip.", + "selectAll": "Select all", + "clearAll": "Clear all", + "method": "Method", + "first": "First", + "last": "Last", + "prefix": "Prefix", + "postfix": "Postfix", + "delayedStart": "Start After First Use", + "expireDays": "Duration", + "days": "Day(s)", + "renew": "Auto Renew", + "renewDesc": "Auto-renewal after expiration. (0 = disable)(unit: day)" + }, + "nodes": { + "title": "Nodes", + "addNode": "Add Node", + "editNode": "Edit Node", + "totalNodes": "Total Nodes", + "onlineNodes": "Online", + "offlineNodes": "Offline", + "avgLatency": "Avg Latency", + "name": "Name", + "namePlaceholder": "e.g. de-frankfurt-1", + "addressPlaceholder": "panel.example.com or 1.2.3.4", + "remark": "Remark", + "scheme": "Scheme", + "address": "Address", + "port": "Port", + "basePath": "Base Path", + "apiToken": "API Token", + "apiTokenPlaceholder": "Token from the remote panel's Settings page", + "apiTokenHint": "The remote panel exposes its API token under Settings → API Token.", + "regenerate": "Regenerate Token", + "regenerateConfirm": "Regenerating invalidates the current token. Any central panel using it will lose access until updated. Continue?", + "enable": "Enabled", + "status": "Status", + "cpu": "CPU", + "mem": "Memory", + "uptime": "Uptime", + "latency": "Latency", + "lastHeartbeat": "Last Heartbeat", + "xrayVersion": "Xray Version", + "actions": "Actions", + "probe": "Probe Now", + "testConnection": "Test Connection", + "connectionOk": "Connection OK ({ms} ms)", + "connectionFailed": "Connection failed", + "never": "never", + "justNow": "just now", + "deleteConfirmTitle": "Delete node \"{name}\"?", + "deleteConfirmContent": "This stops monitoring the node. The remote panel itself is unaffected.", + "statusValues": { + "online": "Online", + "offline": "Offline", + "unknown": "Unknown" + }, + "toasts": { + "list": "Failed to load nodes", + "obtain": "Failed to load node", + "add": "Add node", + "update": "Update node", + "delete": "Delete node", + "deleted": "Node deleted", + "test": "Test connection", + "fillRequired": "Name, address, port and API token are required", + "probeFailed": "Probe failed" + } + }, + "settings": { + "title": "Panel Settings", + "save": "Save", + "infoDesc": "Every change made here needs to be saved. Please restart the panel to apply changes.", + "restartPanel": "Restart Panel", + "restartPanelDesc": "Are you sure you want to restart the panel? If you cannot access the panel after restarting, please view the panel log info on the server.", + "restartPanelSuccess": "The panel was successfully restarted.", + "actions": "Actions", + "resetDefaultConfig": "Reset to Default", + "panelSettings": "General", + "securitySettings": "Authentication", + "TGBotSettings": "Telegram Bot", + "panelListeningIP": "Listen IP", + "panelListeningIPDesc": "The IP address for the web panel. (leave blank to listen on all IPs)", + "panelListeningDomain": "Listen Domain", + "panelListeningDomainDesc": "The domain name for the web panel. (leave blank to listen on all domains and IPs)", + "panelPort": "Listen Port", + "panelPortDesc": "The port number for the web panel. (must be an unused port)", + "publicKeyPath": "Public Key Path", + "publicKeyPathDesc": "The public key file path for the web panel. (begins with ‘/‘)", + "privateKeyPath": "Private Key Path", + "privateKeyPathDesc": "The private key file path for the web panel. (begins with ‘/‘)", + "panelUrlPath": "URI Path", + "panelUrlPathDesc": "The URI path for the web panel. (begins with ‘/‘ and concludes with ‘/‘)", + "pageSize": "Pagination Size", + "pageSizeDesc": "Define page size for inbounds table. (0 = disable)", + "remarkModel": "Remark Model & Separation Character", + "datepicker": "Calendar Type", + "datepickerPlaceholder": "Select date", + "datepickerDescription": "Scheduled tasks will run based on this calendar.", + "sampleRemark": "Sample Remark", + "oldUsername": "Current Username", + "currentPassword": "Current Password", + "newUsername": "New Username", + "newPassword": "New Password", + "telegramBotEnable": "Enable Telegram Bot", + "telegramBotEnableDesc": "Enables the Telegram bot.", + "telegramToken": "Telegram Token", + "telegramTokenDesc": "The Telegram bot token obtained from '{'@'}BotFather'.", + "telegramProxy": "SOCKS Proxy", + "telegramProxyDesc": "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "The Telegram API server to use. Leave blank to use the default server.", + "telegramChatId": "Admin Chat ID", + "telegramChatIdDesc": "The Telegram Admin Chat ID(s). (comma-separated)(get it here {'@'}userinfobot) or (use '/id' command in the bot)", + "telegramNotifyTime": "Notification Time", + "telegramNotifyTimeDesc": "The Telegram bot notification time set for periodic reports. (use the crontab time format)", + "tgNotifyBackup": "Database Backup", + "tgNotifyBackupDesc": "Send a database backup file with a report.", + "tgNotifyLogin": "Login Notification", + "tgNotifyLoginDesc": "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel.", + "sessionMaxAge": "Session Duration", + "sessionMaxAgeDesc": "The duration for which you can stay logged in. (unit: minute)", + "expireTimeDiff": "Expiration Date Notification", + "expireTimeDiffDesc": "Get notified about expiration date when reaching this threshold. (unit: day)", + "trafficDiff": "Traffic Cap Notification", + "trafficDiffDesc": "Get notified about traffic cap when reaching this threshold. (unit: GB)", + "tgNotifyCpu": "CPU Load Notification", + "tgNotifyCpuDesc": "Get notified if CPU load exceeds this threshold. (unit: %)", + "timeZone": "Time Zone", + "timeZoneDesc": "Scheduled tasks will run based on this time zone.", + "subSettings": "Subscription", + "subEnable": "Subscription Service", + "subEnableDesc": "Enable/Disable the subscription service.", + "subJsonEnable": "Enable/Disable the JSON subscription endpoint independently.", + "subTitle": "Subscription Title", + "subTitleDesc": "Title shown in VPN client", + "subSupportUrl": "Support URL", + "subSupportUrlDesc": "Technical support link shown in the VPN client", + "subProfileUrl": "Profile URL", + "subProfileUrlDesc": "A link to your website displayed in the VPN client", + "subAnnounce": "Announce", + "subAnnounceDesc": "The text of the announce displayed in the VPN client", + "subEnableRouting": "Enable routing", + "subEnableRoutingDesc": "Global setting to enable routing in the VPN client. (Only for Happ)", + "subRoutingRules": "Routing rules", + "subRoutingRulesDesc": "Global routing rules for the VPN client. (Only for Happ)", + "subListen": "Listen IP", + "subListenDesc": "The IP address for the subscription service. (leave blank to listen on all IPs)", + "subPort": "Listen Port", + "subPortDesc": "The port number for the subscription service. (must be an unused port)", + "subCertPath": "Public Key Path", + "subCertPathDesc": "The public key file path for the subscription service. (begins with ‘/‘)", + "subKeyPath": "Private Key Path", + "subKeyPathDesc": "The private key file path for the subscription service. (begins with ‘/‘)", + "subPath": "URI Path", + "subPathDesc": "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)", + "subDomain": "Listen Domain", + "subDomainDesc": "The domain name for the subscription service. (leave blank to listen on all domains and IPs)", + "subUpdates": "Update Intervals", + "subUpdatesDesc": "The update intervals of the subscription URL in the client apps. (unit: hour)", + "subEncrypt": "Encode", + "subEncryptDesc": "The returned content of subscription service will be Base64 encoded.", + "subShowInfo": "Show Usage Info", + "subShowInfoDesc": "The remaining traffic and date will be displayed in the client apps.", + "subURI": "Reverse Proxy URI", + "subURIDesc": "The URI path of the subscription URL for use behind proxies.", + "externalTrafficInformEnable": "External Traffic Inform", + "externalTrafficInformEnableDesc": "Inform external API on every traffic update.", + "externalTrafficInformURI": "External Traffic Inform URI", + "externalTrafficInformURIDesc": "Traffic updates are sent to this URI.", + "restartXrayOnClientDisable": "Restart Xray After Auto Disable", + "restartXrayOnClientDisableDesc": "When a client is automatically disabled due to expiration or traffic limit, restart Xray.", + "fragment": "Fragmentation", + "fragmentDesc": "Enable fragmentation for TLS hello packet.", + "fragmentSett": "Fragmentation Settings", + "noisesDesc": "Enable Noises.", + "noisesSett": "Noises Settings", + "mux": "Mux", + "muxDesc": "Transmit multiple independent data streams within an established data stream.", + "muxSett": "Mux Settings", + "direct": "Direct Connection", + "directDesc": "Directly establishes connections with domains or IP ranges of a specific country.", + "notifications": "Notifications", + "certs": "Certificaties", + "externalTraffic": "External Traffic", + "dateAndTime": "Date and Time", + "proxyAndServer": "Proxy and Server", + "intervals": "Intervals", + "information": "Information", + "language": "Language", + "telegramBotLanguage": "Telegram Bot Language", + "security": { + "admin": "Admin credentials", + "twoFactor": "Two-factor authentication", + "twoFactorEnable": "Enable 2FA", + "twoFactorEnableDesc": "Adds an additional layer of authentication to provide more security.", + "twoFactorModalSetTitle": "Enable two-factor authentication", + "twoFactorModalDeleteTitle": "Disable two-factor authentication", + "twoFactorModalSteps": "To set up two-factor authentication, perform a few steps:", + "twoFactorModalFirstStep": "1. Scan this QR code in the app for authentication or copy the token near the QR code and paste it into the app", + "twoFactorModalSecondStep": "2. Enter the code from the app", + "twoFactorModalRemoveStep": "Enter the code from the application to remove two-factor authentication.", + "twoFactorModalChangeCredentialsTitle": "Change credentials", + "twoFactorModalChangeCredentialsStep": "Enter the code from the application to change administrator credentials.", + "twoFactorModalSetSuccess": "Two-factor authentication has been successfully established", + "twoFactorModalDeleteSuccess": "Two-factor authentication has been successfully deleted", + "twoFactorModalError": "Wrong code" + }, + "toasts": { + "modifySettings": "The parameters have been changed.", + "getSettings": "An error occurred while retrieving parameters.", + "modifyUserError": "An error occurred while changing administrator credentials.", + "modifyUser": "You have successfully changed the credentials of the administrator.", + "originalUserPassIncorrect": "The сurrent username or password is invalid", + "userPassMustBeNotEmpty": "The new username and password is empty", + "getOutboundTrafficError": "Error getting traffics", + "resetOutboundTrafficError": "Error in reset outbound traffics" + } + }, + "xray": { + "title": "Xray Configs", + "save": "Save", + "restart": "Restart Xray", + "restartSuccess": "Xray has been successfully relaunched.", + "stopSuccess": "Xray has been successfully stopped.", + "restartError": "There was an error when rebooting the Xray.", + "stopError": "There was an error when stopping the Xray.", + "basicTemplate": "Basics", + "advancedTemplate": "Advanced", + "generalConfigs": "General", + "generalConfigsDesc": "These options will determine general adjustments.", + "logConfigs": "Log", + "logConfigsDesc": "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs", + "blockConfigsDesc": "These options will block traffic based on specific requested protocols and websites.", + "basicRouting": "Basic Routing", + "blockConnectionsConfigsDesc": "These options will block traffic based on the specific requested country.", + "directConnectionsConfigsDesc": "A direct connection ensures that specific traffic is not routed through another server.", + "blockips": "Block IPs", + "blockdomains": "Block Domains", + "directips": "Direct IPs", + "directdomains": "Direct Domains", + "ipv4Routing": "IPv4 Routing", + "ipv4RoutingDesc": "These options will route traffic based on a specific destination via IPv4.", + "warpRouting": "WARP Routing", + "warpRoutingDesc": "These options will route traffic based on a specific destination via WARP.", + "nordRouting": "NordVPN Routing", + "nordRoutingDesc": "These options will route traffic based on a specific destination via NordVPN.", + "Template": "Advanced Xray Configuration Template", + "TemplateDesc": "The final Xray config file will be generated based on this template.", + "FreedomStrategy": "Freedom Protocol Strategy", + "FreedomStrategyDesc": "Set the output strategy for the network in the Freedom Protocol.", + "RoutingStrategy": "Overall Routing Strategy", + "RoutingStrategyDesc": "Set the overall traffic routing strategy for resolving all requests.", + "outboundTestUrl": "Outbound Test URL", + "outboundTestUrlDesc": "URL used when testing outbound connectivity.", + "Torrent": "Block BitTorrent Protocol", + "Inbounds": "Inbounds", + "InboundsDesc": "Accepting the specific clients.", + "Outbounds": "Outbounds", + "Balancers": "Balancers", + "OutboundsDesc": "Set the outgoing traffic pathway.", + "Routings": "Routing Rules", + "RoutingsDesc": "The priority of each rule is important!", + "completeTemplate": "All", + "logLevel": "Log Level", + "logLevelDesc": "The log level for error logs, indicating the information that needs to be recorded.", + "accessLog": "Access Log", + "accessLogDesc": "The file path for the access log. The special value 'none' disabled access logs", + "errorLog": "Error Log", + "errorLogDesc": "The file path for the error log. The special value 'none' disabled error logs", + "dnsLog": "DNS Log", + "dnsLogDesc": "Whether to enable DNS query logs", + "maskAddress": "Mask Address", + "maskAddressDesc": "IP address mask, when enabled, will automatically replace the IP address that appears in the log.", + "statistics": "Statistics", + "statsInboundUplink": "Inbound Upload Statistics", + "statsInboundUplinkDesc": "Enables the statistics collection for upstream traffic of all inbound proxies.", + "statsInboundDownlink": "Inbound Download Statistics", + "statsInboundDownlinkDesc": "Enables the statistics collection for downstream traffic of all inbound proxies.", + "statsOutboundUplink": "Outbound Upload Statistics", + "statsOutboundUplinkDesc": "Enables the statistics collection for upstream traffic of all outbound proxies.", + "statsOutboundDownlink": "Outbound Download Statistics", + "statsOutboundDownlinkDesc": "Enables the statistics collection for downstream traffic of all outbound proxies.", + "rules": { + "first": "First", + "last": "Last", + "up": "Up", + "down": "Down", + "source": "Source", + "dest": "Destination", + "inbound": "Inbound", + "outbound": "Outbound", + "balancer": "Balancer", + "info": "Info", + "add": "Add Rule", + "edit": "Edit Rule", + "useComma": "Comma-separated items" + }, + "outbound": { + "addOutbound": "Add Outbound", + "addReverse": "Add Reverse", + "editOutbound": "Edit Outbound", + "editReverse": "Edit Reverse", + "reverseTag": "Reverse Tag", + "reverseTagDesc": "VLESS simple reverse proxy tag. Leave empty to disable.", + "reverseTagPlaceholder": "reverse tag (leave empty to disable)", + "tag": "Tag", + "tagDesc": "Unique Tag", + "address": "Address", + "reverse": "Reverse", + "domain": "Domain", + "type": "Type", + "bridge": "Bridge", + "portal": "Portal", + "link": "Link", + "intercon": "Interconnection", + "settings": "Settings", + "accountInfo": "Account Information", + "outboundStatus": "Outbound Status", + "sendThrough": "Send Through", + "test": "Test", + "testResult": "Test Result", + "testing": "Testing connection...", + "testSuccess": "Test successful", + "testFailed": "Test failed", + "testError": "Failed to test outbound", + "nordvpn": "NordVPN", + "accessToken": "Access Token", + "country": "Country", + "server": "Server", + "city": "City", + "allCities": "All Cities", + "privateKey": "Private Key", + "load": "Load" + }, + "balancer": { + "addBalancer": "Add Balancer", + "editBalancer": "Edit Balancer", + "balancerStrategy": "Strategy", + "balancerSelectors": "Selectors", + "tag": "Tag", + "tagDesc": "Unique Tag", + "balancerDesc": "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work." + }, + "wireguard": { + "secretKey": "Secret Key", + "publicKey": "Public Key", + "allowedIPs": "Allowed IPs", + "endpoint": "Endpoint", + "psk": "PreShared Key", + "domainStrategy": "Domain Strategy" + }, + "tun": { + "nameDesc": "The name of the TUN interface. Default is 'xray0'", + "mtuDesc": "Maximum Transmission Unit. The maximum size of data packets. Default is 1500", + "userLevel": "User Level", + "userLevelDesc": "All connections made through this inbound will use this user level. Default is 0" + }, + "dns": { + "enable": "Enable DNS", + "enableDesc": "Enable built-in DNS server", + "tag": "DNS Inbound Tag", + "tagDesc": "This tag will be available as an Inbound tag in routing rules.", + "clientIp": "Client IP", + "clientIpDesc": "Used to notify the server of the specified IP location during DNS queries", + "disableCache": "Disable cache", + "disableCacheDesc": "Disables DNS caching", + "disableFallback": "Disable Fallback", + "disableFallbackDesc": "Disables fallback DNS queries", + "disableFallbackIfMatch": "Disable Fallback If Match", + "disableFallbackIfMatchDesc": "Disables fallback DNS queries when the matching domain list of the DNS server is hit", + "enableParallelQuery": "Enable Parallel Query", + "enableParallelQueryDesc": "Enable parallel DNS queries to multiple servers for faster resolution", + "strategy": "Query Strategy", + "strategyDesc": "Overall strategy to resolve domain names", + "add": "Add Server", + "edit": "Edit Server", + "domains": "Domains", + "expectIPs": "Expect IPs", + "unexpectIPs": "Unexpect IPs", + "useSystemHosts": "Use System Hosts", + "useSystemHostsDesc": "Use the hosts file from an installed system", + "usePreset": "Use Preset", + "dnsPresetTitle": "DNS Presets", + "dnsPresetFamily": "Family" + }, + "fakedns": { + "add": "Add Fake DNS", + "edit": "Edit Fake DNS", + "ipPool": "IP Pool Subnet", + "poolSize": "Pool Size" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Custom keyboard closed!", + "noResult": "❗ No result!", + "noQuery": "❌ Query not found! Please use the command again!", + "wentWrong": "❌ Something went wrong!", + "noIpRecord": "❗ No IP Record!", + "noInbounds": "❗ No inbound found!", + "unlimited": "♾ Unlimited(Reset)", + "add": "Add", + "month": "Month", + "months": "Months", + "day": "Day", + "days": "Days", + "hours": "Hours", + "minutes": "Minutes", + "unknown": "Unknown", + "inbounds": "Inbounds", + "clients": "Clients", + "offline": "🔴 Offline", + "online": "🟢 Online", + "commands": { + "unknown": "❗ Unknown command.", + "pleaseChoose": "👇 Please choose:\r\n", + "help": "🤖 Welcome to this bot! It's designed to offer specific data from the web panel and allows you to make modifications as needed.\r\n\r\n", + "start": "👋 Hello {{ .Firstname }}.\r\n", + "welcome": "🤖 Welcome to {{ .Hostname }} management bot.\r\n", + "status": "✅ Bot is OK!", + "usage": "❗ Please provide a text to search!", + "getID": "🆔 Your ID: {{ .ID }}", + "helpAdminCommands": "To restart Xray Core:\r\n/restart\r\n\r\nTo search for a client email:\r\n/usage [Email]\r\n\r\nTo search for inbounds (with client stats):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id", + "helpClientCommands": "To search for statistics, use the following command:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Operation successful!", + "restartFailed": "❗ Error in operation.\r\n\r\nError: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core is not running.", + "startDesc": "Show the main menu", + "helpDesc": "Bot help", + "statusDesc": "Check bot status", + "idDesc": "Show your Telegram ID" + }, + "messages": { + "cpuThreshold": "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%", + "selectUserFailed": "❌ Error in user selection!", + "userSaved": "✅ Telegram User saved.", + "loginSuccess": "✅ Logged in to the panel successfully.\r\n", + "loginFailed": "❗️Login attempt to the panel failed.\r\n", + "2faFailed": "2FA Failed", + "report": "🕰 Scheduled Reports: {{ .RunTime }}\r\n", + "datetime": "⏰ Date&Time: {{ .DateTime }}\r\n", + "hostname": "💻 Host: {{ .Hostname }}\r\n", + "version": "🚀 3X-UI Version: {{ .Version }}\r\n", + "xrayVersion": "📡 Xray Version: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IPs:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Status: {{ .State }}\r\n", + "username": "👤 Username: {{ .Username }}\r\n", + "reason": "❗️ Reason: {{ .Reason }}\r\n", + "time": "⏰ Time: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Port: {{ .Port }}\r\n", + "expire": "📅 Expire Date: {{ .Time }}\r\n", + "expireIn": "📅 Expire In: {{ .Time }}\r\n", + "active": "💡 Active: {{ .Enable }}\r\n", + "enabled": "🚨 Enabled: {{ .Enable }}\r\n", + "online": "🌐 Connection status: {{ .Status }}\r\n", + "lastOnline": "🔙 Last online: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Upload: ↑{{ .Upload }}\r\n", + "download": "🔽 Download: ↓{{ .Download }}\r\n", + "total": "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Telegram User: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Exhausted {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Exhausted {{ .Type }} count:\r\n", + "onlinesCount": "🌐 Online Clients: {{ .Count }}\r\n", + "disabled": "🛑 Disabled: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Backup Time: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n", + "yes": "✅ Yes", + "no": "❌ No", + "received_id": "🔑📥 ID updated.", + "received_password": "🔑📥 Password updated.", + "received_email": "📧📥 Email updated.", + "received_comment": "💬📥 Comment updated.", + "id_prompt": "🔑 Default ID: {{ .ClientId }}\n\nEnter your id.", + "pass_prompt": "🔑 Default Password: {{ .ClientPassword }}\n\nEnter your password.", + "email_prompt": "📧 Default Email: {{ .ClientEmail }}\n\nEnter your email.", + "comment_prompt": "💬 Default Comment: {{ .ClientComment }}\n\nEnter your Comment.", + "inbound_client_data_id": "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!", + "inbound_client_data_pass": "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!", + "cancel": "❌ Process Canceled! \n\nYou can /start again anytime. 🔄", + "error_add_client": "⚠️ Error:\n\n {{ .error }}", + "using_default_value": "Okay, I'll stick with the default value. 😊", + "incorrect_input": "Your input is not valid.\nThe phrases should be continuous without spaces.\nCorrect example: aaaaaa\nIncorrect example: aaa aaa 🚫", + "AreYouSure": "Are you sure? 🤔", + "SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Result: ✅ Success", + "FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Traffic reset process finished for all clients." + }, + "buttons": { + "closeKeyboard": "❌ Close Keyboard", + "cancel": "❌ Cancel", + "cancelReset": "❌ Cancel Reset", + "cancelIpLimit": "❌ Cancel IP Limit", + "confirmResetTraffic": "✅ Confirm Reset Traffic?", + "confirmClearIps": "✅ Confirm Clear IPs?", + "confirmRemoveTGUser": "✅ Confirm Remove Telegram User?", + "confirmToggle": "✅ Confirm Enable/Disable User?", + "dbBackup": "Get DB Backup", + "serverUsage": "Server Usage", + "getInbounds": "Get Inbounds", + "depleteSoon": "Deplete Soon", + "clientUsage": "Get Usage", + "onlines": "Online Clients", + "commands": "Commands", + "refresh": "🔄 Refresh", + "clearIPs": "❌ Clear IPs", + "removeTGUser": "❌ Remove Telegram User", + "selectTGUser": "👤 Select Telegram User", + "selectOneTGUser": "👤 Select a Telegram User:", + "resetTraffic": "📈 Reset Traffic", + "resetExpire": "📅 Change Expiry Date", + "ipLog": "🔢 IP Log", + "ipLimit": "🔢 IP Limit", + "setTGUser": "👤 Set Telegram User", + "toggle": "🔘 Enable / Disable", + "custom": "🔢 Custom", + "confirmNumber": "✅ Confirm: {{ .Num }}", + "confirmNumberAdd": "✅ Confirm adding: {{ .Num }}", + "limitTraffic": "🚧 Traffic Limit", + "getBanLogs": "Get Ban Logs", + "allClients": "All Clients", + "addClient": "Add Client", + "submitDisable": "Submit As Disable ☑️", + "submitEnable": "Submit As Enable ✅", + "use_default": "🏷️ Use default", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Password", + "change_email": "⚙️📧 Email", + "change_comment": "⚙️💬 Comment", + "ResetAllTraffics": "Reset All Traffics", + "SortedTrafficUsageReport": "Sorted Traffic Usage Report" + }, + "answers": { + "successfulOperation": "✅ Operation successful!", + "errorOperation": "❗ Error in operation.", + "getInboundsFailed": "❌ Failed to get inbounds.", + "getClientsFailed": "❌ Failed to get clients.", + "canceled": "❌ {{ .Email }}: Operation canceled.", + "clientRefreshSuccess": "✅ {{ .Email }}: Client refreshed successfully.", + "IpRefreshSuccess": "✅ {{ .Email }}: IPs refreshed successfully.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Client's Telegram User refreshed successfully.", + "resetTrafficSuccess": "✅ {{ .Email }}: Traffic reset successfully.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Traffic limit saved successfully.", + "expireResetSuccess": "✅ {{ .Email }}: Expire days reset successfully.", + "resetIpSuccess": "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully.", + "clearIpSuccess": "✅ {{ .Email }}: IPs cleared successfully.", + "getIpLog": "✅ {{ .Email }}: Get IP Log.", + "getUserInfo": "✅ {{ .Email }}: Get Telegram User Info.", + "removedTGUserSuccess": "✅ {{ .Email }}: Telegram User removed successfully.", + "enableSuccess": "✅ {{ .Email }}: Enabled successfully.", + "disableSuccess": "✅ {{ .Email }}: Disabled successfully.", + "askToAddUserId": "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ChatID in your configuration(s).\r\n\r\nYour ChatID: {{ .TgUserID }}", + "chooseClient": "Choose a Client for Inbound {{ .Inbound }}", + "chooseInbound": "Choose an Inbound" + } + } +} diff --git a/web/translation/es-ES.json b/web/translation/es-ES.json new file mode 100644 index 00000000..22214bf1 --- /dev/null +++ b/web/translation/es-ES.json @@ -0,0 +1,941 @@ +{ + "username": "Nombre de Usuario", + "password": "Contraseña", + "login": "Acceder", + "confirm": "Confirmar", + "cancel": "Cancelar", + "close": "Cerrar", + "save": "Guardar", + "logout": "Cerrar Sesión", + "create": "Crear", + "update": "Actualizar", + "copy": "Copiar", + "copied": "Copiado", + "download": "Descargar", + "remark": "Notas", + "enable": "Habilitar", + "protocol": "Protocolo", + "search": "Buscar", + "filter": "Filtrar", + "loading": "Cargando...", + "second": "Segundo", + "minute": "Minuto", + "hour": "Hora", + "day": "Día", + "check": "Verificar", + "indefinite": "Indefinido", + "unlimited": "Ilimitado", + "none": "None", + "qrCode": "Código QR", + "info": "Más Información", + "edit": "Editar", + "delete": "Eliminar", + "reset": "Restablecer", + "noData": "Sin datos", + "copySuccess": "Copiado exitosamente", + "sure": "Seguro", + "encryption": "Encriptación", + "useIPv4ForHost": "Usar IPv4 para el host", + "transmission": "Transmisión", + "host": "Host", + "path": "Path", + "camouflage": "Camuflaje", + "status": "Estado", + "enabled": "Habilitado", + "disabled": "Deshabilitado", + "depleted": "Agotado", + "depletingSoon": "Agotándose", + "offline": "fuera de línea", + "online": "en línea", + "domainName": "Nombre de dominio", + "monitor": "Listening IP", + "certificate": "Certificado Digital", + "fail": "Falló", + "comment": "Comentario", + "success": "Éxito", + "lastOnline": "Última conexión", + "getVersion": "Obtener versión", + "install": "Instalar", + "clients": "Clientes", + "usage": "Uso", + "twoFactorCode": "Código", + "remained": "Restante", + "security": "Seguridad", + "secAlertTitle": "Alerta de Seguridad", + "secAlertSsl": "Esta conexión no es segura. Por favor, evite ingresar información sensible hasta que se active TLS para la protección de datos.", + "secAlertConf": "Ciertas configuraciones son vulnerables a ataques. Se recomienda reforzar los protocolos de seguridad para prevenir posibles violaciones.", + "secAlertSSL": "El panel carece de una conexión segura. Por favor, instale un certificado TLS para la protección de datos.", + "secAlertPanelPort": "El puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico.", + "secAlertPanelURI": "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja.", + "secAlertSubURI": "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja.", + "secAlertSubJsonURI": "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja.", + "emptyDnsDesc": "No hay servidores DNS añadidos.", + "emptyFakeDnsDesc": "No hay servidores Fake DNS añadidos.", + "emptyBalancersDesc": "No hay balanceadores añadidos.", + "emptyReverseDesc": "No hay proxies inversos añadidos.", + "somethingWentWrong": "Algo salió mal", + "subscription": { + "title": "Información de suscripción", + "subId": "ID de suscripción", + "status": "Estado", + "downloaded": "Descargado", + "uploaded": "Subido", + "expiry": "Caducidad", + "totalQuota": "Cuota total", + "individualLinks": "Enlaces individuales", + "active": "Activo", + "inactive": "Inactivo", + "unlimited": "Ilimitado", + "noExpiry": "Sin caducidad" + }, + "menu": { + "theme": "Tema", + "dark": "Oscuro", + "ultraDark": "Ultra Oscuro", + "dashboard": "Estado del Sistema", + "inbounds": "Entradas", + "settings": "Configuraciones", + "xray": "Ajustes Xray", + "logout": "Cerrar Sesión", + "link": "Gestionar" + }, + "pages": { + "login": { + "hello": "Hola", + "title": "Bienvenido", + "loginAgain": "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente.", + "toasts": { + "invalidFormData": "El formato de los datos de entrada es inválido.", + "emptyUsername": "Por favor ingresa el nombre de usuario.", + "emptyPassword": "Por favor ingresa la contraseña.", + "wrongUsernameOrPassword": "Nombre de usuario, contraseña o código de dos factores incorrecto.", + "successLogin": "Has iniciado sesión en tu cuenta correctamente." + } + }, + "index": { + "title": "Estado del Sistema", + "cpu": "CPU", + "logicalProcessors": "Procesadores lógicos", + "frequency": "Frecuencia", + "swap": "Memoria Virtual", + "storage": "Almacenamiento", + "memory": "RAM", + "threads": "Hilos", + "xrayStatus": "Xray", + "stopXray": "Detener", + "restartXray": "Reiniciar", + "xraySwitch": "Versión", + "xraySwitchClick": "Elige la versión a la que deseas cambiar.", + "xraySwitchClickDesk": "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales.", + "xrayUpdates": "Actualizaciones de Xray", + "updatePanel": "Actualizar panel", + "panelUpdateDesc": "Esto actualizará 3X-UI a la última versión y reiniciará el servicio del panel.", + "currentPanelVersion": "Versión actual del panel", + "latestPanelVersion": "Última versión del panel", + "panelUpToDate": "El panel está actualizado", + "upToDate": "Actualizado", + "xrayStatusUnknown": "Desconocido", + "xrayStatusRunning": "En ejecución", + "xrayStatusStop": "Detenido", + "xrayStatusError": "Error", + "xrayErrorPopoverTitle": "Se produjo un error al ejecutar Xray", + "operationHours": "Tiempo de Funcionamiento", + "systemHistoryTitle": "Historial del Sistema", + "trendLast2Min": "Últimos 2 minutos", + "systemLoad": "Carga del Sistema", + "systemLoadDesc": "promedio de carga del sistema en los últimos 1, 5 y 15 minutos", + "connectionCount": "Número de Conexiones", + "ipAddresses": "Direcciones IP", + "toggleIpVisibility": "Alternar visibilidad de la IP", + "overallSpeed": "Velocidad general", + "upload": "Subida", + "download": "Descarga", + "totalData": "Datos totales", + "sent": "Enviado", + "received": "Recibido", + "documentation": "Documentación", + "xraySwitchVersionDialog": "¿Realmente deseas cambiar la versión de Xray?", + "xraySwitchVersionDialogDesc": "Esto cambiará la versión de Xray a #version#.", + "xraySwitchVersionPopover": "Xray se actualizó correctamente", + "panelUpdateDialog": "¿Deseas actualizar el panel?", + "panelUpdateDialogDesc": "Esto actualizará 3X-UI a la versión #version# y reiniciará el servicio del panel.", + "panelUpdateCheckPopover": "Fallo al comprobar actualización del panel", + "panelUpdateStartedPopover": "Actualización del panel iniciada", + "geofileUpdateDialog": "¿Realmente deseas actualizar el geofichero?", + "geofileUpdateDialogDesc": "Esto actualizará el archivo #filename#.", + "geofilesUpdateDialogDesc": "Esto actualizará todos los archivos.", + "geofilesUpdateAll": "Actualizar todo", + "geofileUpdatePopover": "Geofichero actualizado correctamente", + "dontRefresh": "La instalación está en progreso, por favor no actualices esta página.", + "logs": "Registros", + "config": "Configuración", + "backup": "Сopia de Seguridad", + "backupTitle": "Copia & Restauración", + "exportDatabase": "Copia de seguridad", + "exportDatabaseDesc": "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo.", + "importDatabase": "Restaurar", + "importDatabaseDesc": "Haz clic para seleccionar y cargar un archivo .db desde tu dispositivo para restaurar tu base de datos desde una copia de seguridad.", + "importDatabaseSuccess": "La base de datos se ha importado correctamente", + "importDatabaseError": "Ocurrió un error al importar la base de datos", + "readDatabaseError": "Ocurrió un error al leer la base de datos", + "getDatabaseError": "Ocurrió un error al obtener la base de datos", + "getConfigError": "Ocurrió un error al obtener el archivo de configuración", + "customGeoTitle": "GeoSite / GeoIP personalizados", + "customGeoAdd": "Añadir", + "customGeoType": "Tipo", + "customGeoAlias": "Alias", + "customGeoUrl": "URL", + "customGeoEnabled": "Activado", + "customGeoLastUpdated": "Última actualización", + "customGeoExtColumn": "Enrutamiento (ext:…)", + "customGeoToastUpdateAll": "Todas las fuentes personalizadas se actualizaron", + "customGeoActions": "Acciones", + "customGeoEdit": "Editar", + "customGeoDelete": "Eliminar", + "customGeoDownload": "Actualizar ahora", + "customGeoModalAdd": "Añadir geo personalizado", + "customGeoModalEdit": "Editar geo personalizado", + "customGeoModalSave": "Guardar", + "customGeoDeleteConfirm": "¿Eliminar esta fuente geo personalizada?", + "customGeoRoutingHint": "En reglas de enrutamiento use la columna de valor como ext:archivo.dat:etiqueta (sustituya la etiqueta).", + "customGeoInvalidId": "Id de recurso no válido", + "customGeoAliasesError": "No se pudieron cargar los alias geo personalizados", + "customGeoValidationAlias": "El alias solo puede contener letras minúsculas, dígitos, - y _", + "customGeoValidationUrl": "La URL debe comenzar con http:// o https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (personalizado)", + "customGeoToastList": "Lista de geo personalizado", + "customGeoToastAdd": "Añadir geo personalizado", + "customGeoToastUpdate": "Actualizar geo personalizado", + "customGeoToastDelete": "Geofile personalizado «{{ .fileName }}» eliminado", + "customGeoToastDownload": "Geofile «{{ .fileName }}» actualizado", + "customGeoErrInvalidType": "El tipo debe ser geosite o geoip", + "customGeoErrAliasRequired": "El alias es obligatorio", + "customGeoErrAliasPattern": "El alias contiene caracteres no permitidos", + "customGeoErrAliasReserved": "Este alias está reservado", + "customGeoErrUrlRequired": "La URL es obligatoria", + "customGeoErrInvalidUrl": "La URL no es válida", + "customGeoErrUrlScheme": "La URL debe usar http o https", + "customGeoErrUrlHost": "El host de la URL no es válido", + "customGeoErrDuplicateAlias": "Este alias ya se usa para este tipo", + "customGeoErrNotFound": "Fuente geo personalizada no encontrada", + "customGeoErrDownload": "Error de descarga", + "customGeoErrUpdateAllIncomplete": "No se pudieron actualizar una o más fuentes geo personalizadas", + "customGeoEmpty": "Aún no hay fuentes geo personalizadas — haz clic en Añadir para crear una" + }, + "inbounds": { + "allTimeTraffic": "Tráfico Total", + "allTimeTrafficUsage": "Uso de datos histórico", + "title": "Entradas", + "totalDownUp": "Subidas/Descargas Totales", + "totalUsage": "Uso Total", + "inboundCount": "Número de Entradas", + "operate": "Menú", + "enable": "Habilitar", + "remark": "Notas", + "node": "Nodo", + "deployTo": "Desplegar en", + "localPanel": "Panel local", + "protocol": "Protocolo", + "port": "Puerto", + "portMap": "Puertos de Destino", + "traffic": "Tráfico", + "details": "Detalles", + "transportConfig": "Transporte", + "expireDate": "Fecha de Expiración", + "createdAt": "Creado", + "updatedAt": "Actualizado", + "resetTraffic": "Restablecer Tráfico", + "addInbound": "Agregar Entrada", + "generalActions": "Acciones Generales", + "modifyInbound": "Modificar Entrada", + "deleteInbound": "Eliminar Entrada", + "deleteInboundContent": "¿Confirmar eliminación de entrada?", + "deleteClient": "Eliminar cliente", + "deleteClientContent": "¿Está seguro de que desea eliminar el cliente?", + "resetTrafficContent": "¿Confirmar restablecimiento de tráfico?", + "copyLink": "Copiar Enlace", + "address": "Dirección", + "network": "Red", + "destinationPort": "Puerto de Destino", + "targetAddress": "Dirección de Destino", + "monitorDesc": "Dejar en blanco por defecto", + "meansNoLimit": " = illimitata. (unidad: GB)", + "totalFlow": "Flujo Total", + "leaveBlankToNeverExpire": "Dejar en Blanco para Nunca Expirar", + "noRecommendKeepDefault": "No hay requisitos especiales para mantener la configuración predeterminada", + "certificatePath": "Ruta Cert", + "certificateContent": "Datos Cert", + "publicKey": "Clave Pública", + "privatekey": "Clave Privada", + "clickOnQRcode": "Haz clic en el Código QR para Copiar", + "client": "Cliente", + "export": "Exportar Enlaces", + "clone": "Clonar", + "cloneInbound": "Clonar Entradas", + "cloneInboundContent": "Se aplicarán todas las configuraciones de esta entrada, excepto el Puerto, la IP de Escucha y los Clientes, al clon.", + "cloneInboundOk": "Clonar", + "resetAllTraffic": "Restablecer Tráfico de Todas las Entradas", + "resetAllTrafficTitle": "Restablecer tráfico de todas las entradas", + "resetAllTrafficContent": "¿Estás seguro de que deseas restablecer el tráfico de todas las entradas?", + "resetInboundClientTraffics": "Restablecer Tráfico de Clientes", + "resetInboundClientTrafficTitle": "Restablecer todo el tráfico de clientes", + "resetInboundClientTrafficContent": "¿Estás seguro de que deseas restablecer todo el tráfico para los clientes de esta entrada?", + "resetAllClientTraffics": "Restablecer Tráfico de Todos los Clientes", + "resetAllClientTrafficTitle": "Restablecer todo el tráfico de clientes", + "resetAllClientTrafficContent": "¿Estás seguro de que deseas restablecer todo el tráfico para todos los clientes?", + "delDepletedClients": "Eliminar Clientes Agotados", + "delDepletedClientsTitle": "Eliminar clientes agotados", + "delDepletedClientsContent": "¿Estás seguro de que deseas eliminar todos los clientes agotados?", + "email": "Email", + "emailDesc": "Por favor proporciona una dirección de correo electrónico única.", + "IPLimit": "Límite de IP", + "IPLimitDesc": "Desactiva la entrada si la cantidad supera el valor ingresado (ingresa 0 para desactivar el límite de IP).", + "IPLimitlog": "Registro de IP", + "IPLimitlogDesc": "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro).", + "IPLimitlogclear": "Limpiar el Registro", + "setDefaultCert": "Establecer certificado desde el panel", + "telegramDesc": "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o ({'@'}userinfobot)", + "subscriptionDesc": "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones.", + "info": "Info", + "same": "misma", + "inboundData": "Datos de entrada", + "exportInbound": "Exportación entrante", + "import": "Importar", + "importInbound": "Importar un entrante", + "periodicTrafficResetTitle": "Reset de Tráfico", + "periodicTrafficResetDesc": "Reiniciar automáticamente el contador de tráfico en intervalos especificados", + "lastReset": "Último reinicio", + "periodicTrafficReset": { + "never": "Nunca", + "daily": "Diariamente", + "weekly": "Semanalmente", + "monthly": "Mensualmente", + "hourly": "Cada hora" + }, + "toasts": { + "obtain": "Recibir", + "updateSuccess": "La actualización fue exitosa", + "logCleanSuccess": "El registro ha sido limpiado", + "inboundsUpdateSuccess": "Entradas actualizadas correctamente", + "inboundUpdateSuccess": "Entrada actualizada correctamente", + "inboundCreateSuccess": "Entrada creada correctamente", + "inboundDeleteSuccess": "Entrada eliminada correctamente", + "inboundClientAddSuccess": "Cliente(s) de entrada añadido(s)", + "inboundClientDeleteSuccess": "Cliente de entrada eliminado", + "inboundClientUpdateSuccess": "Cliente de entrada actualizado", + "delDepletedClientsSuccess": "Todos los clientes con tráfico agotado fueron eliminados", + "resetAllClientTrafficSuccess": "Todo el tráfico del cliente ha sido reiniciado", + "resetAllTrafficSuccess": "Todo el tráfico ha sido reiniciado", + "resetInboundClientTrafficSuccess": "El tráfico ha sido reiniciado", + "trafficGetError": "Error al obtener los tráficos", + "getNewX25519CertError": "Error al obtener el certificado X25519.", + "getNewmldsa65Error": "Error al obtener el certificado mldsa65.", + "getNewVlessEncError": "Error al obtener el certificado VlessEnc." + }, + "stream": { + "general": { + "request": "Pedido", + "response": "Respuesta", + "name": "Nombre", + "value": "Valor" + }, + "tcp": { + "version": "Versión", + "method": "Método", + "path": "Camino", + "status": "Estado", + "statusDescription": "Descripción de la Situación", + "requestHeader": "Encabezado de solicitud", + "responseHeader": "Encabezado de respuesta" + } + } + }, + "client": { + "add": "Agregar Cliente", + "edit": "Editar Cliente", + "submitAdd": "Agregar Cliente", + "submitEdit": "Guardar Cambios", + "clientCount": "Número de Clientes", + "bulk": "Agregar en Lote", + "copyFromInbound": "Copiar clientes desde entrada", + "copyToInbound": "Copiar clientes a", + "copySelected": "Copiar seleccionados", + "copySource": "Origen", + "copyEmailPreview": "Vista previa del email resultante", + "copySelectSourceFirst": "Seleccione primero una entrada de origen.", + "copyResult": "Resultado de la copia", + "copyResultSuccess": "Copiado correctamente", + "copyResultNone": "Nada que copiar: ningún cliente seleccionado o el origen está vacío", + "copyResultErrors": "Errores al copiar", + "copyFlowLabel": "Flow para nuevos clientes (VLESS)", + "copyFlowHint": "Se aplica a todos los clientes copiados. Déjelo vacío para omitir.", + "selectAll": "Seleccionar todo", + "clearAll": "Limpiar todo", + "method": "Método", + "first": "Primero", + "last": "Último", + "prefix": "Prefijo", + "postfix": "Sufijo", + "delayedStart": "Iniciar después del primer uso", + "expireDays": "Duración", + "days": "Día(s)", + "renew": "Renovación automática", + "renewDesc": "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)" + }, + "nodes": { + "title": "Nodos", + "addNode": "Agregar nodo", + "editNode": "Editar nodo", + "totalNodes": "Total de nodos", + "onlineNodes": "En línea", + "offlineNodes": "Desconectado", + "avgLatency": "Latencia media", + "name": "Nombre", + "namePlaceholder": "p. ej. de-frankfurt-1", + "addressPlaceholder": "panel.example.com o 1.2.3.4", + "remark": "Notas", + "scheme": "Esquema", + "address": "Dirección", + "port": "Puerto", + "basePath": "Ruta base", + "apiToken": "Token de API", + "apiTokenPlaceholder": "Token desde la página de Configuración del panel remoto", + "apiTokenHint": "El panel remoto expone su token de API en Configuración → Token de API.", + "regenerate": "Regenerar token", + "regenerateConfirm": "Regenerar invalida el token actual. Cualquier panel central que lo use perderá el acceso hasta que se actualice. ¿Continuar?", + "enable": "Habilitado", + "status": "Estado", + "cpu": "CPU", + "mem": "Memoria", + "uptime": "Tiempo activo", + "latency": "Latencia", + "lastHeartbeat": "Último latido", + "xrayVersion": "Versión de Xray", + "actions": "Acciones", + "probe": "Sondear ahora", + "testConnection": "Probar conexión", + "connectionOk": "Conexión correcta ({ms} ms)", + "connectionFailed": "Conexión fallida", + "never": "nunca", + "justNow": "ahora mismo", + "deleteConfirmTitle": "¿Eliminar el nodo \"{name}\"?", + "deleteConfirmContent": "Esto detiene la monitorización del nodo. El panel remoto en sí no se ve afectado.", + "statusValues": { + "online": "En línea", + "offline": "Desconectado", + "unknown": "Desconocido" + }, + "toasts": { + "list": "Error al cargar los nodos", + "obtain": "Error al cargar el nodo", + "add": "Agregar nodo", + "update": "Actualizar nodo", + "delete": "Eliminar nodo", + "deleted": "Nodo eliminado", + "test": "Probar conexión", + "fillRequired": "El nombre, la dirección, el puerto y el token de API son obligatorios", + "probeFailed": "Sondeo fallido" + } + }, + "settings": { + "title": "Configuraciones", + "save": "Guardar", + "infoDesc": "Cada cambio realizado aquí debe ser guardado. Por favor, reinicie el panel para aplicar los cambios.", + "restartPanel": "Reiniciar Panel", + "restartPanelDesc": "¿Está seguro de que desea reiniciar el panel? Haga clic en Aceptar para reiniciar después de 3 segundos. Si no puede acceder al panel después de reiniciar, por favor, consulte la información de registro del panel en el servidor.", + "restartPanelSuccess": "El panel se reinició correctamente", + "actions": "Acciones", + "resetDefaultConfig": "Restablecer a Configuración Predeterminada", + "panelSettings": "Configuraciones del Panel", + "securitySettings": "Configuraciones de Seguridad", + "TGBotSettings": "Configuraciones de Bot de Telegram", + "panelListeningIP": "IP de Escucha del Panel", + "panelListeningIPDesc": "Dejar en blanco por defecto para monitorear todas las IPs.", + "panelListeningDomain": "Dominio de Escucha del Panel", + "panelListeningDomainDesc": "Dejar en blanco por defecto para monitorear todos los dominios e IPs.", + "panelPort": "Puerto del Panel", + "panelPortDesc": "El puerto utilizado para mostrar este panel.", + "publicKeyPath": "Ruta del Archivo de Clave Pública del Certificado del Panel", + "publicKeyPathDesc": "Complete con una ruta absoluta que comience con.", + "privateKeyPath": "Ruta del Archivo de Clave Privada del Certificado del Panel", + "privateKeyPathDesc": "Complete con una ruta absoluta que comience con.", + "panelUrlPath": "Ruta Raíz de la URL del Panel", + "panelUrlPathDesc": "Debe empezar con '/' y terminar con.", + "pageSize": "Tamaño de paginación", + "pageSizeDesc": "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar", + "remarkModel": "Modelo de observación y carácter de separación", + "datepicker": "selector de fechas", + "datepickerPlaceholder": "Seleccionar fecha", + "datepickerDescription": "El tipo de calendario selector especifica la fecha de vencimiento", + "sampleRemark": "Observación de muestra", + "oldUsername": "Nombre de Usuario Actual", + "currentPassword": "Contraseña Actual", + "newUsername": "Nuevo Nombre de Usuario", + "newPassword": "Nueva Contraseña", + "telegramBotEnable": "Habilitar bot de Telegram", + "telegramBotEnableDesc": "Conéctese a las funciones de este panel a través del bot de Telegram.", + "telegramToken": "Token de Telegram", + "telegramTokenDesc": "Debe obtener el token del administrador de bots de Telegram {'@'}botfather.", + "telegramProxy": "Socks5 Proxy", + "telegramProxyDesc": "Si necesita el proxy Socks5 para conectarse a Telegram. Ajuste su configuración según la guía.", + "telegramAPIServer": "API Server de Telegram", + "telegramAPIServerDesc": "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado.", + "telegramChatId": "IDs de Chat de Telegram para Administradores", + "telegramChatIdDesc": "IDs de Chat múltiples separados por comas. Use {'@'}userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat.", + "telegramNotifyTime": "Hora de Notificación del Bot de Telegram", + "telegramNotifyTimeDesc": "Usar el formato de tiempo de Crontab.", + "tgNotifyBackup": "Respaldo de Base de Datos", + "tgNotifyBackupDesc": "Incluir archivo de respaldo de base de datos con notificación de informe.", + "tgNotifyLogin": "Notificación de Inicio de Sesión", + "tgNotifyLoginDesc": "Muestra el nombre de usuario, dirección IP y hora cuando alguien intenta iniciar sesión en su panel.", + "sessionMaxAge": "Edad Máxima de Sesión", + "sessionMaxAgeDesc": "La duración de una sesión de inicio de sesión (unidad: minutos).", + "expireTimeDiff": "Umbral de Expiración para Notificación", + "expireTimeDiffDesc": "Reciba notificaciones sobre la expiración de la cuenta antes del umbral (unidad: días).", + "trafficDiff": "Umbral de Tráfico para Notificación", + "trafficDiffDesc": "Reciba notificaciones sobre el agotamiento del tráfico antes de alcanzar el umbral (unidad: GB).", + "tgNotifyCpu": "Umbral de Alerta de Porcentaje de CPU", + "tgNotifyCpuDesc": "Reciba notificaciones si el uso de la CPU supera este umbral (unidad: %).", + "timeZone": "Zona Horaria", + "timeZoneDesc": "Las tareas programadas se ejecutan de acuerdo con la hora en esta zona horaria.", + "subSettings": "Suscripción", + "subEnable": "Habilitar Servicio", + "subEnableDesc": "Función de suscripción con configuración separada.", + "subJsonEnable": "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente.", + "subTitle": "Título de la Suscripción", + "subTitleDesc": "Título mostrado en el cliente VPN", + "subSupportUrl": "URL de soporte", + "subSupportUrlDesc": "Enlace de soporte técnico mostrado en el cliente VPN", + "subProfileUrl": "URL del perfil", + "subProfileUrlDesc": "Un enlace a tu sitio web mostrado en el cliente VPN", + "subAnnounce": "Anuncio", + "subAnnounceDesc": "El texto del anuncio mostrado en el cliente VPN", + "subEnableRouting": "Habilitar enrutamiento", + "subEnableRoutingDesc": "Configuración global para habilitar el enrutamiento en el cliente VPN. (Solo para Happ)", + "subRoutingRules": "Reglas de enrutamiento", + "subRoutingRulesDesc": "Reglas de enrutamiento globales para el cliente VPN. (Solo para Happ)", + "subListen": "Listening IP", + "subListenDesc": "Dejar en blanco por defecto para monitorear todas las IPs.", + "subPort": "Puerto de Suscripción", + "subPortDesc": "El número de puerto para el servicio de suscripción debe estar sin usar en el servidor.", + "subCertPath": "Ruta del Archivo de Clave Pública del Certificado de Suscripción", + "subCertPathDesc": "Complete con una ruta absoluta que comience con '/'", + "subKeyPath": "Ruta del Archivo de Clave Privada del Certificado de Suscripción", + "subKeyPathDesc": "Complete con una ruta absoluta que comience con '/'", + "subPath": "Ruta Raíz de la URL de Suscripción", + "subPathDesc": "Debe empezar con '/' y terminar con '/'", + "subDomain": "Dominio de Escucha", + "subDomainDesc": "Dejar en blanco por defecto para monitorear todos los dominios e IPs.", + "subUpdates": "Intervalos de Actualización de Suscripción", + "subUpdatesDesc": "Horas de intervalo entre actualizaciones en la aplicación del cliente.", + "subEncrypt": "Encriptar configuraciones", + "subEncryptDesc": "Encriptar las configuraciones devueltas en la suscripción.", + "subShowInfo": "Mostrar información de uso", + "subShowInfoDesc": "Mostrar tráfico restante y fecha después del nombre de configuración.", + "subURI": "URI de proxy inverso", + "externalTrafficInformEnable": "Informe de tráfico externo", + "externalTrafficInformEnableDesc": "Informar a la API externa sobre cada actualización de tráfico.", + "externalTrafficInformURI": "URI de información de tráfico externo", + "externalTrafficInformURIDesc": "Las actualizaciones de tráfico se envían a este URI.", + "restartXrayOnClientDisable": "Reiniciar Xray tras desactivación automática", + "restartXrayOnClientDisableDesc": "Cuando un cliente se desactive automáticamente por vencimiento o límite de tráfico, reiniciar Xray.", + "subURIDesc": "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy", + "fragment": "Fragmentación", + "fragmentDesc": "Habilitar la fragmentación para el paquete de saludo de TLS", + "fragmentSett": "Configuración de Fragmentación", + "noisesDesc": "Activar Sonidos", + "noisesSett": "Configuración de Sonidos", + "mux": "Mux", + "muxDesc": "Transmite múltiples flujos de datos independientes dentro de un flujo de datos establecido.", + "muxSett": "Configuración Mux", + "direct": "Conexión Directa", + "directDesc": "Establece conexiones directas con dominios o rangos de IP de un país específico.", + "notifications": "Notificaciones", + "certs": "Certificados", + "externalTraffic": "Tráfico Externo", + "dateAndTime": "Fecha y Hora", + "proxyAndServer": "Proxy y Servidor", + "intervals": "Intervalos", + "information": "Información", + "language": "Idioma", + "telegramBotLanguage": "Idioma del Bot de Telegram", + "security": { + "admin": "Credenciales de administrador", + "twoFactor": "Autenticación de dos factores", + "twoFactorEnable": "Habilitar 2FA", + "twoFactorEnableDesc": "Añade una capa adicional de autenticación para mayor seguridad.", + "twoFactorModalSetTitle": "Activar autenticación de dos factores", + "twoFactorModalDeleteTitle": "Desactivar autenticación de dos factores", + "twoFactorModalSteps": "Para configurar la autenticación de dos factores, sigue estos pasos:", + "twoFactorModalFirstStep": "1. Escanea este código QR en la aplicación de autenticación o copia el token cerca del código QR y pégalo en la aplicación", + "twoFactorModalSecondStep": "2. Ingresa el código de la aplicación", + "twoFactorModalRemoveStep": "Ingresa el código de la aplicación para eliminar la autenticación de dos factores.", + "twoFactorModalChangeCredentialsTitle": "Cambiar credenciales", + "twoFactorModalChangeCredentialsStep": "Ingrese el código de la aplicación para cambiar las credenciales del administrador.", + "twoFactorModalSetSuccess": "La autenticación de dos factores se ha establecido con éxito", + "twoFactorModalDeleteSuccess": "La autenticación de dos factores se ha eliminado con éxito", + "twoFactorModalError": "Código incorrecto" + }, + "toasts": { + "modifySettings": "Los parámetros han sido modificados.", + "getSettings": "Ocurrió un error al obtener los parámetros.", + "modifyUserError": "Ocurrió un error al cambiar las credenciales del administrador.", + "modifyUser": "Has cambiado exitosamente las credenciales del administrador.", + "originalUserPassIncorrect": "Nombre de usuario o contraseña original incorrectos", + "userPassMustBeNotEmpty": "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos", + "getOutboundTrafficError": "Error al obtener el tráfico saliente", + "resetOutboundTrafficError": "Error al reiniciar el tráfico saliente" + } + }, + "xray": { + "title": "Xray Configuración", + "save": "Guardar configuración", + "restart": "Reiniciar Xray", + "restartSuccess": "Xray se ha reiniciado correctamente", + "stopSuccess": "Xray se ha detenido correctamente", + "restartError": "Ocurrió un error al reiniciar Xray.", + "stopError": "Ocurrió un error al detener Xray.", + "basicTemplate": "Perfil Básico", + "advancedTemplate": "Perfil Avanzado", + "generalConfigs": "Configuraciones Generales", + "generalConfigsDesc": "Estas opciones proporcionarán ajustes generales.", + "logConfigs": "Registro", + "logConfigsDesc": "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlos sabiamente solo en caso de sus necesidades.", + "blockConfigsDesc": "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos.", + "basicRouting": "Enrutamiento Básico", + "blockConnectionsConfigsDesc": "Estas opciones bloquearán el tráfico según el país solicitado específico.", + "directConnectionsConfigsDesc": "Una conexión directa asegura que el tráfico específico no sea enrutado a través de otro servidor.", + "blockips": "Bloquear IPs", + "blockdomains": "Bloquear Dominios", + "directips": "IPs Directas", + "directdomains": "Dominios Directos", + "ipv4Routing": "Enrutamiento IPv4", + "ipv4RoutingDesc": "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4.", + "warpRouting": "Enrutamiento WARP", + "warpRoutingDesc": "Precaución: Antes de usar estas opciones, instale WARP en modo de proxy socks5 en su servidor siguiendo los pasos en el GitHub del panel. WARP enrutará el tráfico a los sitios web a través de los servidores de Cloudflare.", + "nordRouting": "Enrutamiento NordVPN", + "nordRoutingDesc": "Estas opciones enrutarán el tráfico basado en un destino específico a través de NordVPN.", + "Template": "Plantilla de Configuración de Xray", + "TemplateDesc": "Genera el archivo de configuración final de Xray basado en esta plantilla.", + "FreedomStrategy": "Configurar Estrategia para el Protocolo Freedom", + "FreedomStrategyDesc": "Establece la estrategia de salida de la red en el Protocolo Freedom.", + "RoutingStrategy": "Configurar Estrategia de Enrutamiento de Dominios", + "RoutingStrategyDesc": "Establece la estrategia general de enrutamiento para la resolución de DNS.", + "outboundTestUrl": "URL de prueba de outbound", + "outboundTestUrlDesc": "URL usada al probar la conectividad del outbound", + "Torrent": "Prohibir Uso de BitTorrent", + "Inbounds": "Entrante", + "InboundsDesc": "Cambia la plantilla de configuración para aceptar clientes específicos.", + "Outbounds": "Salidas", + "Balancers": "Equilibradores", + "OutboundsDesc": "Cambia la plantilla de configuración para definir formas de salida para este servidor.", + "Routings": "Reglas de enrutamiento", + "RoutingsDesc": "¡La prioridad de cada regla es importante!", + "completeTemplate": "Todos", + "logLevel": "Nivel de registro", + "logLevelDesc": "El nivel de registro para registros de errores, que indica la información que debe registrarse.", + "accessLog": "Registro de acceso", + "accessLogDesc": "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso", + "errorLog": "Registro de Errores", + "errorLogDesc": "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores.", + "dnsLog": "Registro DNS", + "dnsLogDesc": "Si habilitar los registros de consulta DNS", + "maskAddress": "Enmascarar Dirección", + "maskAddressDesc": "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro.", + "statistics": "Estadísticas", + "statsInboundUplink": "Estadísticas de Subida de Entrada", + "statsInboundUplinkDesc": "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de entrada.", + "statsInboundDownlink": "Estadísticas de Bajada de Entrada", + "statsInboundDownlinkDesc": "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de entrada.", + "statsOutboundUplink": "Estadísticas de Subida de Salida", + "statsOutboundUplinkDesc": "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de salida.", + "statsOutboundDownlink": "Estadísticas de Bajada de Salida", + "statsOutboundDownlinkDesc": "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de salida.", + "rules": { + "first": "Primero", + "last": "Último", + "up": "Arriba", + "down": "Abajo", + "source": "Fuente", + "dest": "Destino", + "inbound": "Entrante", + "outbound": "Saliente", + "balancer": "Equilibrador", + "info": "Información", + "add": "Agregar Regla", + "edit": "Editar Regla", + "useComma": "Elementos separados por comas" + }, + "outbound": { + "addOutbound": "Agregar salida", + "addReverse": "Agregar reverso", + "editOutbound": "Editar salida", + "editReverse": "Editar reverso", + "reverseTag": "Etiqueta Reverso", + "reverseTagDesc": "Etiqueta de salida del proxy inverso simple VLESS. Dejar vacío para deshabilitar. Cuando se establece, las conexiones de este cliente pueden usarse como túnel de proxy inverso.", + "reverseTagPlaceholder": "etiqueta de salida (vacío para deshabilitar)", + "tag": "Etiqueta", + "tagDesc": "etiqueta única", + "address": "Dirección", + "reverse": "Reverso", + "domain": "Dominio", + "type": "Tipo", + "bridge": "puente", + "portal": "portal", + "link": "Enlace", + "intercon": "Interconexión", + "settings": "Configuración", + "accountInfo": "Información de la Cuenta", + "outboundStatus": "Estado de Salida", + "sendThrough": "Enviar a través de", + "test": "Probar", + "testResult": "Resultado de la prueba", + "testing": "Probando conexión...", + "testSuccess": "Prueba exitosa", + "testFailed": "Prueba fallida", + "testError": "Error al probar la salida", + "nordvpn": "NordVPN", + "accessToken": "Token de acceso", + "country": "País", + "server": "Servidor", + "city": "Ciudad", + "allCities": "Todas las ciudades", + "privateKey": "Clave privada", + "load": "Carga" + }, + "balancer": { + "addBalancer": "Agregar equilibrador", + "editBalancer": "Editar balanceador", + "balancerStrategy": "Estrategia", + "balancerSelectors": "Selectores", + "tag": "Etiqueta", + "tagDesc": "etiqueta única", + "balancerDesc": "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag." + }, + "wireguard": { + "secretKey": "Llave secreta", + "publicKey": "Llave pública", + "allowedIPs": "IP permitidas", + "endpoint": "Punto final", + "psk": "Clave precompartida", + "domainStrategy": "Estrategia de dominio" + }, + "tun": { + "nameDesc": "El nombre de la interfaz TUN. El valor predeterminado es 'xray0'", + "mtuDesc": "Unidad Máxima de Transmisión. El tamaño máximo de los paquetes de datos. El valor predeterminado es 1500", + "userLevel": "Nivel de Usuario", + "userLevelDesc": "Todas las conexiones realizadas a través de este entrada utilizarán este nivel de usuario. El valor predeterminado es 0" + }, + "dns": { + "enable": "Habilitar DNS", + "enableDesc": "Habilitar servidor DNS incorporado", + "tag": "Etiqueta de Entrada DNS", + "tagDesc": "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento.", + "clientIp": "IP del cliente", + "clientIpDesc": "Se utiliza para notificar al servidor la ubicación IP especificada durante las consultas DNS", + "disableCache": "Desactivar caché", + "disableCacheDesc": "Desactiva el almacenamiento en caché de DNS", + "disableFallback": "Desactivar respaldo", + "disableFallbackDesc": "Desactiva las consultas DNS de respaldo", + "disableFallbackIfMatch": "Desactivar respaldo si coincide", + "disableFallbackIfMatchDesc": "Desactiva las consultas DNS de respaldo cuando se acierta en la lista de dominios coincidentes del servidor DNS", + "enableParallelQuery": "Habilitar consulta paralela", + "enableParallelQueryDesc": "Habilitar consultas DNS paralelas a múltiples servidores para una resolución más rápida", + "strategy": "Estrategia de Consulta", + "strategyDesc": "Estrategia general para resolver nombres de dominio", + "add": "Agregar Servidor", + "edit": "Editar Servidor", + "domains": "Dominios", + "expectIPs": "IPs esperadas", + "unexpectIPs": "IPs inesperadas", + "useSystemHosts": "Usar Hosts del sistema", + "useSystemHostsDesc": "Usar el archivo hosts de un sistema instalado", + "usePreset": "Usar plantilla", + "dnsPresetTitle": "Plantillas DNS", + "dnsPresetFamily": "Familiar" + }, + "fakedns": { + "add": "Agregar DNS Falso", + "edit": "Editar DNS Falso", + "ipPool": "Subred del grupo de IP", + "poolSize": "Tamaño del grupo" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Teclado cerrado!", + "noResult": "❗ ¡Sin resultados!", + "noQuery": "❌ ¡Consulta no encontrada! ¡Por favor, use el comando nuevamente!", + "wentWrong": "❌ ¡Algo salió mal!", + "noIpRecord": "❗ ¡No hay registro de IP!", + "noInbounds": "❗ ¡No se encontraron entradas!", + "unlimited": "♾ Ilimitado (Restablecer)", + "add": "Añadir", + "month": "Mes", + "months": "Meses", + "day": "Día", + "days": "Días", + "hours": "Horas", + "minutes": "Minutos", + "unknown": "Desconocido", + "inbounds": "Entradas", + "clients": "Clientes", + "offline": "🔴 Desconectado", + "online": "🟢 En línea", + "commands": { + "unknown": "❗ Comando desconocido", + "pleaseChoose": "👇 Por favor elige:\r\n", + "help": "🤖 ¡Bienvenido a este bot! Está diseñado para ofrecerte datos específicos del servidor y te permite hacer modificaciones según sea necesario.\r\n\r\n", + "start": "👋 Hola {{ .Firstname }}.\r\n", + "welcome": "🤖 Bienvenido al bot de gestión de {{ .Hostname }}.\r\n", + "status": "✅ ¡El bot está bien!", + "usage": "❗ ¡Por favor proporciona un texto para buscar!", + "getID": "🆔 Tu ID: {{ .ID }}", + "helpAdminCommands": "Para reiniciar Xray Core:\r\n/restart\r\n\r\nPara buscar un correo electrónico de cliente:\r\n/usage [Correo electrónico]\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n/inbound [Observación]\r\n\r\nID de Chat de Telegram:\r\n/id", + "helpClientCommands": "Para buscar estadísticas, utiliza el siguiente comando:\r\n/usage [Correo electrónico]\r\n\r\nID de Chat de Telegram:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ ¡Operación exitosa!", + "restartFailed": "❗ Error en la operación.\r\n\r\nError: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core no está en ejecución.", + "startDesc": "Mostrar el menú principal", + "helpDesc": "Ayuda del bot", + "statusDesc": "Comprobar el estado del bot", + "idDesc": "Mostrar tu ID de Telegram" + }, + "messages": { + "cpuThreshold": "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%", + "selectUserFailed": "❌ ¡Error al seleccionar usuario!", + "userSaved": "✅ Usuario de Telegram guardado.", + "loginSuccess": "✅ Has iniciado sesión en el panel con éxito.\r\n", + "loginFailed": "❗️ Falló el inicio de sesión en el panel.\r\n", + "2faFailed": "Error de 2FA", + "report": "🕰 Informes programados: {{ .RunTime }}\r\n", + "datetime": "⏰ Fecha y Hora: {{ .DateTime }}\r\n", + "hostname": "💻 Nombre del Host: {{ .Hostname }}\r\n", + "version": "🚀 Versión de X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 Versión de Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IPs:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 Conteo de TCP: {{ .Count }}\r\n", + "udpCount": "🔸 Conteo de UDP: {{ .Count }}\r\n", + "traffic": "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Estado de Xray: {{ .State }}\r\n", + "username": "👤 Nombre de usuario: {{ .Username }}\r\n", + "reason": "❗️ Motivo: {{ .Reason }}\r\n", + "time": "⏰ Hora: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Puerto: {{ .Port }}\r\n", + "expire": "📅 Fecha de Vencimiento: {{ .Time }}\r\n", + "expireIn": "📅 Vence en: {{ .Time }}\r\n", + "active": "💡 Activo: {{ .Enable }}\r\n", + "enabled": "🚨 Habilitado: {{ .Enable }}\r\n", + "online": "🌐 Estado de conexión: {{ .Status }}\r\n", + "lastOnline": "🔙 Última conexión: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Subida: ↑{{ .Upload }}\r\n", + "download": "🔽 Bajada: ↓{{ .Download }}\r\n", + "total": "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Usuario de Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Agotado {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Cantidad de Agotados {{ .Type }}:\r\n", + "onlinesCount": "🌐 Clientes en línea: {{ .Count }}\r\n", + "disabled": "🛑 Desactivado: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Se agotará pronto: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n", + "yes": "✅ Sí", + "no": "❌ No", + "received_id": "🔑📥 ID actualizado.", + "received_password": "🔑📥 Contraseña actualizada.", + "received_email": "📧📥 Correo electrónico actualizado.", + "received_comment": "💬📥 Comentario actualizado.", + "id_prompt": "🔑 ID predeterminado: {{ .ClientId }}\n\nIntroduce tu ID.", + "pass_prompt": "🔑 Contraseña predeterminada: {{ .ClientPassword }}\n\nIntroduce tu contraseña.", + "email_prompt": "📧 Correo electrónico predeterminado: {{ .ClientEmail }}\n\nIntroduce tu correo electrónico.", + "comment_prompt": "💬 Comentario predeterminado: {{ .ClientComment }}\n\nIntroduce tu comentario.", + "inbound_client_data_id": "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!", + "inbound_client_data_pass": "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Contraseña: {{ .ClientPass }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!", + "cancel": "❌ ¡Proceso cancelado! \n\nPuedes /start de nuevo en cualquier momento. 🔄", + "error_add_client": "⚠️ Error:\n\n {{ .error }}", + "using_default_value": "Está bien, me quedaré con el valor predeterminado. 😊", + "incorrect_input": "Tu entrada no es válida.\nLas frases deben ser continuas sin espacios.\nEjemplo correcto: aaaaaa\nEjemplo incorrecto: aaa aaa 🚫", + "AreYouSure": "¿Estás seguro? 🤔", + "SuccessResetTraffic": "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ✅ Éxito", + "FailedResetTraffic": "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ❌ Fallido \n\n🛠️ Error: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Proceso de reinicio de tráfico finalizado para todos los clientes." + }, + "buttons": { + "closeKeyboard": "❌ Cerrar Teclado", + "cancel": "❌ Cancelar", + "cancelReset": "❌ Cancelar Reinicio", + "cancelIpLimit": "❌ Cancelar Límite de IP", + "confirmResetTraffic": "✅ ¿Confirmar Reinicio de Tráfico?", + "confirmClearIps": "✅ ¿Confirmar Limpiar IPs?", + "confirmRemoveTGUser": "✅ ¿Confirmar Eliminar Usuario de Telegram?", + "confirmToggle": "✅ ¿Confirmar habilitar/deshabilitar usuario?", + "dbBackup": "Obtener Copia de Seguridad de BD", + "serverUsage": "Uso del Servidor", + "getInbounds": "Obtener Entradas", + "depleteSoon": "Pronto se Agotará", + "clientUsage": "Obtener Uso", + "onlines": "Clientes en línea", + "commands": "Comandos", + "refresh": "🔄 Actualizar", + "clearIPs": "❌ Limpiar IPs", + "removeTGUser": "❌ Eliminar Usuario de Telegram", + "selectTGUser": "👤 Seleccionar Usuario de Telegram", + "selectOneTGUser": "👤 Selecciona un usuario de telegram:", + "resetTraffic": "📈 Reiniciar Tráfico", + "resetExpire": "📅 Cambiar fecha de Vencimiento", + "ipLog": "🔢 Registro de IP", + "ipLimit": "🔢 Límite de IP", + "setTGUser": "👤 Establecer Usuario de Telegram", + "toggle": "🔘 Habilitar / Deshabilitar", + "custom": "🔢 Costumbre", + "confirmNumber": "✅ Confirmar: {{ .Num }}", + "confirmNumberAdd": "✅ Confirmar agregando: {{ .Num }}", + "limitTraffic": "🚧 Límite de tráfico", + "getBanLogs": "Registros de prohibición", + "allClients": "Todos los Clientes", + "addClient": "Añadir cliente", + "submitDisable": "Enviar como deshabilitado ☑️", + "submitEnable": "Enviar como habilitado ✅", + "use_default": "🏷️ Usar por defecto", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Contraseña", + "change_email": "⚙️📧 Correo electrónico", + "change_comment": "⚙️💬 Comentario", + "ResetAllTraffics": "Reiniciar todo el tráfico", + "SortedTrafficUsageReport": "Informe de uso de tráfico ordenado" + }, + "answers": { + "successfulOperation": "✅ ¡Exitosa!", + "errorOperation": "❗ Error en la Operación.", + "getInboundsFailed": "❌ Error al obtener las entradas", + "getClientsFailed": "❌ No se pudo obtener los clientes.", + "canceled": "❌ {{ .Email }} : Operación cancelada.", + "clientRefreshSuccess": "✅ {{ .Email }} : Cliente actualizado exitosamente.", + "IpRefreshSuccess": "✅ {{ .Email }} : IPs actualizadas exitosamente.", + "TGIdRefreshSuccess": "✅ {{ .Email }} : Usuario de Telegram del cliente actualizado exitosamente.", + "resetTrafficSuccess": "✅ {{ .Email }} : Tráfico reiniciado exitosamente.", + "setTrafficLimitSuccess": "✅ {{ .Email }} : Límite de Tráfico guardado exitosamente.", + "expireResetSuccess": "✅ {{ .Email }} : Días de vencimiento reiniciados exitosamente.", + "resetIpSuccess": "✅ {{ .Email }} : Límite de IP {{ .Count }} guardado exitosamente.", + "clearIpSuccess": "✅ {{ .Email }} : IPs limpiadas exitosamente.", + "getIpLog": "✅ {{ .Email }} : Obtener Registro de IP.", + "getUserInfo": "✅ {{ .Email }} : Obtener Información de Usuario de Telegram.", + "removedTGUserSuccess": "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente.", + "enableSuccess": "✅ {{ .Email }} : Habilitado exitosamente.", + "disableSuccess": "✅ {{ .Email }} : Deshabilitado exitosamente.", + "askToAddUserId": "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: {{ .TgUserID }}", + "chooseClient": "Elige un Cliente para Inbound {{ .Inbound }}", + "chooseInbound": "Elige un Inbound" + } + } +} diff --git a/web/translation/fa-IR.json b/web/translation/fa-IR.json new file mode 100644 index 00000000..e622a7a3 --- /dev/null +++ b/web/translation/fa-IR.json @@ -0,0 +1,942 @@ +{ + "username": "نام‌کاربری", + "password": "رمزعبور", + "login": "ورود", + "confirm": "تایید", + "cancel": "انصراف", + "close": "بستن", + "save": "ذخیره", + "logout": "خروج", + "create": "ایجاد", + "update": "به‌روزرسانی", + "copy": "کپی", + "copied": "کپی شد", + "download": "دانلود", + "remark": "نام", + "enable": "فعال", + "protocol": "پروتکل", + "search": "جستجو", + "filter": "فیلتر", + "loading": "...در حال بارگذاری", + "second": "ثانیه", + "minute": "دقیقه", + "hour": "ساعت", + "day": "روز", + "check": "چک کردن", + "indefinite": "نامحدود", + "unlimited": "نامحدود", + "none": "هیچ", + "qrCode": "QRکد", + "info": "اطلاعات بیشتر", + "edit": "ویرایش", + "delete": "حذف", + "reset": "ریست", + "noData": "داده‌ای وجود ندارد.", + "copySuccess": "باموفقیت کپی‌شد", + "sure": "مطمئن", + "encryption": "رمزگذاری", + "useIPv4ForHost": "از IPv4 برای میزبان استفاده کنید", + "transmission": "راه‌اتصال", + "host": "آدرس", + "path": "مسیر", + "camouflage": "مبهم‌سازی", + "status": "وضعیت", + "enabled": "فعال", + "disabled": "غیرفعال", + "depleted": "منقضی", + "depletingSoon": "در‌حال‌انقضا", + "offline": "آفلاین", + "online": "آنلاین", + "domainName": "آدرس دامنه", + "monitor": "آی‌پی اتصال", + "certificate": "گواهی دیجیتال", + "fail": "ناموفق", + "comment": "توضیحات", + "success": "موفق", + "lastOnline": "آخرین فعالیت", + "getVersion": "دریافت نسخه", + "install": "نصب", + "clients": "کاربران", + "usage": "استفاده", + "twoFactorCode": "کد", + "remained": "باقی‌مانده", + "security": "امنیت", + "secAlertTitle": "هشدار‌امنیتی", + "secAlertSsl": "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید", + "secAlertConf": "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه می‌شود پروتکل‌های امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید", + "secAlertSSL": "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تی‌ال‌اس برای محافظت از داده‌ها نصب کنید", + "secAlertPanelPort": "استفاده از پورت پیش‌فرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید", + "secAlertPanelURI": "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید", + "secAlertSubURI": "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید", + "secAlertSubJsonURI": "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید", + "emptyDnsDesc": "هیچ سرور DNS اضافه نشده است.", + "emptyFakeDnsDesc": "هیچ سرور Fake DNS اضافه نشده است.", + "emptyBalancersDesc": "هیچ بالانسر اضافه نشده است.", + "emptyReverseDesc": "هیچ پروکسی معکوس اضافه نشده است.", + "somethingWentWrong": "مشکلی پیش آمد", + "subscription": { + "title": "اطلاعات سابسکریپشن", + "subId": "شناسه اشتراک", + "status": "وضعیت", + "downloaded": "دانلود", + "uploaded": "آپلود", + "expiry": "تاریخ پایان", + "totalQuota": "حجم کلی", + "individualLinks": "لینک‌های تکی", + "active": "فعال", + "inactive": "غیرفعال", + "unlimited": "نامحدود", + "noExpiry": "بدون انقضا" + }, + "menu": { + "theme": "تم", + "dark": "تیره", + "ultraDark": "فوق تیره", + "dashboard": "نمای کلی", + "inbounds": "ورودی‌ها", + "nodes": "نودها", + "settings": "تنظیمات پنل", + "xray": "پیکربندی ایکس‌ری", + "logout": "خروج", + "link": "مدیریت" + }, + "pages": { + "login": { + "hello": "سلام", + "title": "خوش‌آمدید", + "loginAgain": "مدت زمان استفاده به‌اتمام‌رسیده، لطفا دوباره وارد شوید", + "toasts": { + "invalidFormData": "اطلاعات به‌درستی وارد نشده‌است", + "emptyUsername": "لطفا یک نام‌کاربری وارد کنید‌", + "emptyPassword": "لطفا یک رمزعبور وارد کنید", + "wrongUsernameOrPassword": "نام کاربری، رمز عبور یا کد دو مرحله‌ای نامعتبر است.", + "successLogin": "شما با موفقیت به حساب کاربری خود وارد شدید." + } + }, + "index": { + "title": "نمای کلی", + "cpu": "پردازنده", + "logicalProcessors": "پردازنده‌های منطقی", + "frequency": "فرکانس", + "swap": "سواپ", + "storage": "ذخیره‌سازی", + "memory": "حافظه رم", + "threads": "رشته‌ها", + "xrayStatus": "ایکس‌ری", + "stopXray": "توقف", + "restartXray": "شروع‌مجدد", + "xraySwitch": "‌نسخه", + "xraySwitchClick": "نسخه مورد نظر را انتخاب کنید", + "xraySwitchClickDesk": "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمی‌تر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد", + "xrayUpdates": "به‌روزرسانی‌های Xray", + "updatePanel": "به‌روزرسانی پنل", + "panelUpdateDesc": "این عملیات 3X-UI را به آخرین نسخه به‌روزرسانی می‌کند و سرویس پنل را مجدداً راه‌اندازی می‌کند.", + "currentPanelVersion": "نسخه فعلی پنل", + "latestPanelVersion": "آخرین نسخه پنل", + "panelUpToDate": "پنل به‌روز است", + "upToDate": "به‌روز", + "xrayStatusUnknown": "ناشناخته", + "xrayStatusRunning": "در حال اجرا", + "xrayStatusStop": "متوقف", + "xrayStatusError": "خطا", + "xrayErrorPopoverTitle": "خطا در هنگام اجرای Xray رخ داد", + "operationHours": "مدت‌کارکرد", + "systemHistoryTitle": "تاریخچه سیستم", + "trendLast2Min": "۲ دقیقه اخیر", + "systemLoad": "بارسیستم", + "systemLoadDesc": "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته", + "connectionCount": "تعداد کانکشن ها", + "ipAddresses": "آدرس‌های IP", + "toggleIpVisibility": "تغییر وضعیت نمایش IP", + "overallSpeed": "سرعت کلی", + "upload": "آپلود", + "download": "دانلود", + "totalData": "داده‌های کل", + "sent": "ارسال شده", + "received": "دریافت شده", + "documentation": "مستندات", + "xraySwitchVersionDialog": "آیا واقعاً می‌خواهید نسخه Xray را تغییر دهید؟", + "xraySwitchVersionDialogDesc": "این کار نسخه Xray را به #version# تغییر می‌دهد.", + "xraySwitchVersionPopover": "Xray با موفقیت به‌روز شد", + "panelUpdateDialog": "آیا مطمئن هستید که می‌خواهید پنل را به‌روزرسانی کنید؟", + "panelUpdateDialogDesc": "این 3X-UI را به نسخه #version# به‌روزرسانی کرده و سرویس پنل را مجدداً راه‌اندازی می‌کند.", + "panelUpdateCheckPopover": "خطا در بررسی به‌روزرسانی پنل", + "panelUpdateStartedPopover": "به‌روزرسانی پنل آغاز شد", + "geofileUpdateDialog": "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟", + "geofileUpdateDialogDesc": "این عمل فایل #filename# را به‌روز می‌کند.", + "geofilesUpdateDialogDesc": "با این کار همه فایل‌ها به‌روزرسانی می‌شوند.", + "geofilesUpdateAll": "همه را به‌روزرسانی کنید", + "geofileUpdatePopover": "فایل جغرافیایی با موفقیت به‌روز شد", + "dontRefresh": "در حال نصب، لطفا صفحه را رفرش نکنید", + "logs": "گزارش‌ها", + "config": "پیکربندی", + "backup": "پشتیبان‌گیری", + "backupTitle": "پشتیبان‌گیری و بازیابی", + "exportDatabase": "پشتیبان‌گیری", + "exportDatabaseDesc": "برای دانلود یک فایل .db حاوی پشتیبان از پایگاه داده فعلی خود به دستگاهتان کلیک کنید.", + "importDatabase": "بازیابی", + "importDatabaseDesc": "برای انتخاب و آپلود یک فایل .db از دستگاهتان و بازیابی پایگاه داده از یک پشتیبان کلیک کنید.", + "importDatabaseSuccess": "پایگاه داده با موفقیت وارد شد", + "importDatabaseError": "خطا در وارد کردن پایگاه داده", + "readDatabaseError": "خطا در خواندن پایگاه داده", + "getDatabaseError": "خطا در دریافت پایگاه داده", + "getConfigError": "خطا در دریافت فایل پیکربندی", + "customGeoTitle": "GeoSite / GeoIP سفارشی", + "customGeoAdd": "افزودن", + "customGeoType": "نوع", + "customGeoAlias": "نام مستعار", + "customGeoUrl": "URL", + "customGeoEnabled": "فعال", + "customGeoLastUpdated": "آخرین به‌روزرسانی", + "customGeoExtColumn": "مسیریابی (ext:…)", + "customGeoToastUpdateAll": "همه منابع سفارشی به‌روزرسانی شدند", + "customGeoActions": "اقدامات", + "customGeoEdit": "ویرایش", + "customGeoDelete": "حذف", + "customGeoDownload": "به‌روزرسانی اکنون", + "customGeoModalAdd": "افزودن geo سفارشی", + "customGeoModalEdit": "ویرایش geo سفارشی", + "customGeoModalSave": "ذخیره", + "customGeoDeleteConfirm": "این منبع geo سفارشی حذف شود؟", + "customGeoRoutingHint": "در قوانین مسیریابی مقدار را به صورت ext:file.dat:tag استفاده کنید (tag را جایگزین کنید).", + "customGeoInvalidId": "شناسه منبع نامعتبر است", + "customGeoAliasesError": "بارگذاری نام مستعارهای geo سفارشی ناموفق بود", + "customGeoValidationAlias": "نام مستعار فقط حروف کوچک، اعداد، - و _", + "customGeoValidationUrl": "URL باید با http:// یا https:// شروع شود", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (سفارشی)", + "customGeoToastList": "فهرست geo سفارشی", + "customGeoToastAdd": "افزودن geo سفارشی", + "customGeoToastUpdate": "به‌روزرسانی geo سفارشی", + "customGeoToastDelete": "geofile سفارشی «{{ .fileName }}» حذف شد", + "customGeoToastDownload": "geofile «{{ .fileName }}» به‌روزرسانی شد", + "customGeoErrInvalidType": "نوع باید geosite یا geoip باشد", + "customGeoErrAliasRequired": "نام مستعار لازم است", + "customGeoErrAliasPattern": "نام مستعار دارای نویسه نامجاز است", + "customGeoErrAliasReserved": "این نام مستعار رزرو است", + "customGeoErrUrlRequired": "URL لازم است", + "customGeoErrInvalidUrl": "URL نامعتبر است", + "customGeoErrUrlScheme": "URL باید http یا https باشد", + "customGeoErrUrlHost": "میزبان URL نامعتبر است", + "customGeoErrDuplicateAlias": "این نام مستعار برای این نوع قبلاً استفاده شده است", + "customGeoErrNotFound": "منبع geo سفارشی یافت نشد", + "customGeoErrDownload": "بارگیری ناموفق بود", + "customGeoErrUpdateAllIncomplete": "به‌روزرسانی یک یا چند منبع geo سفارشی ناموفق بود", + "customGeoEmpty": "هنوز منبع geo سفارشی‌ای ثبت نشده — برای ایجاد روی «افزودن» کلیک کنید" + }, + "inbounds": { + "node": "نود", + "deployTo": "استقرار روی", + "localPanel": "پنل لوکال", + "allTimeTraffic": "کل ترافیک", + "allTimeTrafficUsage": "کل استفاده در تمام مدت", + "title": "کاربران", + "totalDownUp": "دریافت/ارسال کل", + "totalUsage": "‌‌‌مصرف کل", + "inboundCount": "کل ورودی‌ها", + "operate": "عملیات", + "enable": "فعال", + "remark": "نام", + "protocol": "پروتکل", + "port": "پورت", + "portMap": "پورت‌های نظیر", + "traffic": "ترافیک", + "details": "توضیحات", + "transportConfig": "نحوه اتصال", + "expireDate": "مدت زمان", + "createdAt": "ایجاد", + "updatedAt": "به‌روزرسانی", + "resetTraffic": "ریست ترافیک", + "addInbound": "افزودن ورودی", + "generalActions": "عملیات کلی", + "modifyInbound": "ویرایش ورودی", + "deleteInbound": "حذف ورودی", + "deleteInboundContent": "آیا مطمئن به حذف ورودی هستید؟", + "deleteClient": "حذف کاربر", + "deleteClientContent": "آیا مطمئن به حذف کاربر هستید؟", + "resetTrafficContent": "آیا مطمئن به ریست ترافیک هستید؟", + "copyLink": "کپی لینک", + "address": "آدرس", + "network": "شبکه", + "destinationPort": "پورت مقصد", + "targetAddress": "آدرس مقصد", + "monitorDesc": "به‌طور پیش‌فرض خالی‌بگذارید", + "meansNoLimit": "0 = واحد: گیگابایت) نامحدود)", + "totalFlow": "ترافیک کل", + "leaveBlankToNeverExpire": "برای منقضی‌نشدن خالی‌بگذارید", + "noRecommendKeepDefault": "توصیه‌می‌شود به‌طور پیش‌فرض حفظ‌شود", + "certificatePath": "مسیر فایل", + "certificateContent": "محتوای فایل", + "publicKey": "کلید عمومی", + "privatekey": "کلید خصوصی", + "clickOnQRcode": "برای کپی بر روی کدتصویری کلیک کنید", + "client": "کاربر", + "export": "استخراج لینک‌ها", + "clone": "شبیه‌سازی", + "cloneInbound": "شبیه‌سازی ورودی", + "cloneInboundContent": "همه موارد این ورودی بجز پورت، آی‌پی و کاربر‌ها شبیه‌سازی خواهند شد", + "cloneInboundOk": "ساختن شبیه ساز", + "resetAllTraffic": "ریست ترافیک کل ورودی‌ها", + "resetAllTrafficTitle": "ریست ترافیک کل ورودی‌ها", + "resetAllTrafficContent": "آیا مطمئن به ریست ترافیک تمام ورودی‌ها هستید؟", + "resetInboundClientTraffics": "ریست ترافیک کاربران", + "resetInboundClientTrafficTitle": "ریست ترافیک کاربران", + "resetInboundClientTrafficContent": "آیا مطمئن به ریست ترافیک تمام کاربران این‌ ورودی هستید؟", + "resetAllClientTraffics": "ریست ترافیک کل کاربران", + "resetAllClientTrafficTitle": "ریست ترافیک کل کاربران", + "resetAllClientTrafficContent": "آیا مطمئن به ریست ترافیک تمام کاربران هستید؟", + "delDepletedClients": "حذف کاربران منقضی", + "delDepletedClientsTitle": "حذف کاربران منقضی", + "delDepletedClientsContent": "آیا مطمئن به حذف تمام کاربران منقضی‌شده ‌هستید؟", + "email": "ایمیل", + "emailDesc": "باید یک ایمیل یکتا باشد", + "IPLimit": "محدودیت آی‌پی", + "IPLimitDesc": "(اگر تعداد از مقدار تنظیم شده بیشتر شود، ورودی را غیرفعال می کند. (0 = غیرفعال", + "IPLimitlog": "گزارش‌ها", + "IPLimitlogDesc": "گزارش تاریخچه آی‌پی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید", + "IPLimitlogclear": "پاک کردن گزارش‌ها", + "setDefaultCert": "استفاده از گواهی پنل", + "telegramDesc": "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا ({'@'}userinfobot)", + "subscriptionDesc": "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید", + "info": "اطلاعات", + "same": "همسان", + "inboundData": "داده‌های ورودی", + "exportInbound": "استخراج ورودی", + "import": "افزودن", + "importInbound": "افزودن یک ورودی", + "periodicTrafficResetTitle": "بازنشانی ترافیک", + "periodicTrafficResetDesc": "بازنشانی خودکار شمارنده ترافیک در فواصل زمانی مشخص", + "lastReset": "آخرین بازنشانی", + "periodicTrafficReset": { + "never": "هرگز", + "daily": "روزانه", + "weekly": "هفتگی", + "monthly": "ماهانه", + "hourly": "هر ساعت" + }, + "toasts": { + "obtain": "فراهم‌سازی", + "updateSuccess": "بروزرسانی با موفقیت انجام شد", + "logCleanSuccess": "لاگ پاکسازی شد", + "inboundsUpdateSuccess": "ورودی‌ها با موفقیت به‌روزرسانی شدند", + "inboundUpdateSuccess": "ورودی با موفقیت به‌روزرسانی شد", + "inboundCreateSuccess": "ورودی با موفقیت ایجاد شد", + "inboundDeleteSuccess": "ورودی با موفقیت حذف شد", + "inboundClientAddSuccess": "کلاینت(های) ورودی اضافه شدند", + "inboundClientDeleteSuccess": "کلاینت ورودی حذف شد", + "inboundClientUpdateSuccess": "کلاینت ورودی به‌روزرسانی شد", + "delDepletedClientsSuccess": "تمام کلاینت‌های مصرف شده حذف شدند", + "resetAllClientTrafficSuccess": "تمام ترافیک کلاینت بازنشانی شد", + "resetAllTrafficSuccess": "تمام ترافیک‌ها بازنشانی شدند", + "resetInboundClientTrafficSuccess": "ترافیک بازنشانی شد", + "trafficGetError": "خطا در دریافت ترافیک‌ها", + "getNewX25519CertError": "خطا در دریافت گواهی X25519.", + "getNewmldsa65Error": "خطا در دریافت گواهی mldsa65.", + "getNewVlessEncError": "خطا در دریافت گواهی VlessEnc." + }, + "stream": { + "general": { + "request": "درخواست", + "response": "پاسخ", + "name": "نام", + "value": "مقدار" + }, + "tcp": { + "version": "نسخه", + "method": "متد", + "path": "مسیر", + "status": "وضعیت", + "statusDescription": "توضیحات وضعیت", + "requestHeader": "سربرگ درخواست", + "responseHeader": "سربرگ پاسخ" + } + } + }, + "client": { + "add": "کاربر جدید", + "edit": "ویرایش کاربر", + "submitAdd": "اضافه کردن", + "submitEdit": "ذخیره تغییرات", + "clientCount": "تعداد کاربران", + "bulk": "انبوه‌سازی", + "copyFromInbound": "کپی کاربران از اینباند", + "copyToInbound": "کپی کاربران به", + "copySelected": "کپی انتخاب‌شده‌ها", + "copySource": "منبع", + "copyEmailPreview": "پیش‌نمایش ایمیل نهایی", + "copySelectSourceFirst": "ابتدا یک اینباند منبع انتخاب کنید.", + "copyResult": "نتیجه کپی", + "copyResultSuccess": "با موفقیت کپی شد", + "copyResultNone": "چیزی برای کپی نیست: هیچ کاربری انتخاب نشده یا منبع خالی است", + "copyResultErrors": "خطاهای کپی", + "copyFlowLabel": "Flow برای کاربران جدید (VLESS)", + "copyFlowHint": "برای همه کاربران کپی‌شده اعمال می‌شود. برای نادیده گرفتن، خالی بگذارید.", + "selectAll": "انتخاب همه", + "clearAll": "پاک کردن همه", + "method": "روش", + "first": "از", + "last": "تا", + "prefix": "پیشوند", + "postfix": "پسوند", + "delayedStart": "شروع‌پس‌از‌اولین‌استفاده", + "expireDays": "مدت زمان", + "days": "(روز)", + "renew": "تمدید خودکار", + "renewDesc": "تمدید خودکار پس‌از ‌انقضا. (0 = غیرفعال)(واحد: روز)" + }, + "nodes": { + "title": "نودها", + "addNode": "افزودن نود", + "editNode": "ویرایش نود", + "totalNodes": "کل نودها", + "onlineNodes": "آنلاین", + "offlineNodes": "آفلاین", + "avgLatency": "میانگین تاخیر", + "name": "نام", + "namePlaceholder": "مثلاً de-frankfurt-1", + "addressPlaceholder": "panel.example.com یا 1.2.3.4", + "remark": "توضیحات", + "scheme": "پروتکل", + "address": "آدرس", + "port": "پورت", + "basePath": "مسیر پایه", + "apiToken": "توکن API", + "apiTokenPlaceholder": "توکن از صفحه تنظیمات پنل ریموت", + "apiTokenHint": "پنل ریموت توکن API خودش را در بخش تنظیمات → توکن API نمایش می‌دهد.", + "regenerate": "تولید مجدد توکن", + "regenerateConfirm": "تولید مجدد، توکن فعلی را باطل می‌کند. هر پنل مرکزی‌ای که از این توکن استفاده می‌کند تا زمان به‌روزرسانی، دسترسی‌اش قطع می‌شود. ادامه می‌دهید؟", + "enable": "فعال", + "status": "وضعیت", + "cpu": "پردازنده", + "mem": "حافظه", + "uptime": "زمان کارکرد", + "latency": "تاخیر", + "lastHeartbeat": "آخرین ضربان", + "xrayVersion": "نسخه Xray", + "actions": "عملیات", + "probe": "بررسی فوری", + "testConnection": "تست اتصال", + "connectionOk": "اتصال موفق ({ms} میلی‌ثانیه)", + "connectionFailed": "اتصال ناموفق", + "never": "هرگز", + "justNow": "هم‌اکنون", + "deleteConfirmTitle": "نود «{name}» حذف شود؟", + "deleteConfirmContent": "نظارت روی این نود متوقف می‌شود. خود پنل ریموت تغییری نمی‌کند.", + "statusValues": { + "online": "آنلاین", + "offline": "آفلاین", + "unknown": "نامشخص" + }, + "toasts": { + "list": "بارگذاری نودها ناموفق", + "obtain": "بارگذاری نود ناموفق", + "add": "افزودن نود", + "update": "به‌روزرسانی نود", + "delete": "حذف نود", + "deleted": "نود حذف شد", + "test": "تست اتصال", + "fillRequired": "نام، آدرس، پورت و توکن API الزامی است", + "probeFailed": "بررسی ناموفق" + } + }, + "settings": { + "title": "تنظیمات پنل", + "save": "ذخیره", + "infoDesc": "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید", + "restartPanel": "ریستارت پنل", + "restartPanelDesc": "آیا مطمئن به ریستارت پنل هستید؟ اگر پس‌از ریستارت نمی‌توانید به پنل دسترسی پیدا کنید، لطفاً گزارش‌های موجود در اسکریپت پنل را بررسی کنید", + "restartPanelSuccess": "پنل با موفقیت راه‌اندازی مجدد شد", + "actions": "عملیات ها", + "resetDefaultConfig": "برگشت به پیش‌فرض", + "panelSettings": "پیکربندی", + "securitySettings": "احرازهویت", + "TGBotSettings": "ربات تلگرام", + "panelListeningIP": "آدرس آی‌پی", + "panelListeningIPDesc": "آدرس آی‌پی برای وب پنل. برای گوش‌دادن به‌تمام آی‌پی‌ها خالی‌بگذارید", + "panelListeningDomain": "نام دامنه", + "panelListeningDomainDesc": "آدرس دامنه برای وب پنل. برای گوش دادن به‌تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید", + "panelPort": "پورت", + "panelPortDesc": "شماره پورت برای وب پنل. باید پورت استفاده نشده‌باشد", + "publicKeyPath": "مسیر کلید عمومی", + "publicKeyPathDesc": "مسیر فایل کلیدعمومی برای وب پنل. با '/' شروع‌می‌شود", + "privateKeyPath": "مسیر کلید خصوصی", + "privateKeyPathDesc": "مسیر فایل کلیدخصوصی برای وب پنل. با '/' شروع‌می‌شود", + "panelUrlPath": "URI مسیر", + "panelUrlPathDesc": "برای وب پنل. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر", + "pageSize": "اندازه صفحه بندی جدول", + "pageSizeDesc": "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال", + "remarkModel": "نام‌کانفیگ و جداکننده", + "datepicker": "نوع تقویم", + "datepickerPlaceholder": "انتخاب تاریخ", + "datepickerDescription": "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود", + "sampleRemark": "نمونه‌نام", + "oldUsername": "نام‌کاربری فعلی", + "currentPassword": "رمز‌عبور فعلی", + "newUsername": "نام‌کاربری جدید", + "newPassword": "رمزعبور جدید", + "telegramBotEnable": "فعال‌سازی ربات تلگرام", + "telegramBotEnableDesc": "ربات تلگرام را فعال می‌کند", + "telegramToken": "توکن تلگرام", + "telegramTokenDesc": "دریافت کنید {'@'}botfather توکن را می‌توانید از", + "telegramProxy": "SOCKS پراکسی", + "telegramProxyDesc": "را برای اتصال به تلگرام فعال می کند SOCKS5 پراکسی", + "telegramAPIServer": "سرور API تلگرام", + "telegramAPIServerDesc": "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید", + "telegramChatId": "آی‌دی چت مدیر", + "telegramChatIdDesc": "دریافت ‌کنید ('/id'یا (دستور ({'@'}userinfobot) آی‌دی(های) چت تلگرام مدیر، از", + "telegramNotifyTime": "زمان نوتیفیکیشن", + "telegramNotifyTimeDesc": "زمان‌اطلاع‌رسانی ربات تلگرام برای گزارش های دوره‌ای. از فرمت زمانبندی لینوکس استفاده‌کنید‌", + "tgNotifyBackup": "پشتیبان‌گیری از دیتابیس", + "tgNotifyBackupDesc": "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند", + "tgNotifyLogin": "اعلان ورود", + "tgNotifyLoginDesc": "نام‌کاربری، آدرس آی‌پی، و زمان ورود، فردی که سعی می‌کند وارد پنل شود را نمایش می‌دهد", + "sessionMaxAge": "بیشینه زمان جلسه وب", + "sessionMaxAgeDesc": "(بیشینه زمانی که می‌توانید لاگین بمانید. (واحد: دقیقه", + "expireTimeDiff": "آستانه زمان باقی مانده", + "expireTimeDiffDesc": "(فاصله زمانی هشدار تا رسیدن به زمان انقضا. (واحد: روز", + "trafficDiff": "آستانه ترافیک باقی مانده", + "trafficDiffDesc": "(فاصله زمانی هشدار تا رسیدن به اتمام ترافیک. (واحد: گیگابایت", + "tgNotifyCpu": "آستانه هشدار بار پردازنده", + "tgNotifyCpuDesc": "(اگر بار روی پردازنده ازاین آستانه فراتر رفت، برای شما پیام ارسال می‌شود. (واحد: درصد", + "timeZone": "منطقه زمانی", + "timeZoneDesc": "وظایف برنامه ریزی شده بر اساس این منطقه‌زمانی اجرا می‌شود", + "subSettings": "سابسکریپشن", + "subEnable": "فعال‌سازی سرویس سابسکریپشن", + "subEnableDesc": "سرویس سابسکریپشن‌ را فعال‌می‌کند", + "subJsonEnable": "فعال/غیرفعال‌سازی مستقل نقطه دسترسی سابسکریپشن JSON.", + "subTitle": "عنوان اشتراک", + "subTitleDesc": "عنوان نمایش داده شده در کلاینت VPN", + "subSupportUrl": "آدرس پشتیبانی", + "subSupportUrlDesc": "لینک پشتیبانی فنی که در کلاینت VPN نمایش داده می‌شود", + "subProfileUrl": "آدرس پروفایل", + "subProfileUrlDesc": "لینک وب‌سایت شما که در کلاینت VPN نمایش داده می‌شود", + "subAnnounce": "اعلان", + "subAnnounceDesc": "متن اعلانی که در کلاینت VPN نمایش داده می‌شود", + "subEnableRouting": "فعال‌سازی مسیریابی", + "subEnableRoutingDesc": "تنظیمات سراسری برای فعال‌سازی مسیریابی در کلاینت VPN. (فقط برای Happ)", + "subRoutingRules": "قوانین مسیریابی", + "subRoutingRulesDesc": "قوانین مسیریابی سراسری برای کلاینت VPN. (فقط برای Happ)", + "subListen": "آدرس آی‌پی", + "subListenDesc": "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید", + "subPort": "پورت", + "subPortDesc": "شماره پورت برای سرویس سابسکریپشن. باید پورت استفاده نشده‌باشد", + "subCertPath": "مسیر کلید عمومی", + "subCertPathDesc": "مسیر فایل کلیدعمومی برای سرویس سابیکریپشن. با '/' شروع‌می‌شود", + "subKeyPath": "مسیر کلید خصوصی", + "subKeyPathDesc": "مسیر فایل کلیدخصوصی برای سرویس سابسکریپشن. با '/' شروع‌می‌شود", + "subPath": "URI مسیر", + "subPathDesc": "برای سرویس سابسکریپشن. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر", + "subDomain": "نام دامنه", + "subDomainDesc": "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌", + "subUpdates": "فاصله بروزرسانی‌ سابسکریپشن", + "subUpdatesDesc": "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت", + "subEncrypt": "کدگذاری", + "subEncryptDesc": "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه", + "subShowInfo": "نمایش اطلاعات مصرف", + "subShowInfoDesc": "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد", + "subURI": "پروکسی معکوس URI مسیر", + "subURIDesc": "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر", + "externalTrafficInformEnable": "اطلاع رسانی خارجی مصرف ترافیک", + "externalTrafficInformEnableDesc": "مصرف ترافیک به سرویس خارجی ارسال می شود", + "externalTrafficInformURI": "لینک اطلاع رسانی خارجی مصرف ترافیک", + "externalTrafficInformURIDesc": "ترافیک های مصرفی به این لینک هم ارسال می شود", + "restartXrayOnClientDisable": "ری‌استارت Xray بعد از غیرفعال‌سازی خودکار", + "restartXrayOnClientDisableDesc": "وقتی کاربر به‌صورت خودکار به‌دلیل اتمام زمان یا ترافیک غیرفعال می‌شود، Xray ری‌استارت شود.", + "fragment": "فرگمنت", + "fragmentDesc": "فعال کردن فرگمنت برای بسته‌ی نخست تی‌ال‌اس", + "fragmentSett": "تنظیمات فرگمنت", + "noisesDesc": "فعال کردن Noises.", + "noisesSett": "تنظیمات Noises", + "mux": "ماکس", + "muxDesc": "چندین جریان داده مستقل را در یک جریان داده ثابت منتقل می کند", + "muxSett": "تنظیمات ماکس", + "direct": "اتصال مستقیم", + "directDesc": "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند", + "notifications": "اعلان‌ها", + "certs": "گواهی‌ها", + "externalTraffic": "ترافیک خارجی", + "dateAndTime": "تاریخ و زمان", + "proxyAndServer": "پراکسی و سرور", + "intervals": "فواصل", + "information": "اطلاعات", + "language": "زبان", + "telegramBotLanguage": "زبان ربات تلگرام", + "security": { + "admin": "اعتبارنامه‌های ادمین", + "twoFactor": "احراز هویت دو مرحله‌ای", + "twoFactorEnable": "فعال‌سازی 2FA", + "twoFactorEnableDesc": "یک لایه اضافی امنیتی برای احراز هویت فراهم می‌کند.", + "twoFactorModalSetTitle": "فعال‌سازی احراز هویت دو مرحله‌ای", + "twoFactorModalDeleteTitle": "غیرفعال‌سازی احراز هویت دو مرحله‌ای", + "twoFactorModalSteps": "برای راه‌اندازی احراز هویت دو مرحله‌ای، مراحل زیر را انجام دهید:", + "twoFactorModalFirstStep": "1. این کد QR را در برنامه احراز هویت اسکن کنید یا توکن کنار کد QR را کپی کرده و در برنامه بچسبانید", + "twoFactorModalSecondStep": "2. کد را از برنامه وارد کنید", + "twoFactorModalRemoveStep": "برای حذف احراز هویت دو مرحله‌ای، کد را از برنامه وارد کنید.", + "twoFactorModalChangeCredentialsTitle": "تغییر اعتبارنامه‌ها", + "twoFactorModalChangeCredentialsStep": "برای تغییر اعتبارنامه‌های مدیر، کد را از برنامه وارد کنید.", + "twoFactorModalSetSuccess": "احراز هویت دو مرحله‌ای با موفقیت برقرار شد", + "twoFactorModalDeleteSuccess": "احراز هویت دو مرحله‌ای با موفقیت حذف شد", + "twoFactorModalError": "کد نادرست" + }, + "toasts": { + "modifySettings": "پارامترها تغییر کرده‌اند.", + "getSettings": "خطا در دریافت پارامترها", + "modifyUserError": "خطا در تغییر اعتبارنامه‌های مدیر سیستم.", + "modifyUser": "شما با موفقیت اعتبارنامه‌های مدیر سیستم را تغییر دادید.", + "originalUserPassIncorrect": "نام‌کاربری یا رمزعبور فعلی اشتباه‌است", + "userPassMustBeNotEmpty": "نام‌کاربری یا رمزعبور جدید خالی‌است", + "getOutboundTrafficError": "خطا در دریافت ترافیک خروجی", + "resetOutboundTrafficError": "خطا در بازنشانی ترافیک خروجی" + } + }, + "xray": { + "title": "پیکربندی ایکس‌ری", + "save": "ذخیره", + "restart": "ریستارت ایکس‌ری", + "restartSuccess": "Xray با موفقیت راه‌اندازی مجدد شد", + "stopSuccess": "Xray با موفقیت متوقف شد", + "restartError": "خطا در راه‌اندازی مجدد Xray.", + "stopError": "خطا در توقف Xray.", + "basicTemplate": "پایه", + "advancedTemplate": "پیشرفته", + "generalConfigs": "استراتژی‌ کلی", + "generalConfigsDesc": "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند", + "logConfigs": "گزارش", + "logConfigsDesc": "گزارش‌ها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید", + "blockConfigsDesc": "این گزینه‌ها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود می‌کند", + "basicRouting": "مسیریابی پایه", + "blockConnectionsConfigsDesc": "این گزینه‌ها ترافیک را بر اساس کشور درخواست‌شده خاص مسدود می‌کنند.", + "directConnectionsConfigsDesc": "یک اتصال مستقیم تضمین می‌کند که ترافیک خاص از طریق سرور دیگری مسیریابی نشود.", + "blockips": "مسدود کردن آی‌پی‌ها", + "blockdomains": "مسدود کردن دامنه‌ها", + "directips": "آی‌پی‌های مستقیم", + "directdomains": "دامنه‌های مستقیم", + "ipv4Routing": "IPv4 مسیریابی", + "ipv4RoutingDesc": "این گزینه‌ها ترافیک را از طریق آی‌پی نسخه4 سرور، به مقصد هدایت می‌کند", + "warpRouting": "WARP مسیریابی", + "warpRoutingDesc": "این گزینه‌ها ترافیک‌ را از طریق وارپ کلادفلر به مقصد هدایت می‌کند", + "nordRouting": "مسیریابی NordVPN", + "nordRoutingDesc": "این گزینه‌ها ترافیک را بر اساس مقصد خاص از طریق NordVPN مسیریابی می‌کنند.", + "Template": "‌پیکربندی پیشرفته الگو ایکس‌ری", + "TemplateDesc": "فایل پیکربندی نهایی ایکس‌ری بر اساس این الگو ایجاد می‌شود", + "FreedomStrategy": "Freedom استراتژی پروتکل", + "FreedomStrategyDesc": "تعیین می‌کند Freedom استراتژی خروجی شبکه را برای پروتکل", + "RoutingStrategy": "استراتژی کلی مسیریابی", + "RoutingStrategyDesc": "استراتژی کلی مسیریابی برای حل تمام درخواست‌ها را تعیین می‌کند", + "outboundTestUrl": "آدرس تست خروجی", + "outboundTestUrlDesc": "آدرسی که برای تست اتصال خروجی استفاده می‌شود.", + "Torrent": "مسدودسازی پروتکل بیت‌تورنت", + "Inbounds": "ورودی‌ها", + "InboundsDesc": "پذیرش کلاینت خاص", + "Outbounds": "خروجی‌ها", + "Balancers": "بالانسرها", + "OutboundsDesc": "مسیر ترافیک خروجی را تنظیم کنید", + "Routings": "قوانین مسیریابی", + "RoutingsDesc": "اولویت هر قانون مهم است", + "completeTemplate": "کامل", + "logLevel": "سطح گزارش", + "logLevelDesc": "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند.", + "accessLog": "مسیر گزارش", + "accessLogDesc": "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند.", + "errorLog": "گزارش خطا", + "errorLogDesc": "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند", + "dnsLog": "گزارش DNS", + "dnsLogDesc": "آیا ثبت‌های درخواست DNS را فعال کنید", + "maskAddress": "پنهان کردن آدرس", + "maskAddressDesc": "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند.", + "statistics": "آمار", + "statsInboundUplink": "آمار آپلود ورودی", + "statsInboundUplinkDesc": "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های ورودی را فعال می‌کند.", + "statsInboundDownlink": "آمار دانلود ورودی", + "statsInboundDownlinkDesc": "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های ورودی را فعال می‌کند.", + "statsOutboundUplink": "آمار آپلود خروجی", + "statsOutboundUplinkDesc": "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های خروجی را فعال می‌کند.", + "statsOutboundDownlink": "آمار دانلود خروجی", + "statsOutboundDownlinkDesc": "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های خروجی را فعال می‌کند.", + "rules": { + "first": "اولین", + "last": "آخرین", + "up": "بالا", + "down": "پایین", + "source": "مبدا", + "dest": "مقصد", + "inbound": "ورودی", + "outbound": "خروجی", + "balancer": "بالانسر", + "info": "اطلاعات", + "add": "افزودن قانون", + "edit": "ویرایش قانون", + "useComma": "موارد جدا شده با کاما" + }, + "outbound": { + "addOutbound": "افزودن خروجی", + "addReverse": "افزودن معکوس", + "editOutbound": "ویرایش خروجی", + "editReverse": "ویرایش معکوس", + "reverseTag": "تگ معکوس", + "reverseTagDesc": "تگ خروجی پروکسی معکوس ساده VLESS. برای غیرفعال کردن خالی بگذارید. در صورت تنظیم، اتصالات این کلاینت می‌توانند به عنوان تونل پروکسی معکوس استفاده شوند.", + "reverseTagPlaceholder": "تگ خروجی (خالی = غیرفعال)", + "tag": "برچسب", + "tagDesc": "برچسب یگانه", + "address": "آدرس", + "reverse": "معکوس", + "domain": "دامنه", + "type": "نوع", + "bridge": "پل", + "portal": "پورتال", + "link": "لینک", + "intercon": "اتصال میانی", + "settings": "تنظیمات", + "accountInfo": "اطلاعات حساب", + "outboundStatus": "وضعیت خروجی", + "sendThrough": "ارسال با", + "test": "تست", + "testResult": "نتیجه تست", + "testing": "در حال تست اتصال...", + "testSuccess": "تست موفقیت‌آمیز", + "testFailed": "تست ناموفق", + "testError": "خطا در تست خروجی", + "nordvpn": "NordVPN", + "accessToken": "توکن دسترسی", + "country": "کشور", + "server": "سرور", + "privateKey": "کلید خصوصی", + "city": "شهر", + "allCities": "همه شهرها", + "load": "فشار سرور" + }, + "balancer": { + "addBalancer": "افزودن بالانسر", + "editBalancer": "ویرایش بالانسر", + "balancerStrategy": "استراتژی", + "balancerSelectors": "انتخاب‌گرها", + "tag": "برچسب", + "tagDesc": "برچسب یگانه", + "balancerDesc": "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد." + }, + "wireguard": { + "secretKey": "کلید شخصی", + "publicKey": "کلید عمومی", + "allowedIPs": "آی‌پی‌های مجاز", + "endpoint": "نقطه پایانی", + "psk": "کلید مشترک", + "domainStrategy": "استراتژی حل دامنه" + }, + "tun": { + "nameDesc": "نام رابط TUN. مقدار پیش‌فرض 'xray0' است", + "mtuDesc": "واحد انتقال حداکثر. بیشترین اندازه بسته‌های داده. مقدار پیش‌فرض 1500 است", + "userLevel": "سطح کاربر", + "userLevelDesc": "تمام اتصالات انجام‌شده از طریق این ورودی از این سطح کاربری استفاده خواهند کرد. مقدار پیش‌فرض 0 است" + }, + "dns": { + "enable": "فعال کردن حل دامنه", + "enableDesc": "سرور حل دامنه داخلی را فعال کنید", + "tag": "برچسب", + "tagDesc": "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود", + "clientIp": "آی‌پی کلاینت", + "clientIpDesc": "برای اطلاع‌رسانی به سرور درباره مکان IP مشخص‌شده در طول درخواست‌های DNS استفاده می‌شود", + "disableCache": "غیرفعال‌سازی کش", + "disableCacheDesc": "کش DNS را غیرفعال می‌کند", + "disableFallback": "غیرفعال‌سازی Fallback", + "disableFallbackDesc": "درخواست‌های DNS Fallback را غیرفعال می‌کند", + "disableFallbackIfMatch": "غیرفعال‌سازی Fallback در صورت تطابق", + "disableFallbackIfMatchDesc": "درخواست‌های DNS Fallback را زمانی که لیست دامنه‌های مطابقت‌یافته سرور DNS فعال است، غیرفعال می‌کند", + "enableParallelQuery": "فعال‌سازی پرس‌وجوی موازی", + "enableParallelQueryDesc": "فعال‌سازی پرس‌وجوهای DNS موازی به چندین سرور برای وضوح سریع‌تر", + "strategy": "استراتژی پرس‌وجو", + "strategyDesc": "استراتژی کلی برای حل نام دامنه", + "add": "افزودن سرور", + "edit": "ویرایش سرور", + "domains": "دامنه‌ها", + "expectIPs": "آی‌پی‌های مورد انتظار", + "unexpectIPs": "آی‌پی‌های غیرمنتظره", + "useSystemHosts": "استفاده از Hosts سیستم", + "useSystemHostsDesc": "استفاده از فایل hosts یک سیستم نصب‌شده", + "usePreset": "استفاده از پیش‌تنظیم", + "dnsPresetTitle": "پیش‌تنظیم‌های DNS", + "dnsPresetFamily": "خانوادگی" + }, + "fakedns": { + "add": "افزودن دی‌ان‌اس جعلی", + "edit": "ویرایش دی‌ان‌اس جعلی", + "ipPool": "زیرشبکه استخر آی‌پی", + "poolSize": "اندازه استخر" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ صفحه کلید بسته شد!", + "noResult": "❗ نتیجه ای یافت نشد!", + "noQuery": "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!", + "wentWrong": "❌ مشکلی پیش آمد!", + "noIpRecord": "❗ رکورد آی پی وجود ندارد!", + "noInbounds": "❗ هیچ ورودی یافت نشد!", + "unlimited": "♾ نامحدود(ریست)", + "add": "افزودن", + "month": "ماه", + "months": "ماه", + "day": "روز", + "days": "روز", + "hours": "ساعت", + "minutes": "دقیقه", + "unknown": "نامشخص", + "inbounds": "ورودی ها", + "clients": "کاربران", + "offline": "🔴 آفلاین", + "online": "🟢 آنلاین", + "commands": { + "unknown": "❗ دستور ناشناخته", + "pleaseChoose": "👇 لطفاً انتخاب کنید:\r\n", + "help": "🤖 به این ربات خوش آمدید! این ربات برای ارائه داده‌های خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را می‌دهد.\r\n\r\n", + "start": "👋 سلام {{ .Firstname }}.\r\n", + "welcome": "🤖 به ربات مدیریت {{ .Hostname }} خوش آمدید.\r\n", + "status": "✅ ربات در حالت عادی است!", + "usage": "❗ لطفاً یک متن برای جستجو وارد کنید!", + "getID": "🆔 شناسه شما: {{ .ID }}", + "helpAdminCommands": "برای راه‌اندازی مجدد Xray Core:\r\n/restart\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیحات]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id", + "helpClientCommands": "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n/usage [ایمیل]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ عملیات با موفقیت انجام شد!", + "restartFailed": "❗ خطا در عملیات.\r\n\r\nخطا: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core در حال اجرا نیست.", + "startDesc": "نمایش منوی اصلی", + "helpDesc": "راهنمای ربات", + "statusDesc": "بررسی وضعیت ربات", + "idDesc": "نمایش شناسه تلگرام شما" + }, + "messages": { + "cpuThreshold": "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%", + "selectUserFailed": "❌ خطا در انتخاب کاربر!", + "userSaved": "✅ کاربر تلگرام ذخیره شد.", + "loginSuccess": "✅ با موفقیت به پنل وارد شدید.\r\n", + "loginFailed": "❗️ ورود به پنل ناموفق‌بود \r\n", + "2faFailed": "خطای 2FA", + "report": "🕰 گزارشات‌زمان‌بندی‌شده: {{ .RunTime }}\r\n", + "datetime": "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n", + "hostname": "💻 نام‌میزبان: {{ .Hostname }}\r\n", + "version": "🚀 نسخه‌پنل: {{ .Version }}\r\n", + "xrayVersion": "📡 نسخه‌هسته: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n", + "ips": "🔢 آدرس‌های آی‌پی:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ مدت‌کارکردسیستم: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 بارسیستم: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ وضعیت‌ایکس‌ری: {{ .State }}\r\n", + "username": "👤 نام‌کاربری: {{ .Username }}\r\n", + "reason": "❗️ دلیل: {{ .Reason }}\r\n", + "time": "⏰ زمان: {{ .Time }}\r\n", + "inbound": "📍 نام‌ورودی: {{ .Remark }}\r\n", + "port": "🔌 پورت: {{ .Port }}\r\n", + "expire": "📅 تاریخ‌انقضا: {{ .Time }}\r\n\r\n", + "expireIn": "📅 باقی‌ مانده‌ تا انقضا: {{ .Time }}\r\n\r\n", + "active": "💡 فعال: {{ .Enable }}\r\n", + "enabled": "🚨 وضعیت: {{ .Enable }}\r\n", + "online": "🌐 وضعیت اتصال: {{ .Status }}\r\n", + "lastOnline": "🔙 آخرین فعالیت: {{ .Time }}\r\n", + "email": "📧 ایمیل: {{ .Email }}\r\n", + "upload": "🔼 آپلود↑: {{ .Upload }}\r\n", + "download": "🔽 دانلود↓: {{ .Download }}\r\n", + "total": "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 کاربر تلگرام: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 {{ .Type }} به‌اتمام‌رسیده‌است:\r\n", + "exhaustedCount": "🚨 تعداد {{ .Type }} به‌اتمام‌رسیده‌است:\r\n", + "onlinesCount": "🌐 کاربران‌آنلاین: {{ .Count }}\r\n", + "disabled": "🛑 غیرفعال: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 به‌زودی‌به‌پایان‌خواهدرسید: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 زمان‌پشتیبان‌گیری: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n", + "yes": "✅ بله", + "no": "❌ خیر", + "received_id": "🔑📥 شناسه به‌روزرسانی شد.", + "received_password": "🔑📥 رمز عبور به‌روزرسانی شد.", + "received_email": "📧📥 ایمیل به‌روزرسانی شد.", + "received_comment": "💬📥 نظر به‌روزرسانی شد.", + "id_prompt": "🔑 شناسه پیش‌فرض: {{ .ClientId }}\n\nشناسه خود را وارد کنید.", + "pass_prompt": "🔑 رمز عبور پیش‌فرض: {{ .ClientPassword }}\n\nرمز عبور خود را وارد کنید.", + "email_prompt": "📧 ایمیل پیش‌فرض: {{ .ClientEmail }}\n\nایمیل خود را وارد کنید.", + "comment_prompt": "💬 نظر پیش‌فرض: {{ .ClientComment }}\n\nنظر خود را وارد کنید.", + "inbound_client_data_id": "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 شناسه: {{ .ClientId }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!", + "inbound_client_data_pass": "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 رمز عبور: {{ .ClientPass }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!", + "cancel": "❌ فرآیند لغو شد! \n\nمی‌توانید هر زمان که خواستید /start را دوباره اجرا کنید. 🔄", + "error_add_client": "⚠️ خطا:\n\n {{ .error }}", + "using_default_value": "باشه، از مقدار پیش‌فرض استفاده می‌کنم. 😊", + "incorrect_input": "ورودی شما معتبر نیست.\nعبارت‌ها باید بدون فاصله باشند.\nمثال صحیح: aaaaaa\nمثال نادرست: aaa aaa 🚫", + "AreYouSure": "مطمئنی؟ 🤔", + "SuccessResetTraffic": "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ✅ موفقیت‌آمیز", + "FailedResetTraffic": "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ❌ ناموفق \n\n🛠️ خطا: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 فرآیند بازنشانی ترافیک برای همه مشتریان به پایان رسید." + }, + "buttons": { + "closeKeyboard": "❌ بستن کیبورد", + "cancel": "❌ لغو", + "cancelReset": "❌ لغو تنظیم مجدد", + "cancelIpLimit": "❌ لغو محدودیت آی‌پی", + "confirmResetTraffic": "✅ تأیید تنظیم مجدد ترافیک؟", + "confirmClearIps": "✅ تأیید پاک‌سازی آدرس‌های آی‌پی؟", + "confirmRemoveTGUser": "✅ تأیید حذف کاربر تلگرام؟", + "confirmToggle": "✅ تایید فعال/غیرفعال کردن کاربر؟", + "dbBackup": "دریافت پشتیبان", + "serverUsage": "استفاده از سیستم", + "getInbounds": "دریافت ورودی‌ها", + "depleteSoon": "به‌زودی به پایان خواهد رسید", + "clientUsage": "دریافت آمار کاربر", + "onlines": "کاربران آنلاین", + "commands": "دستورات", + "refresh": "🔄 تازه‌سازی", + "clearIPs": "❌ پاک‌سازی آدرس‌ها", + "removeTGUser": "❌ حذف کاربر تلگرام", + "selectTGUser": "👤 انتخاب کاربر تلگرام", + "selectOneTGUser": "👤 یک کاربر تلگرام را انتخاب کنید:", + "resetTraffic": "📈 تنظیم مجدد ترافیک", + "resetExpire": "📅 تنظیم مجدد تاریخ انقضا", + "ipLog": "🔢 لاگ آدرس‌های IP", + "ipLimit": "🔢 محدودیت IP", + "setTGUser": "👤 تنظیم کاربر تلگرام", + "toggle": "🔘 فعال / غیرفعال", + "custom": "🔢 سفارشی", + "confirmNumber": "✅ تایید: {{ .Num }}", + "confirmNumberAdd": "✅ تایید اضافه کردن: {{ .Num }}", + "limitTraffic": "🚧 محدودیت ترافیک", + "getBanLogs": "گزارش های بلوک را دریافت کنید", + "allClients": "همه مشتریان", + "addClient": "افزودن مشتری", + "submitDisable": "ارسال به عنوان غیرفعال ☑️", + "submitEnable": "ارسال به عنوان فعال ✅", + "use_default": "🏷️ استفاده از پیش‌فرض", + "change_id": "⚙️🔑 شناسه", + "change_password": "⚙️🔑 گذرواژه", + "change_email": "⚙️📧 ایمیل", + "change_comment": "⚙️💬 نظر", + "ResetAllTraffics": "بازنشانی همه ترافیک‌ها", + "SortedTrafficUsageReport": "گزارش استفاده از ترافیک مرتب‌شده" + }, + "answers": { + "successfulOperation": "✅ انجام شد!", + "errorOperation": "❗ خطا در عملیات.", + "getInboundsFailed": "❌ دریافت ورودی‌ها با خطا مواجه شد.", + "getClientsFailed": "❌ دریافت مشتریان با شکست مواجه شد.", + "canceled": "❌ {{ .Email }} : عملیات لغو شد.", + "clientRefreshSuccess": "✅ {{ .Email }} : کلاینت با موفقیت تازه‌سازی شد.", + "IpRefreshSuccess": "✅ {{ .Email }} : آدرس‌ها با موفقیت تازه‌سازی شدند.", + "TGIdRefreshSuccess": "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازه‌سازی شد.", + "resetTrafficSuccess": "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد.", + "setTrafficLimitSuccess": "✅ {{ .Email }} : محدودیت ترافیک با موفقیت ذخیره شد.", + "expireResetSuccess": "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد.", + "resetIpSuccess": "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد.", + "clearIpSuccess": "✅ {{ .Email }} : آدرس‌ها با موفقیت پاک‌سازی شدند.", + "getIpLog": "✅ {{ .Email }} : دریافت لاگ آدرس‌های IP.", + "getUserInfo": "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام.", + "removedTGUserSuccess": "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد.", + "enableSuccess": "✅ {{ .Email }} : با موفقیت فعال شد.", + "disableSuccess": "✅ {{ .Email }} : با موفقیت غیرفعال شد.", + "askToAddUserId": "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: {{ .TgUserID }}", + "chooseClient": "یک مشتری برای ورودی {{ .Inbound }} انتخاب کنید", + "chooseInbound": "یک ورودی انتخاب کنید" + } + } +} diff --git a/web/translation/id-ID.json b/web/translation/id-ID.json new file mode 100644 index 00000000..f4f495e9 --- /dev/null +++ b/web/translation/id-ID.json @@ -0,0 +1,941 @@ +{ + "username": "Nama Pengguna", + "password": "Kata Sandi", + "login": "Masuk", + "confirm": "Konfirmasi", + "cancel": "Batal", + "close": "Tutup", + "save": "Simpan", + "logout": "Keluar", + "create": "Buat", + "update": "Perbarui", + "copy": "Salin", + "copied": "Tersalin", + "download": "Unduh", + "remark": "Catatan", + "enable": "Aktifkan", + "protocol": "Protokol", + "search": "Cari", + "filter": "Filter", + "loading": "Memuat...", + "second": "Detik", + "minute": "Menit", + "hour": "Jam", + "day": "Hari", + "check": "Centang", + "indefinite": "Tak Terbatas", + "unlimited": "Tanpa Batas", + "none": "None", + "qrCode": "Kode QR", + "info": "Informasi Lebih Lanjut", + "edit": "Edit", + "delete": "Hapus", + "reset": "Reset", + "noData": "Tidak ada data.", + "copySuccess": "Berhasil Disalin", + "sure": "Yakin", + "encryption": "Enkripsi", + "useIPv4ForHost": "Gunakan IPv4 untuk host", + "transmission": "Transmisi", + "host": "Host", + "path": "Jalur", + "camouflage": "Obfuscation", + "status": "Status", + "enabled": "Aktif", + "disabled": "Nonaktif", + "depleted": "Habis", + "depletingSoon": "Akan Habis", + "offline": "Offline", + "online": "Online", + "domainName": "Nama Domain", + "monitor": "IP Pemantauan", + "certificate": "Sertifikat Digital", + "fail": "Gagal", + "comment": "Komentar", + "success": "Berhasil", + "lastOnline": "Terakhir online", + "getVersion": "Dapatkan Versi", + "install": "Instal", + "clients": "Klien", + "usage": "Penggunaan", + "twoFactorCode": "Kode", + "remained": "Tersisa", + "security": "Keamanan", + "secAlertTitle": "Peringatan keamanan", + "secAlertSsl": "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data.", + "secAlertConf": "Beberapa pengaturan rentan terhadap serangan. Disarankan untuk memperkuat protokol keamanan guna mencegah pelanggaran potensial.", + "secAlertSSL": "Panel kekurangan koneksi yang aman. Harap instal sertifikat TLS untuk perlindungan data.", + "secAlertPanelPort": "Port default panel rentan. Harap konfigurasi port acak atau tertentu.", + "secAlertPanelURI": "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks.", + "secAlertSubURI": "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks.", + "secAlertSubJsonURI": "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks.", + "emptyDnsDesc": "Tidak ada server DNS yang ditambahkan.", + "emptyFakeDnsDesc": "Tidak ada server Fake DNS yang ditambahkan.", + "emptyBalancersDesc": "Tidak ada penyeimbang yang ditambahkan.", + "emptyReverseDesc": "Tidak ada proxy terbalik yang ditambahkan.", + "somethingWentWrong": "Terjadi kesalahan", + "subscription": { + "title": "Info langganan", + "subId": "ID langganan", + "status": "Status", + "downloaded": "Diunduh", + "uploaded": "Diunggah", + "expiry": "Kedaluwarsa", + "totalQuota": "Kuota total", + "individualLinks": "Tautan individual", + "active": "Aktif", + "inactive": "Nonaktif", + "unlimited": "Tanpa batas", + "noExpiry": "Tanpa kedaluwarsa" + }, + "menu": { + "theme": "Tema", + "dark": "Gelap", + "ultraDark": "Sangat Gelap", + "dashboard": "Ikhtisar", + "inbounds": "Masuk", + "settings": "Pengaturan Panel", + "xray": "Konfigurasi Xray", + "logout": "Keluar", + "link": "Kelola" + }, + "pages": { + "login": { + "hello": "Halo", + "title": "Selamat Datang", + "loginAgain": "Sesi Anda telah berakhir, harap masuk kembali", + "toasts": { + "invalidFormData": "Format data input tidak valid.", + "emptyUsername": "Nama Pengguna diperlukan", + "emptyPassword": "Kata Sandi diperlukan", + "wrongUsernameOrPassword": "Username, kata sandi, atau kode dua faktor tidak valid.", + "successLogin": "Anda telah berhasil masuk ke akun Anda." + } + }, + "index": { + "title": "Ikhtisar", + "cpu": "CPU", + "logicalProcessors": "Prosesor logis", + "frequency": "Frekuensi", + "swap": "Swap", + "storage": "Penyimpanan", + "memory": "RAM", + "threads": "Thread", + "xrayStatus": "Xray", + "stopXray": "Stop", + "restartXray": "Restart", + "xraySwitch": "Versi", + "xraySwitchClick": "Pilih versi yang ingin Anda pindah.", + "xraySwitchClickDesk": "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini.", + "xrayUpdates": "Pembaruan Xray", + "updatePanel": "Perbarui Panel", + "panelUpdateDesc": "Ini akan memperbarui 3X-UI ke rilis terbaru dan me-restart layanan panel.", + "currentPanelVersion": "Versi panel saat ini", + "latestPanelVersion": "Versi panel terbaru", + "panelUpToDate": "Panel sudah terbaru", + "upToDate": "Terbaru", + "xrayStatusUnknown": "Tidak diketahui", + "xrayStatusRunning": "Berjalan", + "xrayStatusStop": "Berhenti", + "xrayStatusError": "Kesalahan", + "xrayErrorPopoverTitle": "Terjadi kesalahan saat menjalankan Xray", + "operationHours": "Waktu Aktif", + "systemHistoryTitle": "Riwayat Sistem", + "trendLast2Min": "2 menit terakhir", + "systemLoad": "Beban Sistem", + "systemLoadDesc": "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir", + "connectionCount": "Statistik Koneksi", + "ipAddresses": "Alamat IP", + "toggleIpVisibility": "Alihkan visibilitas IP", + "overallSpeed": "Kecepatan keseluruhan", + "upload": "Unggah", + "download": "Unduh", + "totalData": "Total data", + "sent": "Dikirim", + "received": "Diterima", + "documentation": "Dokumentasi", + "xraySwitchVersionDialog": "Apakah Anda yakin ingin mengubah versi Xray?", + "xraySwitchVersionDialogDesc": "Ini akan mengubah versi Xray ke #version#.", + "xraySwitchVersionPopover": "Xray berhasil diperbarui", + "panelUpdateDialog": "Apakah Anda benar-benar ingin memperbarui panel?", + "panelUpdateDialogDesc": "Ini akan memperbarui 3X-UI ke #version# dan me-restart layanan panel.", + "panelUpdateCheckPopover": "Pemeriksaan pembaruan panel gagal", + "panelUpdateStartedPopover": "Pembaruan panel dimulai", + "geofileUpdateDialog": "Apakah Anda yakin ingin memperbarui geofile?", + "geofileUpdateDialogDesc": "Ini akan memperbarui file #filename#.", + "geofilesUpdateDialogDesc": "Ini akan memperbarui semua berkas.", + "geofilesUpdateAll": "Perbarui semua", + "geofileUpdatePopover": "Geofile berhasil diperbarui", + "dontRefresh": "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini", + "logs": "Log", + "config": "Konfigurasi", + "backup": "Cadangan", + "backupTitle": "Cadangan & Pulihkan", + "exportDatabase": "Cadangkan", + "exportDatabaseDesc": "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda.", + "importDatabase": "Pulihkan", + "importDatabaseDesc": "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan.", + "importDatabaseSuccess": "Database berhasil diimpor", + "importDatabaseError": "Terjadi kesalahan saat mengimpor database", + "readDatabaseError": "Terjadi kesalahan saat membaca database", + "getDatabaseError": "Terjadi kesalahan saat mengambil database", + "getConfigError": "Terjadi kesalahan saat mengambil file konfigurasi", + "customGeoTitle": "GeoSite / GeoIP kustom", + "customGeoAdd": "Tambah", + "customGeoType": "Jenis", + "customGeoAlias": "Alias", + "customGeoUrl": "URL", + "customGeoEnabled": "Aktif", + "customGeoLastUpdated": "Terakhir diperbarui", + "customGeoExtColumn": "Routing (ext:…)", + "customGeoToastUpdateAll": "Semua sumber kustom telah diperbarui", + "customGeoActions": "Aksi", + "customGeoEdit": "Edit", + "customGeoDelete": "Hapus", + "customGeoDownload": "Perbarui sekarang", + "customGeoModalAdd": "Tambah geo kustom", + "customGeoModalEdit": "Edit geo kustom", + "customGeoModalSave": "Simpan", + "customGeoDeleteConfirm": "Hapus sumber geo kustom ini?", + "customGeoRoutingHint": "Pada aturan routing gunakan kolom nilai sebagai ext:file.dat:tag (ganti tag).", + "customGeoInvalidId": "ID sumber tidak valid", + "customGeoAliasesError": "Gagal memuat alias geo kustom", + "customGeoValidationAlias": "Alias hanya huruf kecil, angka, - dan _", + "customGeoValidationUrl": "URL harus diawali http:// atau https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (kustom)", + "customGeoToastList": "Daftar geo kustom", + "customGeoToastAdd": "Tambah geo kustom", + "customGeoToastUpdate": "Perbarui geo kustom", + "customGeoToastDelete": "Geofile kustom “{{ .fileName }}” dihapus", + "customGeoToastDownload": "Geofile “{{ .fileName }}” diperbarui", + "customGeoErrInvalidType": "Jenis harus geosite atau geoip", + "customGeoErrAliasRequired": "Alias wajib diisi", + "customGeoErrAliasPattern": "Alias berisi karakter yang tidak diizinkan", + "customGeoErrAliasReserved": "Alias ini dicadangkan", + "customGeoErrUrlRequired": "URL wajib diisi", + "customGeoErrInvalidUrl": "URL tidak valid", + "customGeoErrUrlScheme": "URL harus memakai http atau https", + "customGeoErrUrlHost": "Host URL tidak valid", + "customGeoErrDuplicateAlias": "Alias ini sudah dipakai untuk jenis ini", + "customGeoErrNotFound": "Sumber geo kustom tidak ditemukan", + "customGeoErrDownload": "Unduh gagal", + "customGeoErrUpdateAllIncomplete": "Satu atau lebih sumber geo kustom gagal diperbarui", + "customGeoEmpty": "Belum ada sumber geo kustom — klik Tambah untuk membuatnya" + }, + "inbounds": { + "allTimeTraffic": "Total Lalu Lintas", + "allTimeTrafficUsage": "Total Penggunaan Sepanjang Waktu", + "title": "Masuk", + "totalDownUp": "Total Terkirim/Diterima", + "totalUsage": "Penggunaan Total", + "inboundCount": "Total Masuk", + "operate": "Menu", + "enable": "Aktifkan", + "remark": "Catatan", + "node": "Node", + "deployTo": "Terapkan ke", + "localPanel": "Panel lokal", + "protocol": "Protokol", + "port": "Port", + "portMap": "Port Mapping", + "traffic": "Traffic", + "details": "Rincian", + "transportConfig": "Transport", + "expireDate": "Durasi", + "createdAt": "Dibuat", + "updatedAt": "Diperbarui", + "resetTraffic": "Reset Traffic", + "addInbound": "Tambahkan Masuk", + "generalActions": "Tindakan Umum", + "modifyInbound": "Ubah Masuk", + "deleteInbound": "Hapus Masuk", + "deleteInboundContent": "Apakah Anda yakin ingin menghapus masuk?", + "deleteClient": "Hapus Klien", + "deleteClientContent": "Apakah Anda yakin ingin menghapus klien?", + "resetTrafficContent": "Apakah Anda yakin ingin mereset traffic?", + "copyLink": "Salin URL", + "address": "Alamat", + "network": "Jaringan", + "destinationPort": "Port Tujuan", + "targetAddress": "Alamat Target", + "monitorDesc": "Biarkan kosong untuk mendengarkan semua IP", + "meansNoLimit": "= Unlimited. (unit: GB)", + "totalFlow": "Total Aliran", + "leaveBlankToNeverExpire": "Biarkan kosong untuk tidak pernah kedaluwarsa", + "noRecommendKeepDefault": "Disarankan untuk tetap menggunakan pengaturan default", + "certificatePath": "Path Berkas", + "certificateContent": "Konten Berkas", + "publicKey": "Kunci Publik", + "privatekey": "Kunci Pribadi", + "clickOnQRcode": "Klik pada Kode QR untuk Menyalin", + "client": "Klien", + "export": "Ekspor Semua URL", + "clone": "Duplikat", + "cloneInbound": "Duplikat", + "cloneInboundContent": "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat.", + "cloneInboundOk": "Duplikat", + "resetAllTraffic": "Reset Semua Traffic Masuk", + "resetAllTrafficTitle": "Reset Semua Traffic Masuk", + "resetAllTrafficContent": "Apakah Anda yakin ingin mereset traffic semua masuk?", + "resetInboundClientTraffics": "Reset Traffic Klien Masuk", + "resetInboundClientTrafficTitle": "Reset Traffic Klien Masuk", + "resetInboundClientTrafficContent": "Apakah Anda yakin ingin mereset traffic klien masuk ini?", + "resetAllClientTraffics": "Reset Traffic Semua Klien", + "resetAllClientTrafficTitle": "Reset Traffic Semua Klien", + "resetAllClientTrafficContent": "Apakah Anda yakin ingin mereset traffic semua klien?", + "delDepletedClients": "Hapus Klien Habis", + "delDepletedClientsTitle": "Hapus Klien Habis", + "delDepletedClientsContent": "Apakah Anda yakin ingin menghapus semua klien yang habis?", + "email": "Email", + "emailDesc": "Harap berikan alamat email yang unik.", + "IPLimit": "Batas IP", + "IPLimitDesc": "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)", + "IPLimitlog": "Log IP", + "IPLimitlogDesc": "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)", + "IPLimitlogclear": "Hapus Log", + "setDefaultCert": "Atur Sertifikat dari Panel", + "telegramDesc": "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau ({'@'}userinfobot)", + "subscriptionDesc": "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien.", + "info": "Info", + "same": "Sama", + "inboundData": "Data Masuk", + "exportInbound": "Ekspor Masuk", + "import": "Impor", + "importInbound": "Impor Masuk", + "periodicTrafficResetTitle": "Reset Trafik Berkala", + "periodicTrafficResetDesc": "Reset otomatis penghitung trafik pada interval tertentu", + "lastReset": "Reset Terakhir", + "periodicTrafficReset": { + "never": "Tidak Pernah", + "daily": "Harian", + "weekly": "Mingguan", + "monthly": "Bulanan", + "hourly": "Setiap jam" + }, + "toasts": { + "obtain": "Dapatkan", + "updateSuccess": "Pembaruan berhasil", + "logCleanSuccess": "Log telah dibersihkan", + "inboundsUpdateSuccess": "Inbound berhasil diperbarui", + "inboundUpdateSuccess": "Inbound berhasil diperbarui", + "inboundCreateSuccess": "Inbound berhasil dibuat", + "inboundDeleteSuccess": "Inbound berhasil dihapus", + "inboundClientAddSuccess": "Klien inbound telah ditambahkan", + "inboundClientDeleteSuccess": "Klien inbound telah dihapus", + "inboundClientUpdateSuccess": "Klien inbound telah diperbarui", + "delDepletedClientsSuccess": "Semua klien yang habis telah dihapus", + "resetAllClientTrafficSuccess": "Semua lalu lintas klien telah direset", + "resetAllTrafficSuccess": "Semua lalu lintas telah direset", + "resetInboundClientTrafficSuccess": "Lalu lintas telah direset", + "trafficGetError": "Gagal mendapatkan data lalu lintas", + "getNewX25519CertError": "Terjadi kesalahan saat mendapatkan sertifikat X25519.", + "getNewmldsa65Error": "Terjadi kesalahan saat mendapatkan sertifikat mldsa65.", + "getNewVlessEncError": "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc." + }, + "stream": { + "general": { + "request": "Permintaan", + "response": "Respons", + "name": "Nama", + "value": "Nilai" + }, + "tcp": { + "version": "Versi", + "method": "Metode", + "path": "Path", + "status": "Status", + "statusDescription": "Deskripsi Status", + "requestHeader": "Header Permintaan", + "responseHeader": "Header Respons" + } + } + }, + "client": { + "add": "Tambah Klien", + "edit": "Edit Klien", + "submitAdd": "Tambah Klien", + "submitEdit": "Simpan Perubahan", + "clientCount": "Jumlah Klien", + "bulk": "Tambahkan Massal", + "copyFromInbound": "Salin klien dari inbound", + "copyToInbound": "Salin klien ke", + "copySelected": "Salin yang dipilih", + "copySource": "Sumber", + "copyEmailPreview": "Pratinjau email hasil", + "copySelectSourceFirst": "Silakan pilih inbound sumber terlebih dahulu.", + "copyResult": "Hasil penyalinan", + "copyResultSuccess": "Berhasil disalin", + "copyResultNone": "Tidak ada yang disalin: tidak ada klien yang dipilih atau sumber kosong", + "copyResultErrors": "Kesalahan penyalinan", + "copyFlowLabel": "Flow untuk klien baru (VLESS)", + "copyFlowHint": "Diterapkan ke semua klien yang disalin. Biarkan kosong untuk melewati.", + "selectAll": "Pilih semua", + "clearAll": "Hapus semua", + "method": "Metode", + "first": "Pertama", + "last": "Terakhir", + "prefix": "Awalan", + "postfix": "Akhiran", + "delayedStart": "Mulai Awal", + "expireDays": "Durasi", + "days": "Hari", + "renew": "Perpanjang Otomatis", + "renewDesc": "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)" + }, + "nodes": { + "title": "Node", + "addNode": "Tambah Node", + "editNode": "Edit Node", + "totalNodes": "Total Node", + "onlineNodes": "Online", + "offlineNodes": "Offline", + "avgLatency": "Latensi Rata-rata", + "name": "Nama", + "namePlaceholder": "mis. de-frankfurt-1", + "addressPlaceholder": "panel.example.com atau 1.2.3.4", + "remark": "Catatan", + "scheme": "Skema", + "address": "Alamat", + "port": "Port", + "basePath": "Base Path", + "apiToken": "Token API", + "apiTokenPlaceholder": "Token dari halaman Pengaturan panel jarak jauh", + "apiTokenHint": "Panel jarak jauh menampilkan token API-nya di Pengaturan → Token API.", + "regenerate": "Buat Ulang Token", + "regenerateConfirm": "Membuat ulang akan membatalkan token saat ini. Setiap panel pusat yang menggunakannya akan kehilangan akses sampai diperbarui. Lanjutkan?", + "enable": "Aktif", + "status": "Status", + "cpu": "CPU", + "mem": "Memori", + "uptime": "Uptime", + "latency": "Latensi", + "lastHeartbeat": "Heartbeat Terakhir", + "xrayVersion": "Versi Xray", + "actions": "Aksi", + "probe": "Probe Sekarang", + "testConnection": "Tes Koneksi", + "connectionOk": "Koneksi OK ({ms} ms)", + "connectionFailed": "Koneksi gagal", + "never": "tidak pernah", + "justNow": "baru saja", + "deleteConfirmTitle": "Hapus node \"{name}\"?", + "deleteConfirmContent": "Ini menghentikan pemantauan node. Panel jarak jauh itu sendiri tidak terpengaruh.", + "statusValues": { + "online": "Online", + "offline": "Offline", + "unknown": "Tidak diketahui" + }, + "toasts": { + "list": "Gagal memuat node", + "obtain": "Gagal memuat node", + "add": "Tambah node", + "update": "Perbarui node", + "delete": "Hapus node", + "deleted": "Node dihapus", + "test": "Tes koneksi", + "fillRequired": "Nama, alamat, port, dan token API wajib diisi", + "probeFailed": "Probe gagal" + } + }, + "settings": { + "title": "Pengaturan Panel", + "save": "Simpan", + "infoDesc": "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan.", + "restartPanel": "Restart Panel", + "restartPanelDesc": "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server.", + "restartPanelSuccess": "Panel berhasil dimulai ulang", + "actions": "Tindakan", + "resetDefaultConfig": "Reset ke Default", + "panelSettings": "Umum", + "securitySettings": "Otentikasi", + "TGBotSettings": "Bot Telegram", + "panelListeningIP": "IP Pendengar", + "panelListeningIPDesc": "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)", + "panelListeningDomain": "Domain Pendengar", + "panelListeningDomainDesc": "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)", + "panelPort": "Port Pendengar", + "panelPortDesc": "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)", + "publicKeyPath": "Path Kunci Publik", + "publicKeyPathDesc": "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)", + "privateKeyPath": "Path Kunci Privat", + "privateKeyPathDesc": "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)", + "panelUrlPath": "URI Path", + "panelUrlPathDesc": "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)", + "pageSize": "Ukuran Halaman", + "pageSizeDesc": "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)", + "remarkModel": "Model Catatan & Karakter Pemisah", + "datepicker": "Jenis Kalender", + "datepickerPlaceholder": "Pilih tanggal", + "datepickerDescription": "Tugas terjadwal akan berjalan berdasarkan kalender ini.", + "sampleRemark": "Contoh Catatan", + "oldUsername": "Username Saat Ini", + "currentPassword": "Kata Sandi Saat Ini", + "newUsername": "Username Baru", + "newPassword": "Kata Sandi Baru", + "telegramBotEnable": "Aktifkan Bot Telegram", + "telegramBotEnableDesc": "Mengaktifkan bot Telegram.", + "telegramToken": "Token Telegram", + "telegramTokenDesc": "Token bot Telegram yang diperoleh dari '{'@'}BotFather'.", + "telegramProxy": "Proxy SOCKS", + "telegramProxyDesc": "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default.", + "telegramChatId": "ID Obrolan Admin", + "telegramChatIdDesc": "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini {'@'}userinfobot) atau (gunakan perintah '/id' di bot)", + "telegramNotifyTime": "Waktu Notifikasi", + "telegramNotifyTimeDesc": "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)", + "tgNotifyBackup": "Cadangan Database", + "tgNotifyBackupDesc": "Kirim berkas cadangan database dengan laporan.", + "tgNotifyLogin": "Notifikasi Login", + "tgNotifyLoginDesc": "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda.", + "sessionMaxAge": "Durasi Sesi", + "sessionMaxAgeDesc": "Durasi di mana Anda dapat tetap masuk. (unit: menit)", + "expireTimeDiff": "Notifikasi Tanggal Kedaluwarsa", + "expireTimeDiffDesc": "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)", + "trafficDiff": "Notifikasi Batas Traffic", + "trafficDiffDesc": "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)", + "tgNotifyCpu": "Notifikasi Beban CPU", + "tgNotifyCpuDesc": "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)", + "timeZone": "Zone Waktu", + "timeZoneDesc": "Tugas terjadwal akan berjalan berdasarkan zona waktu ini.", + "subSettings": "Langganan", + "subEnable": "Aktifkan Layanan Langganan", + "subEnableDesc": "Mengaktifkan layanan langganan.", + "subJsonEnable": "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri.", + "subTitle": "Judul Langganan", + "subTitleDesc": "Judul yang ditampilkan di klien VPN", + "subSupportUrl": "URL Dukungan", + "subSupportUrlDesc": "Tautan dukungan teknis yang ditampilkan di klien VPN", + "subProfileUrl": "URL Profil", + "subProfileUrlDesc": "Tautan ke situs web Anda yang ditampilkan di klien VPN", + "subAnnounce": "Pengumuman", + "subAnnounceDesc": "Teks pengumuman yang ditampilkan di klien VPN", + "subEnableRouting": "Aktifkan perutean", + "subEnableRoutingDesc": "Pengaturan global untuk mengaktifkan perutean (routing) di klien VPN. (Hanya untuk Happ)", + "subRoutingRules": "Aturan routing", + "subRoutingRulesDesc": "Aturan routing global untuk klien VPN. (Hanya untuk Happ)", + "subListen": "IP Pendengar", + "subListenDesc": "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)", + "subPort": "Port Pendengar", + "subPortDesc": "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)", + "subCertPath": "Path Kunci Publik", + "subCertPathDesc": "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)", + "subKeyPath": "Path Kunci Privat", + "subKeyPathDesc": "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)", + "subPath": "URI Path", + "subPathDesc": "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)", + "subDomain": "Domain Pendengar", + "subDomainDesc": "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)", + "subUpdates": "Interval Pembaruan", + "subUpdatesDesc": "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)", + "subEncrypt": "Encode", + "subEncryptDesc": "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64.", + "subShowInfo": "Tampilkan Info Penggunaan", + "subShowInfoDesc": "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien.", + "subURI": "URI Proxy Terbalik", + "subURIDesc": "Path URI dari URL langganan untuk digunakan di belakang proxy.", + "externalTrafficInformEnable": "Informasikan API eksternal pada setiap pembaruan lalu lintas.", + "externalTrafficInformEnableDesc": "Inform external API on every traffic update.", + "externalTrafficInformURI": "Lalu Lintas Eksternal Menginformasikan URI", + "externalTrafficInformURIDesc": "Pembaruan lalu lintas dikirim ke URI ini.", + "restartXrayOnClientDisable": "Nyalakan Ulang Xray Setelah Nonaktif Otomatis", + "restartXrayOnClientDisableDesc": "Saat klien otomatis dinonaktifkan karena kedaluwarsa atau batas trafik, mulai ulang Xray.", + "fragment": "Fragmentasi", + "fragmentDesc": "Aktifkan fragmentasi untuk paket hello TLS", + "fragmentSett": "Pengaturan Fragmentasi", + "noisesDesc": "Aktifkan Noises.", + "noisesSett": "Pengaturan Noises", + "mux": "Mux", + "muxDesc": "Mengirimkan beberapa aliran data independen dalam aliran data yang sudah ada.", + "muxSett": "Pengaturan Mux", + "direct": "Koneksi langsung", + "directDesc": "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu.", + "notifications": "Notifikasi", + "certs": "Sertifikat", + "externalTraffic": "Lalu Lintas Eksternal", + "dateAndTime": "Tanggal dan Waktu", + "proxyAndServer": "Proxy dan Server", + "intervals": "Interval", + "information": "Informasi", + "language": "Bahasa", + "telegramBotLanguage": "Bahasa Bot Telegram", + "security": { + "admin": "Kredensial admin", + "twoFactor": "Autentikasi dua faktor", + "twoFactorEnable": "Aktifkan 2FA", + "twoFactorEnableDesc": "Menambahkan lapisan autentikasi tambahan untuk keamanan lebih.", + "twoFactorModalSetTitle": "Aktifkan autentikasi dua faktor", + "twoFactorModalDeleteTitle": "Nonaktifkan autentikasi dua faktor", + "twoFactorModalSteps": "Untuk menyiapkan autentikasi dua faktor, lakukan beberapa langkah:", + "twoFactorModalFirstStep": "1. Pindai kode QR ini di aplikasi autentikasi atau salin token di dekat kode QR dan tempelkan ke aplikasi", + "twoFactorModalSecondStep": "2. Masukkan kode dari aplikasi", + "twoFactorModalRemoveStep": "Masukkan kode dari aplikasi untuk menghapus autentikasi dua faktor.", + "twoFactorModalChangeCredentialsTitle": "Ubah kredensial", + "twoFactorModalChangeCredentialsStep": "Masukkan kode dari aplikasi untuk mengubah kredensial administrator.", + "twoFactorModalSetSuccess": "Autentikasi dua faktor telah berhasil dibuat", + "twoFactorModalDeleteSuccess": "Autentikasi dua faktor telah berhasil dihapus", + "twoFactorModalError": "Kode salah" + }, + "toasts": { + "modifySettings": "Parameter telah diubah.", + "getSettings": "Terjadi kesalahan saat mengambil parameter.", + "modifyUserError": "Terjadi kesalahan saat mengubah kredensial administrator.", + "modifyUser": "Anda telah berhasil mengubah kredensial administrator.", + "originalUserPassIncorrect": "Username atau password saat ini tidak valid", + "userPassMustBeNotEmpty": "Username dan password baru tidak boleh kosong", + "getOutboundTrafficError": "Gagal mendapatkan lalu lintas keluar", + "resetOutboundTrafficError": "Gagal mereset lalu lintas keluar" + } + }, + "xray": { + "title": "Konfigurasi Xray", + "save": "Simpan", + "restart": "Restart Xray", + "restartSuccess": "Xray berhasil diluncurkan ulang", + "stopSuccess": "Xray telah berhasil dihentikan", + "restartError": "Terjadi kesalahan saat memulai ulang Xray.", + "stopError": "Terjadi kesalahan saat menghentikan Xray.", + "basicTemplate": "Dasar", + "advancedTemplate": "Lanjutan", + "generalConfigs": "Strategi Umum", + "generalConfigsDesc": "Opsi ini akan menentukan penyesuaian strategi umum.", + "logConfigs": "Catatan", + "logConfigsDesc": "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan", + "blockConfigsDesc": "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta.", + "basicRouting": "Perutean Dasar", + "blockConnectionsConfigsDesc": "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta.", + "directConnectionsConfigsDesc": "Koneksi langsung memastikan bahwa lalu lintas tertentu tidak dialihkan melalui server lain.", + "blockips": "Blokir IP", + "blockdomains": "Blokir Domain", + "directips": "IP Langsung", + "directdomains": "Domain Langsung", + "ipv4Routing": "Perutean IPv4", + "ipv4RoutingDesc": "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4.", + "warpRouting": "Perutean WARP", + "warpRoutingDesc": "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP.", + "nordRouting": "Routing NordVPN", + "nordRoutingDesc": "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui NordVPN.", + "Template": "Template Konfigurasi Xray Lanjutan", + "TemplateDesc": "File konfigurasi Xray akhir akan dibuat berdasarkan template ini.", + "FreedomStrategy": "Strategi Protokol Freedom", + "FreedomStrategyDesc": "Atur strategi output untuk jaringan dalam Protokol Freedom.", + "RoutingStrategy": "Strategi Pengalihan Keseluruhan", + "RoutingStrategyDesc": "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan.", + "outboundTestUrl": "URL tes outbound", + "outboundTestUrlDesc": "URL yang digunakan saat menguji konektivitas outbound", + "Torrent": "Blokir Protokol BitTorrent", + "Inbounds": "Masuk", + "InboundsDesc": "Menerima klien tertentu.", + "Outbounds": "Keluar", + "Balancers": "Penyeimbang", + "OutboundsDesc": "Atur jalur lalu lintas keluar.", + "Routings": "Aturan Pengalihan", + "RoutingsDesc": "Prioritas setiap aturan penting!", + "completeTemplate": "Semua", + "logLevel": "Tingkat Log", + "logLevelDesc": "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat.", + "accessLog": "Log Akses", + "accessLogDesc": "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses", + "errorLog": "Catatan eror", + "errorLogDesc": "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan", + "dnsLog": "Log DNS", + "dnsLogDesc": "Apakah akan mengaktifkan log kueri DNS", + "maskAddress": "Alamat Masker", + "maskAddressDesc": "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log.", + "statistics": "Statistik", + "statsInboundUplink": "Statistik Unggah Masuk", + "statsInboundUplinkDesc": "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy masuk.", + "statsInboundDownlink": "Statistik Unduh Masuk", + "statsInboundDownlinkDesc": "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy masuk.", + "statsOutboundUplink": "Statistik Unggah Keluar", + "statsOutboundUplinkDesc": "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy keluar.", + "statsOutboundDownlink": "Statistik Unduh Keluar", + "statsOutboundDownlinkDesc": "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy keluar.", + "rules": { + "first": "Pertama", + "last": "Terakhir", + "up": "Naik", + "down": "Turun", + "source": "Sumber", + "dest": "Tujuan", + "inbound": "Masuk", + "outbound": "Keluar", + "balancer": "Pengimbang", + "info": "Info", + "add": "Tambahkan Aturan", + "edit": "Edit Aturan", + "useComma": "Item yang dipisahkan koma" + }, + "outbound": { + "addOutbound": "Tambahkan Keluar", + "addReverse": "Tambahkan Revers", + "editOutbound": "Edit Keluar", + "editReverse": "Edit Revers", + "reverseTag": "Tag Revers", + "reverseTagDesc": "Tag outbound proxy revers sederhana VLESS. Kosongkan untuk menonaktifkan.", + "reverseTagPlaceholder": "tag outbound (kosong untuk menonaktifkan)", + "tag": "Tag", + "tagDesc": "Tag Unik", + "address": "Alamat", + "reverse": "Revers", + "domain": "Domain", + "type": "Tipe", + "bridge": "Jembatan", + "portal": "Portal", + "link": "Tautan", + "intercon": "Interkoneksi", + "settings": "Pengaturan", + "accountInfo": "Informasi Akun", + "outboundStatus": "Status Keluar", + "sendThrough": "Kirim Melalui", + "test": "Tes", + "testResult": "Hasil Tes", + "testing": "Menguji koneksi...", + "testSuccess": "Tes berhasil", + "testFailed": "Tes gagal", + "testError": "Gagal menguji outbound", + "nordvpn": "NordVPN", + "accessToken": "Token Akses", + "country": "Negara", + "server": "Server", + "city": "Kota", + "allCities": "Semua Kota", + "privateKey": "Kunci Privat", + "load": "Beban" + }, + "balancer": { + "addBalancer": "Tambahkan Penyeimbang", + "editBalancer": "Sunting Penyeimbang", + "balancerStrategy": "Strategi", + "balancerSelectors": "Penyeleksi", + "tag": "Menandai", + "tagDesc": "Label Unik", + "balancerDesc": "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi." + }, + "wireguard": { + "secretKey": "Kunci Rahasia", + "publicKey": "Kunci Publik", + "allowedIPs": "IP yang Diizinkan", + "endpoint": "Titik Akhir", + "psk": "Kunci Pra-Bagi", + "domainStrategy": "Strategi Domain" + }, + "tun": { + "nameDesc": "Nama antarmuka TUN. Standar adalah 'xray0'", + "mtuDesc": "Unit Transmisi Maksimum. Ukuran maksimum paket data. Standar adalah 1500", + "userLevel": "Level Pengguna", + "userLevelDesc": "Semua koneksi yang dibuat melalui inbound ini akan menggunakan level pengguna ini. Standar adalah 0" + }, + "dns": { + "enable": "Aktifkan DNS", + "enableDesc": "Aktifkan server DNS bawaan", + "tag": "Tanda DNS Masuk", + "tagDesc": "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan.", + "clientIp": "IP Klien", + "clientIpDesc": "Digunakan untuk memberi tahu server tentang lokasi IP yang ditentukan selama kueri DNS", + "disableCache": "Nonaktifkan cache", + "disableCacheDesc": "Menonaktifkan caching DNS", + "disableFallback": "Nonaktifkan Fallback", + "disableFallbackDesc": "Menonaktifkan kueri DNS fallback", + "disableFallbackIfMatch": "Nonaktifkan Fallback Jika Cocok", + "disableFallbackIfMatchDesc": "Menonaktifkan kueri DNS fallback ketika daftar domain yang cocok dari server DNS terpenuhi", + "enableParallelQuery": "Aktifkan Kueri Paralel", + "enableParallelQueryDesc": "Aktifkan kueri DNS paralel ke beberapa server untuk resolusi yang lebih cepat", + "strategy": "Strategi Kueri", + "strategyDesc": "Strategi keseluruhan untuk menyelesaikan nama domain", + "add": "Tambahkan Server", + "edit": "Sunting Server", + "domains": "Domains", + "expectIPs": "IP yang Diharapkan", + "unexpectIPs": "IP tak terduga", + "useSystemHosts": "Gunakan Hosts Sistem", + "useSystemHostsDesc": "Gunakan file hosts dari sistem yang terinstal", + "usePreset": "Gunakan templat", + "dnsPresetTitle": "Templat DNS", + "dnsPresetFamily": "Keluarga" + }, + "fakedns": { + "add": "Tambahkan DNS Palsu", + "edit": "Edit DNS Palsu", + "ipPool": "Subnet Kumpulan IP", + "poolSize": "Ukuran Kolam" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Keyboard ditutup!", + "noResult": "❗ Tidak ada hasil!", + "noQuery": "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!", + "wentWrong": "❌ Terjadi kesalahan!", + "noIpRecord": "❗ Tidak ada Catatan IP!", + "noInbounds": "❗ Tidak ada inbound yang ditemukan!", + "unlimited": "♾ Tidak terbatas (Reset)", + "add": "Tambah", + "month": "Bulan", + "months": "Bulan", + "day": "Hari", + "days": "Hari", + "hours": "Jam", + "minutes": "Menit", + "unknown": "Tidak diketahui", + "inbounds": "Inbound", + "clients": "Klien", + "offline": "🔴 Offline", + "online": "🟢 Online", + "commands": { + "unknown": "❗ Perintah tidak dikenal.", + "pleaseChoose": "👇 Harap pilih:\r\n", + "help": "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n", + "start": "👋 Halo {{ .Firstname }}.\r\n", + "welcome": "🤖 Selamat datang di {{.Hostname }} bot managemen.\r\n", + "status": "✅ Bot dalam keadaan baik!", + "usage": "❗ Harap berikan teks untuk mencari!", + "getID": "🆔 ID Anda: {{ .ID }}", + "helpAdminCommands": "Untuk memulai ulang Xray Core:\r\n/restart\r\n\r\nUntuk mencari email klien:\r\n/usage [Email]\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n/inbound [Catatan]\r\n\r\nID Obrolan Telegram:\r\n/id", + "helpClientCommands": "Untuk mencari statistik, gunakan perintah berikut:\r\n/usage [Email]\r\n\r\nID Obrolan Telegram:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Operasi berhasil!", + "restartFailed": "❗ Kesalahan dalam operasi.\r\n\r\nError: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core tidak berjalan.", + "startDesc": "Tampilkan menu utama", + "helpDesc": "Bantuan bot", + "statusDesc": "Periksa status bot", + "idDesc": "Tampilkan ID Telegram Anda" + }, + "messages": { + "cpuThreshold": "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%", + "selectUserFailed": "❌ Kesalahan dalam pemilihan pengguna!", + "userSaved": "✅ Pengguna Telegram tersimpan.", + "loginSuccess": "✅ Berhasil masuk ke panel.\r\n", + "loginFailed": "❗️ Gagal masuk ke panel.\r\n", + "2faFailed": "2FA Gagal", + "report": "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n", + "datetime": "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n", + "hostname": "💻 Host: {{ .Hostname }}\r\n", + "version": "🚀 Versi 3X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 Versi Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IP:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Status: {{ .State }}\r\n", + "username": "👤 Nama Pengguna: {{ .Username }}\r\n", + "reason": "❗️ Alasan: {{ .Reason }}\r\n", + "time": "⏰ Waktu: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Port: {{ .Port }}\r\n", + "expire": "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n", + "expireIn": "📅 Kadaluarsa Dalam: {{ .Time }}\r\n", + "active": "💡 Aktif: {{ .Enable }}\r\n", + "enabled": "🚨 Diaktifkan: {{ .Enable }}\r\n", + "online": "🌐 Status Koneksi: {{ .Status }}\r\n", + "lastOnline": "🔙 Terakhir online: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Unggah: ↑{{ .Upload }}\r\n", + "download": "🔽 Unduh: ↓{{ .Download }}\r\n", + "total": "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Pengguna Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Habis {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Jumlah Habis {{ .Type }}:\r\n", + "onlinesCount": "🌐 Klien Online: {{ .Count }}\r\n", + "disabled": "🛑 Dinonaktifkan: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Waktu Backup: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n", + "yes": "✅ Ya", + "no": "❌ Tidak", + "received_id": "🔑📥 ID diperbarui.", + "received_password": "🔑📥 Kata sandi diperbarui.", + "received_email": "📧📥 Email diperbarui.", + "received_comment": "💬📥 Komentar diperbarui.", + "id_prompt": "🔑 ID Default: {{ .ClientId }}\n\nMasukkan ID Anda.", + "pass_prompt": "🔑 Kata Sandi Default: {{ .ClientPassword }}\n\nMasukkan kata sandi Anda.", + "email_prompt": "📧 Email Default: {{ .ClientEmail }}\n\nMasukkan email Anda.", + "comment_prompt": "💬 Komentar Default: {{ .ClientComment }}\n\nMasukkan komentar Anda.", + "inbound_client_data_id": "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!", + "inbound_client_data_pass": "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 Kata sandi: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!", + "cancel": "❌ Proses Dibatalkan! \n\nAnda dapat /start lagi kapan saja. 🔄", + "error_add_client": "⚠️ Kesalahan:\n\n {{ .error }}", + "using_default_value": "Oke, saya akan tetap menggunakan nilai default. 😊", + "incorrect_input": "Masukan Anda tidak valid.\nFrasa harus berlanjut tanpa spasi.\nContoh benar: aaaaaa\nContoh salah: aaa aaa 🚫", + "AreYouSure": "Apakah kamu yakin? 🤔", + "SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ✅ Berhasil", + "FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ❌ Gagal \n\n🛠️ Kesalahan: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Proses reset traffic selesai untuk semua klien." + }, + "buttons": { + "closeKeyboard": "❌ Tutup Papan Ketik", + "cancel": "❌ Batal", + "cancelReset": "❌ Batal Reset", + "cancelIpLimit": "❌ Batal Batas IP", + "confirmResetTraffic": "✅ Konfirmasi Reset Lalu Lintas?", + "confirmClearIps": "✅ Konfirmasi Hapus IPs?", + "confirmRemoveTGUser": "✅ Konfirmasi Hapus Pengguna Telegram?", + "confirmToggle": "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?", + "dbBackup": "Dapatkan Cadangan DB", + "serverUsage": "Penggunaan Server", + "getInbounds": "Dapatkan Inbounds", + "depleteSoon": "Habis Sebentar", + "clientUsage": "Dapatkan Penggunaan", + "onlines": "Klien Online", + "commands": "Perintah", + "refresh": "🔄 Perbarui", + "clearIPs": "❌ Hapus IPs", + "removeTGUser": "❌ Hapus Pengguna Telegram", + "selectTGUser": "👤 Pilih Pengguna Telegram", + "selectOneTGUser": "👤 Pilih Pengguna Telegram:", + "resetTraffic": "📈 Reset Lalu Lintas", + "resetExpire": "📅 Ubah Tanggal Kadaluarsa", + "ipLog": "🔢 Log IP", + "ipLimit": "🔢 Batas IP", + "setTGUser": "👤 Set Pengguna Telegram", + "toggle": "🔘 Aktifkan / Nonaktifkan", + "custom": "🔢 Kustom", + "confirmNumber": "✅ Konfirmasi: {{ .Num }}", + "confirmNumberAdd": "✅ Konfirmasi menambahkan: {{ .Num }}", + "limitTraffic": "🚧 Batas Lalu Lintas", + "getBanLogs": "Dapatkan Log Pemblokiran", + "allClients": "Semua Klien", + "addClient": "Tambah Klien", + "submitDisable": "Kirim Sebagai Nonaktif ☑️", + "submitEnable": "Kirim Sebagai Aktif ✅", + "use_default": "🏷️ Gunakan Default", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Kata Sandi", + "change_email": "⚙️📧 Email", + "change_comment": "⚙️💬 Komentar", + "ResetAllTraffics": "Reset Semua Lalu Lintas", + "SortedTrafficUsageReport": "Laporan Penggunaan Lalu Lintas yang Terurut" + }, + "answers": { + "successfulOperation": "✅ Operasi berhasil!", + "errorOperation": "❗ Kesalahan dalam operasi.", + "getInboundsFailed": "❌ Gagal mendapatkan inbounds.", + "getClientsFailed": "❌ Gagal mendapatkan klien.", + "canceled": "❌ {{ .Email }}: Operasi dibatalkan.", + "clientRefreshSuccess": "✅ {{ .Email }}: Klien diperbarui dengan berhasil.", + "IpRefreshSuccess": "✅ {{ .Email }}: IP diperbarui dengan berhasil.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil.", + "resetTrafficSuccess": "✅ {{ .Email }}: Lalu lintas direset dengan berhasil.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil.", + "expireResetSuccess": "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil.", + "resetIpSuccess": "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil.", + "clearIpSuccess": "✅ {{ .Email }}: IP dihapus dengan berhasil.", + "getIpLog": "✅ {{ .Email }}: Dapatkan Log IP.", + "getUserInfo": "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram.", + "removedTGUserSuccess": "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil.", + "enableSuccess": "✅ {{ .Email }}: Diaktifkan dengan berhasil.", + "disableSuccess": "✅ {{ .Email }}: Dinonaktifkan dengan berhasil.", + "askToAddUserId": "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ChatID Telegram Anda dalam konfigurasi Anda.\r\n\r\nChatID Pengguna Anda: {{ .TgUserID }}", + "chooseClient": "Pilih Klien untuk Inbound {{ .Inbound }}", + "chooseInbound": "Pilih Inbound" + } + } +} diff --git a/web/translation/ja-JP.json b/web/translation/ja-JP.json new file mode 100644 index 00000000..25d81814 --- /dev/null +++ b/web/translation/ja-JP.json @@ -0,0 +1,941 @@ +{ + "username": "ユーザー名", + "password": "パスワード", + "login": "ログイン", + "confirm": "確認", + "cancel": "キャンセル", + "close": "閉じる", + "save": "保存", + "logout": "ログアウト", + "create": "作成", + "update": "更新", + "copy": "コピー", + "copied": "コピー済み", + "download": "ダウンロード", + "remark": "備考", + "enable": "有効化", + "protocol": "プロトコル", + "search": "検索", + "filter": "フィルター", + "loading": "読み込み中...", + "second": "秒", + "minute": "分", + "hour": "時間", + "day": "日", + "check": "確認", + "indefinite": "無期限", + "unlimited": "無制限", + "none": "なし", + "qrCode": "QRコード", + "info": "詳細情報", + "edit": "編集", + "delete": "削除", + "reset": "リセット", + "noData": "データなし。", + "copySuccess": "コピー成功", + "sure": "確定", + "encryption": "暗号化", + "useIPv4ForHost": "ホストにIPv4を使用", + "transmission": "伝送", + "host": "ホスト", + "path": "パス", + "camouflage": "偽装", + "status": "ステータス", + "enabled": "有効", + "disabled": "無効", + "depleted": "消耗済み", + "depletingSoon": "間もなく消耗", + "offline": "オフライン", + "online": "オンライン", + "domainName": "ドメイン名", + "monitor": "監視", + "certificate": "証明書", + "fail": "失敗", + "comment": "コメント", + "success": "成功", + "lastOnline": "最終オンライン", + "getVersion": "バージョン取得", + "install": "インストール", + "clients": "クライアント", + "usage": "利用状況", + "twoFactorCode": "コード", + "remained": "残り", + "security": "セキュリティ", + "secAlertTitle": "セキュリティアラート", + "secAlertSsl": "この接続は安全ではありません。TLSを有効にしてデータ保護を行うまで、機密情報を入力しないでください。", + "secAlertConf": "一部の設定は脆弱です。潜在的な脆弱性を防ぐために、セキュリティプロトコルを強化することをお勧めします。", + "secAlertSSL": "セキュアな接続がありません。データ保護のためにTLS証明書をインストールしてください。", + "secAlertPanelPort": "デフォルトのポートにはセキュリティリスクがあります。ランダムなポートまたは特定のポートを設定してください。", + "secAlertPanelURI": "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。", + "secAlertSubURI": "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。", + "secAlertSubJsonURI": "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。", + "emptyDnsDesc": "追加されたDNSサーバーはありません。", + "emptyFakeDnsDesc": "追加されたFake DNSサーバーはありません。", + "emptyBalancersDesc": "追加されたバランサーはありません。", + "emptyReverseDesc": "追加されたリバースプロキシはありません。", + "somethingWentWrong": "エラーが発生しました", + "subscription": { + "title": "サブスクリプション情報", + "subId": "サブスクリプションID", + "status": "ステータス", + "downloaded": "ダウンロード", + "uploaded": "アップロード", + "expiry": "有効期限", + "totalQuota": "合計クォータ", + "individualLinks": "個別リンク", + "active": "有効", + "inactive": "無効", + "unlimited": "無制限", + "noExpiry": "期限なし" + }, + "menu": { + "theme": "テーマ", + "dark": "ダーク", + "ultraDark": "ウルトラダーク", + "dashboard": "ダッシュボード", + "inbounds": "インバウンド一覧", + "settings": "パネル設定", + "xray": "Xray設定", + "logout": "ログアウト", + "link": "リンク管理" + }, + "pages": { + "login": { + "hello": "こんにちは", + "title": "ようこそ", + "loginAgain": "ログインセッションが切れました。再度ログインしてください。", + "toasts": { + "invalidFormData": "データ形式エラー", + "emptyUsername": "ユーザー名を入力してください", + "emptyPassword": "パスワードを入力してください", + "wrongUsernameOrPassword": "ユーザー名、パスワード、または二段階認証コードが無効です。", + "successLogin": "アカウントに正常にログインしました。" + } + }, + "index": { + "title": "システムステータス", + "cpu": "CPU", + "logicalProcessors": "論理プロセッサ", + "frequency": "周波数", + "swap": "スワップ", + "storage": "ストレージ", + "memory": "RAM", + "threads": "スレッド", + "xrayStatus": "Xray", + "stopXray": "停止", + "restartXray": "再起動", + "xraySwitch": "バージョン", + "xraySwitchClick": "切り替えるバージョンを選択してください", + "xraySwitchClickDesk": "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。", + "xrayUpdates": "Xrayの更新", + "updatePanel": "パネルを更新", + "panelUpdateDesc": "これにより3X-UIが最新リリースに更新され、パネルサービスが再起動されます。", + "currentPanelVersion": "現在のパネルバージョン", + "latestPanelVersion": "最新のパネルバージョン", + "panelUpToDate": "パネルは最新です", + "upToDate": "最新", + "xrayStatusUnknown": "不明", + "xrayStatusRunning": "実行中", + "xrayStatusStop": "停止", + "xrayStatusError": "エラー", + "xrayErrorPopoverTitle": "Xrayの実行中にエラーが発生しました", + "operationHours": "システム稼働時間", + "systemHistoryTitle": "システム履歴", + "trendLast2Min": "直近2分", + "systemLoad": "システム負荷", + "systemLoadDesc": "過去1、5、15分間のシステム平均負荷", + "connectionCount": "接続数", + "ipAddresses": "IPアドレス", + "toggleIpVisibility": "IPの表示を切り替える", + "overallSpeed": "全体の速度", + "upload": "アップロード", + "download": "ダウンロード", + "totalData": "総データ量", + "sent": "送信", + "received": "受信", + "documentation": "ドキュメント", + "xraySwitchVersionDialog": "Xrayのバージョンを本当に変更しますか?", + "xraySwitchVersionDialogDesc": "Xrayのバージョンが#version#に変更されます。", + "xraySwitchVersionPopover": "Xrayの更新が成功しました", + "panelUpdateDialog": "本当にパネルを更新しますか?", + "panelUpdateDialogDesc": "これにより3X-UIが#version#に更新され、パネルサービスが再起動されます。", + "panelUpdateCheckPopover": "パネルの更新確認に失敗しました", + "panelUpdateStartedPopover": "パネルの更新を開始しました", + "geofileUpdateDialog": "ジオファイルを本当に更新しますか?", + "geofileUpdateDialogDesc": "これにより#filename#ファイルが更新されます。", + "geofilesUpdateDialogDesc": "これにより、すべてのファイルが更新されます。", + "geofilesUpdateAll": "すべて更新", + "geofileUpdatePopover": "ジオファイルの更新が成功しました", + "dontRefresh": "インストール中、このページをリロードしないでください", + "logs": "ログ", + "config": "設定", + "backup": "バックアップ", + "backupTitle": "バックアップと復元", + "exportDatabase": "バックアップ", + "exportDatabaseDesc": "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。", + "importDatabase": "復元", + "importDatabaseDesc": "クリックして、デバイスから .db ファイルを選択し、アップロードしてバックアップからデータベースを復元します。", + "importDatabaseSuccess": "データベースのインポートに成功しました", + "importDatabaseError": "データベースのインポート中にエラーが発生しました", + "readDatabaseError": "データベースの読み取り中にエラーが発生しました", + "getDatabaseError": "データベースの取得中にエラーが発生しました", + "getConfigError": "設定ファイルの取得中にエラーが発生しました", + "customGeoTitle": "カスタム GeoSite / GeoIP", + "customGeoAdd": "追加", + "customGeoType": "種類", + "customGeoAlias": "エイリアス", + "customGeoUrl": "URL", + "customGeoEnabled": "有効", + "customGeoLastUpdated": "最終更新", + "customGeoExtColumn": "ルーティング (ext:…)", + "customGeoToastUpdateAll": "すべてのカスタムソースを更新しました", + "customGeoActions": "操作", + "customGeoEdit": "編集", + "customGeoDelete": "削除", + "customGeoDownload": "今すぐ更新", + "customGeoModalAdd": "カスタム geo を追加", + "customGeoModalEdit": "カスタム geo を編集", + "customGeoModalSave": "保存", + "customGeoDeleteConfirm": "このカスタム geo ソースを削除しますか?", + "customGeoRoutingHint": "ルーティングでは値を ext:ファイル.dat:タグ(タグを置換)として使います。", + "customGeoInvalidId": "無効なリソース ID", + "customGeoAliasesError": "カスタム geo エイリアスの読み込みに失敗しました", + "customGeoValidationAlias": "エイリアスは小文字・数字・- と _ のみ使用できます", + "customGeoValidationUrl": "URL は http:// または https:// で始めてください", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": "(カスタム)", + "customGeoToastList": "カスタム geo 一覧", + "customGeoToastAdd": "カスタム geo を追加", + "customGeoToastUpdate": "カスタム geo を更新", + "customGeoToastDelete": "カスタム geofile「{{ .fileName }}」を削除しました", + "customGeoToastDownload": "geofile「{{ .fileName }}」を更新しました", + "customGeoErrInvalidType": "種類は geosite または geoip である必要があります", + "customGeoErrAliasRequired": "エイリアスが必要です", + "customGeoErrAliasPattern": "エイリアスに使用できない文字が含まれています", + "customGeoErrAliasReserved": "このエイリアスは予約されています", + "customGeoErrUrlRequired": "URL が必要です", + "customGeoErrInvalidUrl": "URL が無効です", + "customGeoErrUrlScheme": "URL は http または https を使用してください", + "customGeoErrUrlHost": "URL のホストが無効です", + "customGeoErrDuplicateAlias": "この種類ですでにこのエイリアスが使われています", + "customGeoErrNotFound": "カスタム geo ソースが見つかりません", + "customGeoErrDownload": "ダウンロードに失敗しました", + "customGeoErrUpdateAllIncomplete": "カスタム geo ソースの 1 件以上を更新できませんでした", + "customGeoEmpty": "カスタム geo ソースはまだありません — 「追加」をクリックして作成してください" + }, + "inbounds": { + "allTimeTraffic": "総トラフィック", + "allTimeTrafficUsage": "これまでの総使用量", + "title": "インバウンド一覧", + "totalDownUp": "総アップロード / ダウンロード", + "totalUsage": "総使用量", + "inboundCount": "インバウンド数", + "operate": "メニュー", + "enable": "有効化", + "remark": "備考", + "node": "ノード", + "deployTo": "デプロイ先", + "localPanel": "ローカルパネル", + "protocol": "プロトコル", + "port": "ポート", + "portMap": "ポートマッピング", + "traffic": "トラフィック", + "details": "詳細情報", + "transportConfig": "トランスポート設定", + "expireDate": "有効期限", + "createdAt": "作成", + "updatedAt": "更新", + "resetTraffic": "トラフィックリセット", + "addInbound": "インバウンド追加", + "generalActions": "一般操作", + "modifyInbound": "インバウンド修正", + "deleteInbound": "インバウンド削除", + "deleteInboundContent": "インバウンドを削除してもよろしいですか?", + "deleteClient": "クライアント削除", + "deleteClientContent": "クライアントを削除してもよろしいですか?", + "resetTrafficContent": "トラフィックをリセットしてもよろしいですか?", + "copyLink": "リンクをコピー", + "address": "アドレス", + "network": "ネットワーク", + "destinationPort": "宛先ポート", + "targetAddress": "宛先アドレス", + "monitorDesc": "空白にするとすべてのIPを監視", + "meansNoLimit": "= 無制限(単位:GB)", + "totalFlow": "総トラフィック", + "leaveBlankToNeverExpire": "空白にすると期限なし", + "noRecommendKeepDefault": "デフォルト値を保持することをお勧めします", + "certificatePath": "ファイルパス", + "certificateContent": "ファイル内容", + "publicKey": "公開鍵", + "privatekey": "秘密鍵", + "clickOnQRcode": "QRコードをクリックしてコピー", + "client": "クライアント", + "export": "リンクエクスポート", + "clone": "複製", + "cloneInbound": "複製", + "cloneInboundContent": "このインバウンドルールは、ポート(Port)、リスニングIP(Listening IP)、クライアント(Clients)を除くすべての設定がクローンされます", + "cloneInboundOk": "クローン作成", + "resetAllTraffic": "すべてのインバウンドトラフィックをリセット", + "resetAllTrafficTitle": "すべてのインバウンドトラフィックをリセット", + "resetAllTrafficContent": "すべてのインバウンドトラフィックをリセットしてもよろしいですか?", + "resetInboundClientTraffics": "クライアントトラフィックをリセット", + "resetInboundClientTrafficTitle": "すべてのクライアントトラフィックをリセット", + "resetInboundClientTrafficContent": "このインバウンドクライアントのすべてのトラフィックをリセットしてもよろしいですか?", + "resetAllClientTraffics": "すべてのクライアントトラフィックをリセット", + "resetAllClientTrafficTitle": "すべてのクライアントトラフィックをリセット", + "resetAllClientTrafficContent": "すべてのクライアントのトラフィックをリセットしてもよろしいですか?", + "delDepletedClients": "トラフィックが尽きたクライアントを削除", + "delDepletedClientsTitle": "トラフィックが尽きたクライアントを削除", + "delDepletedClientsContent": "トラフィックが尽きたすべてのクライアントを削除してもよろしいですか?", + "email": "メールアドレス", + "emailDesc": "メールアドレスは一意でなければなりません", + "IPLimit": "IP制限", + "IPLimitDesc": "設定値を超えるとインバウンドトラフィックが無効になります。(0 = 無効)", + "IPLimitlog": "IPログ", + "IPLimitlogDesc": "IP履歴ログ(無効なインバウンドトラフィックを有効にするには、ログをクリアしてください)", + "IPLimitlogclear": "ログをクリア", + "setDefaultCert": "パネル設定から証明書を設定", + "telegramDesc": "TelegramチャットIDを提供してください。(ボットで'/id'コマンドを使用)または({'@'}userinfobot)", + "subscriptionDesc": "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。", + "info": "情報", + "same": "同じ", + "inboundData": "インバウンドデータ", + "exportInbound": "インバウンドルールをエクスポート", + "import": "インポート", + "importInbound": "インバウンドルールをインポート", + "periodicTrafficResetTitle": "トラフィックリセット", + "periodicTrafficResetDesc": "指定された間隔でトラフィックカウンタを自動的にリセット", + "lastReset": "最後のリセット", + "periodicTrafficReset": { + "never": "なし", + "daily": "毎日", + "weekly": "毎週", + "monthly": "毎月", + "hourly": "毎時" + }, + "toasts": { + "obtain": "取得", + "updateSuccess": "更新が成功しました", + "logCleanSuccess": "ログがクリアされました", + "inboundsUpdateSuccess": "インバウンドが正常に更新されました", + "inboundUpdateSuccess": "インバウンドが正常に更新されました", + "inboundCreateSuccess": "インバウンドが正常に作成されました", + "inboundDeleteSuccess": "インバウンドが正常に削除されました", + "inboundClientAddSuccess": "インバウンドクライアントが追加されました", + "inboundClientDeleteSuccess": "インバウンドクライアントが削除されました", + "inboundClientUpdateSuccess": "インバウンドクライアントが更新されました", + "delDepletedClientsSuccess": "すべての枯渇したクライアントが削除されました", + "resetAllClientTrafficSuccess": "クライアントのすべてのトラフィックがリセットされました", + "resetAllTrafficSuccess": "すべてのトラフィックがリセットされました", + "resetInboundClientTrafficSuccess": "トラフィックがリセットされました", + "trafficGetError": "トラフィックの取得中にエラーが発生しました", + "getNewX25519CertError": "X25519証明書の取得中にエラーが発生しました。", + "getNewmldsa65Error": "mldsa65証明書の取得中にエラーが発生しました。", + "getNewVlessEncError": "VlessEnc証明書の取得中にエラーが発生しました。" + }, + "stream": { + "general": { + "request": "リクエスト", + "response": "レスポンス", + "name": "名前", + "value": "値" + }, + "tcp": { + "version": "バージョン", + "method": "方法", + "path": "パス", + "status": "ステータス", + "statusDescription": "ステータス説明", + "requestHeader": "リクエストヘッダー", + "responseHeader": "レスポンスヘッダー" + } + } + }, + "client": { + "add": "クライアント追加", + "edit": "クライアント編集", + "submitAdd": "クライアント追加", + "submitEdit": "変更を保存", + "clientCount": "クライアント数", + "bulk": "一括作成", + "copyFromInbound": "インバウンドからクライアントをコピー", + "copyToInbound": "クライアントのコピー先", + "copySelected": "選択項目をコピー", + "copySource": "ソース", + "copyEmailPreview": "結果メールのプレビュー", + "copySelectSourceFirst": "先にソースインバウンドを選択してください。", + "copyResult": "コピー結果", + "copyResultSuccess": "正常にコピーされました", + "copyResultNone": "コピーする項目がありません: クライアントが選択されていないかソースが空です", + "copyResultErrors": "コピーエラー", + "copyFlowLabel": "新規クライアントの Flow (VLESS)", + "copyFlowHint": "すべてのコピー対象クライアントに適用されます。空のままにするとスキップします。", + "selectAll": "すべて選択", + "clearAll": "すべて解除", + "method": "方法", + "first": "最初", + "last": "最後", + "prefix": "プレフィックス", + "postfix": "サフィックス", + "delayedStart": "初回使用後に開始", + "expireDays": "期間", + "days": "日", + "renew": "自動更新", + "renewDesc": "期限が切れた後に自動更新。(0 = 無効)(単位:日)" + }, + "nodes": { + "title": "ノード", + "addNode": "ノードを追加", + "editNode": "ノードを編集", + "totalNodes": "ノード総数", + "onlineNodes": "オンライン", + "offlineNodes": "オフライン", + "avgLatency": "平均レイテンシ", + "name": "名前", + "namePlaceholder": "例: de-frankfurt-1", + "addressPlaceholder": "panel.example.com または 1.2.3.4", + "remark": "備考", + "scheme": "スキーム", + "address": "アドレス", + "port": "ポート", + "basePath": "ベースパス", + "apiToken": "APIトークン", + "apiTokenPlaceholder": "リモートパネルの設定ページから取得したトークン", + "apiTokenHint": "リモートパネルでは、設定 → APIトークン でAPIトークンを確認できます。", + "regenerate": "トークンを再生成", + "regenerateConfirm": "再生成すると現在のトークンは無効になります。これを使用しているすべての中央パネルは更新されるまでアクセスできなくなります。続行しますか?", + "enable": "有効", + "status": "ステータス", + "cpu": "CPU", + "mem": "メモリ", + "uptime": "稼働時間", + "latency": "レイテンシ", + "lastHeartbeat": "最後のハートビート", + "xrayVersion": "Xrayバージョン", + "actions": "操作", + "probe": "今すぐプローブ", + "testConnection": "接続テスト", + "connectionOk": "接続OK ({ms} ms)", + "connectionFailed": "接続に失敗しました", + "never": "なし", + "justNow": "たった今", + "deleteConfirmTitle": "ノード「{name}」を削除しますか?", + "deleteConfirmContent": "ノードの監視を停止します。リモートパネル自体には影響しません。", + "statusValues": { + "online": "オンライン", + "offline": "オフライン", + "unknown": "不明" + }, + "toasts": { + "list": "ノードの読み込みに失敗しました", + "obtain": "ノードの読み込みに失敗しました", + "add": "ノードを追加", + "update": "ノードを更新", + "delete": "ノードを削除", + "deleted": "ノードを削除しました", + "test": "接続テスト", + "fillRequired": "名前、アドレス、ポート、APIトークンは必須です", + "probeFailed": "プローブに失敗しました" + } + }, + "settings": { + "title": "パネル設定", + "save": "保存", + "infoDesc": "ここでのすべての変更は、保存してパネルを再起動する必要があります", + "restartPanel": "パネル再起動", + "restartPanelDesc": "パネルを再起動してもよろしいですか?再起動後にパネルにアクセスできない場合は、サーバーでパネルログを確認してください", + "restartPanelSuccess": "パネルの再起動に成功しました", + "actions": "操作", + "resetDefaultConfig": "デフォルト設定にリセット", + "panelSettings": "一般", + "securitySettings": "セキュリティ設定", + "TGBotSettings": "Telegramボット設定", + "panelListeningIP": "パネル監視IP", + "panelListeningIPDesc": "デフォルトではすべてのIPを監視する", + "panelListeningDomain": "パネル監視ドメイン", + "panelListeningDomainDesc": "デフォルトで空白の場合、すべてのドメインとIPアドレスを監視する", + "panelPort": "パネル監視ポート", + "panelPortDesc": "再起動で有効", + "publicKeyPath": "パネル証明書公開鍵ファイルパス", + "publicKeyPathDesc": "'/'で始まる絶対パスを入力", + "privateKeyPath": "パネル証明書秘密鍵ファイルパス", + "privateKeyPathDesc": "'/'で始まる絶対パスを入力", + "panelUrlPath": "パネルURLルートパス", + "panelUrlPathDesc": "'/'で始まり、'/'で終わる必要があります", + "pageSize": "ページサイズ", + "pageSizeDesc": "インバウンドテーブルのページサイズを定義します。0を設定すると無効化されます", + "remarkModel": "備考モデルと区切り記号", + "datepicker": "日付ピッカー", + "datepickerPlaceholder": "日付を選択", + "datepickerDescription": "日付選択カレンダーで有効期限を指定する", + "sampleRemark": "備考の例", + "oldUsername": "旧ユーザー名", + "currentPassword": "旧パスワード", + "newUsername": "新しいユーザー名", + "newPassword": "新しいパスワード", + "telegramBotEnable": "Telegramボットを有効にする", + "telegramBotEnableDesc": "Telegramボット機能を有効にする", + "telegramToken": "Telegramボットトークン", + "telegramTokenDesc": "'{'@'}BotFather'から取得したTelegramボットトークン", + "telegramProxy": "SOCKS5プロキシ", + "telegramProxyDesc": "SOCKS5プロキシを有効にしてTelegramに接続する(ガイドに従って設定を調整)", + "telegramAPIServer": "Telegram APIサーバー", + "telegramAPIServerDesc": "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する", + "telegramChatId": "管理者チャットID", + "telegramChatIdDesc": "Telegram管理者チャットID(複数の場合はカンマで区切る){'@'}userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する", + "telegramNotifyTime": "通知時間", + "telegramNotifyTimeDesc": "定期的なTelegramボット通知時間を設定する(crontab時間形式を使用)", + "tgNotifyBackup": "データベースバックアップ", + "tgNotifyBackupDesc": "レポート付きのデータベースバックアップファイルを送信", + "tgNotifyLogin": "ログイン通知", + "tgNotifyLoginDesc": "誰かがパネルにログインしようとしたときに、ユーザー名、IPアドレス、時間を表示する", + "sessionMaxAge": "セッション期間", + "sessionMaxAgeDesc": "ログイン状態を保持する期間(単位:分)", + "expireTimeDiff": "有効期限通知のしきい値", + "expireTimeDiffDesc": "このしきい値に達した場合、有効期限に関する通知を受け取る(単位:日)", + "trafficDiff": "トラフィック消耗しきい値", + "trafficDiffDesc": "このしきい値に達した場合、トラフィック消耗に関する通知を受け取る(単位:GB)", + "tgNotifyCpu": "CPU負荷通知しきい値", + "tgNotifyCpuDesc": "CPU負荷がこのしきい値を超えた場合、通知を受け取る(単位:%)", + "timeZone": "タイムゾーン", + "timeZoneDesc": "定時タスクはこのタイムゾーンの時間に従って実行される", + "subSettings": "サブスクリプション設定", + "subEnable": "サブスクリプションサービスを有効にする", + "subEnableDesc": "サブスクリプションサービス機能を有効にする", + "subJsonEnable": "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。", + "subTitle": "サブスクリプションタイトル", + "subTitleDesc": "VPNクライアントに表示されるタイトル", + "subSupportUrl": "サポートURL", + "subSupportUrlDesc": "VPNクライアントに表示されるテクニカルサポートへのリンク", + "subProfileUrl": "プロフィールURL", + "subProfileUrlDesc": "VPNクライアントに表示されるWebサイトへのリンク", + "subAnnounce": "お知らせ", + "subAnnounceDesc": "VPNクライアントに表示されるお知らせのテキスト", + "subEnableRouting": "ルーティングを有効化", + "subEnableRoutingDesc": "VPNクライアントでルーティングを有効にするためのグローバル設定。(Happのみ)", + "subRoutingRules": "ルーティングルール", + "subRoutingRulesDesc": "VPNクライアントのグローバルルーティングルール。(Happのみ)", + "subListen": "監視IP", + "subListenDesc": "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)", + "subPort": "監視ポート", + "subPortDesc": "サブスクリプションサービスが監視するポート番号(使用されていないポートである必要があります)", + "subCertPath": "公開鍵パス", + "subCertPathDesc": "サブスクリプションサービスで使用する公開鍵ファイルのパス('/'で始まる)", + "subKeyPath": "秘密鍵パス", + "subKeyPathDesc": "サブスクリプションサービスで使用する秘密鍵ファイルのパス('/'で始まる)", + "subPath": "URIパス", + "subPathDesc": "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)", + "subDomain": "監視ドメイン", + "subDomainDesc": "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)", + "subUpdates": "更新間隔", + "subUpdatesDesc": "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)", + "subEncrypt": "エンコード", + "subEncryptDesc": "サブスクリプションサービスが返す内容をBase64エンコードする", + "subShowInfo": "利用情報を表示", + "subShowInfoDesc": "クライアントアプリで残りのトラフィックと日付情報を表示する", + "subURI": "リバースプロキシURI", + "subURIDesc": "プロキシ後ろのサブスクリプションURLのURIパスに使用する", + "externalTrafficInformEnable": "外部トラフィック情報", + "externalTrafficInformEnableDesc": "トラフィックの更新ごとに外部 API に通知します。", + "externalTrafficInformURI": "外部トラフィック通知 URI", + "externalTrafficInformURIDesc": "トラフィックの更新ごとに外部 API に通知します。", + "restartXrayOnClientDisable": "自動無効化後に Xray を再起動", + "restartXrayOnClientDisableDesc": "有効期限切れまたはトラフィック上限でクライアントが自動的に無効化されたとき、Xray を再起動します。", + "fragment": "フラグメント", + "fragmentDesc": "TLS helloパケットのフラグメントを有効にする", + "fragmentSett": "設定", + "noisesDesc": "Noisesを有効にする", + "noisesSett": "Noises設定", + "mux": "マルチプレクサ", + "muxDesc": "確立されたストリーム内で複数の独立したストリームを伝送する", + "muxSett": "マルチプレクサ設定", + "direct": "直接接続", + "directDesc": "特定の国のドメインまたはIP範囲に直接接続する", + "notifications": "通知", + "certs": "証明書", + "externalTraffic": "外部トラフィック", + "dateAndTime": "日付と時刻", + "proxyAndServer": "プロキシとサーバー", + "intervals": "間隔", + "information": "情報", + "language": "言語", + "telegramBotLanguage": "Telegram Botの言語", + "security": { + "admin": "管理者の資格情報", + "twoFactor": "二段階認証", + "twoFactorEnable": "2FAを有効化", + "twoFactorEnableDesc": "セキュリティを強化するために追加の認証層を追加します。", + "twoFactorModalSetTitle": "二段階認証を有効にする", + "twoFactorModalDeleteTitle": "二段階認証を無効にする", + "twoFactorModalSteps": "二段階認証を設定するには、次の手順を実行してください:", + "twoFactorModalFirstStep": "1. 認証アプリでこのQRコードをスキャンするか、QRコード近くのトークンをコピーしてアプリに貼り付けます", + "twoFactorModalSecondStep": "2. アプリからコードを入力してください", + "twoFactorModalRemoveStep": "二段階認証を削除するには、アプリからコードを入力してください。", + "twoFactorModalChangeCredentialsTitle": "認証情報の変更", + "twoFactorModalChangeCredentialsStep": "管理者の認証情報を変更するには、アプリケーションからコードを入力してください。", + "twoFactorModalSetSuccess": "二要素認証が正常に設定されました", + "twoFactorModalDeleteSuccess": "二要素認証が正常に削除されました", + "twoFactorModalError": "コードが間違っています" + }, + "toasts": { + "modifySettings": "パラメーターが変更されました。", + "getSettings": "パラメーターの取得中にエラーが発生しました", + "modifyUserError": "管理者認証情報の変更中にエラーが発生しました。", + "modifyUser": "管理者の認証情報を正常に変更しました。", + "originalUserPassIncorrect": "旧ユーザー名または旧パスワードが間違っています", + "userPassMustBeNotEmpty": "新しいユーザー名と新しいパスワードは空にできません", + "getOutboundTrafficError": "送信トラフィックの取得エラー", + "resetOutboundTrafficError": "送信トラフィックのリセットエラー" + } + }, + "xray": { + "title": "Xray 設定", + "save": "保存", + "restart": "Xray 再起動", + "restartSuccess": "Xrayの再起動に成功しました", + "stopSuccess": "Xrayが正常に停止しました", + "restartError": "Xrayの再起動中にエラーが発生しました。", + "stopError": "Xrayの停止中にエラーが発生しました。", + "basicTemplate": "基本設定", + "advancedTemplate": "高度な設定", + "generalConfigs": "一般設定", + "generalConfigsDesc": "これらのオプションは一般設定を決定します", + "logConfigs": "ログ", + "logConfigsDesc": "ログはサーバーのパフォーマンスに影響を与える可能性があるため、必要な場合にのみ有効にすることをお勧めします", + "blockConfigsDesc": "これらのオプションは、特定のプロトコルやウェブサイトへのユーザー接続をブロックします", + "basicRouting": "基本ルーティング", + "blockConnectionsConfigsDesc": "これらのオプションにより、特定のリクエスト元の国に基づいてトラフィックをブロックします。", + "directConnectionsConfigsDesc": "直接接続により、特定のトラフィックが他のサーバーを経由しないようにします。", + "blockips": "IPをブロック", + "blockdomains": "ドメインをブロック", + "directips": "直接IP", + "directdomains": "直接ドメイン", + "ipv4Routing": "IPv4 ルーティング", + "ipv4RoutingDesc": "このオプションはIPv4のみを介してターゲットドメインへルーティングします", + "warpRouting": "WARP ルーティング", + "warpRoutingDesc": "注意:これらのオプションを使用する前に、パネルのGitHubの手順に従って、サーバーにsocks5プロキシモードでWARPをインストールしてください。WARPはCloudflareサーバー経由でトラフィックをウェブサイトにルーティングします。", + "nordRouting": "NordVPN ルーティング", + "nordRoutingDesc": "これらのオプションはNordVPN経由で特定の宛先にトラフィックをルーティングします。", + "Template": "高度なXray設定テンプレート", + "TemplateDesc": "最終的なXray設定ファイルはこのテンプレートに基づいて生成されます", + "FreedomStrategy": "Freedom プロトコル戦略", + "FreedomStrategyDesc": "Freedomプロトコル内のネットワークの出力戦略を設定する", + "RoutingStrategy": "ルーティングドメイン戦略設定", + "RoutingStrategyDesc": "DNS解決の全体的なルーティング戦略を設定する", + "outboundTestUrl": "アウトバウンドテスト URL", + "outboundTestUrlDesc": "アウトバウンド接続テストに使用する URL。既定値", + "Torrent": "BitTorrent プロトコルをブロック", + "Inbounds": "インバウンドルール", + "InboundsDesc": "特定のクライアントからのトラフィックを受け入れる", + "Outbounds": "アウトバウンドルール", + "Balancers": "負荷分散", + "OutboundsDesc": "アウトバウンドトラフィックの送信方法を設定する", + "Routings": "ルーティングルール", + "RoutingsDesc": "各ルールの優先順位が重要です", + "completeTemplate": "すべて", + "logLevel": "ログレベル", + "logLevelDesc": "エラーログのレベルを指定し、記録する情報を示します", + "accessLog": "アクセスログ", + "accessLogDesc": "アクセスログのファイルパス。特殊値 'none' はアクセスログを無効にします", + "errorLog": "エラーログ", + "errorLogDesc": "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします", + "dnsLog": "DNS ログ", + "dnsLogDesc": "DNSクエリのログを有効にするかどうか", + "maskAddress": "アドレスをマスク", + "maskAddressDesc": "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます", + "statistics": "統計", + "statsInboundUplink": "インバウンドアップロード統計", + "statsInboundUplinkDesc": "すべてのインバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。", + "statsInboundDownlink": "インバウンドダウンロード統計", + "statsInboundDownlinkDesc": "すべてのインバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。", + "statsOutboundUplink": "アウトバウンドアップロード統計", + "statsOutboundUplinkDesc": "すべてのアウトバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。", + "statsOutboundDownlink": "アウトバウンドダウンロード統計", + "statsOutboundDownlinkDesc": "すべてのアウトバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。", + "rules": { + "first": "最初", + "last": "最後", + "up": "上へ", + "down": "下へ", + "source": "ソース", + "dest": "宛先アドレス", + "inbound": "インバウンド", + "outbound": "アウトバウンド", + "balancer": "負荷分散", + "info": "情報", + "add": "ルール追加", + "edit": "ルール編集", + "useComma": "カンマ区切りの項目" + }, + "outbound": { + "addOutbound": "アウトバウンド追加", + "addReverse": "リバース追加", + "editOutbound": "アウトバウンド編集", + "editReverse": "リバース編集", + "reverseTag": "リバースタグ", + "reverseTagDesc": "VLESSシンプルリバースプロキシのアウトバウンドタグ。無効にするには空欄にしてください。", + "reverseTagPlaceholder": "アウトバウンドタグ(空欄で無効)", + "tag": "タグ", + "tagDesc": "一意のタグ", + "address": "アドレス", + "reverse": "リバース", + "domain": "ドメイン", + "type": "タイプ", + "bridge": "ブリッジ", + "portal": "ポータル", + "link": "リンク", + "intercon": "インターコネクション", + "settings": "設定", + "accountInfo": "アカウント情報", + "outboundStatus": "アウトバウンドステータス", + "sendThrough": "送信経路", + "test": "テスト", + "testResult": "テスト結果", + "testing": "接続をテスト中...", + "testSuccess": "テスト成功", + "testFailed": "テスト失敗", + "testError": "アウトバウンドのテストに失敗しました", + "nordvpn": "NordVPN", + "accessToken": "アクセストークン", + "country": "国", + "server": "サーバー", + "city": "都市", + "allCities": "すべての都市", + "privateKey": "秘密鍵", + "load": "負荷" + }, + "balancer": { + "addBalancer": "負荷分散追加", + "editBalancer": "負荷分散編集", + "balancerStrategy": "戦略", + "balancerSelectors": "セレクター", + "tag": "タグ", + "tagDesc": "一意のタグ", + "balancerDesc": "balancerTagとoutboundTagは同時に使用できません。同時に使用された場合、outboundTagのみが有効になります。" + }, + "wireguard": { + "secretKey": "シークレットキー", + "publicKey": "公開鍵", + "allowedIPs": "許可されたIP", + "endpoint": "エンドポイント", + "psk": "共有キー", + "domainStrategy": "ドメイン戦略" + }, + "tun": { + "nameDesc": "TUN インターフェースの名前。デフォルトは 'xray0' です", + "mtuDesc": "最大伝送単位。データパケットの最大サイズ。デフォルトは 1500 です", + "userLevel": "ユーザーレベル", + "userLevelDesc": "このインバウンドを通じて確立されたすべての接続は、このユーザーレベルを使用します。デフォルトは 0 です" + }, + "dns": { + "enable": "DNSを有効にする", + "enableDesc": "組み込みDNSサーバーを有効にする", + "tag": "DNSインバウンドタグ", + "tagDesc": "このタグはルーティングルールでインバウンドタグとして使用できます", + "clientIp": "クライアントIP", + "clientIpDesc": "DNSクエリ中に指定されたIPの位置をサーバーに通知するために使用されます", + "disableCache": "キャッシュを無効にする", + "disableCacheDesc": "DNSキャッシュを無効にします", + "disableFallback": "フォールバックを無効にする", + "disableFallbackDesc": "フォールバックDNSクエリを無効にします", + "disableFallbackIfMatch": "一致した場合にフォールバックを無効にする", + "disableFallbackIfMatchDesc": "DNSサーバーの一致するドメインリストにヒットした場合、フォールバックDNSクエリを無効にします", + "enableParallelQuery": "並列クエリを有効にする", + "enableParallelQueryDesc": "複数のサーバーへの並列DNSクエリを有効にして、より高速な解決を実現", + "strategy": "クエリ戦略", + "strategyDesc": "ドメイン名解決の全体的な戦略", + "add": "サーバー追加", + "edit": "サーバー編集", + "domains": "ドメイン", + "expectIPs": "期待されるIP", + "unexpectIPs": "予期しないIP", + "useSystemHosts": "システムのHostsを使用", + "useSystemHostsDesc": "インストール済みシステムのhostsファイルを使用する", + "usePreset": "テンプレートを使用", + "dnsPresetTitle": "DNSテンプレート", + "dnsPresetFamily": "ファミリー" + }, + "fakedns": { + "add": "フェイクDNS追加", + "edit": "フェイクDNS編集", + "ipPool": "IPプールサブネット", + "poolSize": "プールサイズ" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ キーボードを閉じました!", + "noResult": "❗ 結果がありません!", + "noQuery": "❌ クエリが見つかりません!コマンドを再利用してください!", + "wentWrong": "❌ 何かがうまくいかなかった!", + "noIpRecord": "❗ IPレコードがありません!", + "noInbounds": "❗ インバウンドが見つかりません!", + "unlimited": "♾ 無制限(リセット)", + "add": "追加", + "month": "月", + "months": "ヶ月", + "day": "日", + "days": "日間", + "hours": "時間", + "minutes": "分", + "unknown": "不明", + "inbounds": "インバウンド", + "clients": "クライアント", + "offline": "🔴 オフライン", + "online": "🟢 オンライン", + "commands": { + "unknown": "❗ 不明なコマンド", + "pleaseChoose": "👇 選択してください:\r\n", + "help": "🤖 このボットをご利用いただきありがとうございます!サーバーから特定のデータを提供し、必要な変更を行うことができます。\r\n\r\n", + "start": "👋 こんにちは、{{ .Firstname }}。\r\n", + "welcome": "🤖 {{ .Hostname }} 管理ボットへようこそ。\r\n", + "status": "✅ ボットは正常に動作しています!", + "usage": "❗ 検索するテキストを入力してください!", + "getID": "🆔 あなたのIDは:{{ .ID }}", + "helpAdminCommands": "Xray Coreを再起動するには:\r\n/restart\r\n\r\nクライアントの電子メールを検索するには:\r\n/usage [電子メール]\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n/inbound [備考]\r\n\r\nTelegramチャットID:\r\n/id", + "helpClientCommands": "統計情報を検索するには、次のコマンドを使用してください:\r\n/usage [電子メール]\r\n\r\nTelegramチャットID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ 操作成功!", + "restartFailed": "❗ 操作エラー。\r\n\r\nエラー: {{ .Error }}", + "xrayNotRunning": "❗ Xray Core は動作していません。", + "startDesc": "メインメニューを表示", + "helpDesc": "ボットのヘルプ", + "statusDesc": "ボットの状態を確認", + "idDesc": "Telegram IDを表示" + }, + "messages": { + "cpuThreshold": "🔴 CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました", + "selectUserFailed": "❌ ユーザーの選択に失敗しました!", + "userSaved": "✅ Telegramユーザーが保存されました。", + "loginSuccess": "✅ パネルに正常にログインしました。\r\n", + "loginFailed": "❗️ パネルのログインに失敗しました。\r\n", + "2faFailed": "2FAエラー", + "report": "🕰 定期報告:{{ .RunTime }}\r\n", + "datetime": "⏰ 日時:{{ .DateTime }}\r\n", + "hostname": "💻 ホスト名:{{ .Hostname }}\r\n", + "version": "🚀 X-UI バージョン:{{ .Version }}\r\n", + "xrayVersion": "📡 Xray バージョン: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6:{{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4:{{ .IPv4 }}\r\n", + "ip": "🌐 IP:{{ .IP }}\r\n", + "ips": "🔢 IPアドレス:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ サーバー稼働時間:{{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 サーバー負荷:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 サーバーメモリ:{{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP接続数:{{ .Count }}\r\n", + "udpCount": "🔸 UDP接続数:{{ .Count }}\r\n", + "traffic": "🚦 トラフィック:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Xrayステータス:{{ .State }}\r\n", + "username": "👤 ユーザー名:{{ .Username }}\r\n", + "reason": "❗️ 理由:{{ .Reason }}\r\n", + "time": "⏰ 時間:{{ .Time }}\r\n", + "inbound": "📍 インバウンド:{{ .Remark }}\r\n", + "port": "🔌 ポート:{{ .Port }}\r\n", + "expire": "📅 有効期限:{{ .Time }}\r\n", + "expireIn": "📅 残り時間:{{ .Time }}\r\n", + "active": "💡 有効:{{ .Enable }}\r\n", + "enabled": "🚨 有効化済み:{{ .Enable }}\r\n", + "online": "🌐 接続ステータス:{{ .Status }}\r\n", + "lastOnline": "🔙 最終オンライン: {{ .Time }}\r\n", + "email": "📧 メール:{{ .Email }}\r\n", + "upload": "🔼 アップロード↑:{{ .Upload }}\r\n", + "download": "🔽 ダウンロード↓:{{ .Download }}\r\n", + "total": "📊 合計:{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Telegramユーザー:{{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 消耗済みの {{ .Type }}:\r\n", + "exhaustedCount": "🚨 消耗済みの {{ .Type }} 数量:\r\n", + "onlinesCount": "🌐 オンラインクライアント:{{ .Count }}\r\n", + "disabled": "🛑 無効化:{{ .Disabled }}\r\n", + "depleteSoon": "🔜 間もなく消耗:{{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 バックアップ時間:{{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n", + "yes": "✅ はい", + "no": "❌ いいえ", + "received_id": "🔑📥 IDが更新されました。", + "received_password": "🔑📥 パスワードが更新されました。", + "received_email": "📧📥 メールが更新されました。", + "received_comment": "💬📥 コメントが更新されました。", + "id_prompt": "🔑 デフォルトID: {{ .ClientId }}\n\nIDを入力してください。", + "pass_prompt": "🔑 デフォルトパスワード: {{ .ClientPassword }}\n\nパスワードを入力してください。", + "email_prompt": "📧 デフォルトメール: {{ .ClientEmail }}\n\nメールを入力してください。", + "comment_prompt": "💬 デフォルトコメント: {{ .ClientComment }}\n\nコメントを入力してください。", + "inbound_client_data_id": "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!", + "inbound_client_data_pass": "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 パスワード: {{ .ClientPass }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!", + "cancel": "❌ プロセスがキャンセルされました!\n\nいつでも /start で再開できます。 🔄", + "error_add_client": "⚠️ エラー:\n\n {{ .error }}", + "using_default_value": "わかりました、デフォルト値を使用します。 😊", + "incorrect_input": "入力が無効です。\nフレーズはスペースなしで続けて入力してください。\n正しい例: aaaaaa\n間違った例: aaa aaa 🚫", + "AreYouSure": "本当にいいですか?🤔", + "SuccessResetTraffic": "📧 メール: {{ .ClientEmail }}\n🏁 結果: ✅ 成功", + "FailedResetTraffic": "📧 メール: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ エラー: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 すべてのクライアントのトラフィックリセットが完了しました。" + }, + "buttons": { + "closeKeyboard": "❌ キーボードを閉じる", + "cancel": "❌ キャンセル", + "cancelReset": "❌ リセットをキャンセル", + "cancelIpLimit": "❌ IP制限をキャンセル", + "confirmResetTraffic": "✅ トラフィックをリセットしますか?", + "confirmClearIps": "✅ IPをクリアしますか?", + "confirmRemoveTGUser": "✅ Telegramユーザーを削除しますか?", + "confirmToggle": "✅ ユーザーを有効/無効にしますか?", + "dbBackup": "データベースバックアップを取得", + "serverUsage": "サーバーの使用状況", + "getInbounds": "インバウンド情報を取得", + "depleteSoon": "間もなく消耗", + "clientUsage": "使用状況を取得", + "onlines": "オンラインクライアント", + "commands": "コマンド", + "refresh": "🔄 更新", + "clearIPs": "❌ IPをクリア", + "removeTGUser": "❌ Telegramユーザーを削除", + "selectTGUser": "👤 Telegramユーザーを選択", + "selectOneTGUser": "👤 1人のTelegramユーザーを選択:", + "resetTraffic": "📈 トラフィックをリセット", + "resetExpire": "📅 有効期限を変更", + "ipLog": "🔢 IPログ", + "ipLimit": "🔢 IP制限", + "setTGUser": "👤 Telegramユーザーを設定", + "toggle": "🔘 有効/無効", + "custom": "🔢 カスタム", + "confirmNumber": "✅ 確認: {{ .Num }}", + "confirmNumberAdd": "✅ 追加を確認:{{ .Num }}", + "limitTraffic": "🚧 トラフィック制限", + "getBanLogs": "禁止ログ", + "allClients": "すべてのクライアント", + "addClient": "クライアントを追加", + "submitDisable": "無効として送信 ☑️", + "submitEnable": "有効として送信 ✅", + "use_default": "🏷️ デフォルトを使用", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 パスワード", + "change_email": "⚙️📧 メールアドレス", + "change_comment": "⚙️💬 コメント", + "ResetAllTraffics": "すべてのトラフィックをリセット", + "SortedTrafficUsageReport": "ソートされたトラフィック使用レポート" + }, + "answers": { + "successfulOperation": "✅ 成功!", + "errorOperation": "❗ 操作エラー。", + "getInboundsFailed": "❌ インバウンド情報の取得に失敗しました。", + "getClientsFailed": "❌ クライアントの取得に失敗しました。", + "canceled": "❌ {{ .Email }}:操作がキャンセルされました。", + "clientRefreshSuccess": "✅ {{ .Email }}:クライアントが正常に更新されました。", + "IpRefreshSuccess": "✅ {{ .Email }}:IPが正常に更新されました。", + "TGIdRefreshSuccess": "✅ {{ .Email }}:クライアントのTelegramユーザーが正常に更新されました。", + "resetTrafficSuccess": "✅ {{ .Email }}:トラフィックが正常にリセットされました。", + "setTrafficLimitSuccess": "✅ {{ .Email }}:トラフィック制限が正常に保存されました。", + "expireResetSuccess": "✅ {{ .Email }}:有効期限の日数が正常にリセットされました。", + "resetIpSuccess": "✅ {{ .Email }}:IP制限数が正常に保存されました:{{ .Count }}。", + "clearIpSuccess": "✅ {{ .Email }}:IPが正常にクリアされました。", + "getIpLog": "✅ {{ .Email }}:IPログの取得。", + "getUserInfo": "✅ {{ .Email }}:Telegramユーザー情報の取得。", + "removedTGUserSuccess": "✅ {{ .Email }}:Telegramユーザーが正常に削除されました。", + "enableSuccess": "✅ {{ .Email }}:正常に有効化されました。", + "disableSuccess": "✅ {{ .Email }}:正常に無効化されました。", + "askToAddUserId": "設定が見つかりませんでした!\r\n管理者に問い合わせて、設定にTelegramユーザーのChatIDを使用してください。\r\n\r\nあなたのユーザーChatID:{{ .TgUserID }}", + "chooseClient": "インバウンド {{ .Inbound }} のクライアントを選択", + "chooseInbound": "インバウンドを選択" + } + } +} diff --git a/web/translation/pt-BR.json b/web/translation/pt-BR.json new file mode 100644 index 00000000..dbd754ef --- /dev/null +++ b/web/translation/pt-BR.json @@ -0,0 +1,941 @@ +{ + "username": "Nome de Usuário", + "password": "Senha", + "login": "Entrar", + "confirm": "Confirmar", + "cancel": "Cancelar", + "close": "Fechar", + "save": "Salvar", + "logout": "Sair", + "create": "Criar", + "update": "Atualizar", + "copy": "Copiar", + "copied": "Copiado", + "download": "Baixar", + "remark": "Observação", + "enable": "Ativado", + "protocol": "Protocolo", + "search": "Pesquisar", + "filter": "Filtrar", + "loading": "Carregando...", + "second": "Segundo", + "minute": "Minuto", + "hour": "Hora", + "day": "Dia", + "check": "Verificar", + "indefinite": "Indeterminado", + "unlimited": "Ilimitado", + "none": "Nada", + "qrCode": "Código QR", + "info": "Mais Informações", + "edit": "Editar", + "delete": "Excluir", + "reset": "Redefinir", + "noData": "Sem dados.", + "copySuccess": "Copiado com Sucesso", + "sure": "Certo", + "encryption": "Criptografia", + "useIPv4ForHost": "Usar IPv4 para o host", + "transmission": "Transmissão", + "host": "Servidor", + "path": "Caminho", + "camouflage": "Ofuscação", + "status": "Status", + "enabled": "Ativado", + "disabled": "Desativado", + "depleted": "Encerrado", + "depletingSoon": "Esgotando", + "offline": "Offline", + "online": "Online", + "domainName": "Nome de Domínio", + "monitor": "IP de Escuta", + "certificate": "Certificado Digital", + "fail": "Falhou", + "comment": "Comentário", + "success": "Com Sucesso", + "lastOnline": "Última vez online", + "getVersion": "Obter Versão", + "install": "Instalar", + "clients": "Clientes", + "usage": "Uso", + "twoFactorCode": "Código", + "remained": "Restante", + "security": "Segurança", + "secAlertTitle": "Alerta de Segurança", + "secAlertSsl": "Esta conexão não é segura. Evite inserir informações confidenciais até que o TLS seja ativado para proteção de dados.", + "secAlertConf": "Algumas configurações estão vulneráveis a ataques. Recomenda-se reforçar os protocolos de segurança para evitar possíveis violações.", + "secAlertSSL": "O painel não possui uma conexão segura. Instale o certificado TLS para proteção de dados.", + "secAlertPanelPort": "A porta padrão do painel é vulnerável. Configure uma porta aleatória ou específica.", + "secAlertPanelURI": "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo.", + "secAlertSubURI": "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo.", + "secAlertSubJsonURI": "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo.", + "emptyDnsDesc": "Nenhum servidor DNS adicionado.", + "emptyFakeDnsDesc": "Nenhum servidor Fake DNS adicionado.", + "emptyBalancersDesc": "Nenhum balanceador adicionado.", + "emptyReverseDesc": "Nenhum proxy reverso adicionado.", + "somethingWentWrong": "Algo deu errado", + "subscription": { + "title": "Informações da assinatura", + "subId": "ID da assinatura", + "status": "Status", + "downloaded": "Baixado", + "uploaded": "Enviado", + "expiry": "Validade", + "totalQuota": "Cota total", + "individualLinks": "Links individuais", + "active": "Ativo", + "inactive": "Inativo", + "unlimited": "Ilimitado", + "noExpiry": "Sem validade" + }, + "menu": { + "theme": "Tema", + "dark": "Escuro", + "ultraDark": "Ultra Escuro", + "dashboard": "Visão Geral", + "inbounds": "Inbounds", + "settings": "Panel Settings", + "xray": "Xray Configs", + "logout": "Sair", + "link": "Gerenciar" + }, + "pages": { + "login": { + "hello": "Olá", + "title": "Bem-vindo", + "loginAgain": "Sua sessão expirou, faça login novamente", + "toasts": { + "invalidFormData": "O formato dos dados de entrada é inválido.", + "emptyUsername": "Nome de usuário é obrigatório", + "emptyPassword": "Senha é obrigatória", + "wrongUsernameOrPassword": "Nome de usuário, senha ou código de dois fatores inválido.", + "successLogin": "Você entrou na sua conta com sucesso." + } + }, + "index": { + "title": "Visão Geral", + "cpu": "CPU", + "logicalProcessors": "Processadores lógicos", + "frequency": "Frequência", + "swap": "Swap", + "storage": "Armazenamento", + "memory": "RAM", + "threads": "Threads", + "xrayStatus": "Xray", + "stopXray": "Parar", + "restartXray": "Reiniciar", + "xraySwitch": "Versão", + "xraySwitchClick": "Escolha a versão para a qual deseja alternar.", + "xraySwitchClickDesk": "Escolha com cuidado, pois versões mais antigas podem não ser compatíveis com as configurações atuais.", + "xrayUpdates": "Atualizações do Xray", + "updatePanel": "Atualizar painel", + "panelUpdateDesc": "Isso atualizará o 3X-UI para a versão mais recente e reiniciará o serviço do painel.", + "currentPanelVersion": "Versão atual do painel", + "latestPanelVersion": "Última versão do painel", + "panelUpToDate": "O painel está atualizado", + "upToDate": "Atualizado", + "xrayStatusUnknown": "Desconhecido", + "xrayStatusRunning": "Em execução", + "xrayStatusStop": "Parado", + "xrayStatusError": "Erro", + "xrayErrorPopoverTitle": "Ocorreu um erro ao executar o Xray", + "operationHours": "Tempo de Atividade", + "systemHistoryTitle": "Histórico do Sistema", + "trendLast2Min": "Últimos 2 minutos", + "systemLoad": "Carga do Sistema", + "systemLoadDesc": "Média de carga do sistema nos últimos 1, 5 e 15 minutos", + "connectionCount": "Estatísticas de Conexão", + "ipAddresses": "Endereços IP", + "toggleIpVisibility": "Alternar visibilidade do IP", + "overallSpeed": "Velocidade geral", + "upload": "Upload", + "download": "Download", + "totalData": "Dados totais", + "sent": "Enviado", + "received": "Recebido", + "documentation": "Documentação", + "xraySwitchVersionDialog": "Você realmente deseja alterar a versão do Xray?", + "xraySwitchVersionDialogDesc": "Isso mudará a versão do Xray para #version#.", + "xraySwitchVersionPopover": "Xray atualizado com sucesso", + "panelUpdateDialog": "Deseja realmente atualizar o painel?", + "panelUpdateDialogDesc": "Isso atualizará o 3X-UI para #version# e reiniciará o serviço do painel.", + "panelUpdateCheckPopover": "Falha na verificação de atualização do painel", + "panelUpdateStartedPopover": "Atualização do painel iniciada", + "geofileUpdateDialog": "Você realmente deseja atualizar o geofile?", + "geofileUpdateDialogDesc": "Isso atualizará o arquivo #filename#.", + "geofilesUpdateDialogDesc": "Isso atualizará todos os arquivos.", + "geofilesUpdateAll": "Atualizar tudo", + "geofileUpdatePopover": "Geofile atualizado com sucesso", + "dontRefresh": "Instalação em andamento, por favor não atualize a página", + "logs": "Logs", + "config": "Configuração", + "backup": "Backup", + "backupTitle": "Backup & Restauração", + "exportDatabase": "Backup", + "exportDatabaseDesc": "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo.", + "importDatabase": "Restaurar", + "importDatabaseDesc": "Clique para selecionar e enviar um arquivo .db do seu dispositivo para restaurar seu banco de dados a partir de um backup.", + "importDatabaseSuccess": "O banco de dados foi importado com sucesso", + "importDatabaseError": "Ocorreu um erro ao importar o banco de dados", + "readDatabaseError": "Ocorreu um erro ao ler o banco de dados", + "getDatabaseError": "Ocorreu um erro ao recuperar o banco de dados", + "getConfigError": "Ocorreu um erro ao recuperar o arquivo de configuração", + "customGeoTitle": "GeoSite / GeoIP personalizados", + "customGeoAdd": "Adicionar", + "customGeoType": "Tipo", + "customGeoAlias": "Alias", + "customGeoUrl": "URL", + "customGeoEnabled": "Ativado", + "customGeoLastUpdated": "Última atualização", + "customGeoExtColumn": "Roteamento (ext:…)", + "customGeoToastUpdateAll": "Todas as fontes personalizadas foram atualizadas", + "customGeoActions": "Ações", + "customGeoEdit": "Editar", + "customGeoDelete": "Excluir", + "customGeoDownload": "Atualizar agora", + "customGeoModalAdd": "Adicionar geo personalizado", + "customGeoModalEdit": "Editar geo personalizado", + "customGeoModalSave": "Salvar", + "customGeoDeleteConfirm": "Excluir esta fonte geo personalizada?", + "customGeoRoutingHint": "Nas regras de roteamento use a coluna de valor como ext:arquivo.dat:tag (substitua a tag).", + "customGeoInvalidId": "ID de recurso inválido", + "customGeoAliasesError": "Falha ao carregar aliases geo personalizados", + "customGeoValidationAlias": "O alias só pode conter letras minúsculas, dígitos, - e _", + "customGeoValidationUrl": "A URL deve começar com http:// ou https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (personalizado)", + "customGeoToastList": "Lista de geo personalizado", + "customGeoToastAdd": "Adicionar geo personalizado", + "customGeoToastUpdate": "Atualizar geo personalizado", + "customGeoToastDelete": "Geofile personalizado “{{ .fileName }}” excluído", + "customGeoToastDownload": "Geofile “{{ .fileName }}” atualizado", + "customGeoErrInvalidType": "O tipo deve ser geosite ou geoip", + "customGeoErrAliasRequired": "Alias é obrigatório", + "customGeoErrAliasPattern": "O alias contém caracteres não permitidos", + "customGeoErrAliasReserved": "Este alias é reservado", + "customGeoErrUrlRequired": "URL é obrigatória", + "customGeoErrInvalidUrl": "URL inválida", + "customGeoErrUrlScheme": "A URL deve usar http ou https", + "customGeoErrUrlHost": "Host da URL inválido", + "customGeoErrDuplicateAlias": "Este alias já está em uso para este tipo", + "customGeoErrNotFound": "Fonte geo personalizada não encontrada", + "customGeoErrDownload": "Falha no download", + "customGeoErrUpdateAllIncomplete": "Falha ao atualizar uma ou mais fontes geo personalizadas", + "customGeoEmpty": "Ainda não há fontes geo personalizadas — clique em Adicionar para criar uma" + }, + "inbounds": { + "allTimeTraffic": "Tráfego Total", + "allTimeTrafficUsage": "Uso total de todos os tempos", + "title": "Inbounds", + "totalDownUp": "Total Enviado/Recebido", + "totalUsage": "Uso Total", + "inboundCount": "Total de Inbounds", + "operate": "Menu", + "enable": "Ativado", + "remark": "Observação", + "node": "Nó", + "deployTo": "Implantar em", + "localPanel": "Painel local", + "protocol": "Protocolo", + "port": "Porta", + "portMap": "Porta Mapeada", + "traffic": "Tráfego", + "details": "Detalhes", + "transportConfig": "Transporte", + "expireDate": "Duração", + "createdAt": "Criado", + "updatedAt": "Atualizado", + "resetTraffic": "Redefinir Tráfego", + "addInbound": "Adicionar Inbound", + "generalActions": "Ações Gerais", + "modifyInbound": "Modificar Inbound", + "deleteInbound": "Excluir Inbound", + "deleteInboundContent": "Tem certeza de que deseja excluir o inbound?", + "deleteClient": "Excluir Cliente", + "deleteClientContent": "Tem certeza de que deseja excluir o cliente?", + "resetTrafficContent": "Tem certeza de que deseja redefinir o tráfego?", + "copyLink": "Copiar URL", + "address": "Endereço", + "network": "Rede", + "destinationPort": "Porta de Destino", + "targetAddress": "Endereço de Destino", + "monitorDesc": "Deixe em branco para ouvir todos os IPs", + "meansNoLimit": "= Ilimitado. (unidade: GB)", + "totalFlow": "Fluxo Total", + "leaveBlankToNeverExpire": "Deixe em branco para nunca expirar", + "noRecommendKeepDefault": "Recomenda-se manter o padrão", + "certificatePath": "Caminho", + "certificateContent": "Conteúdo", + "publicKey": "Chave Pública", + "privatekey": "Chave Privada", + "clickOnQRcode": "Clique no Código QR para Copiar", + "client": "Cliente", + "export": "Exportar Todos os URLs", + "clone": "Clonar", + "cloneInbound": "Clonar", + "cloneInboundContent": "Todas as configurações deste inbound, exceto Porta, IP de Escuta e Clientes, serão aplicadas ao clone.", + "cloneInboundOk": "Clonar", + "resetAllTraffic": "Redefinir Tráfego de Todos os Inbounds", + "resetAllTrafficTitle": "Redefinir Tráfego de Todos os Inbounds", + "resetAllTrafficContent": "Tem certeza de que deseja redefinir o tráfego de todos os inbounds?", + "resetInboundClientTraffics": "Redefinir Tráfego dos Clientes", + "resetInboundClientTrafficTitle": "Redefinir Tráfego dos Clientes", + "resetInboundClientTrafficContent": "Tem certeza de que deseja redefinir o tráfego dos clientes deste inbound?", + "resetAllClientTraffics": "Redefinir Tráfego de Todos os Clientes", + "resetAllClientTrafficTitle": "Redefinir Tráfego de Todos os Clientes", + "resetAllClientTrafficContent": "Tem certeza de que deseja redefinir o tráfego de todos os clientes?", + "delDepletedClients": "Excluir Clientes Esgotados", + "delDepletedClientsTitle": "Excluir Clientes Esgotados", + "delDepletedClientsContent": "Tem certeza de que deseja excluir todos os clientes esgotados?", + "email": "Email", + "emailDesc": "Por favor, forneça um endereço de e-mail único.", + "IPLimit": "Limite de IP", + "IPLimitDesc": "Desativa o inbound se o número ultrapassar o valor definido. (0 = desativar)", + "IPLimitlog": "Log de IP", + "IPLimitlogDesc": "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)", + "IPLimitlogclear": "Limpar o Log", + "setDefaultCert": "Definir Certificado pelo Painel", + "telegramDesc": "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou ({'@'}userinfobot)", + "subscriptionDesc": "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes.", + "info": "Informações", + "same": "Igual", + "inboundData": "Dados do Inbound", + "exportInbound": "Exportar Inbound", + "import": "Importar", + "importInbound": "Importar um Inbound", + "periodicTrafficResetTitle": "Reset de Tráfego", + "periodicTrafficResetDesc": "Reinicia automaticamente o contador de tráfego em intervalos especificados", + "lastReset": "Último Reset", + "periodicTrafficReset": { + "never": "Nunca", + "daily": "Diariamente", + "weekly": "Semanalmente", + "monthly": "Mensalmente", + "hourly": "A cada hora" + }, + "toasts": { + "obtain": "Obter", + "updateSuccess": "A atualização foi bem-sucedida", + "logCleanSuccess": "O log foi limpo", + "inboundsUpdateSuccess": "Entradas atualizadas com sucesso", + "inboundUpdateSuccess": "Entrada atualizada com sucesso", + "inboundCreateSuccess": "Entrada criada com sucesso", + "inboundDeleteSuccess": "Entrada excluída com sucesso", + "inboundClientAddSuccess": "Cliente(s) de entrada adicionado(s)", + "inboundClientDeleteSuccess": "Cliente de entrada excluído", + "inboundClientUpdateSuccess": "Cliente de entrada atualizado", + "delDepletedClientsSuccess": "Todos os clientes esgotados foram excluídos", + "resetAllClientTrafficSuccess": "Todo o tráfego do cliente foi reiniciado", + "resetAllTrafficSuccess": "Todo o tráfego foi reiniciado", + "resetInboundClientTrafficSuccess": "O tráfego foi reiniciado", + "trafficGetError": "Erro ao obter tráfegos", + "getNewX25519CertError": "Erro ao obter o certificado X25519.", + "getNewmldsa65Error": "Erro ao obter o certificado mldsa65.", + "getNewVlessEncError": "Erro ao obter o certificado VlessEnc." + }, + "stream": { + "general": { + "request": "Requisição", + "response": "Resposta", + "name": "Nome", + "value": "Valor" + }, + "tcp": { + "version": "Versão", + "method": "Método", + "path": "Caminho", + "status": "Status", + "statusDescription": "Descrição do Status", + "requestHeader": "Cabeçalho da Requisição", + "responseHeader": "Cabeçalho da Resposta" + } + } + }, + "client": { + "add": "Adicionar Cliente", + "edit": "Editar Cliente", + "submitAdd": "Adicionar Cliente", + "submitEdit": "Salvar Alterações", + "clientCount": "Número de Clientes", + "bulk": "Adicionar Vários", + "copyFromInbound": "Copiar clientes da entrada", + "copyToInbound": "Copiar clientes para", + "copySelected": "Copiar selecionados", + "copySource": "Origem", + "copyEmailPreview": "Prévia do email resultante", + "copySelectSourceFirst": "Selecione primeiro uma entrada de origem.", + "copyResult": "Resultado da cópia", + "copyResultSuccess": "Copiado com sucesso", + "copyResultNone": "Nada a copiar: nenhum cliente selecionado ou origem vazia", + "copyResultErrors": "Erros ao copiar", + "copyFlowLabel": "Flow para novos clientes (VLESS)", + "copyFlowHint": "Aplicado a todos os clientes copiados. Deixe em branco para ignorar.", + "selectAll": "Selecionar tudo", + "clearAll": "Limpar tudo", + "method": "Método", + "first": "Primeiro", + "last": "Último", + "prefix": "Prefixo", + "postfix": "Sufixo", + "delayedStart": "Iniciar Após Primeiro Uso", + "expireDays": "Duração", + "days": "Dia(s)", + "renew": "Renovação Automática", + "renewDesc": "Renovação automática após expiração. (0 = desativado)(unidade: dia)" + }, + "nodes": { + "title": "Nós", + "addNode": "Adicionar nó", + "editNode": "Editar nó", + "totalNodes": "Total de nós", + "onlineNodes": "Online", + "offlineNodes": "Offline", + "avgLatency": "Latência média", + "name": "Nome", + "namePlaceholder": "ex.: de-frankfurt-1", + "addressPlaceholder": "panel.example.com ou 1.2.3.4", + "remark": "Observação", + "scheme": "Esquema", + "address": "Endereço", + "port": "Porta", + "basePath": "Caminho base", + "apiToken": "Token da API", + "apiTokenPlaceholder": "Token da página de Configurações do painel remoto", + "apiTokenHint": "O painel remoto exibe o token da API em Configurações → Token da API.", + "regenerate": "Regenerar token", + "regenerateConfirm": "Regenerar invalida o token atual. Qualquer painel central que o utilize perderá acesso até ser atualizado. Continuar?", + "enable": "Ativado", + "status": "Status", + "cpu": "CPU", + "mem": "Memória", + "uptime": "Tempo ativo", + "latency": "Latência", + "lastHeartbeat": "Último heartbeat", + "xrayVersion": "Versão do Xray", + "actions": "Ações", + "probe": "Sondar agora", + "testConnection": "Testar conexão", + "connectionOk": "Conexão OK ({ms} ms)", + "connectionFailed": "Falha na conexão", + "never": "nunca", + "justNow": "agora mesmo", + "deleteConfirmTitle": "Excluir o nó \"{name}\"?", + "deleteConfirmContent": "Isso interrompe o monitoramento do nó. O painel remoto em si não é afetado.", + "statusValues": { + "online": "Online", + "offline": "Offline", + "unknown": "Desconhecido" + }, + "toasts": { + "list": "Falha ao carregar os nós", + "obtain": "Falha ao carregar o nó", + "add": "Adicionar nó", + "update": "Atualizar nó", + "delete": "Excluir nó", + "deleted": "Nó excluído", + "test": "Testar conexão", + "fillRequired": "Nome, endereço, porta e token da API são obrigatórios", + "probeFailed": "Falha na sondagem" + } + }, + "settings": { + "title": "Configurações do Painel", + "save": "Salvar", + "infoDesc": "Toda alteração feita aqui precisa ser salva. Reinicie o painel para aplicar as alterações.", + "restartPanel": "Reiniciar Painel", + "restartPanelDesc": "Tem certeza de que deseja reiniciar o painel? Se não conseguir acessar o painel após reiniciar, consulte os logs do painel no servidor.", + "restartPanelSuccess": "O painel foi reiniciado com sucesso", + "actions": "Ações", + "resetDefaultConfig": "Redefinir para Padrão", + "panelSettings": "Geral", + "securitySettings": "Autenticação", + "TGBotSettings": "Bot do Telegram", + "panelListeningIP": "IP de Escuta", + "panelListeningIPDesc": "O endereço IP para o painel web. (deixe em branco para escutar em todos os IPs)", + "panelListeningDomain": "Domínio de Escuta", + "panelListeningDomainDesc": "O nome de domínio para o painel web. (deixe em branco para escutar em todos os domínios e IPs)", + "panelPort": "Porta de Escuta", + "panelPortDesc": "O número da porta para o painel web. (deve ser uma porta não usada)", + "publicKeyPath": "Caminho da Chave Pública", + "publicKeyPathDesc": "O caminho do arquivo de chave pública para o painel web. (começa com ‘/‘)", + "privateKeyPath": "Caminho da Chave Privada", + "privateKeyPathDesc": "O caminho do arquivo de chave privada para o painel web. (começa com ‘/‘)", + "panelUrlPath": "Caminho URI", + "panelUrlPathDesc": "O caminho URI para o painel web. (começa com ‘/‘ e termina com ‘/‘)", + "pageSize": "Tamanho da Paginação", + "pageSizeDesc": "Definir o tamanho da página para a tabela de entradas. (0 = desativado)", + "remarkModel": "Modelo de Observação & Caractere de Separação", + "datepicker": "Tipo de Calendário", + "datepickerPlaceholder": "Selecionar data", + "datepickerDescription": "Tarefas agendadas serão executadas com base neste calendário.", + "sampleRemark": "Exemplo de Observação", + "oldUsername": "Nome de Usuário Atual", + "currentPassword": "Senha Atual", + "newUsername": "Novo Nome de Usuário", + "newPassword": "Nova Senha", + "telegramBotEnable": "Ativar Bot do Telegram", + "telegramBotEnableDesc": "Ativa o bot do Telegram.", + "telegramToken": "Token do Telegram", + "telegramTokenDesc": "O token do bot do Telegram obtido de '{'@'}BotFather'.", + "telegramProxy": "Proxy SOCKS", + "telegramProxyDesc": "Ativa o proxy SOCKS5 para conectar ao Telegram. (ajuste as configurações conforme o guia)", + "telegramAPIServer": "API Server do Telegram", + "telegramAPIServerDesc": "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão.", + "telegramChatId": "ID de Chat do Administrador", + "telegramChatIdDesc": "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui {'@'}userinfobot) ou (use o comando '/id' no bot)", + "telegramNotifyTime": "Hora da Notificação", + "telegramNotifyTimeDesc": "O horário de notificação do bot do Telegram configurado para relatórios periódicos. (use o formato de tempo do crontab)", + "tgNotifyBackup": "Backup do Banco de Dados", + "tgNotifyBackupDesc": "Enviar arquivo de backup do banco de dados junto com o relatório.", + "tgNotifyLogin": "Notificação de Login", + "tgNotifyLoginDesc": "Receba notificações sobre o nome de usuário, endereço IP e horário sempre que alguém tentar fazer login no seu painel web.", + "sessionMaxAge": "Duração da Sessão", + "sessionMaxAgeDesc": "A duração pela qual você pode permanecer logado. (unidade: minuto)", + "expireTimeDiff": "Notificação de Expiração", + "expireTimeDiffDesc": "Receba notificações sobre a data de expiração ao atingir esse limite. (unidade: dia)", + "trafficDiff": "Notificação de Limite de Tráfego", + "trafficDiffDesc": "Receba notificações sobre o limite de tráfego ao atingir esse limite. (unidade: GB)", + "tgNotifyCpu": "Notificação de Carga da CPU", + "tgNotifyCpuDesc": "Receba notificações se a carga da CPU ultrapassar esse limite. (unidade: %)", + "timeZone": "Fuso Horário", + "timeZoneDesc": "As tarefas agendadas serão executadas com base nesse fuso horário.", + "subSettings": "Assinatura", + "subEnable": "Ativar Serviço de Assinatura", + "subEnableDesc": "Ativa o serviço de assinatura.", + "subJsonEnable": "Ativar/Desativar o endpoint de assinatura JSON de forma independente.", + "subTitle": "Título da Assinatura", + "subTitleDesc": "Título exibido no cliente VPN", + "subSupportUrl": "URL de Suporte", + "subSupportUrlDesc": "Link de suporte técnico exibido no cliente VPN", + "subProfileUrl": "URL de Perfil", + "subProfileUrlDesc": "Um link para o seu site exibido no cliente VPN", + "subAnnounce": "Anúncio", + "subAnnounceDesc": "O texto do anúncio exibido no cliente VPN", + "subEnableRouting": "Ativar roteamento", + "subEnableRoutingDesc": "Configuração global para habilitar o roteamento no cliente VPN. (Apenas para Happ)", + "subRoutingRules": "Regras de roteamento", + "subRoutingRulesDesc": "Regras de roteamento globais para o cliente VPN. (Apenas para Happ)", + "subListen": "IP de Escuta", + "subListenDesc": "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)", + "subPort": "Porta de Escuta", + "subPortDesc": "O número da porta para o serviço de assinatura. (deve ser uma porta não usada)", + "subCertPath": "Caminho da Chave Pública", + "subCertPathDesc": "O caminho do arquivo de chave pública para o serviço de assinatura. (começa com ‘/‘)", + "subKeyPath": "Caminho da Chave Privada", + "subKeyPathDesc": "O caminho do arquivo de chave privada para o serviço de assinatura. (começa com ‘/‘)", + "subPath": "Caminho URI", + "subPathDesc": "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)", + "subDomain": "Domínio de Escuta", + "subDomainDesc": "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)", + "subUpdates": "Intervalos de Atualização", + "subUpdatesDesc": "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)", + "subEncrypt": "Codificar", + "subEncryptDesc": "O conteúdo retornado pelo serviço de assinatura será codificado em Base64.", + "subShowInfo": "Mostrar Informações de Uso", + "subShowInfoDesc": "O tráfego restante e a data serão exibidos nos aplicativos de cliente.", + "subURI": "URI de Proxy Reverso", + "subURIDesc": "O caminho URI da URL de assinatura para uso por trás de proxies.", + "externalTrafficInformEnable": "Informações de tráfego externo", + "externalTrafficInformEnableDesc": "Informar a API externa sobre cada atualização de tráfego.", + "externalTrafficInformURI": "URI de informação de tráfego externo", + "externalTrafficInformURIDesc": "As atualizações de tráfego são enviadas para este URI.", + "restartXrayOnClientDisable": "Reiniciar Xray Após Desativação Automática", + "restartXrayOnClientDisableDesc": "Quando um cliente for desativado automaticamente por expiração ou limite de tráfego, reinicie o Xray.", + "fragment": "Fragmentação", + "fragmentDesc": "Ativa a fragmentação para o pacote TLS hello.", + "fragmentSett": "Configurações de Fragmentação", + "noisesDesc": "Ativar Noises.", + "noisesSett": "Configurações de Noises", + "mux": "Mux", + "muxDesc": "Transmitir múltiplos fluxos de dados independentes dentro de um fluxo de dados estabelecido.", + "muxSett": "Configurações de Mux", + "direct": "Conexão Direta", + "directDesc": "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico.", + "notifications": "Notificações", + "certs": "Certificados", + "externalTraffic": "Tráfego Externo", + "dateAndTime": "Data e Hora", + "proxyAndServer": "Proxy e Servidor", + "intervals": "Intervalos", + "information": "Informação", + "language": "Idioma", + "telegramBotLanguage": "Idioma do Bot do Telegram", + "security": { + "admin": "Credenciais de administrador", + "twoFactor": "Autenticação de dois fatores", + "twoFactorEnable": "Ativar 2FA", + "twoFactorEnableDesc": "Adiciona uma camada extra de autenticação para mais segurança.", + "twoFactorModalSetTitle": "Ativar autenticação de dois fatores", + "twoFactorModalDeleteTitle": "Desativar autenticação de dois fatores", + "twoFactorModalSteps": "Para configurar a autenticação de dois fatores, siga alguns passos:", + "twoFactorModalFirstStep": "1. Escaneie este QR code no aplicativo de autenticação ou copie o token próximo ao QR code e cole no aplicativo", + "twoFactorModalSecondStep": "2. Digite o código do aplicativo", + "twoFactorModalRemoveStep": "Digite o código do aplicativo para remover a autenticação de dois fatores.", + "twoFactorModalChangeCredentialsTitle": "Alterar credenciais", + "twoFactorModalChangeCredentialsStep": "Insira o código do aplicativo para alterar as credenciais do administrador.", + "twoFactorModalSetSuccess": "A autenticação de dois fatores foi estabelecida com sucesso", + "twoFactorModalDeleteSuccess": "A autenticação de dois fatores foi excluída com sucesso", + "twoFactorModalError": "Código incorreto" + }, + "toasts": { + "modifySettings": "Os parâmetros foram alterados.", + "getSettings": "Ocorreu um erro ao recuperar os parâmetros.", + "modifyUserError": "Ocorreu um erro ao alterar as credenciais do administrador.", + "modifyUser": "Você alterou com sucesso as credenciais do administrador.", + "originalUserPassIncorrect": "O nome de usuário ou senha atual é inválido", + "userPassMustBeNotEmpty": "O novo nome de usuário e senha não podem estar vazios", + "getOutboundTrafficError": "Erro ao obter tráfego de saída", + "resetOutboundTrafficError": "Erro ao redefinir tráfego de saída" + } + }, + "xray": { + "title": "Configurações Xray", + "save": "Salvar", + "restart": "Reiniciar Xray", + "restartSuccess": "Xray foi reiniciado com sucesso", + "stopSuccess": "Xray foi interrompido com sucesso", + "restartError": "Ocorreu um erro ao reiniciar o Xray.", + "stopError": "Ocorreu um erro ao parar o Xray.", + "basicTemplate": "Básico", + "advancedTemplate": "Avançado", + "generalConfigs": "Geral", + "generalConfigsDesc": "Essas opções determinam ajustes gerais.", + "logConfigs": "Log", + "logConfigsDesc": "Os logs podem afetar a eficiência do servidor. É recomendável habilitá-los com sabedoria apenas se necessário.", + "blockConfigsDesc": "Essas opções bloqueiam tráfego com base em protocolos e sites específicos solicitados.", + "basicRouting": "Roteamento Básico", + "blockConnectionsConfigsDesc": "Essas opções bloquearão o tráfego com base no país solicitado.", + "directConnectionsConfigsDesc": "Uma conexão direta garante que o tráfego específico não seja roteado por outro servidor.", + "blockips": "Bloquear IPs", + "blockdomains": "Bloquear Domínios", + "directips": "IPs Diretos", + "directdomains": "Domínios Diretos", + "ipv4Routing": "Roteamento IPv4", + "ipv4RoutingDesc": "Essas opções roteam o tráfego para um destino específico via IPv4.", + "warpRouting": "Roteamento WARP", + "warpRoutingDesc": "Essas opções roteam o tráfego para um destino específico via WARP.", + "nordRouting": "Roteamento NordVPN", + "nordRoutingDesc": "Essas opções roteiam o tráfego para um destino específico via NordVPN.", + "Template": "Modelo de Configuração Avançada do Xray", + "TemplateDesc": "O arquivo final de configuração do Xray será gerado com base neste modelo.", + "FreedomStrategy": "Estratégia do Protocolo Freedom", + "FreedomStrategyDesc": "Definir a estratégia de saída para a rede no Protocolo Freedom.", + "RoutingStrategy": "Estratégia Geral de Roteamento", + "RoutingStrategyDesc": "Definir a estratégia geral de roteamento de tráfego para resolver todas as solicitações.", + "outboundTestUrl": "URL de teste de outbound", + "outboundTestUrlDesc": "URL usada ao testar conectividade do outbound", + "Torrent": "Bloquear Protocolo BitTorrent", + "Inbounds": "Inbounds", + "InboundsDesc": "Aceitar clientes específicos.", + "Outbounds": "Outbounds", + "Balancers": "Balanceadores", + "OutboundsDesc": "Definir o caminho de saída do tráfego.", + "Routings": "Regras de Roteamento", + "RoutingsDesc": "A prioridade de cada regra é importante!", + "completeTemplate": "Todos", + "logLevel": "Nível de Log", + "logLevelDesc": "O nível de log para erros, indicando a informação que precisa ser registrada.", + "accessLog": "Log de Acesso", + "accessLogDesc": "O caminho do arquivo para o log de acesso. O valor especial 'none' desativa os logs de acesso.", + "errorLog": "Log de Erros", + "errorLogDesc": "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro.", + "dnsLog": "Log DNS", + "dnsLogDesc": "Se ativar logs de consulta DNS", + "maskAddress": "Mascarar Endereço", + "maskAddressDesc": "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log.", + "statistics": "Estatísticas", + "statsInboundUplink": "Estatísticas de Upload de Entrada", + "statsInboundUplinkDesc": "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de entrada.", + "statsInboundDownlink": "Estatísticas de Download de Entrada", + "statsInboundDownlinkDesc": "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de entrada.", + "statsOutboundUplink": "Estatísticas de Upload de Saída", + "statsOutboundUplinkDesc": "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de saída.", + "statsOutboundDownlink": "Estatísticas de Download de Saída", + "statsOutboundDownlinkDesc": "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de saída.", + "rules": { + "first": "Primeiro", + "last": "Último", + "up": "Cima", + "down": "Baixo", + "source": "Fonte", + "dest": "Destino", + "inbound": "Entrada", + "outbound": "Saída", + "balancer": "Balanceador", + "info": "Info", + "add": "Adicionar Regra", + "edit": "Editar Regra", + "useComma": "Itens separados por vírgula" + }, + "outbound": { + "addOutbound": "Adicionar Saída", + "addReverse": "Adicionar Reverso", + "editOutbound": "Editar Saída", + "editReverse": "Editar Reverso", + "reverseTag": "Tag de Reverso", + "reverseTagDesc": "Tag de saída do proxy reverso simples VLESS. Deixe vazio para desabilitar.", + "reverseTagPlaceholder": "tag de saída (vazio para desabilitar)", + "tag": "Tag", + "tagDesc": "Tag Única", + "address": "Endereço", + "reverse": "Reverso", + "domain": "Domínio", + "type": "Tipo", + "bridge": "Ponte", + "portal": "Portal", + "link": "Link", + "intercon": "Interconexão", + "settings": "Configurações", + "accountInfo": "Informações da Conta", + "outboundStatus": "Status de Saída", + "sendThrough": "Enviar Através de", + "test": "Testar", + "testResult": "Resultado do teste", + "testing": "Testando conexão...", + "testSuccess": "Teste bem-sucedido", + "testFailed": "Teste falhou", + "testError": "Falha ao testar saída", + "nordvpn": "NordVPN", + "accessToken": "Token de Acesso", + "country": "País", + "server": "Servidor", + "city": "Cidade", + "allCities": "Todas as Cidades", + "privateKey": "Chave Privada", + "load": "Carga" + }, + "balancer": { + "addBalancer": "Adicionar Balanceador", + "editBalancer": "Editar Balanceador", + "balancerStrategy": "Estratégia", + "balancerSelectors": "Seletores", + "tag": "Tag", + "tagDesc": "Tag Única", + "balancerDesc": "Não é possível usar balancerTag e outboundTag ao mesmo tempo. Se usados simultaneamente, apenas outboundTag funcionará." + }, + "wireguard": { + "secretKey": "Chave Secreta", + "publicKey": "Chave Pública", + "allowedIPs": "IPs Permitidos", + "endpoint": "Ponto Final", + "psk": "Chave Pré-Compartilhada", + "domainStrategy": "Estratégia de Domínio" + }, + "tun": { + "nameDesc": "O nome da interface TUN. O padrão é 'xray0'", + "mtuDesc": "Unidade Máxima de Transmissão. O tamanho máximo dos pacotes de dados. O padrão é 1500", + "userLevel": "Nível do Usuário", + "userLevelDesc": "Todas as conexões feitas através deste inbound usarão este nível de usuário. O padrão é 0" + }, + "dns": { + "enable": "Ativar DNS", + "enableDesc": "Ativar o servidor DNS integrado", + "tag": "Tag de Entrada DNS", + "tagDesc": "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento.", + "clientIp": "IP do Cliente", + "clientIpDesc": "Usado para notificar o servidor sobre a localização IP especificada durante consultas DNS", + "disableCache": "Desativar cache", + "disableCacheDesc": "Desativa o cache de DNS", + "disableFallback": "Desativar Fallback", + "disableFallbackDesc": "Desativa consultas DNS de fallback", + "disableFallbackIfMatch": "Desativar Fallback Se Corresponder", + "disableFallbackIfMatchDesc": "Desativa consultas DNS de fallback quando a lista de domínios correspondentes do servidor DNS é atingida", + "enableParallelQuery": "Habilitar Consulta Paralela", + "enableParallelQueryDesc": "Habilitar consultas DNS paralelas para múltiplos servidores para resolução mais rápida", + "strategy": "Estratégia de Consulta", + "strategyDesc": "Estratégia geral para resolver nomes de domínio", + "add": "Adicionar Servidor", + "edit": "Editar Servidor", + "domains": "Domínios", + "expectIPs": "IPs Esperadas", + "unexpectIPs": "IPs inesperados", + "useSystemHosts": "Usar Hosts do sistema", + "useSystemHostsDesc": "Usar o arquivo hosts de um sistema instalado", + "usePreset": "Usar modelo", + "dnsPresetTitle": "Modelos DNS", + "dnsPresetFamily": "Familiar" + }, + "fakedns": { + "add": "Adicionar Fake DNS", + "edit": "Editar Fake DNS", + "ipPool": "Sub-rede do Pool de IP", + "poolSize": "Tamanho do Pool" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Teclado fechado!", + "noResult": "❗ Nenhum resultado!", + "noQuery": "❌ Consulta não encontrada! Por favor, use o comando novamente!", + "wentWrong": "❌ Algo deu errado!", + "noIpRecord": "❗ Nenhum registro de IP!", + "noInbounds": "❗ Nenhum inbound encontrado!", + "unlimited": "♾ Ilimitado (Reset)", + "add": "Adicionar", + "month": "Mês", + "months": "Meses", + "day": "Dia", + "days": "Dias", + "hours": "Horas", + "minutes": "Minutos", + "unknown": "Desconhecido", + "inbounds": "Inbounds", + "clients": "Clientes", + "offline": "🔴 Offline", + "online": "🟢 Online", + "commands": { + "unknown": "❗ Comando desconhecido.", + "pleaseChoose": "👇 Escolha:\r\n", + "help": "🤖 Bem-vindo a este bot! Ele foi projetado para oferecer dados específicos do painel da web e permite que você faça as modificações necessárias.\r\n\r\n", + "start": "👋 Olá {{ .Firstname }}.\r\n", + "welcome": "🤖 Bem-vindo ao bot de gerenciamento do {{ .Hostname }}.\r\n", + "status": "✅ Bot está OK!", + "usage": "❗ Por favor, forneça um texto para pesquisar!", + "getID": "🆔 Seu ID: {{ .ID }}", + "helpAdminCommands": "Para reiniciar o Xray Core:\r\n/restart\r\n\r\nPara pesquisar por um email de cliente:\r\n/usage [Email]\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id", + "helpClientCommands": "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Operação bem-sucedida!", + "restartFailed": "❗ Erro na operação.\r\n\r\nErro: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core não está em execução.", + "startDesc": "Mostrar menu principal", + "helpDesc": "Ajuda do bot", + "statusDesc": "Verificar status do bot", + "idDesc": "Mostrar seu ID do Telegram" + }, + "messages": { + "cpuThreshold": "🔴 A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%", + "selectUserFailed": "❌ Erro na seleção do usuário!", + "userSaved": "✅ Usuário do Telegram salvo.", + "loginSuccess": "✅ Conectado ao painel com sucesso.\r\n", + "loginFailed": "❗️Tentativa de login no painel falhou.\r\n", + "2faFailed": "Falha no 2FA", + "report": "🕰 Relatórios agendados: {{ .RunTime }}\r\n", + "datetime": "⏰ Data&Hora: {{ .DateTime }}\r\n", + "hostname": "💻 Host: {{ .Hostname }}\r\n", + "version": "🚀 Versão 3X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 Versão Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IPs:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Tempo de atividade: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Carga do sistema: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 Tráfego: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Status: {{ .State }}\r\n", + "username": "👤 Nome de usuário: {{ .Username }}\r\n", + "reason": "❗️ Motivo: {{ .Reason }}\r\n", + "time": "⏰ Hora: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Porta: {{ .Port }}\r\n", + "expire": "📅 Data de expiração: {{ .Time }}\r\n", + "expireIn": "📅 Expira em: {{ .Time }}\r\n", + "active": "💡 Ativo: {{ .Enable }}\r\n", + "enabled": "🚨 Ativado: {{ .Enable }}\r\n", + "online": "🌐 Status da conexão: {{ .Status }}\r\n", + "lastOnline": "🔙 Última vez online: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Upload: ↑{{ .Upload }}\r\n", + "download": "🔽 Download: ↓{{ .Download }}\r\n", + "total": "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Usuário do Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 {{ .Type }} esgotado:\r\n", + "exhaustedCount": "🚨 Contagem de {{ .Type }} esgotado:\r\n", + "onlinesCount": "🌐 Clientes online: {{ .Count }}\r\n", + "disabled": "🛑 Desativado: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Esgotar em breve: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Hora do backup: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Atualizado em: {{ .Time }}\r\n\r\n", + "yes": "✅ Sim", + "no": "❌ Não", + "received_id": "🔑📥 ID atualizado.", + "received_password": "🔑📥 Senha atualizada.", + "received_email": "📧📥 E-mail atualizado.", + "received_comment": "💬📥 Comentário atualizado.", + "id_prompt": "🔑 ID Padrão: {{ .ClientId }}\n\nDigite seu ID.", + "pass_prompt": "🔑 Senha Padrão: {{ .ClientPassword }}\n\nDigite sua senha.", + "email_prompt": "📧 E-mail Padrão: {{ .ClientEmail }}\n\nDigite seu e-mail.", + "comment_prompt": "💬 Comentário Padrão: {{ .ClientComment }}\n\nDigite seu comentário.", + "inbound_client_data_id": "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!", + "inbound_client_data_pass": "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Senha: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!", + "cancel": "❌ Processo Cancelado! \n\nVocê pode iniciar novamente a qualquer momento com /start. 🔄", + "error_add_client": "⚠️ Erro:\n\n {{ .error }}", + "using_default_value": "Tudo bem, vou manter o valor padrão. 😊", + "incorrect_input": "Sua entrada não é válida.\nAs frases devem ser contínuas, sem espaços.\nExemplo correto: aaaaaa\nExemplo incorreto: aaa aaa 🚫", + "AreYouSure": "Você tem certeza? 🤔", + "SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ✅ Sucesso", + "FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ❌ Falhou \n\n🛠️ Erro: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Processo de redefinição de tráfego concluído para todos os clientes." + }, + "buttons": { + "closeKeyboard": "❌ Fechar teclado", + "cancel": "❌ Cancelar", + "cancelReset": "❌ Cancelar redefinição", + "cancelIpLimit": "❌ Cancelar limite de IP", + "confirmResetTraffic": "✅ Confirmar redefinição de tráfego?", + "confirmClearIps": "✅ Confirmar limpar IPs?", + "confirmRemoveTGUser": "✅ Confirmar remover usuário do Telegram?", + "confirmToggle": "✅ Confirmar ativar/desativar usuário?", + "dbBackup": "Obter backup do DB", + "serverUsage": "Uso do servidor", + "getInbounds": "Obter Inbounds", + "depleteSoon": "Esgotar em breve", + "clientUsage": "Obter uso", + "onlines": "Clientes online", + "commands": "Comandos", + "refresh": "🔄 Atualizar", + "clearIPs": "❌ Limpar IPs", + "removeTGUser": "❌ Remover usuário do Telegram", + "selectTGUser": "👤 Selecionar usuário do Telegram", + "selectOneTGUser": "👤 Selecione um usuário do Telegram:", + "resetTraffic": "📈 Redefinir tráfego", + "resetExpire": "📅 Alterar data de expiração", + "ipLog": "🔢 Log de IP", + "ipLimit": "🔢 Limite de IP", + "setTGUser": "👤 Definir usuário do Telegram", + "toggle": "🔘 Ativar / Desativar", + "custom": "🔢 Personalizado", + "confirmNumber": "✅ Confirmar: {{ .Num }}", + "confirmNumberAdd": "✅ Confirmar adicionar: {{ .Num }}", + "limitTraffic": "🚧 Limite de tráfego", + "getBanLogs": "Obter logs de banimento", + "allClients": "Todos os clientes", + "addClient": "Adicionar Cliente", + "submitDisable": "Enviar como Desativado ☑️", + "submitEnable": "Enviar como Ativado ✅", + "use_default": "🏷️ Usar padrão", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Senha", + "change_email": "⚙️📧 E-mail", + "change_comment": "⚙️💬 Comentário", + "ResetAllTraffics": "Redefinir Todo o Tráfego", + "SortedTrafficUsageReport": "Relatório de Uso de Tráfego Ordenado" + }, + "answers": { + "successfulOperation": "✅ Operação bem-sucedida!", + "errorOperation": "❗ Erro na operação.", + "getInboundsFailed": "❌ Falha ao obter inbounds.", + "getClientsFailed": "❌ Falha ao obter clientes.", + "canceled": "❌ {{ .Email }}: Operação cancelada.", + "clientRefreshSuccess": "✅ {{ .Email }}: Cliente atualizado com sucesso.", + "IpRefreshSuccess": "✅ {{ .Email }}: IPs atualizados com sucesso.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Usuário do Telegram do cliente atualizado com sucesso.", + "resetTrafficSuccess": "✅ {{ .Email }}: Tráfego redefinido com sucesso.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Limite de tráfego salvo com sucesso.", + "expireResetSuccess": "✅ {{ .Email }}: Dias de expiração redefinidos com sucesso.", + "resetIpSuccess": "✅ {{ .Email }}: Limite de IP {{ .Count }} salvo com sucesso.", + "clearIpSuccess": "✅ {{ .Email }}: IPs limpos com sucesso.", + "getIpLog": "✅ {{ .Email }}: Obter log de IP.", + "getUserInfo": "✅ {{ .Email }}: Obter informações do usuário do Telegram.", + "removedTGUserSuccess": "✅ {{ .Email }}: Usuário do Telegram removido com sucesso.", + "enableSuccess": "✅ {{ .Email }}: Ativado com sucesso.", + "disableSuccess": "✅ {{ .Email }}: Desativado com sucesso.", + "askToAddUserId": "Sua configuração não foi encontrada!\r\nPeça ao seu administrador para usar seu Telegram ChatID em suas configurações.\r\n\r\nSeu ChatID: {{ .TgUserID }}", + "chooseClient": "Escolha um cliente para Inbound {{ .Inbound }}", + "chooseInbound": "Escolha um Inbound" + } + } +} diff --git a/web/translation/ru-RU.json b/web/translation/ru-RU.json new file mode 100644 index 00000000..f4212f4e --- /dev/null +++ b/web/translation/ru-RU.json @@ -0,0 +1,941 @@ +{ + "username": "Имя пользователя", + "password": "Пароль", + "login": "Войти", + "confirm": "Подтвердить", + "cancel": "Отмена", + "close": "Закрыть", + "save": "Сохранить", + "logout": "Выход", + "create": "Создать", + "update": "Обновить", + "copy": "Копировать", + "copied": "Скопировано", + "download": "Скачать", + "remark": "Примечание", + "enable": "Включить", + "protocol": "Протокол", + "search": "Поиск", + "filter": "Фильтр", + "loading": "Загрузка...", + "second": "Секунда", + "minute": "Минута", + "hour": "Час", + "day": "День", + "check": "Проверить", + "indefinite": "Бесконечно", + "unlimited": "Безлимит", + "none": "Пусто", + "qrCode": "QR-код", + "info": "Информация", + "edit": "Изменить", + "delete": "Удалить", + "reset": "Сбросить", + "noData": "Нет данных.", + "copySuccess": "Скопировано", + "sure": "Да", + "encryption": "Шифрование", + "useIPv4ForHost": "Использовать IPv4 для подключения к хосту", + "transmission": "Транспорт", + "host": "Хост", + "path": "Путь", + "camouflage": "Маскировка", + "status": "Статус", + "enabled": "Включено", + "disabled": "Отключено", + "depleted": "Исчерпано", + "depletingSoon": "Почти исчерпано", + "offline": "Офлайн", + "online": "Онлайн", + "domainName": "Домен", + "monitor": "Мониторинг IP", + "certificate": "SSL-сертификат", + "fail": "Сбой", + "comment": "Комментарий", + "success": "Успешно", + "lastOnline": "Был(а) в сети", + "getVersion": "Узнать версию", + "install": "Установка", + "clients": "Клиенты", + "usage": "Использование", + "twoFactorCode": "Код 2FA", + "remained": "Остаток", + "security": "Безопасность", + "secAlertTitle": "Предупреждение системы безопасности", + "secAlertSsl": "Соединение не защищено. Не вводите конфиденциальные данные до установки SSL-сертификата.", + "secAlertConf": "Некоторые настройки уязвимы. Рекомендуется усилить защиту для предотвращения атак.", + "secAlertSSL": "Подключение к панели не защищено. Установите SSL-сертификат для защиты данных.", + "secAlertPanelPort": "Порт панели по умолчанию небезопасен. Установите нестандартный или случайный порт.", + "secAlertPanelURI": "Адрес панели по умолчанию небезопасен. Настройте уникальный и сложный URI.", + "secAlertSubURI": "URI подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес.", + "secAlertSubJsonURI": "URI JSON-подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес.", + "emptyDnsDesc": "Нет добавленных DNS-серверов.", + "emptyFakeDnsDesc": "Нет добавленных Fake DNS-серверов.", + "emptyBalancersDesc": "Нет добавленных балансировщиков.", + "emptyReverseDesc": "Нет добавленных реверс-прокси.", + "somethingWentWrong": "Что-то пошло не так", + "subscription": { + "title": "Информация о подписке", + "subId": "ID подписки", + "status": "Статус", + "downloaded": "Загружено", + "uploaded": "Отправлено", + "expiry": "Срок действия", + "totalQuota": "Общий лимит", + "individualLinks": "Индивидуальные ссылки", + "active": "Активна", + "inactive": "Неактивна", + "unlimited": "Неограниченно", + "noExpiry": "Бессрочно" + }, + "menu": { + "theme": "Тема", + "dark": "Темная", + "ultraDark": "Очень темная", + "dashboard": "Дашборд", + "inbounds": "Подключения", + "settings": "Настройки", + "xray": "Настройки Xray", + "logout": "Выход", + "link": "Управление" + }, + "pages": { + "login": { + "hello": "Привет!", + "title": "Добро пожаловать!", + "loginAgain": "Сессия истекла. Войдите в систему снова", + "toasts": { + "invalidFormData": "Недопустимый формат данных", + "emptyUsername": "Введите имя пользователя", + "emptyPassword": "Введите пароль", + "wrongUsernameOrPassword": "Неверные данные учетной записи.", + "successLogin": "Вход выполнен успешно" + } + }, + "index": { + "title": "Дашборд", + "cpu": "ЦП", + "logicalProcessors": "Логические процессоры", + "frequency": "Частота", + "swap": "Файл подкачки", + "storage": "Диск", + "memory": "ОЗУ", + "threads": "Потоки", + "xrayStatus": "Xray", + "stopXray": "Остановить", + "restartXray": "Перезапустить", + "xraySwitch": "Выбор версии", + "xraySwitchClick": "Выберите нужную версию", + "xraySwitchClickDesk": "Важно: старые версии могут не поддерживать текущие настройки", + "xrayUpdates": "Обновления Xray", + "updatePanel": "Обновить панель", + "panelUpdateDesc": "Это обновит 3X-UI до последнего релиза и перезапустит сервис панели.", + "currentPanelVersion": "Текущая версия панели", + "latestPanelVersion": "Последняя версия панели", + "panelUpToDate": "Панель обновлена", + "upToDate": "Обновлено", + "xrayStatusUnknown": "Неизвестно", + "xrayStatusRunning": "Запущен", + "xrayStatusStop": "Остановлен", + "xrayStatusError": "Ошибка", + "xrayErrorPopoverTitle": "Ошибка при запуске Xray", + "operationHours": "Время работы системы", + "systemHistoryTitle": "История системы", + "trendLast2Min": "Последние 2 минуты", + "systemLoad": "Нагрузка на систему", + "systemLoadDesc": "Средняя загрузка системы за последние 1, 5 и 15 минут", + "connectionCount": "Количество соединений", + "ipAddresses": "IP-адреса сервера", + "toggleIpVisibility": "Скрыть или показать IP-адреса сервера", + "overallSpeed": "Общая скорость передачи трафика", + "upload": "Отправка", + "download": "Загрузка", + "totalData": "Общий объем трафика", + "sent": "Отправлено", + "received": "Получено", + "documentation": "Документация", + "xraySwitchVersionDialog": "Переключить версию Xray", + "xraySwitchVersionDialogDesc": "Вы точно хотите сменить версию Xray?", + "xraySwitchVersionPopover": "Xray успешно обновлён", + "panelUpdateDialog": "Вы действительно хотите обновить панель?", + "panelUpdateDialogDesc": "Это обновит 3X-UI до версии #version# и перезапустит сервис панели.", + "panelUpdateCheckPopover": "Проверка обновления панели не удалась", + "panelUpdateStartedPopover": "Обновление панели началось", + "geofileUpdateDialog": "Вы действительно хотите обновить геофайл?", + "geofileUpdateDialogDesc": "Это обновит файл #filename#.", + "geofilesUpdateDialogDesc": "Это обновит все геофайлы.", + "geofilesUpdateAll": "Обновить все", + "geofileUpdatePopover": "Геофайлы успешно обновлены", + "customGeoTitle": "Пользовательские GeoSite / GeoIP", + "customGeoAdd": "Добавить", + "customGeoType": "Тип", + "customGeoAlias": "Псевдоним", + "customGeoUrl": "URL", + "customGeoEnabled": "Включено", + "customGeoLastUpdated": "Обновлено", + "customGeoExtColumn": "Маршрутизация (ext:…)", + "customGeoToastUpdateAll": "Все пользовательские источники обновлены", + "customGeoActions": "Действия", + "customGeoEdit": "Изменить", + "customGeoDelete": "Удалить", + "customGeoDownload": "Обновить сейчас", + "customGeoModalAdd": "Добавить источник", + "customGeoModalEdit": "Изменить источник", + "customGeoModalSave": "Сохранить", + "customGeoDeleteConfirm": "Удалить этот пользовательский источник?", + "customGeoRoutingHint": "В правилах маршрутизации используйте значение как ext:файл.dat:тег (замените тег).", + "customGeoInvalidId": "Некорректный идентификатор", + "customGeoAliasesError": "Не удалось загрузить список пользовательских geo", + "customGeoValidationAlias": "Псевдоним: только a-z, цифры, - и _", + "customGeoValidationUrl": "URL должен начинаться с http:// или https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (свой)", + "customGeoToastList": "Список пользовательских geo", + "customGeoToastAdd": "Добавить пользовательский geo", + "customGeoToastUpdate": "Изменить пользовательский geo", + "customGeoToastDelete": "Пользовательский geo-файл «{{ .fileName }}» удалён", + "customGeoToastDownload": "Geofile «{{ .fileName }}» обновлен", + "customGeoErrInvalidType": "Тип должен быть geosite или geoip", + "customGeoErrAliasRequired": "Укажите псевдоним", + "customGeoErrAliasPattern": "Псевдоним содержит недопустимые символы", + "customGeoErrAliasReserved": "Этот псевдоним зарезервирован", + "customGeoErrUrlRequired": "Укажите URL", + "customGeoErrInvalidUrl": "Некорректный URL", + "customGeoErrUrlScheme": "URL должен использовать http или https", + "customGeoErrUrlHost": "Некорректный хост URL", + "customGeoErrDuplicateAlias": "Такой псевдоним уже используется для этого типа", + "customGeoErrNotFound": "Источник не найден", + "customGeoErrDownload": "Ошибка загрузки", + "customGeoErrUpdateAllIncomplete": "Не удалось обновить один или несколько пользовательских источников", + "customGeoEmpty": "Пользовательских источников geo пока нет — нажмите «Добавить», чтобы создать", + "dontRefresh": "Установка в процессе. Не обновляйте страницу", + "logs": "Журнал", + "config": "Конфигурация", + "backup": "Резервная копия", + "backupTitle": "Бэкап и восстановление", + "exportDatabase": "Экспорт базы данных", + "exportDatabaseDesc": "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство.", + "importDatabase": "Импорт базы данных", + "importDatabaseDesc": "Нажмите, чтобы выбрать и загрузить файл .db с вашего устройства для восстановления базы данных из резервной копии.", + "importDatabaseSuccess": "База данных успешно импортирована", + "importDatabaseError": "Произошла ошибка при импорте базы данных", + "readDatabaseError": "Произошла ошибка при чтении базы данных", + "getDatabaseError": "Произошла ошибка при получении базы данных", + "getConfigError": "Произошла ошибка при получении конфигурационного файла" + }, + "inbounds": { + "allTimeTraffic": "Общий трафик", + "allTimeTrafficUsage": "Общее использование за все время", + "title": "Подключения", + "totalDownUp": "Отправлено/получено", + "totalUsage": "Всего трафика", + "inboundCount": "Всего подключений", + "operate": "Меню", + "enable": "Включить", + "remark": "Примечание", + "node": "Узел", + "deployTo": "Развернуть на", + "localPanel": "Локальная панель", + "protocol": "Протокол", + "port": "Порт", + "portMap": "Порт-маппинг", + "traffic": "Трафик", + "details": "Подробнее", + "transportConfig": "Транспорт", + "expireDate": "Дата окончания", + "createdAt": "Создано", + "updatedAt": "Обновлено", + "resetTraffic": "Сброс трафика", + "addInbound": "Создать подключение", + "generalActions": "Общие действия", + "modifyInbound": "Изменить подключение", + "deleteInbound": "Удалить подключение", + "deleteInboundContent": "Вы уверены, что хотите удалить подключение?", + "deleteClient": "Удалить клиента", + "deleteClientContent": "Вы уверены, что хотите удалить клиента?", + "resetTrafficContent": "Вы уверены, что хотите сбросить трафик?", + "copyLink": "Копировать ссылку", + "address": "Адрес", + "network": "Сеть", + "destinationPort": "Порт назначения", + "targetAddress": "Целевой адрес", + "monitorDesc": "Оставьте пустым для прослушивания всех IP-адресов", + "meansNoLimit": "= Без ограничений (значение: ГБ)", + "totalFlow": "Общий расход", + "leaveBlankToNeverExpire": "Оставьте пустым, чтобы было бесконечным", + "noRecommendKeepDefault": "Рекомендуется оставить настройки по умолчанию", + "certificatePath": "Путь к сертификату", + "certificateContent": "Содержимое сертификата", + "publicKey": "Публичный ключ", + "privatekey": "Приватный ключ", + "clickOnQRcode": "Нажмите на QR-код, чтобы скопировать", + "client": "Клиент", + "export": "Экспорт ссылок", + "clone": "Клонировать", + "cloneInbound": "Клонировать", + "cloneInboundContent": "Будут клонированы все настройки подключений, кроме списка клиентов, порта и IP-адреса прослушивания", + "cloneInboundOk": "Клонировано", + "resetAllTraffic": "Сброс трафика всех подключений", + "resetAllTrafficTitle": "Сброс трафика всех подключений", + "resetAllTrafficContent": "Вы уверены, что хотите сбросить трафик всех подключений?", + "resetInboundClientTraffics": "Сброс трафика клиента", + "resetInboundClientTrafficTitle": "Сброс трафика клиентов", + "resetInboundClientTrafficContent": "Вы уверены, что хотите сбросить трафик для этих клиентов?", + "resetAllClientTraffics": "Сброс трафика всех клиентов", + "resetAllClientTrafficTitle": "Сброс трафика всех клиентов", + "resetAllClientTrafficContent": "Вы уверены, что хотите сбросить трафик всех клиентов?", + "delDepletedClients": "Удалить отключенных клиентов", + "delDepletedClientsTitle": "Удаление отключенных клиентов", + "delDepletedClientsContent": "Вы уверены, что хотите удалить всех отключенных клиентов?", + "email": "Email", + "emailDesc": "Пожалуйста, укажите уникальный Email", + "IPLimit": "Лимит по количеству IP", + "IPLimitDesc": "Ограничение числа одновременных подключений с разных IP (0 – отключить)", + "IPLimitlog": "Лог IP-адресов", + "IPLimitlogDesc": "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить лог)", + "IPLimitlogclear": "Очистить лог", + "setDefaultCert": "Установить сертификат панели", + "telegramDesc": "Пожалуйста, укажите Chat ID Telegram. (используйте команду '/id' в боте) или ({'@'}userinfobot)", + "subscriptionDesc": "Вы можете найти свою ссылку подписки в разделе 'Подробнее'", + "info": "Информация", + "same": "Тот же", + "inboundData": "Данные подключений", + "exportInbound": "Экспорт подключений", + "import": "Импортировать", + "importInbound": "Импорт подключений", + "periodicTrafficResetTitle": "Сброс трафика", + "periodicTrafficResetDesc": "Автоматический сброс счетчика трафика через указанные интервалы", + "lastReset": "Последний сброс", + "periodicTrafficReset": { + "never": "Никогда", + "daily": "Ежедневно", + "weekly": "Еженедельно", + "monthly": "Ежемесячно", + "hourly": "Ежечасно" + }, + "toasts": { + "obtain": "Получить", + "updateSuccess": "Обновление прошло успешно", + "logCleanSuccess": "Лог был очищен", + "inboundsUpdateSuccess": "Подключения успешно обновлены", + "inboundUpdateSuccess": "Подключение успешно обновлено", + "inboundCreateSuccess": "Подключение успешно создано", + "inboundDeleteSuccess": "Подключение успешно удалено", + "inboundClientAddSuccess": "Клиент(ы) подключения добавлен(ы)", + "inboundClientDeleteSuccess": "Клиент подключения удалён", + "inboundClientUpdateSuccess": "Клиент подключения обновлён", + "delDepletedClientsSuccess": "Все исчерпанные клиенты удалены", + "resetAllClientTrafficSuccess": "Весь трафик клиента сброшен", + "resetAllTrafficSuccess": "Весь трафик сброшен", + "resetInboundClientTrafficSuccess": "Трафик сброшен", + "trafficGetError": "Ошибка получения данных о трафике", + "getNewX25519CertError": "Ошибка при получении сертификата X25519.", + "getNewmldsa65Error": "Ошибка при получении сертификата mldsa65.", + "getNewVlessEncError": "Ошибка при получении сертификата VlessEnc." + }, + "stream": { + "general": { + "request": "Запрос", + "response": "Ответ", + "name": "Имя", + "value": "Значение" + }, + "tcp": { + "version": "Версия", + "method": "Метод", + "path": "Путь", + "status": "Статус", + "statusDescription": "Описание статуса", + "requestHeader": "Заголовок запроса", + "responseHeader": "Заголовок ответа" + } + } + }, + "client": { + "add": "Добавить клиента", + "edit": "Редактировать клиента", + "submitAdd": "Добавить", + "submitEdit": "Сохранить изменения", + "clientCount": "Количество клиентов", + "bulk": "Добавить несколько", + "copyFromInbound": "Скопировать клиентов из инбаунда", + "copyToInbound": "Скопировать клиентов в", + "copySelected": "Скопировать выбранных", + "copySource": "Источник", + "copyEmailPreview": "Предпросмотр итоговых email", + "copySelectSourceFirst": "Сначала выберите источник.", + "copyResult": "Результат копирования", + "copyResultSuccess": "Успешно скопировано", + "copyResultNone": "Нечего копировать: ни одного клиента не выбрано или список источника пуст", + "copyResultErrors": "Ошибки при копировании", + "copyFlowLabel": "Flow для новых клиентов (VLESS)", + "copyFlowHint": "Применится ко всем копируемым клиентам. Оставьте пустым, чтобы не задавать.", + "selectAll": "Выбрать всех", + "clearAll": "Снять всё", + "method": "Метод", + "first": "Первый", + "last": "Последний", + "prefix": "Префикс", + "postfix": "Постфикс", + "delayedStart": "Начало использования", + "expireDays": "Длительность", + "days": "дней", + "renew": "Автопродление", + "renewDesc": "Автопродление после истечения срока действия. (0 = отключить)(единица: день)" + }, + "nodes": { + "title": "Узлы", + "addNode": "Добавить узел", + "editNode": "Редактировать узел", + "totalNodes": "Всего узлов", + "onlineNodes": "Онлайн", + "offlineNodes": "Офлайн", + "avgLatency": "Средняя задержка", + "name": "Имя", + "namePlaceholder": "напр. de-frankfurt-1", + "addressPlaceholder": "panel.example.com или 1.2.3.4", + "remark": "Примечание", + "scheme": "Схема", + "address": "Адрес", + "port": "Порт", + "basePath": "Базовый путь", + "apiToken": "Токен API", + "apiTokenPlaceholder": "Токен со страницы Настроек удалённой панели", + "apiTokenHint": "Удалённая панель показывает свой токен API в разделе Настройки → Токен API.", + "regenerate": "Сгенерировать токен заново", + "regenerateConfirm": "Повторная генерация аннулирует текущий токен. Любая центральная панель, использующая его, потеряет доступ до обновления. Продолжить?", + "enable": "Включён", + "status": "Статус", + "cpu": "CPU", + "mem": "Память", + "uptime": "Время работы", + "latency": "Задержка", + "lastHeartbeat": "Последний пинг", + "xrayVersion": "Версия Xray", + "actions": "Действия", + "probe": "Проверить сейчас", + "testConnection": "Проверить соединение", + "connectionOk": "Соединение в порядке ({ms} мс)", + "connectionFailed": "Не удалось подключиться", + "never": "никогда", + "justNow": "только что", + "deleteConfirmTitle": "Удалить узел \"{name}\"?", + "deleteConfirmContent": "Это остановит мониторинг узла. Сама удалённая панель не будет затронута.", + "statusValues": { + "online": "Онлайн", + "offline": "Офлайн", + "unknown": "Неизвестно" + }, + "toasts": { + "list": "Не удалось загрузить узлы", + "obtain": "Не удалось загрузить узел", + "add": "Добавить узел", + "update": "Обновить узел", + "delete": "Удалить узел", + "deleted": "Узел удалён", + "test": "Проверить соединение", + "fillRequired": "Имя, адрес, порт и токен API обязательны", + "probeFailed": "Проверка не удалась" + } + }, + "settings": { + "title": "Настройки", + "save": "Сохранить", + "infoDesc": "Сохраните изменения и перезапустите панель для их применения.", + "restartPanel": "Перезапуск панели", + "restartPanelDesc": "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера", + "restartPanelSuccess": "Панель успешно перезапущена", + "actions": "Действия", + "resetDefaultConfig": "Восстановить настройки по умолчанию", + "panelSettings": "Панель", + "securitySettings": "Учетная запись", + "TGBotSettings": "Telegram-Бот", + "panelListeningIP": "IP-адрес для управления панелью", + "panelListeningIPDesc": "Оставьте пустым для подключения с любого IP", + "panelListeningDomain": "Домен панели", + "panelListeningDomainDesc": "Оставьте пустым для подключения с любых доменов и IP.", + "panelPort": "Порт панели", + "panelPortDesc": "Порт, на котором работает панель", + "publicKeyPath": "Путь к файлу публичного ключа сертификата панели", + "publicKeyPathDesc": "Введите полный путь, начинающийся с '/'", + "privateKeyPath": "Путь к файлу приватного ключа сертификата панели", + "privateKeyPathDesc": "Введите полный путь, начинающийся с '/'", + "panelUrlPath": "Корневой путь URL адреса панели", + "panelUrlPathDesc": "Должен начинаться с '/' и заканчиваться '/'", + "pageSize": "Размер нумерации страниц", + "pageSizeDesc": "Определить размер страницы для таблицы подключений. Установите 0, чтобы отключить", + "remarkModel": "Модель примечания и символ разделения", + "datepicker": "Тип календаря", + "datepickerPlaceholder": "Выберите дату", + "datepickerDescription": "Запланированные задачи будут выполняться в соответствии с этим календарем.", + "sampleRemark": "Пример примечания", + "oldUsername": "Текущий логин", + "currentPassword": "Текущий пароль", + "newUsername": "Новый логин", + "newPassword": "Новый пароль", + "telegramBotEnable": "Включить Telegram бота", + "telegramBotEnableDesc": "Доступ к функциям панели через Telegram-бота", + "telegramToken": "Токен Telegram бота", + "telegramTokenDesc": "Необходимо получить токен у менеджера ботов Telegram {'@'}botfather", + "telegramProxy": "Прокси-сервер Socks5", + "telegramProxyDesc": "Если для подключения к Telegram вам нужен прокси Socks5, настройте его параметры согласно руководству.", + "telegramAPIServer": "API-сервер Telegram", + "telegramAPIServerDesc": "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию.", + "telegramChatId": "User ID администратора бота", + "telegramChatIdDesc": "Один или несколько User ID администратора(-ов) Telegram-бота. Для получения User ID используйте {'@'}userinfobot или команду '/id' в боте.", + "telegramNotifyTime": "Частота уведомлений для администраторов от бота", + "telegramNotifyTimeDesc": "Укажите интервал уведомлений в формате Crontab", + "tgNotifyBackup": "Резервное копирование базы данных", + "tgNotifyBackupDesc": "Отправлять уведомление с файлом резервной копии базы данных", + "tgNotifyLogin": "Уведомление о входе", + "tgNotifyLoginDesc": "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель.", + "sessionMaxAge": "Продолжительность сессии", + "sessionMaxAgeDesc": "Продолжительность сессии в системе (значение: минута)", + "expireTimeDiff": "Задержка уведомления об истечении сессии", + "expireTimeDiffDesc": "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)", + "trafficDiff": "Порог трафика для уведомления", + "trafficDiffDesc": "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)", + "tgNotifyCpu": "Порог нагрузки на ЦП для уведомления", + "tgNotifyCpuDesc": "Уведомление администраторов в Telegram, если нагрузка на ЦП превышает этот порог (значение: %)", + "timeZone": "Часовой пояс", + "timeZoneDesc": "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе", + "subSettings": "Подписка", + "subEnable": "Включить подписку", + "subEnableDesc": "Функция подписки с отдельной конфигурацией", + "subJsonEnable": "Включить/отключить JSON-эндпоинт подписки независимо.", + "subTitle": "Заголовок подписки", + "subTitleDesc": "Название подписки, которое видит клиент в VPN-клиенте", + "subSupportUrl": "URL поддержки", + "subSupportUrlDesc": "Ссылка на техническую поддержку, отображаемая в VPN-клиенте", + "subProfileUrl": "URL профиля", + "subProfileUrlDesc": "Ссылка на ваш сайт, отображаемая в VPN-клиенте", + "subAnnounce": "Объявление", + "subAnnounceDesc": "Текст объявления, отображаемый в VPN-клиенте", + "subEnableRouting": "Включить маршрутизацию", + "subEnableRoutingDesc": "Глобальная настройка для включения маршрутизации в VPN-клиенте. (Только для Happ)", + "subRoutingRules": "Правила маршрутизации", + "subRoutingRulesDesc": "Глобальные правила маршрутизации для VPN-клиента. (Только для Happ)", + "subListen": "Прослушивание IP", + "subListenDesc": "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса", + "subPort": "Порт подписки", + "subPortDesc": "Номер порта для обслуживания службы подписки не должен использоваться на сервере", + "subCertPath": "Путь к файлу публичного ключа сертификата подписки", + "subCertPathDesc": "Введите полный путь, начинающийся с '/'", + "subKeyPath": "Путь к файлу приватного ключа сертификата подписки", + "subKeyPathDesc": "Введите полный путь, начинающийся с '/'", + "subPath": "Корневой путь URL-адреса подписки", + "subPathDesc": "Должен начинаться с '/' и заканчиваться на '/'", + "subDomain": "Домен прослушивания", + "subDomainDesc": "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса", + "subUpdates": "Интервалы обновления подписки", + "subUpdatesDesc": "Интервал между обновлениями в клиентском приложении (в часах)", + "subEncrypt": "Шифровать конфиги", + "subEncryptDesc": "Шифровать возвращенные конфиги в подписке", + "subShowInfo": "Показать информацию об использовании", + "subShowInfoDesc": "Отображать остаток трафика и дату окончания после имени конфигурации", + "subURI": "URI обратного прокси", + "subURIDesc": "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами", + "externalTrafficInformEnable": "Информация о внешнем трафике", + "externalTrafficInformEnableDesc": "Информировать внешний API о каждом обновлении трафика", + "externalTrafficInformURI": "URI информации о внешнем трафике", + "externalTrafficInformURIDesc": "Обновления трафика отправляются на этот URI", + "restartXrayOnClientDisable": "Перезапускать Xray после автоотключения", + "restartXrayOnClientDisableDesc": "Когда клиент автоматически отключается из-за окончания срока действия или лимита трафика, перезапускать Xray.", + "fragment": "Фрагментация", + "fragmentDesc": "Включить фрагментацию TLS-хэндшейка", + "fragmentSett": "Настройки фрагментации", + "noisesDesc": "Включить Noises.", + "noisesSett": "Настройки Noises", + "mux": "Mux", + "muxDesc": "Передача нескольких независимых потоков данных в одном соединении.", + "muxSett": "Настройки Mux", + "direct": "Прямое подключение", + "directDesc": "Устанавливает прямые соединения с доменами или IP-адресами определённой страны.", + "notifications": "Уведомления", + "certs": "Сертификаты", + "externalTraffic": "Внешний трафик", + "dateAndTime": "Дата и время", + "proxyAndServer": "Прокси и сервер", + "intervals": "Интервалы", + "information": "Информация", + "language": "Язык интерфейса", + "telegramBotLanguage": "Язык Telegram-бота", + "security": { + "admin": "Учетные данные администратора", + "twoFactor": "Двухфакторная аутентификация", + "twoFactorEnable": "Включить 2FA", + "twoFactorEnableDesc": "Добавляет дополнительный уровень аутентификации для повышения безопасности.", + "twoFactorModalSetTitle": "Включить двухфакторную аутентификацию", + "twoFactorModalDeleteTitle": "Отключить двухфакторную аутентификацию", + "twoFactorModalSteps": "Для настройки двухфакторной аутентификации выполните несколько шагов:", + "twoFactorModalFirstStep": "1. Отсканируйте этот QR-код в приложении для аутентификации или скопируйте токен рядом с QR-кодом и вставьте его в приложение", + "twoFactorModalSecondStep": "2. Введите код из приложения", + "twoFactorModalRemoveStep": "Введите код из приложения, чтобы отключить двухфакторную аутентификацию.", + "twoFactorModalChangeCredentialsTitle": "Изменить учетные данные", + "twoFactorModalChangeCredentialsStep": "Введите код из приложения, чтобы изменить учетные данные администратора.", + "twoFactorModalSetSuccess": "Двухфакторная аутентификация была успешно установлена", + "twoFactorModalDeleteSuccess": "Двухфакторная аутентификация была успешно удалена", + "twoFactorModalError": "Неверный код" + }, + "toasts": { + "modifySettings": "Настройки изменены", + "getSettings": "Произошла ошибка при получении параметров.", + "modifyUserError": "Произошла ошибка при изменении учетных данных администратора.", + "modifyUser": "Вы успешно изменили учетные данные администратора.", + "originalUserPassIncorrect": "Неверное имя пользователя или пароль", + "userPassMustBeNotEmpty": "Новое имя пользователя и новый пароль должны быть заполнены", + "getOutboundTrafficError": "Ошибка получения трафика исходящего подключения", + "resetOutboundTrafficError": "Ошибка сброса трафика исходящего подключения" + } + }, + "xray": { + "title": "Настройки Xray", + "save": "Сохранить", + "restart": "Перезапуск Xray", + "restartSuccess": "Xray успешно перезапущен", + "stopSuccess": "Xray успешно остановлен", + "restartError": "Произошла ошибка при перезапуске Xray.", + "stopError": "Произошла ошибка при остановке Xray.", + "basicTemplate": "Основное", + "advancedTemplate": "Расширенный шаблон", + "generalConfigs": "Основные настройки", + "generalConfigsDesc": "Эти параметры описывают общие настройки", + "logConfigs": "Логи", + "logConfigsDesc": "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!", + "blockConfigsDesc": "Настройте, чтобы клиенты не имели доступа к определенным протоколам", + "basicRouting": "Базовые соединения", + "blockConnectionsConfigsDesc": "Эти параметры будут блокировать трафик в зависимости от страны назначения.", + "directConnectionsConfigsDesc": "Прямое соединение означает, что определенный трафик не будет перенаправлен через другой сервер.", + "blockips": "Заблокированные IP-адреса", + "blockdomains": "Заблокированные домены", + "directips": "Прямые IP-адреса", + "directdomains": "Прямые домены", + "ipv4Routing": "Правила IPv4", + "ipv4RoutingDesc": "Эти параметры позволят клиентам маршрутизироваться к целевым доменам только через IPv4", + "warpRouting": "Правила WARP", + "warpRoutingDesc": " Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через WARP.", + "nordRouting": "Маршрутизация NordVPN", + "nordRoutingDesc": "Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через NordVPN.", + "Template": "Шаблон конфигурации Xray", + "TemplateDesc": "На основе шаблона создаётся конфигурационный файл Xray.", + "FreedomStrategy": "Настройка стратегии протокола Freedom", + "FreedomStrategyDesc": "Установка стратегии вывода сети в протоколе Freedom", + "RoutingStrategy": "Настройка маршрутизации доменов", + "RoutingStrategyDesc": "Установка общей стратегии маршрутизации разрешения DNS", + "outboundTestUrl": "URL для теста исходящего", + "outboundTestUrlDesc": "URL для проверки подключения исходящего", + "Torrent": "Заблокировать BitTorrent", + "Inbounds": "Входящие подключения", + "InboundsDesc": "Изменение шаблона конфигурации для подключения определенных клиентов", + "Outbounds": "Исходящие подключения", + "Balancers": "Балансировщик", + "OutboundsDesc": "Изменение шаблона конфигурации, чтобы определить исходящие подключения для этого сервера", + "Routings": "Маршрутизация", + "RoutingsDesc": "Важен приоритет каждого правила!", + "completeTemplate": "Все", + "logLevel": "Уровень логов", + "logLevelDesc": "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать.", + "accessLog": "Логи доступа", + "accessLogDesc": "Путь к файлу журнала доступа. Специальное значение «none» отключает логи доступа.", + "errorLog": "Логи ошибок", + "errorLogDesc": "Путь к файлу логов ошибок. Специальное значение «none» отключает логи ошибок.", + "dnsLog": "Логи DNS", + "dnsLogDesc": "Включить логи запросов DNS", + "maskAddress": "Маскировка адреса", + "maskAddressDesc": "При активации реальный IP-адрес заменяется на маскировочный в логах.", + "statistics": "Статистика", + "statsInboundUplink": "Статистика входящего аплинка", + "statsInboundUplinkDesc": "Включает сбор статистики для исходящего трафика всех входящих прокси.", + "statsInboundDownlink": "Статистика входящего даунлинка", + "statsInboundDownlinkDesc": "Включает сбор статистики для входящего трафика всех входящих прокси.", + "statsOutboundUplink": "Статистика исходящего аплинка", + "statsOutboundUplinkDesc": "Включает сбор статистики для исходящего трафика всех исходящих прокси.", + "statsOutboundDownlink": "Статистика исходящего даунлинка", + "statsOutboundDownlinkDesc": "Включает сбор статистики для входящего трафика всех исходящих прокси.", + "rules": { + "first": "Первый", + "last": "Последний", + "up": "Поднять вверх", + "down": "Опустить вниз", + "source": "Источник", + "dest": "Пункт назначения", + "inbound": "Входящее подключение", + "outbound": "Исходящее подключение", + "balancer": "Балансировщик", + "info": "Информация", + "add": "Создать правило", + "edit": "Редактировать правило", + "useComma": "Элементы, разделённые запятыми" + }, + "outbound": { + "addOutbound": "Создать исходящее подключение", + "addReverse": "Создать реверс-прокси", + "editOutbound": "Изменить исходящее подключение", + "editReverse": "Редактировать реверс-прокси", + "reverseTag": "Тег реверс-прокси", + "reverseTagDesc": "Тег исходящего подключения для простого реверс-прокси VLESS. Оставьте пустым для отключения.", + "reverseTagPlaceholder": "тег исходящего (пусто = отключено)", + "tag": "Тег", + "tagDesc": "Уникальный тег", + "address": "Адрес", + "reverse": "Реверс-прокси", + "domain": "Домен", + "type": "Тип", + "bridge": "Мост", + "portal": "Портал", + "link": "Ссылка", + "intercon": "Соединение", + "settings": "Настройки", + "accountInfo": "Информация об учетной записи", + "outboundStatus": "Статус исходящего подключения", + "sendThrough": "Отправить через", + "test": "Тест", + "testResult": "Результат теста", + "testing": "Тестирование соединения...", + "testSuccess": "Тест успешен", + "testFailed": "Тест не пройден", + "testError": "Не удалось протестировать исходящее подключение", + "nordvpn": "NordVPN", + "accessToken": "Токен доступа", + "country": "Страна", + "server": "Сервер", + "city": "Город", + "allCities": "Все города", + "privateKey": "Приватный ключ", + "load": "Нагрузка" + }, + "balancer": { + "addBalancer": "Создать балансировщик", + "editBalancer": "Редактировать балансировщик", + "balancerStrategy": "Стратегия", + "balancerSelectors": "Селекторы", + "tag": "Тег", + "tagDesc": "Уникальный тег", + "balancerDesc": "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." + }, + "wireguard": { + "secretKey": "Секретный ключ", + "publicKey": "Публичный ключ", + "allowedIPs": "Разрешенные IP-адреса", + "endpoint": "Конечная точка", + "psk": "Общий ключ", + "domainStrategy": "Стратегия домена" + }, + "tun": { + "nameDesc": "Имя интерфейса TUN. Значение по умолчанию - 'xray0'", + "mtuDesc": "Максимальная единица передачи. Максимальный размер пакетов данных. Значение по умолчанию - 1500", + "userLevel": "Уровень пользователя", + "userLevelDesc": "Все соединения, установленные через этот входящий поток, будут использовать этот уровень пользователя. Значение по умолчанию - 0" + }, + "dns": { + "enable": "Включить DNS", + "enableDesc": "Включить встроенный DNS-сервер", + "tag": "Название тега DNS", + "tagDesc": "Этот тег будет доступен как входящий тег в правилах маршрутизации.", + "clientIp": "IP клиента", + "clientIpDesc": "Используется для уведомления сервера о указанном местоположении IP во время DNS-запросов", + "disableCache": "Отключить кэш", + "disableCacheDesc": "Отключает кэширование DNS", + "disableFallback": "Отключить резервный DNS", + "disableFallbackDesc": "Отключает резервные DNS-запросы", + "disableFallbackIfMatch": "Отключить резервный DNS при совпадении", + "disableFallbackIfMatchDesc": "Отключает резервные DNS-запросы при совпадении списка доменов DNS-сервера", + "enableParallelQuery": "Включить параллельные запросы", + "enableParallelQueryDesc": "Включить параллельные DNS-запросы к нескольким серверам для более быстрого разрешения", + "strategy": "Стратегия запроса", + "strategyDesc": "Общая стратегия разрешения доменных имен", + "add": "Создать DNS", + "edit": "Редактировать DNS", + "domains": "Домены", + "expectIPs": "Ожидаемые IP", + "unexpectIPs": "Неожидаемые IP", + "useSystemHosts": "Использовать системные Hosts", + "useSystemHostsDesc": "Использовать файл hosts из установленной системы", + "usePreset": "Использовать шаблон", + "dnsPresetTitle": "Шаблоны DNS", + "dnsPresetFamily": "Семейный" + }, + "fakedns": { + "add": "Создать Fake DNS", + "edit": "Редактировать Fake DNS", + "ipPool": "Подсеть пула IP", + "poolSize": "Размер пула" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Клавиатура закрыта.", + "noResult": "❗ Нет результатов.", + "noQuery": "❌ Запрос не найден. Пожалуйста, повторите команду.", + "wentWrong": "❌ Что-то пошло не так...", + "noIpRecord": "❗ Нет записей об IP-адресе.", + "noInbounds": "❗ У вас не настроено ни одного входящего подключения.", + "unlimited": "♾ Безлимит", + "add": "Добавить", + "month": "Месяц", + "months": "Месяцев", + "day": "День", + "days": "Дней", + "hours": "Часов", + "minutes": "Минуты", + "unknown": "Неизвестно", + "inbounds": "Входящие подключения", + "clients": "Клиенты", + "offline": "🔴 Офлайн", + "online": "🟢 Онлайн", + "commands": { + "unknown": "❗ Неизвестная команда", + "pleaseChoose": "👇 Пожалуйста, выберите:\r\n", + "help": "🤖 Добро пожаловать! Этот бот предназначен для предоставления вам данных с сервера и позволяет вносить изменения на него.\r\n\r\n", + "start": "👋 Привет, {{ .Firstname }}.\r\n", + "welcome": "🤖 Добро пожаловать в бота управления {{ .Hostname }}!\r\n", + "status": "✅ Бот функционирует нормально.", + "usage": "❗ Пожалуйста, укажите email для поиска.", + "getID": "🆔 Ваш User ID: {{ .ID }}", + "helpAdminCommands": "🔃 Для перезапуска Xray Core:\r\n/restart\r\n\r\n🔎 Для поиска клиента по email:\r\n/usage [Email]\r\n\r\n📊 Для поиска входящих подключений (со статистикой клиентов):\r\n/inbound [имя подключения]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id", + "helpClientCommands": "💲 Для просмотра информации о вашей подписке используйте команду:\r\n/usage [Email]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Ядро Xray успешно перезапущено.", + "restartFailed": "❗ Ошибка при перезапуске Xray-core.\r\n\r\nОшибка: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core не запущен.", + "startDesc": "Показать главное меню", + "helpDesc": "Справка по боту", + "statusDesc": "Проверить статус бота", + "idDesc": "Показать ваш Telegram ID" + }, + "messages": { + "cpuThreshold": "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%", + "selectUserFailed": "❌ Ошибка при выборе пользователя.", + "userSaved": "✅ Пользователь Telegram сохранен.", + "loginSuccess": "✅ Успешный вход в панель.\r\n", + "loginFailed": "❗️ Ошибка входа в панель.\r\n", + "2faFailed": "Ошибка 2FA", + "report": "🕰 Запланированные отчеты: {{ .RunTime }}\r\n", + "datetime": "⏰ Дата и время: {{ .DateTime }}\r\n", + "hostname": "💻 Имя хоста: {{ .Hostname }}\r\n", + "version": "🚀 Версия X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 Версия Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IP-адреса:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Нагрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 ОЗУ сервера: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 Количество TCP-соединений: {{ .Count }}\r\n", + "udpCount": "🔸 Количество UDP-соединений: {{ .Count }}\r\n", + "traffic": "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Состояние Xray: {{ .State }}\r\n", + "username": "👤 Имя пользователя: {{ .Username }}\r\n", + "reason": "❗️ Причина: {{ .Reason }}\r\n", + "time": "⏰ Время: {{ .Time }}\r\n", + "inbound": "📍 Входящее подключение: {{ .Remark }}\r\n", + "port": "🔌 Порт: {{ .Port }}\r\n", + "expire": "📅 Дата окончания: {{ .Time }}\r\n", + "expireIn": "📅 Окончание через: {{ .Time }}\r\n", + "active": "💡 Активен: {{ .Enable }}\r\n", + "enabled": "🚨 Активен: {{ .Enable }}\r\n", + "online": "🌐 Статус соединения: {{ .Status }}\r\n", + "lastOnline": "🔙 Был(а) в сети: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n", + "download": "🔽 Входящий трафик: ↓{{ .Download }}\r\n", + "total": "📊 Всего: ↑↓{{ .UpDown }} из {{ .Total }}\r\n", + "TGUser": "👤 Telegram User ID: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Исчерпаны {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Количество исчерпанных {{ .Type }}:\r\n", + "onlinesCount": "🌐 Клиентов онлайн: {{ .Count }}\r\n", + "disabled": "🛑 Отключено: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Клиенты, у которых скоро исчерпание: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Время резервного копирования: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n", + "yes": "✅ Да", + "no": "❌ Нет", + "received_id": "🔑📥 ID обновлён.", + "received_password": "🔑📥 Пароль обновлён.", + "received_email": "📧📥 Email обновлен.", + "received_comment": "💬📥 Комментарий обновлён.", + "id_prompt": "🔑 Стандартный ID: {{ .ClientId }}\n\nВведите ваш ID.", + "pass_prompt": "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль.", + "email_prompt": "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email.", + "comment_prompt": "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий.", + "inbound_client_data_id": "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!", + "inbound_client_data_pass": "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!", + "cancel": "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄", + "error_add_client": "⚠️ Ошибка:\n\n {{ .error }}", + "using_default_value": "Используется значение по умолчанию👌", + "incorrect_input": "Ваш ввод недействителен.\nФразы должны быть непрерывными без пробелов.\nПравильный пример: aaaaaa\nНеправильный пример: aaa aaa 🚫", + "AreYouSure": "Вы уверены? 🤔", + "SuccessResetTraffic": "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успешно", + "FailedResetTraffic": "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ❌ Неудача \n\n🛠️ Ошибка: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Сброс трафика завершён для всех клиентов." + }, + "buttons": { + "closeKeyboard": "❌ Закрыть клавиатуру", + "cancel": "❌ Отмена", + "cancelReset": "❌ Отменить сброс", + "cancelIpLimit": "❌ Отменить лимит IP", + "confirmResetTraffic": "✅ Подтвердить сброс трафика?", + "confirmClearIps": "✅ Подтвердить очистку IP?", + "confirmRemoveTGUser": "✅ Подтвердить удаление пользователя Telegram?", + "confirmToggle": "✅ Подтвердить вкл/выкл пользователя?", + "dbBackup": "📂 Бэкап БД", + "serverUsage": "💻 Состояние сервера", + "getInbounds": "🔌 Входящие подключения", + "depleteSoon": "⚠️ Скоро конец", + "clientUsage": "Статистика клиента", + "onlines": "🟢 Онлайн", + "commands": "🖱️ Команды", + "refresh": "🔄 Обновить", + "clearIPs": "❌ Очистить IP", + "removeTGUser": "❌ Удалить пользователя Telegram", + "selectTGUser": "👤 Выбрать пользователя Telegram", + "selectOneTGUser": "👤 Выберите пользователя Telegram:", + "resetTraffic": "📈 Сбросить трафик", + "resetExpire": "📅 Изменить дату окончания", + "ipLog": "🔢 Лог IP", + "ipLimit": "🔢 Лимит IP", + "setTGUser": "👤 Установить пользователя Telegram", + "toggle": "🔘 Вкл./Выкл.", + "custom": "🔢 Свой", + "confirmNumber": "✅ Подтвердить: {{ .Num }}", + "confirmNumberAdd": "✅ Подтвердить добавление: {{ .Num }}", + "limitTraffic": "🚧 Лимит трафика", + "getBanLogs": "📄 Лог банов", + "allClients": "👥 Все клиенты", + "addClient": "➕ Новый клиент", + "submitDisable": "Добавить отключенным ☑️", + "submitEnable": "Добавить включенным ✅", + "use_default": "🏷️ Использовать по умолчанию", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Пароль", + "change_email": "⚙️📧 Email", + "change_comment": "⚙️💬 Комментарий", + "ResetAllTraffics": "Сбросить весь трафик", + "SortedTrafficUsageReport": "Отсортированный отчет об использовании трафика" + }, + "answers": { + "successfulOperation": "✅ Успешно!", + "errorOperation": "❗ Ошибка в операции.", + "getInboundsFailed": "❌ Не удалось получить входящие подключения.", + "getClientsFailed": "❌ Не удалось получить клиентов.", + "canceled": "❌ {{ .Email }}: Операция отменена.", + "clientRefreshSuccess": "✅ {{ .Email }}: Клиент успешно обновлен.", + "IpRefreshSuccess": "✅ {{ .Email }}: IP-адреса успешно обновлены.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен.", + "resetTrafficSuccess": "✅ {{ .Email }}: Трафик успешно сброшен.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Лимит трафика успешно установлен.", + "expireResetSuccess": "✅ {{ .Email }}: Срок действия успешно сброшен.", + "resetIpSuccess": "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен.", + "clearIpSuccess": "✅ {{ .Email }}: IP-адреса успешно очищены.", + "getIpLog": "✅ {{ .Email }}: Получен лог IP.", + "getUserInfo": "✅ {{ .Email }}: Получена информация о пользователе Telegram.", + "removedTGUserSuccess": "✅ {{ .Email }}: Пользователь Telegram успешно удален.", + "enableSuccess": "✅ {{ .Email }}: Включено успешно.", + "disableSuccess": "✅ {{ .Email }}: Отключено успешно.", + "askToAddUserId": "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: {{ .TgUserID }}", + "chooseClient": "Выберите клиента для входящего подключения {{ .Inbound }}", + "chooseInbound": "Выберите входящее подключение" + } + } +} diff --git a/web/translation/tr-TR.json b/web/translation/tr-TR.json new file mode 100644 index 00000000..d4d7b8cc --- /dev/null +++ b/web/translation/tr-TR.json @@ -0,0 +1,941 @@ +{ + "username": "Kullanıcı Adı", + "password": "Şifre", + "login": "Giriş Yap", + "confirm": "Onayla", + "cancel": "İptal", + "close": "Kapat", + "save": "Kaydet", + "logout": "Çıkış Yap", + "create": "Oluştur", + "update": "Güncelle", + "copy": "Kopyala", + "copied": "Kopyalandı", + "download": "İndir", + "remark": "Açıklama", + "enable": "Etkin", + "protocol": "Protokol", + "search": "Ara", + "filter": "Filtrele", + "loading": "Yükleniyor...", + "second": "Saniye", + "minute": "Dakika", + "hour": "Saat", + "day": "Gün", + "check": "Kontrol Et", + "indefinite": "Belirsiz", + "unlimited": "Sınırsız", + "none": "Hiçbiri", + "qrCode": "QR Kod", + "info": "Daha Fazla Bilgi", + "edit": "Düzenle", + "delete": "Sil", + "reset": "Sıfırla", + "noData": "Veri yok.", + "copySuccess": "Başarıyla Kopyalandı", + "sure": "Emin misiniz", + "encryption": "Şifreleme", + "useIPv4ForHost": "Ana bilgisayar için IPv4 kullan", + "transmission": "İletim", + "host": "Sunucu", + "path": "Yol", + "camouflage": "Kandırma", + "status": "Durum", + "enabled": "Etkin", + "disabled": "Devre Dışı", + "depleted": "Bitti", + "depletingSoon": "Bitmek Üzere", + "offline": "Çevrimdışı", + "online": "Çevrimiçi", + "domainName": "Alan Adı", + "monitor": "Dinleme IP", + "certificate": "Dijital Sertifika", + "fail": "Başarısız", + "comment": "Yorum", + "success": "Başarılı", + "lastOnline": "Son çevrimiçi", + "getVersion": "Sürümü Al", + "install": "Yükle", + "clients": "Müşteriler", + "usage": "Kullanım", + "twoFactorCode": "Kod", + "remained": "Kalan", + "security": "Güvenlik", + "secAlertTitle": "Güvenlik Uyarısı", + "secAlertSsl": "Bu bağlantı güvenli değil. Verilerin korunması için TLS etkinleştirilene kadar hassas bilgiler girmekten kaçının.", + "secAlertConf": "Bazı ayarlar saldırılara açıktır. Olası ihlalleri önlemek için güvenlik protokollerini güçlendirmeniz önerilir.", + "secAlertSSL": "Panelde güvenli bağlantı yok. Verilerin korunması için TLS sertifikası yükleyin.", + "secAlertPanelPort": "Panel varsayılan portu savunmasız. Rastgele veya belirli bir port yapılandırın.", + "secAlertPanelURI": "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın.", + "secAlertSubURI": "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın.", + "secAlertSubJsonURI": "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın.", + "emptyDnsDesc": "Eklenmiş DNS sunucusu yok.", + "emptyFakeDnsDesc": "Eklenmiş Fake DNS sunucusu yok.", + "emptyBalancersDesc": "Eklenmiş dengeleyici yok.", + "emptyReverseDesc": "Eklenmiş ters proxy yok.", + "somethingWentWrong": "Bir şeyler yanlış gitti", + "subscription": { + "title": "Abonelik Bilgisi", + "subId": "Abonelik Kimliği", + "status": "Durum", + "downloaded": "İndirilen", + "uploaded": "Yüklenen", + "expiry": "Son Kullanma", + "totalQuota": "Toplam Kota", + "individualLinks": "Bireysel Bağlantılar", + "active": "Aktif", + "inactive": "Pasif", + "unlimited": "Sınırsız", + "noExpiry": "Süresiz" + }, + "menu": { + "theme": "Tema", + "dark": "Koyu", + "ultraDark": "Ultra Koyu", + "dashboard": "Genel Bakış", + "inbounds": "Gelenler", + "settings": "Panel Ayarları", + "xray": "Xray Yapılandırmaları", + "logout": "Çıkış Yap", + "link": "Yönet" + }, + "pages": { + "login": { + "hello": "Merhaba", + "title": "Hoş Geldiniz", + "loginAgain": "Oturum süreniz doldu, lütfen tekrar giriş yapın", + "toasts": { + "invalidFormData": "Girdi verisi formatı geçersiz.", + "emptyUsername": "Kullanıcı adı gerekli", + "emptyPassword": "Şifre gerekli", + "wrongUsernameOrPassword": "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu.", + "successLogin": "Hesabınıza başarıyla giriş yaptınız." + } + }, + "index": { + "title": "Genel Bakış", + "cpu": "İşlemci", + "logicalProcessors": "Mantıksal işlemciler", + "frequency": "Frekans", + "swap": "Takas", + "storage": "Depolama", + "memory": "RAM", + "threads": "İş parçacıkları", + "xrayStatus": "Xray", + "stopXray": "Durdur", + "restartXray": "Yeniden Başlat", + "xraySwitch": "Sürüm", + "xraySwitchClick": "Geçiş yapmak istediğiniz sürümü seçin.", + "xraySwitchClickDesk": "Dikkatli seçin, eski sürümler mevcut yapılandırmalarla uyumlu olmayabilir.", + "xrayUpdates": "Xray Güncellemeleri", + "updatePanel": "Paneli Güncelle", + "panelUpdateDesc": "Bu, 3X-UI'yi en son sürüme güncelleyecek ve panel servisini yeniden başlatacaktır.", + "currentPanelVersion": "Mevcut panel sürümü", + "latestPanelVersion": "Panelin en son sürümü", + "panelUpToDate": "Panel güncel", + "upToDate": "Güncel", + "xrayStatusUnknown": "Bilinmiyor", + "xrayStatusRunning": "Çalışıyor", + "xrayStatusStop": "Durduruldu", + "xrayStatusError": "Hata", + "xrayErrorPopoverTitle": "Xray çalıştırılırken bir hata oluştu", + "operationHours": "Çalışma Süresi", + "systemHistoryTitle": "Sistem Geçmişi", + "trendLast2Min": "Son 2 dakika", + "systemLoad": "Sistem Yükü", + "systemLoadDesc": "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması", + "connectionCount": "Bağlantı İstatistikleri", + "ipAddresses": "IP adresleri", + "toggleIpVisibility": "IP görünürlüğünü değiştir", + "overallSpeed": "Genel hız", + "upload": "Yükleme", + "download": "İndirme", + "totalData": "Toplam veri", + "sent": "Gönderilen", + "received": "Alınan", + "documentation": "Dokümantasyon", + "xraySwitchVersionDialog": "Xray sürümünü gerçekten değiştirmek istiyor musunuz?", + "xraySwitchVersionDialogDesc": "Bu işlem Xray sürümünü #version# olarak değiştirecektir.", + "xraySwitchVersionPopover": "Xray başarıyla güncellendi", + "panelUpdateDialog": "Gerçekten paneli güncellemek istiyor musunuz?", + "panelUpdateDialogDesc": "Bu, 3X-UI'yi #version# sürümüne güncelleyecek ve panel servisini yeniden başlatacaktır.", + "panelUpdateCheckPopover": "Panel güncelleme kontrolü başarısız oldu", + "panelUpdateStartedPopover": "Panel güncellemesi başlatıldı", + "geofileUpdateDialog": "Geofile'ı gerçekten güncellemek istiyor musunuz?", + "geofileUpdateDialogDesc": "Bu işlem #filename# dosyasını güncelleyecektir.", + "geofilesUpdateDialogDesc": "Bu, tüm dosyaları güncelleyecektir.", + "geofilesUpdateAll": "Tümünü güncelle", + "geofileUpdatePopover": "Geofile başarıyla güncellendi", + "dontRefresh": "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin", + "logs": "Günlükler", + "config": "Yapılandırma", + "backup": "Yedek", + "backupTitle": "Yedekleme & Geri Yükleme", + "exportDatabase": "Yedekle", + "exportDatabaseDesc": "Mevcut veritabanınızın yedeğini içeren bir .db dosyasını cihazınıza indirmek için tıklayın.", + "importDatabase": "Geri Yükle", + "importDatabaseDesc": "Cihazınızdan bir .db dosyası seçip yükleyerek veritabanınızı yedekten geri yüklemek için tıklayın.", + "importDatabaseSuccess": "Veritabanı başarıyla içe aktarıldı", + "importDatabaseError": "Veritabanı içe aktarılırken bir hata oluştu", + "readDatabaseError": "Veritabanı okunurken bir hata oluştu", + "getDatabaseError": "Veritabanı alınırken bir hata oluştu", + "getConfigError": "Yapılandırma dosyası alınırken bir hata oluştu", + "customGeoTitle": "Özel GeoSite / GeoIP", + "customGeoAdd": "Ekle", + "customGeoType": "Tür", + "customGeoAlias": "Takma ad", + "customGeoUrl": "URL", + "customGeoEnabled": "Etkin", + "customGeoLastUpdated": "Son güncelleme", + "customGeoExtColumn": "Yönlendirme (ext:…)", + "customGeoToastUpdateAll": "Tüm özel kaynaklar güncellendi", + "customGeoActions": "İşlemler", + "customGeoEdit": "Düzenle", + "customGeoDelete": "Sil", + "customGeoDownload": "Şimdi güncelle", + "customGeoModalAdd": "Özel geo ekle", + "customGeoModalEdit": "Özel geo düzenle", + "customGeoModalSave": "Kaydet", + "customGeoDeleteConfirm": "Bu özel geo kaynağını silinsin mi?", + "customGeoRoutingHint": "Yönlendirme kurallarında değer sütununu ext:dosya.dat:etiket olarak kullanın (etiketi değiştirin).", + "customGeoInvalidId": "Geçersiz kaynak kimliği", + "customGeoAliasesError": "Özel geo takma adları yüklenemedi", + "customGeoValidationAlias": "Takma ad yalnızca küçük harf, rakam, - ve _ içerebilir", + "customGeoValidationUrl": "URL http:// veya https:// ile başlamalıdır", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (özel)", + "customGeoToastList": "Özel geo listesi", + "customGeoToastAdd": "Özel geo ekle", + "customGeoToastUpdate": "Özel geo güncelle", + "customGeoToastDelete": "Özel geofile \"{{ .fileName }}\" silindi", + "customGeoToastDownload": "\"{{ .fileName }}\" geofile güncellendi", + "customGeoErrInvalidType": "Tür geosite veya geoip olmalıdır", + "customGeoErrAliasRequired": "Takma ad gerekli", + "customGeoErrAliasPattern": "Takma ad izin verilmeyen karakterler içeriyor", + "customGeoErrAliasReserved": "Bu takma ad ayrılmış", + "customGeoErrUrlRequired": "URL gerekli", + "customGeoErrInvalidUrl": "URL geçersiz", + "customGeoErrUrlScheme": "URL http veya https kullanmalıdır", + "customGeoErrUrlHost": "URL ana bilgisayarı geçersiz", + "customGeoErrDuplicateAlias": "Bu takma ad bu tür için zaten kullanılıyor", + "customGeoErrNotFound": "Özel geo kaynağı bulunamadı", + "customGeoErrDownload": "İndirme başarısız", + "customGeoErrUpdateAllIncomplete": "Bir veya daha fazla özel geo kaynağı güncellenemedi", + "customGeoEmpty": "Henüz özel geo kaynağı yok — oluşturmak için Ekle'ye tıklayın" + }, + "inbounds": { + "allTimeTraffic": "Toplam Trafik", + "allTimeTrafficUsage": "Tüm Zamanların Toplam Kullanımı", + "title": "Gelenler", + "totalDownUp": "Toplam Gönderilen/Alınan", + "totalUsage": "Toplam Kullanım", + "inboundCount": "Toplam Gelen", + "operate": "Menü", + "enable": "Etkin", + "remark": "Açıklama", + "node": "Düğüm", + "deployTo": "Şuraya dağıt", + "localPanel": "Yerel panel", + "protocol": "Protokol", + "port": "Port", + "portMap": "Port Atama", + "traffic": "Trafik", + "details": "Detaylar", + "transportConfig": "Taşıma", + "expireDate": "Süre", + "createdAt": "Oluşturuldu", + "updatedAt": "Güncellendi", + "resetTraffic": "Trafiği Sıfırla", + "addInbound": "Gelen Ekle", + "generalActions": "Genel Eylemler", + "modifyInbound": "Geleni Düzenle", + "deleteInbound": "Geleni Sil", + "deleteInboundContent": "Geleni silmek istediğinizden emin misiniz?", + "deleteClient": "Müşteriyi Sil", + "deleteClientContent": "Müşteriyi silmek istediğinizden emin misiniz?", + "resetTrafficContent": "Trafiği sıfırlamak istediğinizden emin misiniz?", + "copyLink": "URL'yi Kopyala", + "address": "Adres", + "network": "Ağ", + "destinationPort": "Hedef Port", + "targetAddress": "Hedef Adres", + "monitorDesc": "Tüm IP'leri dinlemek için boş bırakın", + "meansNoLimit": "= Sınırsız. (birim: GB)", + "totalFlow": "Toplam Akış", + "leaveBlankToNeverExpire": "Hiçbir zaman sona ermemesi için boş bırakın", + "noRecommendKeepDefault": "Varsayılanı korumanız önerilir", + "certificatePath": "Dosya Yolu", + "certificateContent": "Dosya İçeriği", + "publicKey": "Genel Anahtar", + "privatekey": "Özel Anahtar", + "clickOnQRcode": "Kopyalamak için QR Kodu Tıklayın", + "client": "Müşteri", + "export": "Tüm URL'leri Dışa Aktar", + "clone": "Klonla", + "cloneInbound": "Klonla", + "cloneInboundContent": "Bu gelenin tüm ayarları, Port, Dinleme IP ve Müşteriler hariç, klona uygulanacaktır.", + "cloneInboundOk": "Klonla", + "resetAllTraffic": "Tüm Gelen Trafiğini Sıfırla", + "resetAllTrafficTitle": "Tüm Gelen Trafiğini Sıfırla", + "resetAllTrafficContent": "Tüm gelenlerin trafiğini sıfırlamak istediğinizden emin misiniz?", + "resetInboundClientTraffics": "Müşteri Trafiklerini Sıfırla", + "resetInboundClientTrafficTitle": "Müşteri Trafiklerini Sıfırla", + "resetInboundClientTrafficContent": "Bu gelenin müşterilerinin trafiğini sıfırlamak istediğinizden emin misiniz?", + "resetAllClientTraffics": "Tüm Müşteri Trafiklerini Sıfırla", + "resetAllClientTrafficTitle": "Tüm Müşteri Trafiklerini Sıfırla", + "resetAllClientTrafficContent": "Tüm müşterilerin trafiğini sıfırlamak istediğinizden emin misiniz?", + "delDepletedClients": "Bitmiş Müşterileri Sil", + "delDepletedClientsTitle": "Bitmiş Müşterileri Sil", + "delDepletedClientsContent": "Tüm bitmiş müşterileri silmek istediğinizden emin misiniz?", + "email": "E-posta", + "emailDesc": "Lütfen benzersiz bir e-posta adresi sağlayın.", + "IPLimit": "IP Limiti", + "IPLimitDesc": "Sayının aşılması durumunda gelen devre dışı bırakılır. (0 = devre dışı)", + "IPLimitlog": "IP Günlüğü", + "IPLimitlogDesc": "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)", + "IPLimitlogclear": "Günlüğü Temizle", + "setDefaultCert": "Panelden Sertifikayı Ayarla", + "telegramDesc": "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya ({'@'}userinfobot)", + "subscriptionDesc": "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz.", + "info": "Bilgi", + "same": "Aynı", + "inboundData": "Gelenin Verileri", + "exportInbound": "Geleni Dışa Aktar", + "import": "İçe Aktar", + "importInbound": "Bir Gelen İçe Aktar", + "periodicTrafficResetTitle": "Trafik Sıfırlama", + "periodicTrafficResetDesc": "Belirtilen aralıklarla trafik sayacını otomatik olarak sıfırla", + "lastReset": "Son Sıfırlama", + "periodicTrafficReset": { + "never": "Asla", + "daily": "Günlük", + "weekly": "Haftalık", + "monthly": "Aylık", + "hourly": "Saatlik" + }, + "toasts": { + "obtain": "Elde Et", + "updateSuccess": "Güncelleme başarılı oldu", + "logCleanSuccess": "Günlük temizlendi", + "inboundsUpdateSuccess": "Gelen bağlantılar başarıyla güncellendi", + "inboundUpdateSuccess": "Gelen bağlantı başarıyla güncellendi", + "inboundCreateSuccess": "Gelen bağlantı başarıyla oluşturuldu", + "inboundDeleteSuccess": "Gelen bağlantı başarıyla silindi", + "inboundClientAddSuccess": "Gelen bağlantı istemci(leri) eklendi", + "inboundClientDeleteSuccess": "Gelen bağlantı istemcisi silindi", + "inboundClientUpdateSuccess": "Gelen bağlantı istemcisi güncellendi", + "delDepletedClientsSuccess": "Tüm tükenmiş istemciler silindi", + "resetAllClientTrafficSuccess": "İstemcinin tüm trafiği sıfırlandı", + "resetAllTrafficSuccess": "Tüm trafik sıfırlandı", + "resetInboundClientTrafficSuccess": "Trafik sıfırlandı", + "trafficGetError": "Trafik bilgisi alınırken hata oluştu", + "getNewX25519CertError": "X25519 sertifikası alınırken hata oluştu.", + "getNewmldsa65Error": "mldsa65 sertifikası alınırken hata oluştu.", + "getNewVlessEncError": "VlessEnc sertifikası alınırken hata oluştu." + }, + "stream": { + "general": { + "request": "İstek", + "response": "Yanıt", + "name": "Ad", + "value": "Değer" + }, + "tcp": { + "version": "Sürüm", + "method": "Yöntem", + "path": "Yol", + "status": "Durum", + "statusDescription": "Durum Açıklaması", + "requestHeader": "İstek Başlığı", + "responseHeader": "Yanıt Başlığı" + } + } + }, + "client": { + "add": "Müşteri Ekle", + "edit": "Müşteriyi Düzenle", + "submitAdd": "Müşteri Ekle", + "submitEdit": "Değişiklikleri Kaydet", + "clientCount": "Müşteri Sayısı", + "bulk": "Toplu Ekle", + "copyFromInbound": "Gelen bağlantıdan istemcileri kopyala", + "copyToInbound": "İstemcileri şuraya kopyala", + "copySelected": "Seçilenleri kopyala", + "copySource": "Kaynak", + "copyEmailPreview": "Sonuç e-posta önizlemesi", + "copySelectSourceFirst": "Önce bir kaynak gelen bağlantı seçin.", + "copyResult": "Kopyalama sonucu", + "copyResultSuccess": "Başarıyla kopyalandı", + "copyResultNone": "Kopyalanacak bir şey yok: istemci seçilmedi veya kaynak boş", + "copyResultErrors": "Kopyalama hataları", + "copyFlowLabel": "Yeni istemciler için Flow (VLESS)", + "copyFlowHint": "Kopyalanan tüm istemcilere uygulanır. Boş bırakırsanız atlanır.", + "selectAll": "Tümünü seç", + "clearAll": "Tümünü temizle", + "method": "Yöntem", + "first": "İlk", + "last": "Son", + "prefix": "Önek", + "postfix": "Sonek", + "delayedStart": "İlk Kullanımdan Sonra Başlat", + "expireDays": "Süre", + "days": "Gün", + "renew": "Otomatik Yenile", + "renewDesc": "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)" + }, + "nodes": { + "title": "Düğümler", + "addNode": "Düğüm Ekle", + "editNode": "Düğümü Düzenle", + "totalNodes": "Toplam Düğüm", + "onlineNodes": "Çevrimiçi", + "offlineNodes": "Çevrimdışı", + "avgLatency": "Ortalama Gecikme", + "name": "Ad", + "namePlaceholder": "ör. de-frankfurt-1", + "addressPlaceholder": "panel.example.com veya 1.2.3.4", + "remark": "Açıklama", + "scheme": "Şema", + "address": "Adres", + "port": "Port", + "basePath": "Temel Yol", + "apiToken": "API Token", + "apiTokenPlaceholder": "Uzak panelin Ayarlar sayfasındaki token", + "apiTokenHint": "Uzak panel API token'ını Ayarlar → API Token altında gösterir.", + "regenerate": "Token'ı Yeniden Oluştur", + "regenerateConfirm": "Yeniden oluşturmak mevcut token'ı geçersiz kılar. Onu kullanan tüm merkezi paneller, güncellenene kadar erişimini kaybeder. Devam edilsin mi?", + "enable": "Etkin", + "status": "Durum", + "cpu": "CPU", + "mem": "Bellek", + "uptime": "Çalışma Süresi", + "latency": "Gecikme", + "lastHeartbeat": "Son Sinyal", + "xrayVersion": "Xray Sürümü", + "actions": "İşlemler", + "probe": "Şimdi Test Et", + "testConnection": "Bağlantıyı Test Et", + "connectionOk": "Bağlantı tamam ({ms} ms)", + "connectionFailed": "Bağlantı başarısız", + "never": "asla", + "justNow": "şimdi", + "deleteConfirmTitle": "\"{name}\" düğümü silinsin mi?", + "deleteConfirmContent": "Bu, düğüm izlemeyi durdurur. Uzak panelin kendisi etkilenmez.", + "statusValues": { + "online": "Çevrimiçi", + "offline": "Çevrimdışı", + "unknown": "Bilinmiyor" + }, + "toasts": { + "list": "Düğümler yüklenemedi", + "obtain": "Düğüm yüklenemedi", + "add": "Düğüm ekle", + "update": "Düğümü güncelle", + "delete": "Düğümü sil", + "deleted": "Düğüm silindi", + "test": "Bağlantıyı test et", + "fillRequired": "Ad, adres, port ve API token gereklidir", + "probeFailed": "Test başarısız" + } + }, + "settings": { + "title": "Panel Ayarları", + "save": "Kaydet", + "infoDesc": "Burada yapılan her değişikliğin kaydedilmesi gerekir. Değişikliklerin uygulanması için paneli yeniden başlatın.", + "restartPanel": "Paneli Yeniden Başlat", + "restartPanelDesc": "Paneli yeniden başlatmak istediğinizden emin misiniz? Yeniden başlattıktan sonra panele erişemezseniz, sunucudaki panel günlük bilgilerini görüntüleyin.", + "restartPanelSuccess": "Panel başarıyla yeniden başlatıldı", + "actions": "Eylemler", + "resetDefaultConfig": "Varsayılana Sıfırla", + "panelSettings": "Genel", + "securitySettings": "Kimlik Doğrulama", + "TGBotSettings": "Telegram Bot", + "panelListeningIP": "Dinleme IP", + "panelListeningIPDesc": "Web paneli için IP adresi. (tüm IP'leri dinlemek için boş bırakın)", + "panelListeningDomain": "Dinleme Alan Adı", + "panelListeningDomainDesc": "Web paneli için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)", + "panelPort": "Dinleme Portu", + "panelPortDesc": "Web paneli için port numarası. (kullanılmayan bir port olmalıdır)", + "publicKeyPath": "Genel Anahtar Yolu", + "publicKeyPathDesc": "Web paneli için genel anahtar dosya yolu. ('/' ile başlar)", + "privateKeyPath": "Özel Anahtar Yolu", + "privateKeyPathDesc": "Web paneli için özel anahtar dosya yolu. ('/' ile başlar)", + "panelUrlPath": "URI Yolu", + "panelUrlPathDesc": "Web paneli için URI yolu. ('/' ile başlar ve '/' ile biter)", + "pageSize": "Sayfa Boyutu", + "pageSizeDesc": "Gelenler tablosu için sayfa boyutunu belirleyin. (0 = devre dışı)", + "remarkModel": "Açıklama Modeli & Ayırma Karakteri", + "datepicker": "Takvim Türü", + "datepickerPlaceholder": "Tarih Seçin", + "datepickerDescription": "Planlanmış görevler bu takvime göre çalışacaktır.", + "sampleRemark": "Örnek Açıklama", + "oldUsername": "Mevcut Kullanıcı Adı", + "currentPassword": "Mevcut Şifre", + "newUsername": "Yeni Kullanıcı Adı", + "newPassword": "Yeni Şifre", + "telegramBotEnable": "Telegram Botunu Etkinleştir", + "telegramBotEnableDesc": "Telegram botunu etkinleştirir.", + "telegramToken": "Telegram Token", + "telegramTokenDesc": "'{'@'}BotFather'dan alınan Telegram bot token.", + "telegramProxy": "SOCKS Proxy", + "telegramProxyDesc": "Telegram'a bağlanmak için SOCKS5 proxy'sini etkinleştirir. (ayarları kılavuzda belirtilen şekilde ayarlayın)", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın.", + "telegramChatId": "Yönetici Sohbet Kimliği", + "telegramChatIdDesc": "Telegram Yönetici Sohbet Kimliği(leri). (virgülle ayrılmış)(buradan alın {'@'}userinfobot) veya (botta '/id' komutunu kullanın)", + "telegramNotifyTime": "Bildirim Zamanı", + "telegramNotifyTimeDesc": "Periyodik raporlar için ayarlanan Telegram bot bildirim zamanı. (crontab zaman formatını kullanın)", + "tgNotifyBackup": "Veritabanı Yedeği", + "tgNotifyBackupDesc": "Bir rapor ile birlikte veritabanı yedek dosyasını gönder.", + "tgNotifyLogin": "Giriş Bildirimi", + "tgNotifyLoginDesc": "Birisi web panelinize giriş yapmaya çalıştığında kullanıcı adı, IP adresi ve zaman hakkında bildirim alın.", + "sessionMaxAge": "Oturum Süresi", + "sessionMaxAgeDesc": "Giriş yaptıktan sonra oturum süresi. (birim: dakika)", + "expireTimeDiff": "Son Kullanma Tarihi Bildirimi", + "expireTimeDiffDesc": "Bu eşik seviyesine ulaşıldığında son kullanma tarihi hakkında bildirim alın. (birim: gün)", + "trafficDiff": "Trafik Sınırı Bildirimi", + "trafficDiffDesc": "Bu eşik seviyesine ulaşıldığında trafik sınırı hakkında bildirim alın. (birim: GB)", + "tgNotifyCpu": "CPU Yükü Bildirimi", + "tgNotifyCpuDesc": "CPU yükü bu eşik seviyesini aşarsa bildirim alın. (birim: %)", + "timeZone": "Saat Dilimi", + "timeZoneDesc": "Planlanmış görevler bu saat dilimine göre çalışacaktır.", + "subSettings": "Abonelik", + "subEnable": "Abonelik Hizmetini Etkinleştir", + "subEnableDesc": "Abonelik hizmetini etkinleştirir.", + "subJsonEnable": "JSON abonelik uç noktasını bağımsız olarak Etkinleştir/Devre Dışı bırak.", + "subTitle": "Abonelik Başlığı", + "subTitleDesc": "VPN istemcisinde gösterilen başlık", + "subSupportUrl": "Destek URL'si", + "subSupportUrlDesc": "VPN istemcisinde gösterilen teknik destek bağlantısı", + "subProfileUrl": "Profil URL'si", + "subProfileUrlDesc": "VPN istemcisinde görüntülenen web sitenize giden bağlantı", + "subAnnounce": "Duyuru", + "subAnnounceDesc": "VPN istemcisinde görüntülenen duyuru metni", + "subEnableRouting": "Yönlendirmeyi etkinleştir", + "subEnableRoutingDesc": "VPN istemcisinde yönlendirmeyi etkinleştirmek için genel ayar. (Yalnızca Happ için)", + "subRoutingRules": "Yönlendirme kuralları", + "subRoutingRulesDesc": "VPN istemcisi için genel yönlendirme kuralları. (Yalnızca Happ için)", + "subListen": "Dinleme IP", + "subListenDesc": "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)", + "subPort": "Dinleme Portu", + "subPortDesc": "Abonelik hizmeti için port numarası. (kullanılmayan bir port olmalıdır)", + "subCertPath": "Genel Anahtar Yolu", + "subCertPathDesc": "Abonelik hizmeti için genel anahtar dosya yolu. ('/' ile başlar)", + "subKeyPath": "Özel Anahtar Yolu", + "subKeyPathDesc": "Abonelik hizmeti için özel anahtar dosya yolu. ('/' ile başlar)", + "subPath": "URI Yolu", + "subPathDesc": "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)", + "subDomain": "Dinleme Alan Adı", + "subDomainDesc": "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)", + "subUpdates": "Güncelleme Aralıkları", + "subUpdatesDesc": "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)", + "subEncrypt": "Şifrele", + "subEncryptDesc": "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir.", + "subShowInfo": "Kullanım Bilgisini Göster", + "subShowInfoDesc": "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir.", + "subURI": "Ters Proxy URI", + "subURIDesc": "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu.", + "externalTrafficInformEnable": "Harici Trafik Bilgisi", + "externalTrafficInformEnableDesc": "Her trafik güncellemesinde harici API'yi bilgilendirin.", + "externalTrafficInformURI": "Harici Trafik Bilgisi URI'si", + "externalTrafficInformURIDesc": "Trafik güncellemeleri bu URI'ye gönderildi.", + "restartXrayOnClientDisable": "Otomatik Devre Dışı Sonrası Xray'i Yeniden Başlat", + "restartXrayOnClientDisableDesc": "Bir istemci süre dolumu veya trafik limiti nedeniyle otomatik devre dışı bırakıldığında Xray'i yeniden başlat.", + "fragment": "Parçalama", + "fragmentDesc": "TLS merhaba paketinin parçalanmasını etkinleştir.", + "fragmentSett": "Parçalama Ayarları", + "noisesDesc": "Noises'i Etkinleştir.", + "noisesSett": "Noises Ayarları", + "mux": "Mux", + "muxDesc": "Kurulmuş bir veri akışında birden çok bağımsız veri akışını iletir.", + "muxSett": "Mux Ayarları", + "direct": "Doğrudan Bağlantı", + "directDesc": "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar.", + "notifications": "Bildirimler", + "certs": "Sertifikalar", + "externalTraffic": "Harici Trafik", + "dateAndTime": "Tarih ve Saat", + "proxyAndServer": "Proxy ve Sunucu", + "intervals": "Aralıklar", + "information": "Bilgi", + "language": "Dil", + "telegramBotLanguage": "Telegram Bot Dili", + "security": { + "admin": "Yönetici kimlik bilgileri", + "twoFactor": "İki adımlı doğrulama", + "twoFactorEnable": "2FA'yı Etkinleştir", + "twoFactorEnableDesc": "Daha fazla güvenlik için ek bir doğrulama katmanı ekler.", + "twoFactorModalSetTitle": "İki adımlı doğrulamayı etkinleştir", + "twoFactorModalDeleteTitle": "İki adımlı doğrulamayı devre dışı bırak", + "twoFactorModalSteps": "İki adımlı doğrulamayı ayarlamak için şu adımları izleyin:", + "twoFactorModalFirstStep": "1. Bu QR kodunu doğrulama uygulamasında tarayın veya QR kodunun yanındaki token'ı kopyalayıp uygulamaya yapıştırın", + "twoFactorModalSecondStep": "2. Uygulamadaki kodu girin", + "twoFactorModalRemoveStep": "İki adımlı doğrulamayı kaldırmak için uygulamadaki kodu girin.", + "twoFactorModalChangeCredentialsTitle": "Kimlik bilgilerini değiştir", + "twoFactorModalChangeCredentialsStep": "Yönetici kimlik bilgilerini değiştirmek için uygulamadaki kodu girin.", + "twoFactorModalSetSuccess": "İki faktörlü kimlik doğrulama başarıyla kuruldu", + "twoFactorModalDeleteSuccess": "İki faktörlü kimlik doğrulama başarıyla silindi", + "twoFactorModalError": "Yanlış kod" + }, + "toasts": { + "modifySettings": "Parametreler değiştirildi.", + "getSettings": "Parametreler alınırken bir hata oluştu.", + "modifyUserError": "Yönetici kimlik bilgileri değiştirilirken bir hata oluştu.", + "modifyUser": "Yönetici kimlik bilgilerini başarıyla değiştirdiniz.", + "originalUserPassIncorrect": "Mevcut kullanıcı adı veya şifre geçersiz", + "userPassMustBeNotEmpty": "Yeni kullanıcı adı ve şifre boş olamaz", + "getOutboundTrafficError": "Giden trafik alınırken hata", + "resetOutboundTrafficError": "Giden trafik sıfırlanırken hata" + } + }, + "xray": { + "title": "Xray Yapılandırmaları", + "save": "Kaydet", + "restart": "Xray'i Yeniden Başlat", + "restartSuccess": "Xray başarıyla yeniden başlatıldı", + "stopSuccess": "Xray başarıyla durduruldu", + "restartError": "Xray yeniden başlatılırken bir hata oluştu.", + "stopError": "Xray durdurulurken bir hata oluştu.", + "basicTemplate": "Temeller", + "advancedTemplate": "Gelişmiş", + "generalConfigs": "Genel", + "generalConfigsDesc": "Bu seçenekler genel ayarlamaları belirler.", + "logConfigs": "Günlük", + "logConfigsDesc": "Günlükler sunucunuzun verimliliğini etkileyebilir. Yalnızca ihtiyaç durumunda akıllıca etkinleştirmeniz önerilir", + "blockConfigsDesc": "Bu seçenekler belirli istek protokolleri ve web siteleri temelinde trafiği engeller.", + "basicRouting": "Temel Yönlendirme", + "blockConnectionsConfigsDesc": "Bu seçenekler belirli bir istenen ülkeye göre trafiği engelleyecektir.", + "directConnectionsConfigsDesc": "Doğrudan bağlantı, belirli bir trafiğin başka bir sunucu üzerinden yönlendirilmediğini sağlar.", + "blockips": "IP'leri Engelle", + "blockdomains": "Alan Adlarını Engelle", + "directips": "Doğrudan IP'ler", + "directdomains": "Doğrudan Alan Adları", + "ipv4Routing": "IPv4 Yönlendirme", + "ipv4RoutingDesc": "Bu seçenekler belirli bir varış yerine IPv4 üzerinden trafiği yönlendirir.", + "warpRouting": "WARP Yönlendirme", + "warpRoutingDesc": "Bu seçenekler belirli bir varış yerine WARP üzerinden trafiği yönlendirir.", + "nordRouting": "NordVPN Yönlendirme", + "nordRoutingDesc": "Bu seçenekler belirli bir varış yerine NordVPN üzerinden trafiği yönlendirir.", + "Template": "Gelişmiş Xray Yapılandırma Şablonu", + "TemplateDesc": "Nihai Xray yapılandırma dosyası bu şablona göre oluşturulacaktır.", + "FreedomStrategy": "Freedom Protokol Stratejisi", + "FreedomStrategyDesc": "Freedom Protokolünde ağın çıkış stratejisini ayarlayın.", + "RoutingStrategy": "Genel Yönlendirme Stratejisi", + "RoutingStrategyDesc": "Tüm istekleri çözmek için genel trafik yönlendirme stratejisini ayarlayın.", + "outboundTestUrl": "Outbound test URL", + "outboundTestUrlDesc": "Outbound bağlantı testinde kullanılan URL", + "Torrent": "BitTorrent Protokolünü Engelle", + "Inbounds": "Gelenler", + "InboundsDesc": "Belirli müşterileri kabul eder.", + "Outbounds": "Gidenler", + "Balancers": "Dengeler", + "OutboundsDesc": "Giden trafiğin yolunu ayarlayın.", + "Routings": "Yönlendirme Kuralları", + "RoutingsDesc": "Her kuralın önceliği önemlidir!", + "completeTemplate": "Tümü", + "logLevel": "Günlük Seviyesi", + "logLevelDesc": "Hata günlükleri için günlük seviyesi, kaydedilmesi gereken bilgileri belirtir.", + "accessLog": "Erişim Günlüğü", + "accessLogDesc": "Erişim günlüğü için dosya yolu. 'none' özel değeri erişim günlüklerini devre dışı bırakır", + "errorLog": "Hata Günlüğü", + "errorLogDesc": "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır", + "dnsLog": "DNS Günlüğü", + "dnsLogDesc": "DNS sorgu günlüklerini etkinleştirin", + "maskAddress": "Adres Maskesi", + "maskAddressDesc": "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir.", + "statistics": "İstatistikler", + "statsInboundUplink": "Gelen Yükleme İstatistikleri", + "statsInboundUplinkDesc": "Tüm gelen proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir.", + "statsInboundDownlink": "Gelen İndirme İstatistikleri", + "statsInboundDownlinkDesc": "Tüm gelen proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir.", + "statsOutboundUplink": "Giden Yükleme İstatistikleri", + "statsOutboundUplinkDesc": "Tüm giden proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir.", + "statsOutboundDownlink": "Giden İndirme İstatistikleri", + "statsOutboundDownlinkDesc": "Tüm giden proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir.", + "rules": { + "first": "İlk", + "last": "Son", + "up": "Yukarı", + "down": "Aşağı", + "source": "Kaynak", + "dest": "Hedef", + "inbound": "Gelen", + "outbound": "Giden", + "balancer": "Dengeler", + "info": "Bilgi", + "add": "Kural Ekle", + "edit": "Kuralı Düzenle", + "useComma": "Virgülle ayrılmış öğeler" + }, + "outbound": { + "addOutbound": "Giden Ekle", + "addReverse": "Ters Ekle", + "editOutbound": "Gideni Düzenle", + "editReverse": "Tersi Düzenle", + "reverseTag": "Ters Etiket", + "reverseTagDesc": "VLESS basit ters proxy çıkış etiketi. Devre dışı bırakmak için boş bırakın.", + "reverseTagPlaceholder": "çıkış etiketi (boş = devre dışı)", + "tag": "Etiket", + "tagDesc": "Benzersiz Etiket", + "address": "Adres", + "reverse": "Ters", + "domain": "Alan Adı", + "type": "Tür", + "bridge": "Köprü", + "portal": "Portal", + "link": "Bağlantı", + "intercon": "Bağlantı", + "settings": "Ayarlar", + "accountInfo": "Hesap Bilgileri", + "outboundStatus": "Giden Durumu", + "sendThrough": "Üzerinden Gönder", + "test": "Test", + "testResult": "Test Sonucu", + "testing": "Bağlantı test ediliyor...", + "testSuccess": "Test başarılı", + "testFailed": "Test başarısız", + "testError": "Giden test edilemedi", + "nordvpn": "NordVPN", + "accessToken": "Erişim Jetonu", + "country": "Ülke", + "server": "Sunucu", + "city": "Şehir", + "allCities": "Tüm Şehirler", + "privateKey": "Özel Anahtar", + "load": "Yük" + }, + "balancer": { + "addBalancer": "Dengeleyici Ekle", + "editBalancer": "Dengeleyiciyi Düzenle", + "balancerStrategy": "Strateji", + "balancerSelectors": "Seçiciler", + "tag": "Etiket", + "tagDesc": "Benzersiz Etiket", + "balancerDesc": "Dengeleyici Etiketi ve Giden Etiketi aynı anda kullanılamaz. Aynı anda kullanıldığında yalnızca giden etiketi çalışır." + }, + "wireguard": { + "secretKey": "Gizli Anahtar", + "publicKey": "Genel Anahtar", + "allowedIPs": "İzin Verilen IP'ler", + "endpoint": "Uç Nokta", + "psk": "Ön Paylaşılan Anahtar", + "domainStrategy": "Alan Adı Stratejisi" + }, + "tun": { + "nameDesc": "TUN arabiriminin adı. Varsayılan değer 'xray0'dir", + "mtuDesc": "Maksimum İletim Birimi. Veri paketlerinin maksimum boyutu. Varsayılan değer 1500'dür", + "userLevel": "Kullanıcı Seviyesi", + "userLevelDesc": "Bu giriş yoluyla yapılan tüm bağlantılar bu kullanıcı seviyesini kullanacaktır. Varsayılan değer 0'dır" + }, + "dns": { + "enable": "DNS'yi Etkinleştir", + "enableDesc": "Dahili DNS sunucusunu etkinleştir", + "tag": "DNS Gelen Etiketi", + "tagDesc": "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir.", + "clientIp": "İstemci IP", + "clientIpDesc": "DNS sorguları sırasında belirtilen IP konumunu sunucuya bildirmek için kullanılır", + "disableCache": "Önbelleği devre dışı bırak", + "disableCacheDesc": "DNS önbelleğini devre dışı bırakır", + "disableFallback": "Yedeklemeyi devre dışı bırak", + "disableFallbackDesc": "Yedek DNS sorgularını devre dışı bırakır", + "disableFallbackIfMatch": "Eşleşirse Yedeklemeyi Devre Dışı Bırak", + "disableFallbackIfMatchDesc": "DNS sunucusunun eşleşen alan adı listesi vurulduğunda yedek DNS sorgularını devre dışı bırakır", + "enableParallelQuery": "Paralel Sorguyu Etkinleştir", + "enableParallelQueryDesc": "Daha hızlı çözümleme için birden fazla sunucuya paralel DNS sorgularını etkinleştir", + "strategy": "Sorgu Stratejisi", + "strategyDesc": "Alan adlarını çözmek için genel strateji", + "add": "Sunucu Ekle", + "edit": "Sunucuyu Düzenle", + "domains": "Alan Adları", + "expectIPs": "Beklenen IP'ler", + "unexpectIPs": "Beklenmeyen IP'ler", + "useSystemHosts": "Sistem Hosts'larını Kullan", + "useSystemHostsDesc": "Yüklü bir sistemden hosts dosyasını kullan", + "usePreset": "Şablon kullan", + "dnsPresetTitle": "DNS Şablonları", + "dnsPresetFamily": "Aile" + }, + "fakedns": { + "add": "Sahte DNS Ekle", + "edit": "Sahte DNS'i Düzenle", + "ipPool": "IP Havuzu Alt Ağı", + "poolSize": "Havuz Boyutu" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Klavye kapatıldı!", + "noResult": "❗ Sonuç yok!", + "noQuery": "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!", + "wentWrong": "❌ Bir şeyler yanlış gitti!", + "noIpRecord": "❗ IP Kaydı Yok!", + "noInbounds": "❗ Gelen bağlantı bulunamadı!", + "unlimited": "♾ Sınırsız (Sıfırla)", + "add": "Ekle", + "month": "Ay", + "months": "Aylar", + "day": "Gün", + "days": "Günler", + "hours": "Saatler", + "minutes": "Dakika", + "unknown": "Bilinmeyen", + "inbounds": "Gelenler", + "clients": "İstemciler", + "offline": "🔴 Çevrimdışı", + "online": "🟢 Çevrimiçi", + "commands": { + "unknown": "❗ Bilinmeyen komut.", + "pleaseChoose": "👇 Lütfen seçin:\r\n", + "help": "🤖 Bu bota hoş geldiniz! Web panelinden belirli verileri sunmak ve gerektiğinde değişiklik yapmanıza olanak tanımak için tasarlanmıştır.\r\n\r\n", + "start": "👋 Merhaba {{ .Firstname }}.\r\n", + "welcome": "🤖 {{ .Hostname }} yönetim botuna hoş geldiniz.\r\n", + "status": "✅ Bot çalışıyor!", + "usage": "❗ Lütfen aramak için bir metin sağlayın!", + "getID": "🆔 Kimliğiniz: {{ .ID }}", + "helpAdminCommands": "Xray Core'u yeniden başlatmak için:\r\n/restart\r\n\r\nBir müşteri e-postasını aramak için:\r\n/usage [E-posta]\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n/inbound [Açıklama]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id", + "helpClientCommands": "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n/usage [E-posta]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ İşlem başarılı!", + "restartFailed": "❗ İşlem hatası.\r\n\r\nHata: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core çalışmıyor.", + "startDesc": "Ana menüyü göster", + "helpDesc": "Bot yardımı", + "statusDesc": "Bot durumunu kontrol et", + "idDesc": "Telegram ID'nizi göster" + }, + "messages": { + "cpuThreshold": "🔴 CPU Yükü {{ .Percent }}% eşiği {{ .Threshold }}%'yi aşıyor", + "selectUserFailed": "❌ Kullanıcı seçiminde hata!", + "userSaved": "✅ Telegram Kullanıcısı kaydedildi.", + "loginSuccess": "✅ Panele başarıyla giriş yapıldı.\r\n", + "loginFailed": "❗️Panele giriş denemesi başarısız oldu.\r\n", + "2faFailed": "2FA Hatası", + "report": "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n", + "datetime": "⏰ Tarih&Zaman: {{ .DateTime }}\r\n", + "hostname": "💻 Sunucu: {{ .Hostname }}\r\n", + "version": "🚀 3X-UI Sürümü: {{ .Version }}\r\n", + "xrayVersion": "📡 Xray Sürümü: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IP'ler:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Çalışma Süresi: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Sistem Yükü: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 Trafik: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Durum: {{ .State }}\r\n", + "username": "👤 Kullanıcı Adı: {{ .Username }}\r\n", + "reason": "❗️ Sebep: {{ .Reason }}\r\n", + "time": "⏰ Zaman: {{ .Time }}\r\n", + "inbound": "📍 Gelen: {{ .Remark }}\r\n", + "port": "🔌 Port: {{ .Port }}\r\n", + "expire": "📅 Son Kullanma Tarihi: {{ .Time }}\r\n", + "expireIn": "📅 Sona Erecek: {{ .Time }}\r\n", + "active": "💡 Aktif: {{ .Enable }}\r\n", + "enabled": "🚨 Etkin: {{ .Enable }}\r\n", + "online": "🌐 Bağlantı durumu: {{ .Status }}\r\n", + "lastOnline": "🔙 Son çevrimiçi: {{ .Time }}\r\n", + "email": "📧 E-posta: {{ .Email }}\r\n", + "upload": "🔼 Yükleme: ↑{{ .Upload }}\r\n", + "download": "🔽 İndirme: ↓{{ .Download }}\r\n", + "total": "📊 Toplam: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Telegram Kullanıcısı: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Tükenmiş {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Tükenmiş {{ .Type }} sayısı:\r\n", + "onlinesCount": "🌐 Çevrimiçi Müşteriler: {{ .Count }}\r\n", + "disabled": "🛑 Devre Dışı: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Yakında Tükenecek: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Yedekleme Zamanı: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Yenilendi: {{ .Time }}\r\n\r\n", + "yes": "✅ Evet", + "no": "❌ Hayır", + "received_id": "🔑📥 Kimlik güncellendi.", + "received_password": "🔑📥 Şifre güncellendi.", + "received_email": "📧📥 E-posta güncellendi.", + "received_comment": "💬📥 Yorum güncellendi.", + "id_prompt": "🔑 Varsayılan Kimlik: {{ .ClientId }}\n\nKimliğinizi girin.", + "pass_prompt": "🔑 Varsayılan Şifre: {{ .ClientPassword }}\n\nŞifrenizi girin.", + "email_prompt": "📧 Varsayılan E-posta: {{ .ClientEmail }}\n\nE-postanızı girin.", + "comment_prompt": "💬 Varsayılan Yorum: {{ .ClientComment }}\n\nYorumunuzu girin.", + "inbound_client_data_id": "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Kimlik: {{ .ClientId }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!", + "inbound_client_data_pass": "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Şifre: {{ .ClientPass }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!", + "cancel": "❌ İşlem iptal edildi! \n\nİstediğiniz zaman /start ile yeniden başlayabilirsiniz. 🔄", + "error_add_client": "⚠️ Hata:\n\n {{ .error }}", + "using_default_value": "Tamam, varsayılan değeri kullanacağım. 😊", + "incorrect_input": "Girdiğiniz değer geçerli değil.\nKelime öbekleri boşluk olmadan devam etmelidir.\nDoğru örnek: aaaaaa\nYanlış örnek: aaa aaa 🚫", + "AreYouSure": "Emin misin? 🤔", + "SuccessResetTraffic": "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ✅ Başarılı", + "FailedResetTraffic": "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ❌ Başarısız \n\n🛠️ Hata: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Tüm müşteriler için trafik sıfırlama işlemi tamamlandı." + }, + "buttons": { + "closeKeyboard": "❌ Klavyeyi Kapat", + "cancel": "❌ İptal", + "cancelReset": "❌ Sıfırlamayı İptal Et", + "cancelIpLimit": "❌ IP Limitini İptal Et", + "confirmResetTraffic": "✅ Trafiği Sıfırlamayı Onayla?", + "confirmClearIps": "✅ IP'leri Temizlemeyi Onayla?", + "confirmRemoveTGUser": "✅ Telegram Kullanıcısını Kaldırmayı Onayla?", + "confirmToggle": "✅ Kullanıcıyı Etkinleştirme/Devre Dışı Bırakmayı Onayla?", + "dbBackup": "Veritabanı Yedeği Al", + "serverUsage": "Sunucu Kullanımı", + "getInbounds": "Gelenleri Al", + "depleteSoon": "Yakında Tükenecek", + "clientUsage": "Kullanımı Al", + "onlines": "Çevrimiçi Müşteriler", + "commands": "Komutlar", + "refresh": "🔄 Yenile", + "clearIPs": "❌ IP'leri Temizle", + "removeTGUser": "❌ Telegram Kullanıcısını Kaldır", + "selectTGUser": "👤 Telegram Kullanıcısını Seç", + "selectOneTGUser": "👤 Bir Telegram Kullanıcısını Seçin:", + "resetTraffic": "📈 Trafiği Sıfırla", + "resetExpire": "📅 Son Kullanma Tarihini Değiştir", + "ipLog": "🔢 IP Günlüğü", + "ipLimit": "🔢 IP Limiti", + "setTGUser": "👤 Telegram Kullanıcısını Ayarla", + "toggle": "🔘 Etkinleştir / Devre Dışı Bırak", + "custom": "🔢 Özel", + "confirmNumber": "✅ Onayla: {{ .Num }}", + "confirmNumberAdd": "✅ Ekleme onayı: {{ .Num }}", + "limitTraffic": "🚧 Trafik Sınırı", + "getBanLogs": "Yasak Günlüklerini Al", + "allClients": "Tüm Müşteriler", + "addClient": "Müşteri Ekle", + "submitDisable": "Devre Dışı Olarak Gönder ☑️", + "submitEnable": "Etkin Olarak Gönder ✅", + "use_default": "🏷️ Varsayılanı Kullan", + "change_id": "⚙️🔑 Kimlik", + "change_password": "⚙️🔑 Şifre", + "change_email": "⚙️📧 E-posta", + "change_comment": "⚙️💬 Yorum", + "ResetAllTraffics": "Tüm Trafikleri Sıfırla", + "SortedTrafficUsageReport": "Sıralı Trafik Kullanım Raporu" + }, + "answers": { + "successfulOperation": "✅ İşlem başarılı!", + "errorOperation": "❗ İşlemde hata.", + "getInboundsFailed": "❌ Gelenler alınamadı.", + "getClientsFailed": "❌ Müşteriler alınamadı.", + "canceled": "❌ {{ .Email }}: İşlem iptal edildi.", + "clientRefreshSuccess": "✅ {{ .Email }}: Müşteri başarıyla yenilendi.", + "IpRefreshSuccess": "✅ {{ .Email }}: IP'ler başarıyla yenilendi.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Müşterinin Telegram Kullanıcısı başarıyla yenilendi.", + "resetTrafficSuccess": "✅ {{ .Email }}: Trafik başarıyla sıfırlandı.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Trafik limiti başarıyla kaydedildi.", + "expireResetSuccess": "✅ {{ .Email }}: Son kullanma günleri başarıyla sıfırlandı.", + "resetIpSuccess": "✅ {{ .Email }}: IP limiti {{ .Count }} başarıyla kaydedildi.", + "clearIpSuccess": "✅ {{ .Email }}: IP'ler başarıyla temizlendi.", + "getIpLog": "✅ {{ .Email }}: IP Günlüğü alındı.", + "getUserInfo": "✅ {{ .Email }}: Telegram Kullanıcı Bilgisi alındı.", + "removedTGUserSuccess": "✅ {{ .Email }}: Telegram Kullanıcısı başarıyla kaldırıldı.", + "enableSuccess": "✅ {{ .Email }}: Başarıyla etkinleştirildi.", + "disableSuccess": "✅ {{ .Email }}: Başarıyla devre dışı bırakıldı.", + "askToAddUserId": "Yapılandırmanız bulunamadı!\r\nLütfen yöneticinizden yapılandırmalarınıza Telegram ChatID'nizi eklemesini isteyin.\r\n\r\nKullanıcı ChatID'niz: {{ .TgUserID }}", + "chooseClient": "Gelen {{ .Inbound }} için bir Müşteri Seçin", + "chooseInbound": "Bir Gelen Seçin" + } + } +} diff --git a/web/translation/translate.ar_EG.toml b/web/translation/translate.ar_EG.toml deleted file mode 100644 index 8180a3d9..00000000 --- a/web/translation/translate.ar_EG.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "اسم المستخدم" -"password" = "الباسورد" -"login" = "تسجيل الدخول" -"confirm" = "تأكيد" -"cancel" = "إلغاء" -"close" = "إغلاق" -"save" = "حفظ" -"logout" = "تسجيل خروج" -"create" = "إنشاء" -"update" = "تحديث" -"copy" = "نسخ" -"copied" = "اتنسخ" -"download" = "تحميل" -"remark" = "ملاحظة" -"enable" = "مفعل" -"protocol" = "بروتوكول" -"search" = "بحث" -"filter" = "فلترة" -"loading" = "جاري التحميل..." -"second" = "ثانية" -"minute" = "دقيقة" -"hour" = "ساعة" -"day" = "يوم" -"check" = "شيك" -"indefinite" = "غير محدد" -"unlimited" = "غير محدود" -"none" = "مفيش" -"qrCode" = "كود QR" -"info" = "معلومات أكتر" -"edit" = "تعديل" -"delete" = "مسح" -"reset" = "إعادة ضبط" -"noData" = "لا توجد بيانات." -"copySuccess" = "اتنسخ بنجاح" -"sure" = "متأكد؟" -"encryption" = "تشفير" -"useIPv4ForHost" = "استخدم IPv4 للمضيف" -"transmission" = "نقل" -"host" = "المستضيف" -"path" = "مسار" -"camouflage" = "تمويه" -"status" = "الحالة" -"enabled" = "مفعل" -"disabled" = "معطل" -"depleted" = "خلص" -"depletingSoon" = "هينتهي قريب" -"offline" = "أوفلاين" -"online" = "أونلاين" -"domainName" = "اسم الدومين" -"monitor" = "المسمع IP" -"certificate" = "شهادة رقمية" -"fail" = "فشل" -"comment" = "تعليق" -"success" = "تم بنجاح" -"lastOnline" = "آخر متصل" -"getVersion" = "جيب النسخة" -"install" = "تثبيت" -"clients" = "عملاء" -"usage" = "استخدام" -"twoFactorCode" = "الكود" -"remained" = "المتبقي" -"security" = "أمان" -"secAlertTitle" = "تنبيه أمني" -"secAlertSsl" = "الاتصال ده مش آمن. ابعد عن إدخال معلومات حساسة لغاية ما تشغل TLS لحماية البيانات." -"secAlertConf" = "بعض الإعدادات معرضة لهجمات. ينصح بتعزيز بروتوكولات الأمان عشان تمنع الاختراقات المحتملة." -"secAlertSSL" = "البانل مش مؤمن. حمّل شهادة TLS لحماية البيانات." -"secAlertPanelPort" = "بورت البانل الافتراضي معرض للخطر. ياريت تغير لبورت عشوائي أو محدد." -"secAlertPanelURI" = "مسار URI الافتراضي للبانل مش آمن. ياريت تضبط مسار URI معقد." -"secAlertSubURI" = "مسار URI الافتراضي للاشتراك مش آمن. ياريت تضبط مسار URI معقد." -"secAlertSubJsonURI" = "مسار URI الافتراضي لاشتراك JSON مش آمن. ياريت تضبط مسار URI معقد." -"emptyDnsDesc" = "مفيش سيرفر DNS مضاف." -"emptyFakeDnsDesc" = "مفيش سيرفر Fake DNS مضاف." -"emptyBalancersDesc" = "مفيش موازن تحميل مضاف." -"emptyReverseDesc" = "مفيش بروكسي عكسي مضاف." -"somethingWentWrong" = "حدث خطأ ما" - -[subscription] -"title" = "معلومات الاشتراك" -"subId" = "معرّف الاشتراك" -"status" = "الحالة" -"downloaded" = "التنزيل" -"uploaded" = "الرفع" -"expiry" = "تاريخ الانتهاء" -"totalQuota" = "الحصة الإجمالية" -"individualLinks" = "روابط فردية" -"active" = "نشط" -"inactive" = "غير نشط" -"unlimited" = "غير محدود" -"noExpiry" = "بدون انتهاء" - -[menu] -"theme" = "الثيم" -"dark" = "داكن" -"ultraDark" = "داكن جدًا" -"dashboard" = "نظرة عامة" -"inbounds" = "الإدخالات" -"settings" = "إعدادات البانل" -"xray" = "إعدادات Xray" -"logout" = "تسجيل خروج" -"link" = "إدارة" - -[pages.login] -"hello" = "أهلا" -"title" = "أهلاً وسهلاً" -"loginAgain" = "انتهت صلاحية الجلسة، سجل دخول تاني" - -[pages.login.toasts] -"invalidFormData" = "تنسيق البيانات المدخلة مش صحيح." -"emptyUsername" = "اسم المستخدم مطلوب" -"emptyPassword" = "الباسورد مطلوب" -"wrongUsernameOrPassword" = "اسم المستخدم أو كلمة المرور أو كود المصادقة الثنائية غير صحيح." -"successLogin" = "لقد تم تسجيل الدخول إلى حسابك بنجاح." - -[pages.index] -"title" = "نظرة عامة" -"cpu" = "المعالج" -"logicalProcessors" = "المعالجات المنطقية" -"frequency" = "التردد" -"swap" = "Swap" -"storage" = "تخزين" -"memory" = "رام" -"threads" = "خيوط المعالجة" -"xrayStatus" = "Xray" -"stopXray" = "إيقاف" -"restartXray" = "إعادة تشغيل" -"xraySwitch" = "النسخة" -"xraySwitchClick" = "اختار النسخة اللي عايز تتحول لها." -"xraySwitchClickDesk" = "اختار بحذر، النسخ القديمة ممكن ما تتوافقش مع الإعدادات الحالية." -"xrayUpdates" = "تحديثات Xray" -"updatePanel" = "تحديث البانل" -"panelUpdateDesc" = "ده هيحدث 3X-UI لآخر إصدار وهيعيد تشغيل خدمة البانل." -"currentPanelVersion" = "إصدار البانل الحالي" -"latestPanelVersion" = "أحدث إصدار للبانل" -"panelUpToDate" = "البانل محدث لآخر إصدار" -"upToDate" = "محدث" -"xrayStatusUnknown" = "مش معروف" -"xrayStatusRunning" = "شغالة" -"xrayStatusStop" = "متوقفة" -"xrayStatusError" = "فيها غلطة" -"xrayErrorPopoverTitle" = "حصل خطأ أثناء تشغيل Xray" -"operationHours" = "مدة التشغيل" -"systemLoad" = "تحميل النظام" -"systemLoadDesc" = "متوسط تحميل النظام في الدقائق 1, 5, و15" -"connectionCount" = "إحصائيات الاتصال" -"ipAddresses" = "عناوين IP" -"toggleIpVisibility" = "بدل إظهار IP" -"overallSpeed" = "السرعة الكلية" -"upload" = "رفع" -"download" = "تنزيل" -"totalData" = "إجمالي البيانات" -"sent" = "مرسل" -"received" = "مستقبل" -"documentation" = "التوثيق" -"xraySwitchVersionDialog" = "هل تريد حقًا تغيير إصدار Xray؟" -"xraySwitchVersionDialogDesc" = "سيؤدي هذا إلى تغيير إصدار Xray إلى #version#." -"xraySwitchVersionPopover" = "تم تحديث Xray بنجاح" -"panelUpdateDialog" = "هل فعلاً عايز تحدث البانل؟" -"panelUpdateDialogDesc" = "ده هيحدث 3X-UI للإصدار #version# وهيعيد تشغيل البانل." -"panelUpdateCheckPopover" = "فشل التحقق من تحديث البانل" -"panelUpdateStartedPopover" = "بدأ تحديث البانل" -"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟" -"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#." -"geofilesUpdateDialogDesc" = "سيؤدي هذا إلى تحديث كافة الملفات." -"geofilesUpdateAll" = "تحديث الكل" -"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح" -"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة" -"logs" = "السجلات" -"config" = "الإعدادات" -"backup" = "نسخة احتياطية" -"backupTitle" = "نسخة احتياطية واسترجاع قاعدة البيانات" -"exportDatabase" = "اخزن نسخة" -"exportDatabaseDesc" = "اضغط عشان تحمل ملف .db يحتوي على نسخة احتياطية لقاعدة البيانات الحالية على جهازك." -"importDatabase" = "استرجاع" -"importDatabaseDesc" = "اضغط عشان تختار وتحمل ملف .db من جهازك لاسترجاع قاعدة البيانات من نسخة احتياطية." -"importDatabaseSuccess" = "تم استيراد قاعدة البيانات بنجاح" -"importDatabaseError" = "حدث خطأ أثناء استيراد قاعدة البيانات" -"readDatabaseError" = "حدث خطأ أثناء قراءة قاعدة البيانات" -"getDatabaseError" = "حدث خطأ أثناء استرجاع قاعدة البيانات" -"getConfigError" = "حدث خطأ أثناء استرجاع ملف الإعدادات" -"customGeoTitle" = "GeoSite / GeoIP مخصص" -"customGeoAdd" = "إضافة" -"customGeoType" = "النوع" -"customGeoAlias" = "الاسم المستعار" -"customGeoUrl" = "URL" -"customGeoEnabled" = "مفعّل" -"customGeoLastUpdated" = "آخر تحديث" -"customGeoExtColumn" = "التوجيه (ext:…)" -"customGeoToastUpdateAll" = "تم تحديث جميع المصادر المخصصة" -"customGeoActions" = "إجراءات" -"customGeoEdit" = "تعديل" -"customGeoDelete" = "حذف" -"customGeoDownload" = "تحديث الآن" -"customGeoModalAdd" = "إضافة geo مخصص" -"customGeoModalEdit" = "تعديل geo مخصص" -"customGeoModalSave" = "حفظ" -"customGeoDeleteConfirm" = "حذف مصدر geo المخصص هذا؟" -"customGeoRoutingHint" = "في قواعد التوجيه استخدم العمود كـ ext:file.dat:tag (استبدل tag)." -"customGeoInvalidId" = "معرّف المورد غير صالح" -"customGeoAliasesError" = "تعذّر تحميل أسماء geo المخصصة" -"customGeoValidationAlias" = "الاسم المستعار: أحرف صغيرة وأرقام و - و _ فقط" -"customGeoValidationUrl" = "يجب أن يبدأ الرابط بـ http:// أو https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (مخصص)" -"customGeoToastList" = "قائمة geo المخصص" -"customGeoToastAdd" = "إضافة geo مخصص" -"customGeoToastUpdate" = "تحديث geo مخصص" -"customGeoToastDelete" = "تم حذف geofile «{{ .fileName }}» المخصص" -"customGeoToastDownload" = "تم تحديث geofile «{{ .fileName }}»" -"customGeoErrInvalidType" = "يجب أن يكون النوع geosite أو geoip" -"customGeoErrAliasRequired" = "الاسم المستعار مطلوب" -"customGeoErrAliasPattern" = "الاسم المستعار يحتوي على أحرف غير مسموحة" -"customGeoErrAliasReserved" = "هذا الاسم محجوز" -"customGeoErrUrlRequired" = "الرابط مطلوب" -"customGeoErrInvalidUrl" = "الرابط غير صالح" -"customGeoErrUrlScheme" = "يجب أن يستخدم الرابط http أو https" -"customGeoErrUrlHost" = "مضيف الرابط غير صالح" -"customGeoErrDuplicateAlias" = "هذا الاسم مستخدم مسبقاً لهذا النوع" -"customGeoErrNotFound" = "مصدر geo المخصص غير موجود" -"customGeoErrDownload" = "فشل التنزيل" -"customGeoErrUpdateAllIncomplete" = "تعذر تحديث مصدر واحد أو أكثر من مصادر geo المخصصة" -"customGeoEmpty" = "لا توجد مصادر geo مخصصة بعد — انقر على «إضافة» لإنشاء واحد" - -[pages.inbounds] -"allTimeTraffic" = "إجمالي حركة المرور" -"allTimeTrafficUsage" = "إجمالي الاستخدام طوال الوقت" -"title" = "الإدخالات" -"totalDownUp" = "إجمالي المرسل/المستقبل" -"totalUsage" = "إجمالي الاستخدام" -"inboundCount" = "عدد الإدخالات" -"operate" = "القائمة" -"enable" = "مفعل" -"remark" = "ملاحظة" -"protocol" = "بروتوكول" -"port" = "بورت" -"portMap" = "خريطة البورت" -"traffic" = "الترافيك" -"details" = "تفاصيل" -"transportConfig" = "نقل" -"expireDate" = "المدة" -"createdAt" = "تاريخ الإنشاء" -"updatedAt" = "تاريخ التحديث" -"resetTraffic" = "إعادة ضبط الترافيك" -"addInbound" = "أضف إدخال" -"generalActions" = "إجراءات عامة" -"autoRefresh" = "تحديث تلقائي" -"autoRefreshInterval" = "الفاصل" -"modifyInbound" = "تعديل الإدخال" -"deleteInbound" = "حذف الإدخال" -"deleteInboundContent" = "متأكد إنك عايز تحذف الإدخال؟" -"deleteClient" = "حذف العميل" -"deleteClientContent" = "متأكد إنك عايز تحذف العميل؟" -"resetTrafficContent" = "متأكد إنك عايز تعيد ضبط الترافيك؟" -"copyLink" = "انسخ الرابط" -"address" = "العنوان" -"network" = "الشبكة" -"destinationPort" = "بورت الوجهة" -"targetAddress" = "عنوان الهدف" -"monitorDesc" = "سيبها فاضية لو عايز تستمع على كل الـ IPs" -"meansNoLimit" = "= غير محدود. (الوحدة: جيجابايت)" -"totalFlow" = "إجمالي التدفق" -"leaveBlankToNeverExpire" = "سيبها فاضية عشان ماتنتهيش" -"noRecommendKeepDefault" = "ننصح باستخدام الافتراضي" -"certificatePath" = "مسار الملف" -"certificateContent" = "محتوى الملف" -"publicKey" = "المفتاح العام" -"privatekey" = "المفتاح الخاص" -"clickOnQRcode" = "اضغط على كود QR للنسخ" -"client" = "عميل" -"export" = "تصدير كل الروابط" -"clone" = "استنساخ" -"cloneInbound" = "استنساخ الإدخال" -"cloneInboundContent" = "كل إعدادات الإدخال ده، غير البورت، IP الاستماع، والعملاء، هتتطبق على الاستنساخ." -"cloneInboundOk" = "استنساخ" -"resetAllTraffic" = "إعادة ضبط ترافيك كل الإدخالات" -"resetAllTrafficTitle" = "إعادة ضبط ترافيك كل الإدخالات" -"resetAllTrafficContent" = "متأكد إنك عايز تعيد ضبط الترافيك لكل الإدخالات؟" -"resetInboundClientTraffics" = "إعادة ضبط ترافيك العملاء" -"resetInboundClientTrafficTitle" = "إعادة ضبط ترافيك العملاء" -"resetInboundClientTrafficContent" = "متأكد إنك عايز تعيد ضبط ترافيك عملاء الإدخال ده؟" -"resetAllClientTraffics" = "إعادة ضبط ترافيك كل العملاء" -"resetAllClientTrafficTitle" = "إعادة ضبط ترافيك كل العملاء" -"resetAllClientTrafficContent" = "متأكد إنك عايز تعيد ضبط ترافيك كل العملاء؟" -"delDepletedClients" = "حذف العملاء اللي خلصت" -"delDepletedClientsTitle" = "حذف العملاء اللي خلصت" -"delDepletedClientsContent" = "متأكد إنك عايز تحذف كل العملاء اللي خلصت؟" -"email" = "الإيميل" -"emailDesc" = "ادخل إيميل فريد." -"IPLimit" = "تحديد IP" -"IPLimitDesc" = "بيعطل الإدخال لو العدد زاد عن القيمة المحددة. (0 = تعطيل)" -"IPLimitlog" = "سجل IP" -"IPLimitlogDesc" = "سجل تاريخ الـ IPs. (عشان تفعل الإدخال بعد التعطيل، امسح السجل)" -"IPLimitlogclear" = "امسح السجل" -"setDefaultCert" = "استخدم شهادة البانل" -"telegramDesc" = "ادخل ID شات Telegram. (استخدم '/id' في البوت) أو (@userinfobot)" -"subscriptionDesc" = "عشان تلاقي رابط الاشتراك، ادخل على 'التفاصيل'. وكمان ممكن تستخدم نفس الاسم لعدة عملاء." -"info" = "معلومات" -"same" = "نفسه" -"inboundData" = "بيانات الإدخال" -"exportInbound" = "تصدير الإدخال" -"import" = "استيراد" -"importInbound" = "استيراد إدخال" -"periodicTrafficResetTitle" = "إعادة تعيين حركة المرور" -"periodicTrafficResetDesc" = "إعادة تعيين عداد حركة المرور تلقائيًا في فترات محددة" -"lastReset" = "آخر إعادة تعيين" - -[pages.client] -"add" = "أضف عميل" -"edit" = "تعديل عميل" -"submitAdd" = "أضف العميل" -"submitEdit" = "احفظ التعديلات" -"clientCount" = "عدد العملاء" -"bulk" = "إضافة بالجملة" -"copyFromInbound" = "نسخ العملاء من الـ Inbound" -"copyToInbound" = "نسخ العملاء إلى" -"copySelected" = "نسخ المحدد" -"copySource" = "المصدر" -"copyEmailPreview" = "معاينة البريد الإلكتروني الناتج" -"copySelectSourceFirst" = "الرجاء اختيار الـ Inbound المصدر أولاً." -"copyResult" = "نتيجة النسخ" -"copyResultSuccess" = "تم النسخ بنجاح" -"copyResultNone" = "لا يوجد شيء للنسخ: لم يتم اختيار أي عميل أو أن المصدر فارغ" -"copyResultErrors" = "أخطاء النسخ" -"copyFlowLabel" = "Flow للعملاء الجدد (VLESS)" -"copyFlowHint" = "يُطبَّق على جميع العملاء المنسوخين. اتركه فارغاً لتخطيه." -"selectAll" = "تحديد الكل" -"clearAll" = "مسح الكل" -"method" = "طريقة" -"first" = "أول واحد" -"last" = "آخر واحد" -"prefix" = "بادئة" -"postfix" = "لاحقة" -"delayedStart" = "ابدأ بعد أول استخدام" -"expireDays" = "المدة" -"days" = "يوم/أيام" -"renew" = "تجديد تلقائي" -"renewDesc" = "تجديد تلقائي بعد انتهاء الصلاحية. (0 = تعطيل)(الوحدة: يوم)" - -[pages.inbounds.periodicTrafficReset] -"never" = "أبداً" -"daily" = "يومياً" -"weekly" = "أسبوعياً" -"monthly" = "شهرياً" -"hourly" = "كل ساعة" - -[pages.inbounds.toasts] -"obtain" = "تم الحصول عليه" -"updateSuccess" = "تم التحديث بنجاح" -"logCleanSuccess" = "تم مسح السجل" -"inboundsUpdateSuccess" = "تم تحديث الواردات بنجاح" -"inboundUpdateSuccess" = "تم تحديث الوارد بنجاح" -"inboundCreateSuccess" = "تم إنشاء الوارد بنجاح" -"inboundDeleteSuccess" = "تم حذف الوارد بنجاح" -"inboundClientAddSuccess" = "تمت إضافة عميل(عملاء) وارد" -"inboundClientDeleteSuccess" = "تم حذف عميل وارد" -"inboundClientUpdateSuccess" = "تم تحديث عميل وارد" -"delDepletedClientsSuccess" = "تم حذف جميع العملاء المستنفذين" -"resetAllClientTrafficSuccess" = "تم إعادة تعيين كل حركة المرور من العميل" -"resetAllTrafficSuccess" = "تم إعادة تعيين كل حركة المرور" -"resetInboundClientTrafficSuccess" = "تم إعادة تعيين حركة المرور" -"trafficGetError" = "خطأ في الحصول على حركات المرور" -"getNewX25519CertError" = "حدث خطأ أثناء الحصول على شهادة X25519." -"getNewmldsa65Error" = "حدث خطاء في الحصول على mldsa65." -"getNewVlessEncError" = "حدث خطأ أثناء الحصول على VlessEnc." - -[pages.inbounds.stream.general] -"request" = "طلب" -"response" = "رد" -"name" = "اسم" -"value" = "قيمة" - -[pages.inbounds.stream.tcp] -"version" = "نسخة" -"method" = "طريقة" -"path" = "مسار" -"status" = "الحالة" -"statusDescription" = "وصف الحالة" -"requestHeader" = "رأس الطلب" -"responseHeader" = "رأس الرد" - -[pages.settings] -"title" = "إعدادات البانل" -"save" = "حفظ" -"infoDesc" = "كل تغيير هتعمله هنا لازم يتخزن. ياريت تعيد تشغيل البانل عشان التعديلات تتفعل." -"restartPanel" = "إعادة تشغيل البانل" -"restartPanelDesc" = "متأكد إنك عايز تعيد تشغيل البانل؟ لو ماقدرتش تدخل بعد إعادة التشغيل، شوف سجل البانل على السيرفر." -"restartPanelSuccess" = "تم إعادة تشغيل اللوحة بنجاح" -"actions" = "إجراءات" -"resetDefaultConfig" = "استرجاع الافتراضي" -"panelSettings" = "عام" -"securitySettings" = "المصادقة" -"TGBotSettings" = "بوت Telegram" -"panelListeningIP" = "IP الاستماع" -"panelListeningIPDesc" = "عنوان IP للبانل. (سيبه فاضي عشان يستمع على كل الـ IPs)" -"panelListeningDomain" = "دومين الاستماع" -"panelListeningDomainDesc" = "اسم الدومين للبانل. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)" -"panelPort" = "بورت الاستماع" -"panelPortDesc" = "رقم البورت للبانل. (لازم يكون بورت فاضي)" -"publicKeyPath" = "مسار المفتاح العام" -"publicKeyPathDesc" = "مسار ملف المفتاح العام للبانل. (يبدأ بـ '/')" -"privateKeyPath" = "مسار المفتاح الخاص" -"privateKeyPathDesc" = "مسار ملف المفتاح الخاص للبانل. (يبدأ بـ '/')" -"panelUrlPath" = "مسار URI" -"panelUrlPathDesc" = "مسار URI للبانل. (يبدأ بـ '/' وبينتهي بـ '/')" -"pageSize" = "حجم الصفحة" -"pageSizeDesc" = "حدد حجم الصفحة لجدول الإدخالات. (0 = تعطيل)" -"remarkModel" = "نموذج الملاحظة وحرف الفصل" -"datepicker" = "نوع التقويم" -"datepickerPlaceholder" = "اختار التاريخ" -"datepickerDescription" = "المهام المجدولة هتشتغل بناءً على التقويم ده." -"sampleRemark" = "مثال للملاحظة" -"oldUsername" = "اسم المستخدم الحالي" -"currentPassword" = "الباسورد الحالي" -"newUsername" = "اسم المستخدم الجديد" -"newPassword" = "الباسورد الجديد" -"telegramBotEnable" = "تفعيل بوت Telegram" -"telegramBotEnableDesc" = "يفعل بوت Telegram." -"telegramToken" = "توكن Telegram" -"telegramTokenDesc" = "توكن البوت اللي جبت من '@BotFather'." -"telegramProxy" = "بروكسي SOCKS" -"telegramProxyDesc" = "يفعل بروكسي SOCKS5 للاتصال بـ Telegram. (اضبط الإعدادات حسب الدليل)" -"telegramAPIServer" = "سيرفر Telegram API" -"telegramAPIServerDesc" = "سيرفر Telegram API المستخدم. سيبه فاضي لاستخدام الافتراضي." -"telegramChatId" = "ID شات الأدمن" -"telegramChatIdDesc" = "ID شات الأدمن في Telegram. (مفصول بفواصل)(تقدر تجيبه من @userinfobot) أو (استخدم '/id' في البوت)" -"telegramNotifyTime" = "وقت الإشعار" -"telegramNotifyTimeDesc" = "وقت إشعار البوت للتقارير الدورية. (استخدم صيغة وقت crontab)" -"tgNotifyBackup" = "نسخة احتياطية لقاعدة البيانات" -"tgNotifyBackupDesc" = "ابعت ملف النسخة الاحتياطية لقاعدة البيانات مع التقرير." -"tgNotifyLogin" = "إشعار بتسجيل الدخول" -"tgNotifyLoginDesc" = "استقبل إشعار بكل محاولة تسجيل دخول للبانل مع اسم المستخدم، الـ IP، والوقت." -"sessionMaxAge" = "مدة الجلسة" -"sessionMaxAgeDesc" = "المدة اللي تفضل فيها مسجل دخول. (الوحدة: دقيقة)" -"expireTimeDiff" = "تنبيه بتاريخ الانتهاء" -"expireTimeDiffDesc" = "استقبل تنبيه قبل ما توصل لتاريخ الانتهاء بالمدة المحددة. (الوحدة: يوم)" -"trafficDiff" = "تنبيه حد الترافيك" -"trafficDiffDesc" = "استقبل تنبيه عند وصول الترافيك للحد المحدد. (الوحدة: جيجابايت)" -"tgNotifyCpu" = "تنبيه حمل المعالج" -"tgNotifyCpuDesc" = "استقبل تنبيه لو حمل المعالج عدى الحد المحدد. (الوحدة: %)" -"timeZone" = "المنطقة الزمنية" -"timeZoneDesc" = "المهام المجدولة هتشتغل بناءً على المنطقة الزمنية دي." -"subSettings" = "الاشتراك" -"subEnable" = "تفعيل خدمة الاشتراك" -"subEnableDesc" = "يفعل خدمة الاشتراك." -"subJsonEnable" = "تمكين/تعطيل نقطة نهاية اشتراك JSON بشكل مستقل." -"subTitle" = "عنوان الاشتراك" -"subTitleDesc" = "العنوان اللي هيظهر في عميل VPN" -"subSupportUrl" = "رابط الدعم" -"subSupportUrlDesc" = "رابط الدعم الفني المعروض في عميل VPN" -"subProfileUrl" = "رابط الملف الشخصي" -"subProfileUrlDesc" = "رابط لموقعك الإلكتروني يظهر في عميل VPN" -"subAnnounce" = "إعلان" -"subAnnounceDesc" = "نص الإعلان المعروض في عميل VPN" -"subEnableRouting" = "تفعيل التوجيه" -"subEnableRoutingDesc" = "إعداد عام لتمكين التوجيه (Routing) في عميل VPN. (فقط لـ Happ)" -"subRoutingRules" = "قواعد التوجيه" -"subRoutingRulesDesc" = "قواعد التوجيه العامة لعميل VPN. (فقط لـ Happ)" -"subListen" = "IP الاستماع" -"subListenDesc" = "عنوان IP لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الـ IPs)" -"subPort" = "بورت الاستماع" -"subPortDesc" = "رقم البورت لخدمة الاشتراك. (لازم يكون بورت فاضي)" -"subCertPath" = "مسار المفتاح العام" -"subCertPathDesc" = "مسار ملف المفتاح العام لخدمة الاشتراك. (يبدأ بـ '/')" -"subKeyPath" = "مسار المفتاح الخاص" -"subKeyPathDesc" = "مسار ملف المفتاح الخاص لخدمة الاشتراك. (يبدأ بـ '/')" -"subPath" = "مسار URI" -"subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')" -"subDomain" = "دومين الاستماع" -"subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)" -"subUpdates" = "فترات التحديث" -"subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)" -"subEncrypt" = "تشفير" -"subEncryptDesc" = "المحتوى اللي هيترجع من خدمة الاشتراك هيكون مشفر بـ Base64." -"subShowInfo" = "اظهر معلومات الاستخدام" -"subShowInfoDesc" = "هيظهر الترافيك المتبقي والتاريخ في تطبيقات العملاء." -"subURI" = "مسار البروكسي العكسي" -"subURIDesc" = "مسار URI لرابط الاشتراك عشان تستخدمه ورا البروكسي." -"externalTrafficInformEnable" = "تنبيه الترافيك الخارجي" -"externalTrafficInformEnableDesc" = "يبعت تنبيه لـ API خارجي مع كل تحديث للترافيك." -"externalTrafficInformURI" = "مسار تنبيه الترافيك الخارجي" -"externalTrafficInformURIDesc" = "تحديثات الترافيك هتتبعت للمسار ده." -"restartXrayOnClientDisable" = "إعادة تشغيل Xray بعد التعطيل التلقائي" -"restartXrayOnClientDisableDesc" = "عند تعطيل العميل تلقائيا بسبب انتهاء الصلاحية أو حد حركة المرور، أعد تشغيل Xray." -"fragment" = "تجزئة" -"fragmentDesc" = "يفعل تجزئة لحزمة TLS hello." -"fragmentSett" = "إعدادات التجزئة" -"noisesDesc" = "يفعل التشويش." -"noisesSett" = "إعدادات التشويش" -"mux" = "MUX" -"muxDesc" = "ينقل أكثر من تيار بيانات مستقل خلال تيار بيانات واحد قائم." -"muxSett" = "إعدادات MUX" -"direct" = "اتصال مباشر" -"directDesc" = "ينشئ اتصال مباشر مع الدومينات أو نطاقات IP لدولة معينة." -"notifications" = "الإشعارات" -"certs" = "الشهادات" -"externalTraffic" = "الترافيك الخارجي" -"dateAndTime" = "التاريخ والوقت" -"proxyAndServer" = "البروكسي والسيرفر" -"intervals" = "الفترات" -"information" = "المعلومات" -"language" = "اللغة" -"telegramBotLanguage" = "لغة بوت Telegram" - -[pages.xray] -"title" = "إعدادات Xray" -"save" = "احفظ" -"restart" = "أعد تشغيل Xray" -"restartSuccess" = "تم إعادة تشغيل Xray بنجاح" -"stopSuccess" = "تم إيقاف Xray بنجاح" -"restartError" = "حدث خطأ أثناء إعادة تشغيل Xray." -"stopError" = "حدث خطأ أثناء إيقاف Xray." -"basicTemplate" = "أساسي" -"advancedTemplate" = "متقدم" -"generalConfigs" = "إعدادات عامة" -"generalConfigsDesc" = "الخيارات دي هتحدد التعديلات العامة." -"logConfigs" = "السجلات" -"logConfigsDesc" = "السجلات ممكن تأثر على كفاءة السيرفر. ننصح بتفعيلها بحكمة لما تكون محتاجها." -"blockConfigsDesc" = "الخيارات دي هتحجب الترافيك بناءً على بروتوكولات ومواقع محددة." -"basicRouting" = "توجيه أساسي" -"blockConnectionsConfigsDesc" = "الخيارات دي هتحجب الترافيك بناءً على الدولة المطلوبة." -"directConnectionsConfigsDesc" = "الاتصال المباشر بيضمن إن الترافيك المعين مايمرش من سيرفر تاني." -"blockips" = "حظر IPs" -"blockdomains" = "حظر دومينات" -"directips" = "اتصالات مباشرة لـ IPs" -"directdomains" = "اتصالات مباشرة للدومينات" -"ipv4Routing" = "توجيه IPv4" -"ipv4RoutingDesc" = "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر IPv4." -"warpRouting" = "توجيه WARP" -"warpRoutingDesc" = "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر WARP." -"nordRouting" = "توجيه NordVPN" -"nordRoutingDesc" = "الخيارات دي هتوجه الترافيك بناءً على وجهة معينة عبر NordVPN." -"Template" = "قالب إعدادات Xray المتقدم" -"TemplateDesc" = "ملف إعدادات Xray النهائي هيتولد بناءً على القالب ده." -"FreedomStrategy" = "استراتيجية بروتوكول الحرية" -"FreedomStrategyDesc" = "اختار استراتيجية المخرجات للشبكة في بروتوكول الحرية." -"RoutingStrategy" = "استراتيجية التوجيه العامة" -"RoutingStrategyDesc" = "حدد استراتيجية التوجيه الإجمالية لحل كل الطلبات." -"outboundTestUrl" = "رابط اختبار المخرج" -"outboundTestUrlDesc" = "الرابط المستخدم عند اختبار اتصال المخرج" -"Torrent" = "حظر بروتوكول التورنت" -"Inbounds" = "الإدخالات" -"InboundsDesc" = "قبول العملاء المعينين." -"Outbounds" = "المخرجات" -"Balancers" = "موازنات التحميل" -"OutboundsDesc" = "حدد مسار الترافيك الصادر." -"Routings" = "قواعد التوجيه" -"RoutingsDesc" = "أولوية كل قاعدة مهمة جداً!" -"completeTemplate" = "الكل" -"logLevel" = "مستوى السجلات" -"logLevelDesc" = "مستوى السجل الخاص بالأخطاء، اللي بيوضح المعلومات المطلوبة للتسجيل." -"accessLog" = "سجل الوصول" -"accessLogDesc" = "مسار ملف سجل الوصول. القيمة الخاصة 'none' بتعطل سجل الوصول." -"errorLog" = "سجل الأخطاء" -"errorLogDesc" = "مسار ملف سجل الأخطاء. القيمة الخاصة 'none' بتعطل سجل الأخطاء." -"dnsLog" = "سجل DNS" -"dnsLogDesc" = "لو هتسجل استعلامات DNS." -"maskAddress" = "إخفاء العنوان" -"maskAddressDesc" = "إخفاء عنوان الـ IP؛ لو مفعل، هيستبدل تلقائياً عنوان IP اللي بيظهر في السجل." -"statistics" = "إحصائيات" -"statsInboundUplink" = "إحصائيات رفع الإدخال" -"statsInboundUplinkDesc" = "تفعيل جمع الإحصائيات لترافيك الرفع لكل بروكسي من الإدخالات." -"statsInboundDownlink" = "إحصائيات تنزيل الإدخال" -"statsInboundDownlinkDesc" = "تفعيل جمع الإحصائيات لترافيك التنزيل لكل بروكسي من الإدخالات." -"statsOutboundUplink" = "إحصائيات رفع المخرجات" -"statsOutboundUplinkDesc" = "تفعيل جمع الإحصائيات لترافيك الرفع لكل بروكسي من المخرجات." -"statsOutboundDownlink" = "إحصائيات تنزيل المخرجات" -"statsOutboundDownlinkDesc" = "تفعيل جمع الإحصائيات لترافيك التنزيل لكل بروكسي من المخرجات." - -[pages.xray.rules] -"first" = "أول" -"last" = "آخر" -"up" = "فوق" -"down" = "تحت" -"source" = "المصدر" -"dest" = "الوجهة" -"inbound" = "إدخال" -"outbound" = "مخرج" -"balancer" = "موازن" -"info" = "معلومات" -"add" = "أضف قاعدة" -"edit" = "عدل القاعدة" -"useComma" = "عناصر مفصولة بفواصل" - -[pages.xray.outbound] -"addOutbound" = "أضف مخرج" -"addReverse" = "أضف عكسي" -"editOutbound" = "عدل المخرج" -"editReverse" = "عدل العكسي" -"reverseTag" = "وسم العكسي" -"reverseTagDesc" = "وسم الخروج لبروكسي VLESS العكسي البسيط. اتركه فارغاً لتعطيله." -"reverseTagPlaceholder" = "وسم الخروج (اتركه فارغاً للتعطيل)" -"tag" = "تاج" -"tagDesc" = "تاج فريد" -"address" = "العنوان" -"reverse" = "عكسي" -"domain" = "دومين" -"type" = "النوع" -"bridge" = "جسر" -"portal" = "بوابة" -"link" = "رابط" -"intercon" = "تواصل" -"settings" = "إعدادات" -"accountInfo" = "معلومات الحساب" -"outboundStatus" = "حالة المخرج" -"sendThrough" = "أرسل من خلال" -"test" = "اختبار" -"testResult" = "نتيجة الاختبار" -"testing" = "جاري اختبار الاتصال..." -"testSuccess" = "الاختبار ناجح" -"testFailed" = "فشل الاختبار" -"testError" = "فشل اختبار المخرج" -"nordvpn" = "NordVPN" -"accessToken" = "رمز الوصول" -"country" = "الدولة" -"server" = "الخادم" -"city" = "المدينة" -"allCities" = "كل المدن" -"privateKey" = "المفتاح الخاص" -"load" = "الحمل" - -[pages.xray.balancer] -"addBalancer" = "أضف موازن تحميل" -"editBalancer" = "عدل موازن التحميل" -"balancerStrategy" = "استراتيجية الموازن" -"balancerSelectors" = "المحددات" -"tag" = "تاج" -"tagDesc" = "تاج فريد" -"balancerDesc" = "ماينفعش تستخدم balancerTag و outboundTag مع بعض. لو اتستخدموا مع بعض، outboundTag هو اللي هيشتغل." - -[pages.xray.wireguard] -"secretKey" = "المفتاح السري" -"publicKey" = "المفتاح العام" -"allowedIPs" = "عناوين IP المسموح بها" -"endpoint" = "النهاية" -"psk" = "المفتاح المشترك" -"domainStrategy" = "استراتيجية الدومين" - -[pages.xray.tun] -"nameDesc" = "اسم واجهة TUN. القيمة الافتراضية هي 'xray0'" -"mtuDesc" = "وحدة النقل الأقصى. الحد الأقصى لحجم حزم البيانات. القيمة الافتراضية هي 1500" -"userLevel" = "مستوى المستخدم" -"userLevelDesc" = "ستستخدم جميع الاتصالات المُرسلة عبر هذا الإدخال مستوى المستخدم هذا. القيمة الافتراضية هي 0" - -[pages.xray.dns] -"enable" = "فعل DNS" -"enableDesc" = "فعل سيرفر DNS المدمج" -"tag" = "تاج إدخال DNS" -"tagDesc" = "التاج ده هيبقى متاح كإدخال في قواعد التوجيه." -"clientIp" = "IP العميل" -"clientIpDesc" = "بيحدد موقع العميل خلال استعلامات DNS" -"disableCache" = "تعطيل الكاش" -"disableCacheDesc" = "بيعطل تخزين نتائج DNS مؤقتاً" -"disableFallback" = "تعطيل النسخ الاحتياطي" -"disableFallbackDesc" = "بيعطل استعلامات DNS الاحتياطية" -"disableFallbackIfMatch" = "تعطيل النسخ الاحتياطي عند التطابق" -"disableFallbackIfMatchDesc" = "بيعطل استعلامات DNS الاحتياطية لما يتحقق تطابق مع قائمة الدومينات" -"enableParallelQuery" = "تفعيل الاستعلام المتوازي" -"enableParallelQueryDesc" = "تفعيل استعلامات DNS المتوازية لعدة خوادم لحل أسرع" -"strategy" = "استراتيجية الاستعلام" -"strategyDesc" = "الاستراتيجية العامة لحل أسماء الدومين" -"add" = "أضف سيرفر" -"edit" = "عدل السيرفر" -"domains" = "الدومينات" -"expectIPs" = "العناوين المتوقعة" -"unexpectIPs" = "عناوين IP غير متوقعة" -"useSystemHosts" = "استخدام ملف Hosts الخاص بالنظام" -"useSystemHostsDesc" = "استخدام ملف hosts من نظام مثبت" -"usePreset" = "استخدام النموذج" -"dnsPresetTitle" = "قوالب DNS" -"dnsPresetFamily" = "العائلي" - -[pages.xray.fakedns] -"add" = "أضف Fake DNS" -"edit" = "عدل Fake DNS" -"ipPool" = "نطاق IP Pool" -"poolSize" = "حجم المجموعة" - -[pages.settings.security] -"admin" = "بيانات الأدمن" -"twoFactor" = "المصادقة الثنائية" -"twoFactorEnable" = "تفعيل المصادقة الثنائية" -"twoFactorEnableDesc" = "يضيف طبقة إضافية من المصادقة لتعزيز الأمان." -"twoFactorModalSetTitle" = "تفعيل المصادقة الثنائية" -"twoFactorModalDeleteTitle" = "تعطيل المصادقة الثنائية" -"twoFactorModalSteps" = "لإعداد المصادقة الثنائية، قم ببعض الخطوات:" -"twoFactorModalFirstStep" = "1. امسح رمز QR هذا في تطبيق المصادقة أو انسخ الرمز الموجود بجانب رمز QR والصقه في التطبيق" -"twoFactorModalSecondStep" = "2. أدخل الرمز من التطبيق" -"twoFactorModalRemoveStep" = "أدخل الرمز من التطبيق لإزالة المصادقة الثنائية." -"twoFactorModalChangeCredentialsTitle" = "تغيير بيانات الاعتماد" -"twoFactorModalChangeCredentialsStep" = "أدخل الرمز من التطبيق لتغيير بيانات اعتماد المسؤول." -"twoFactorModalSetSuccess" = "تم إنشاء المصادقة الثنائية بنجاح" -"twoFactorModalDeleteSuccess" = "تم حذف المصادقة الثنائية بنجاح" -"twoFactorModalError" = "رمز خاطئ" - -[pages.settings.toasts] -"modifySettings" = "تم تغيير المعلمات." -"getSettings" = "حدث خطأ أثناء استرداد المعلمات." -"modifyUserError" = "حدث خطأ أثناء تغيير بيانات اعتماد المسؤول." -"modifyUser" = "لقد قمت بتغيير بيانات اعتماد المسؤول بنجاح." -"originalUserPassIncorrect" = "اسم المستخدم أو الباسورد الحالي غير صحيح" -"userPassMustBeNotEmpty" = "اسم المستخدم والباسورد الجديدين فاضيين" -"getOutboundTrafficError" = "خطأ في الحصول على حركات المرور الصادرة" -"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة" - -[tgbot] -"keyboardClosed" = "❌ لوحة المفاتيح مغلقة!" -"noResult" = "❗ لا يوجد نتائج!" -"noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!" -"wentWrong" = "❌ حدث خطأ ما!" -"noIpRecord" = "❗ لا يوجد سجل IP!" -"noInbounds" = "❗ لم يتم العثور على أي وارد!" -"unlimited" = "♾ غير محدود (إعادة تعيين)" -"add" = "إضافة" -"month" = "شهر" -"months" = "أشهر" -"day" = "يوم" -"days" = "أيام" -"hours" = "ساعات" -"minutes" = "دقائق" -"unknown" = "غير معروف" -"inbounds" = "الواردات" -"clients" = "العملاء" -"offline" = "🔴 غير متصل" -"online" = "🟢 متصل" - -[tgbot.commands] -"unknown" = "❗ أمر مش معروف." -"pleaseChoose" = "👇 من فضلك اختار:\r\n" -"help" = "🤖 أهلا بيك في البوت! البوت ده معمول عشان يديك بيانات معينة من البانل ويسمحلك بالتعديلات." -"start" = "👋 أهلا {{ .Firstname }}.\r\n" -"welcome" = "🤖 أهلا بيك في بوت إدارة {{ .Hostname }}.\r\n" -"status" = "✅ البوت شغال!" -"usage" = "❗ من فضلك ادخل نص للتبحث عنه!" -"getID" = "🆔 الـ ID بتاعك: {{ .ID }}" -"helpAdminCommands" = "عشان تعيد تشغيل Xray Core:\r\n/restart\r\n\r\nعشان تدور على إيميل عميل:\r\n/usage [Email]\r\n\r\nعشان تدور على إدخالات (مع إحصائيات العملاء):\r\n/inbound [Remark]\r\n\r\nID شات Telegram:\r\n/id" -"helpClientCommands" = "عشان تدور على الإحصائيات، استخدم الأمر ده:\r\n\r\n/usage [Email]\r\n\r\nID شات Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ العملية نجحت!" -"restartFailed" = "❗ حصل خطأ في العملية.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core مش شغال." -"startDesc" = "عرض القائمة الرئيسية" -"helpDesc" = "مساعدة البوت" -"statusDesc" = "التحقق من حالة البوت" -"idDesc" = "عرض معرف Telegram الخاص بك" - -[tgbot.messages] -"cpuThreshold" = "🔴 حمل المعالج {{ .Percent }}% عدى الحد المسموح ({{ .Threshold }}%)" -"selectUserFailed" = "❌ حصل خطأ في اختيار المستخدم!" -"userSaved" = "✅ حفظت بيانات مستخدم Telegram." -"loginSuccess" = "✅ تسجيل الدخول للبانل تم بنجاح.\r\n" -"loginFailed" = "❗️فشل محاولة تسجيل الدخول للبانل.\r\n" -"2faFailed" = "فشل 2FA" -"report" = "🕰 التقارير المجدولة: {{ .RunTime }}\r\n" -"datetime" = "⏰ التاريخ والوقت: {{ .DateTime }}\r\n" -"hostname" = "💻 السيرفر: {{ .Hostname }}\r\n" -"version" = "🚀 نسخة 3X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 نسخة Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 عناوين IP:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ وقت التشغيل: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 تحميل النظام: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 الرام: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 الترافيك: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ الحالة: {{ .State }}\r\n" -"username" = "👤 اسم المستخدم: {{ .Username }}\r\n" -"reason" = "❗️ السبب: {{ .Reason }}\r\n" -"time" = "⏰ الوقت: {{ .Time }}\r\n" -"inbound" = "📍 الإدخال: {{ .Remark }}\r\n" -"port" = "🔌 البورت: {{ .Port }}\r\n" -"expire" = "📅 تاريخ الانتهاء: {{ .Time }}\r\n" -"expireIn" = "📅 هيخلص بعد: {{ .Time }}\r\n" -"active" = "💡 مفعل: {{ .Enable }}\r\n" -"enabled" = "🚨 مفعل: {{ .Enable }}\r\n" -"online" = "🌐 حالة الاتصال: {{ .Status }}\r\n" -"lastOnline" = "🔙 آخر متصل: {{ .Time }}\r\n" -"email" = "📧 الإيميل: {{ .Email }}\r\n" -"upload" = "🔼 رفع: ↑{{ .Upload }}\r\n" -"download" = "🔽 تنزيل: ↓{{ .Download }}\r\n" -"total" = "📊 الإجمالي: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 مستخدم Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 نفذ {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 عدد النفاذ لـ {{ .Type }}:\r\n" -"onlinesCount" = "🌐 العملاء الأونلاين: {{ .Count }}\r\n" -"disabled" = "🛑 معطل: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 هينتهي قريب: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 وقت النسخة الاحتياطية: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 اتحدّث في: {{ .Time }}\r\n\r\n" -"yes" = "✅ أيوه" -"no" = "❌ لأ" -"received_id" = "🔑📥 الـ ID اتحدث." -"received_password" = "🔑📥 الباسورد اتحدث." -"received_email" = "📧📥 الإيميل اتحدث." -"received_comment" = "💬📥 التعليق اتحدث." -"id_prompt" = "🔑 الـ ID الافتراضي: {{ .ClientId }}\n\nادخل الـ ID بتاعك." -"pass_prompt" = "🔑 الباسورد الافتراضي: {{ .ClientPassword }}\n\nادخل الباسورد بتاعك." -"email_prompt" = "📧 الإيميل الافتراضي: {{ .ClientEmail }}\n\nادخل الإيميل بتاعك." -"comment_prompt" = "💬 التعليق الافتراضي: {{ .ClientComment }}\n\nادخل تعليقك." -"inbound_client_data_id" = "🔄 الدخول: {{ .InboundRemark }}\n\n🔑 المعرف: {{ .ClientId }}\n📧 البريد الإلكتروني: {{ .ClientEmail }}\n📊 الترافيك: {{ .ClientTraffic }}\n📅 تاريخ الانتهاء: {{ .ClientExp }}\n🌐 حدّ IP: {{ .IpLimit }}\n💬 تعليق: {{ .ClientComment }}\n\nدلوقتي تقدر تضيف العميل على الدخول!" -"inbound_client_data_pass" = "🔄 الدخول: {{ .InboundRemark }}\n\n🔑 كلمة المرور: {{ .ClientPass }}\n📧 البريد الإلكتروني: {{ .ClientEmail }}\n📊 الترافيك: {{ .ClientTraffic }}\n📅 تاريخ الانتهاء: {{ .ClientExp }}\n🌐 حدّ IP: {{ .IpLimit }}\n💬 تعليق: {{ .ClientComment }}\n\nدلوقتي تقدر تضيف العميل على الدخول!" -"cancel" = "❌ العملية اتلغت! \n\nممكن تبدأ من /start في أي وقت. 🔄" -"error_add_client" = "⚠️ حصل خطأ:\n\n {{ .error }}" -"using_default_value" = "تمام، هشيل على القيمة الافتراضية. 😊" -"incorrect_input" = "المدخلات مش صحيحة.\nالكلمات لازم تكون متصلة من غير فراغات.\nمثال صحيح: aaaaaa\nمثال غلط: aaa aaa 🚫" -"AreYouSure" = "إنت متأكد؟ 🤔" -"SuccessResetTraffic" = "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ✅ تم بنجاح" -"FailedResetTraffic" = "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ❌ فشل \n\n🛠️ الخطأ: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 عملية إعادة ضبط الترافيك خلصت لكل العملاء." - -[tgbot.buttons] -"closeKeyboard" = "❌ اقفل الكيبورد" -"cancel" = "❌ إلغاء" -"cancelReset" = "❌ إلغاء إعادة الضبط" -"cancelIpLimit" = "❌ إلغاء حد الـ IP" -"confirmResetTraffic" = "✅ تأكيد إعادة ضبط الترافيك؟" -"confirmClearIps" = "✅ تأكيد مسح الـ IPs؟" -"confirmRemoveTGUser" = "✅ تأكيد حذف مستخدم Telegram؟" -"confirmToggle" = "✅ تأكيد تفعيل/تعطيل المستخدم؟" -"dbBackup" = "احصل على نسخة DB" -"serverUsage" = "استخدام السيرفر" -"getInbounds" = "احصل على الإدخالات" -"depleteSoon" = "هينتهي قريب" -"clientUsage" = "استخدام العميل" -"onlines" = "العملاء الأونلاين" -"commands" = "الأوامر" -"refresh" = "🔄 تجديد" -"clearIPs" = "❌ مسح الـ IPs" -"removeTGUser" = "❌ حذف مستخدم Telegram" -"selectTGUser" = "👤 اختار مستخدم Telegram" -"selectOneTGUser" = "👤 اختار مستخدم Telegram:" -"resetTraffic" = "📈 إعادة ضبط الترافيك" -"resetExpire" = "📅 تغيير تاريخ الانتهاء" -"ipLog" = "🔢 سجل الـ IP" -"ipLimit" = "🔢 حد الـ IP" -"setTGUser" = "👤 ضبط مستخدم Telegram" -"toggle" = "🔘 تفعيل / تعطيل" -"custom" = "🔢 مخصص" -"confirmNumber" = "✅ تأكيد: {{ .Num }}" -"confirmNumberAdd" = "✅ تأكيد إضافة: {{ .Num }}" -"limitTraffic" = "🚧 حد الترافيك" -"getBanLogs" = "احصل على سجلات الحظر" -"allClients" = "كل العملاء" -"addClient" = "إضافة عميل" -"submitDisable" = "إرسال كمعطّل ☑️" -"submitEnable" = "إرسال كمفعّل ✅" -"use_default" = "🏷️ استخدام الإعدادات الافتراضية" -"change_id" = "⚙️🔑 المعرّف" -"change_password" = "⚙️🔑 كلمة السر" -"change_email" = "⚙️📧 البريد الإلكتروني" -"change_comment" = "⚙️💬 تعليق" -"ResetAllTraffics" = "إعادة ضبط جميع الترافيك" -"SortedTrafficUsageReport" = "تقرير استخدام الترافيك المرتب" - -[tgbot.answers] -"successfulOperation" = "✅ العملية نجحت!" -"errorOperation" = "❗ حصل خطأ في العملية." -"getInboundsFailed" = "❌ فشل الحصول على الإدخالات." -"getClientsFailed" = "❌ فشل الحصول على العملاء." -"canceled" = "❌ {{ .Email }}: العملية اتلغت." -"clientRefreshSuccess" = "✅ {{ .Email }}: العميل اتحدث بنجاح." -"IpRefreshSuccess" = "✅ {{ .Email }}: الـ IPs اتحدثت بنجاح." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: مستخدم Telegram اتحدث بنجاح." -"resetTrafficSuccess" = "✅ {{ .Email }}: الترافيك اتظبط بنجاح." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: حد الترافيك اتسجل بنجاح." -"expireResetSuccess" = "✅ {{ .Email }}: أيام الانتهاء اتظبطت بنجاح." -"resetIpSuccess" = "✅ {{ .Email }}: حد الـ IP ({{ .Count }}) اتسجل بنجاح." -"clearIpSuccess" = "✅ {{ .Email }}: الـ IPs اتمسحت بنجاح." -"getIpLog" = "✅ {{ .Email }}: سجل الـ IP اتجاب." -"getUserInfo" = "✅ {{ .Email }}: بيانات مستخدم Telegram اتجاب." -"removedTGUserSuccess" = "✅ {{ .Email }}: مستخدم Telegram اتحذف بنجاح." -"enableSuccess" = "✅ {{ .Email }}: اتفعل بنجاح." -"disableSuccess" = "✅ {{ .Email }}: اتعطل بنجاح." -"askToAddUserId" = "مافيش إعدادات ليك!\r\nاطلب من الأدمن يضيف الـ Telegram ChatID الخاص بيك في إعداداتك.\r\n\r\nالـ ChatID بتاعك: {{ .TgUserID }}" -"chooseClient" = "اختار عميل للإدخال {{ .Inbound }}" -"chooseInbound" = "اختار الإدخال" diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml deleted file mode 100644 index c313ba1c..00000000 --- a/web/translation/translate.en_US.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Username" -"password" = "Password" -"login" = "Log In" -"confirm" = "Confirm" -"cancel" = "Cancel" -"close" = "Close" -"save" = "Save" -"logout" = "Log Out" -"create" = "Create" -"update" = "Update" -"copy" = "Copy" -"copied" = "Copied" -"download" = "Download" -"remark" = "Remark" -"enable" = "Enabled" -"protocol" = "Protocol" -"search" = "Search" -"filter" = "Filter" -"loading" = "Loading..." -"second" = "Second" -"minute" = "Minute" -"hour" = "Hour" -"day" = "Day" -"check" = "Check" -"indefinite" = "Indefinite" -"unlimited" = "Unlimited" -"none" = "None" -"qrCode" = "QR Code" -"info" = "More Information" -"edit" = "Edit" -"delete" = "Delete" -"reset" = "Reset" -"noData" = "No data." -"copySuccess" = "Copied Successful" -"sure" = "Sure" -"encryption" = "Encryption" -"useIPv4ForHost" = "Use IPv4 for host" -"transmission" = "Transmission" -"host" = "Host" -"path" = "Path" -"camouflage" = "Obfuscation" -"status" = "Status" -"enabled" = "Enabled" -"disabled" = "Disabled" -"depleted" = "Ended" -"depletingSoon" = "Depleting" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Domain Name" -"monitor" = "Listen IP" -"certificate" = "Digital Certificate" -"fail" = "Failed" -"comment" = "Comment" -"success" = "Successfully" -"lastOnline" = "Last Online" -"getVersion" = "Get Version" -"install" = "Install" -"clients" = "Clients" -"usage" = "Usage" -"twoFactorCode" = "Code" -"remained" = "Remained" -"security" = "Security" -"secAlertTitle" = "Security Alert" -"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection." -"secAlertConf" = "Certain settings are vulnerable to attacks. It is recommended to reinforce security protocols to prevent potential breaches." -"secAlertSSL" = "Panel lacks secure connection. Please install TLS certificate for data protection." -"secAlertPanelPort" = "Panel default port is vulnerable. Please configure a random or specific port." -"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path." -"secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path." -"secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path." -"emptyDnsDesc" = "No added DNS servers." -"emptyFakeDnsDesc" = "No added Fake DNS servers." -"emptyBalancersDesc" = "No added balancers." -"emptyReverseDesc" = "No added reverse proxies." -"somethingWentWrong" = "Something went wrong" - -[subscription] -"title" = "Subscription info" -"subId" = "Subscription ID" -"status" = "Status" -"downloaded" = "Downloaded" -"uploaded" = "Uploaded" -"expiry" = "Expiry" -"totalQuota" = "Total quota" -"individualLinks" = "Individual links" -"active" = "Active" -"inactive" = "Inactive" -"unlimited" = "Unlimited" -"noExpiry" = "No expiry" - -[menu] -"theme" = "Theme" -"dark" = "Dark" -"ultraDark" = "Ultra Dark" -"dashboard" = "Overview" -"inbounds" = "Inbounds" -"settings" = "Panel Settings" -"xray" = "Xray Configs" -"logout" = "Log Out" -"link" = "Manage" - -[pages.login] -"hello" = "Hello" -"title" = "Welcome" -"loginAgain" = "Your session has expired, please log in again" - -[pages.login.toasts] -"invalidFormData" = "The Input data format is invalid." -"emptyUsername" = "Username is required" -"emptyPassword" = "Password is required" -"wrongUsernameOrPassword" = "Invalid username or password or two-factor code." -"successLogin" = " You have successfully logged into your account." - -[pages.index] -"title" = "Overview" -"cpu" = "CPU" -"logicalProcessors" = "Logical Processors" -"frequency" = "Frequency" -"swap" = "Swap" -"storage" = "Storage" -"memory" = "RAM" -"threads" = "Threads" -"xrayStatus" = "Xray" -"stopXray" = "Stop" -"restartXray" = "Restart" -"xraySwitch" = "Version" -"xrayUpdates" = "Xray Updates" -"xraySwitchClick" = "Choose the version you want to switch to." -"xraySwitchClickDesk" = "Choose carefully, as older versions may not be compatible with current configurations." -"updatePanel" = "Update Panel" -"panelUpdateDesc" = "This will update 3X-UI itself to the latest release and restart the panel service." -"currentPanelVersion" = "Current panel version" -"latestPanelVersion" = "Latest panel version" -"panelUpToDate" = "Panel is up to date" -"upToDate" = "Up to date" -"xrayStatusUnknown" = "Unknown" -"xrayStatusRunning" = "Running" -"xrayStatusStop" = "Stop" -"xrayStatusError" = "Error" -"xrayErrorPopoverTitle" = "An error occurred while running Xray" -"operationHours" = "Uptime" -"systemLoad" = "System Load" -"systemLoadDesc" = "System load average for the past 1, 5, and 15 minutes" -"connectionCount" = "Connection Stats" -"ipAddresses" = "IP Addresses" -"toggleIpVisibility" = "Toggle visibility of the IP" -"overallSpeed" = "Overall Speed" -"upload" = "Upload" -"download" = "Download" -"totalData" = "Total Data" -"sent" = "Sent" -"received" = "Received" -"documentation" = "Documentation" -"xraySwitchVersionDialog" = "Do you really want to change the Xray version?" -"xraySwitchVersionDialogDesc" = "This will change the Xray version to #version#." -"xraySwitchVersionPopover" = "Xray updated successfully" -"panelUpdateDialog" = "Do you really want to update the panel?" -"panelUpdateDialogDesc" = "This will update 3X-UI to #version# and restart the panel service." -"panelUpdateCheckPopover" = "Panel update check failed" -"panelUpdateStartedPopover" = "Panel update started" -"geofileUpdateDialog" = "Do you really want to update the geofile?" -"geofileUpdateDialogDesc" = "This will update the #filename# file." -"geofilesUpdateDialogDesc" = "This will update all geofiles." -"geofilesUpdateAll" = "Update all" -"geofileUpdatePopover" = "Geofile updated successfully" -"customGeoTitle" = "Custom GeoSite / GeoIP" -"customGeoAdd" = "Add" -"customGeoType" = "Type" -"customGeoAlias" = "Alias" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Enabled" -"customGeoLastUpdated" = "Last updated" -"customGeoExtColumn" = "Routing (ext:…)" -"customGeoToastUpdateAll" = "All custom geo sources updated" -"customGeoActions" = "Actions" -"customGeoEdit" = "Edit" -"customGeoDelete" = "Delete" -"customGeoDownload" = "Update now" -"customGeoModalAdd" = "Add custom geo" -"customGeoModalEdit" = "Edit custom geo" -"customGeoModalSave" = "Save" -"customGeoDeleteConfirm" = "Delete this custom geo source?" -"customGeoRoutingHint" = "In routing rules use the value column as ext:file.dat:tag (replace tag)." -"customGeoInvalidId" = "Invalid resource id" -"customGeoAliasesError" = "Failed to load custom geo aliases" -"customGeoValidationAlias" = "Alias may only contain lowercase letters, digits, - and _" -"customGeoValidationUrl" = "URL must start with http:// or https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (custom)" -"customGeoToastList" = "Custom geo list" -"customGeoToastAdd" = "Add custom geo" -"customGeoToastUpdate" = "Update custom geo" -"customGeoToastDelete" = "Custom geo file “{{ .fileName }}” deleted" -"customGeoToastDownload" = "Geofile “{{ .fileName }}” updated" -"customGeoErrInvalidType" = "Type must be geosite or geoip" -"customGeoErrAliasRequired" = "Alias is required" -"customGeoErrAliasPattern" = "Alias must match allowed characters" -"customGeoErrAliasReserved" = "This alias is reserved" -"customGeoErrUrlRequired" = "URL is required" -"customGeoErrInvalidUrl" = "URL is invalid" -"customGeoErrUrlScheme" = "URL must use http or https" -"customGeoErrUrlHost" = "URL host is invalid" -"customGeoErrDuplicateAlias" = "This alias is already used for this type" -"customGeoErrNotFound" = "Custom geo source not found" -"customGeoErrDownload" = "Download failed" -"customGeoErrUpdateAllIncomplete" = "One or more custom geo sources failed to update" -"customGeoEmpty" = "No custom geo sources yet — click Add to create one" -"dontRefresh" = "Installation is in progress, please do not refresh this page" -"logs" = "Logs" -"config" = "Config" -"backup" = "Backup" -"backupTitle" = "Database Backup & Restore" -"exportDatabase" = "Back Up" -"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device." -"importDatabase" = "Restore" -"importDatabaseDesc" = "Click to select and upload a .db file from your device to restore your database from a backup." -"importDatabaseSuccess" = "The database has been successfully imported." -"importDatabaseError" = "An error occurred while importing the database." -"readDatabaseError" = "An error occurred while reading the database." -"getDatabaseError" = "An error occurred while retrieving the database." -"getConfigError" = "An error occurred while retrieving the config file." - -[pages.inbounds] -"allTimeTraffic" = "All-time Traffic" -"allTimeTrafficUsage" = "All Time Total Usage" -"title" = "Inbounds" -"totalDownUp" = "Total Sent/Received" -"totalUsage" = "Total Usage" -"inboundCount" = "Total Inbounds" -"operate" = "Menu" -"enable" = "Enabled" -"remark" = "Remark" -"protocol" = "Protocol" -"port" = "Port" -"portMap" = "Port Mapping" -"traffic" = "Traffic" -"details" = "Details" -"transportConfig" = "Transport" -"expireDate" = "Duration" -"createdAt" = "Created" -"updatedAt" = "Updated" -"resetTraffic" = "Reset Traffic" -"addInbound" = "Add Inbound" -"generalActions" = "General Actions" -"autoRefresh" = "Auto-refresh" -"autoRefreshInterval" = "Interval" -"modifyInbound" = "Modify Inbound" -"deleteInbound" = "Delete Inbound" -"deleteInboundContent" = "Are you sure you want to delete inbound?" -"deleteClient" = "Delete Client" -"deleteClientContent" = "Are you sure you want to delete client?" -"resetTrafficContent" = "Are you sure you want to reset traffic?" -"copyLink" = "Copy URL" -"address" = "Address" -"network" = "Network" -"destinationPort" = "Destination Port" -"targetAddress" = "Target Address" -"monitorDesc" = "Leave blank to listen on all IPs" -"meansNoLimit" = "= Unlimited. (unit: GB)" -"totalFlow" = "Total Flow" -"leaveBlankToNeverExpire" = "Leave blank to never expire" -"noRecommendKeepDefault" = "It is recommended to keep the default" -"certificatePath" = "File Path" -"certificateContent" = "File Content" -"publicKey" = "Public Key" -"privatekey" = "Private Key" -"clickOnQRcode" = "Click on QR Code to Copy" -"client" = "Client" -"export" = "Export All URLs" -"clone" = "Clone" -"cloneInbound" = "Clone" -"cloneInboundContent" = "All settings of this inbound, except Port, Listening IP, and Clients, will be applied to the clone." -"cloneInboundOk" = "Clone" -"resetAllTraffic" = "Reset All Inbounds Traffic" -"resetAllTrafficTitle" = "Reset All Inbounds Traffic" -"resetAllTrafficContent" = "Are you sure you want to reset the traffic of all inbounds?" -"resetInboundClientTraffics" = "Reset Clients Traffic" -"resetInboundClientTrafficTitle" = "Reset Clients Traffic" -"resetInboundClientTrafficContent" = "Are you sure you want to reset the traffic of this inbound's clients?" -"resetAllClientTraffics" = "Reset All Clients Traffic" -"resetAllClientTrafficTitle" = "Reset All Clients Traffic" -"resetAllClientTrafficContent" = "Are you sure you want to reset the traffic of all clients?" -"delDepletedClients" = "Delete Depleted Clients" -"delDepletedClientsTitle" = "Delete Depleted Clients" -"delDepletedClientsContent" = "Are you sure you want to delete all the depleted clients?" -"email" = "Email" -"emailDesc" = "Please provide a unique email address." -"IPLimit" = "IP Limit" -"IPLimitDesc" = "Disables inbound if the count exceeds the set value. (0 = disable)" -"IPLimitlog" = "IP Log" -"IPLimitlogDesc" = "The IPs history log. (to enable inbound after disabling, clear the log)" -"IPLimitlogclear" = "Clear The Log" -"setDefaultCert" = "Set Cert from Panel" -"telegramDesc" = "Please provide Telegram Chat ID. (use '/id' command in the bot) or (@userinfobot)" -"subscriptionDesc" = "To find your subscription URL, navigate to the 'Details'. Additionally, you can use the same name for several clients." -"info" = "Info" -"same" = "Same" -"inboundData" = "Inbound's Data" -"exportInbound" = "Export Inbound" -"import" = "Import" -"importInbound" = "Import an Inbound" -"periodicTrafficResetTitle" = "Traffic Reset" -"periodicTrafficResetDesc" = "Automatically reset traffic counter at specified intervals" -"lastReset" = "Last Reset" - -[pages.client] -"add" = "Add Client" -"edit" = "Edit Client" -"submitAdd" = "Add Client" -"submitEdit" = "Save Changes" -"clientCount" = "Number of Clients" -"bulk" = "Add Bulk" -"copyFromInbound" = "Copy Clients from Inbound" -"copyToInbound" = "Copy clients to" -"copySelected" = "Copy Selected" -"copySource" = "Source" -"copyEmailPreview" = "Resulting email preview" -"copySelectSourceFirst" = "Please select a source inbound first." -"copyResult" = "Copy result" -"copyResultSuccess" = "Copied successfully" -"copyResultNone" = "Nothing to copy: no clients selected or source is empty" -"copyResultErrors" = "Copy errors" -"copyFlowLabel" = "Flow for new clients (VLESS)" -"copyFlowHint" = "Applied to all copied clients. Leave empty to skip." -"selectAll" = "Select all" -"clearAll" = "Clear all" -"method" = "Method" -"first" = "First" -"last" = "Last" -"prefix" = "Prefix" -"postfix" = "Postfix" -"delayedStart" = "Start After First Use" -"expireDays" = "Duration" -"days" = "Day(s)" -"renew" = "Auto Renew" -"renewDesc" = "Auto-renewal after expiration. (0 = disable)(unit: day)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Never" -"daily" = "Daily" -"weekly" = "Weekly" -"monthly" = "Monthly" -"hourly" = "Hourly" - -[pages.inbounds.toasts] -"obtain" = "Obtain" -"updateSuccess" = "The update was successful." -"logCleanSuccess" = "The log has been cleared." -"inboundsUpdateSuccess" = "Inbounds have been successfully updated." -"inboundUpdateSuccess" = "Inbound has been successfully updated." -"inboundCreateSuccess" = "Inbound has been successfully created." -"inboundDeleteSuccess" = "Inbound has been successfully deleted." -"inboundClientAddSuccess" = "Inbound client(s) have been added." -"inboundClientDeleteSuccess" = "Inbound client has been deleted." -"inboundClientUpdateSuccess" = "Inbound client has been updated." -"delDepletedClientsSuccess" = "All depleted clients are deleted." -"resetAllClientTrafficSuccess" = "All traffic from the client has been reset." -"resetAllTrafficSuccess" = "All traffic has been reset." -"resetInboundClientTrafficSuccess" = "Traffic has been reset." -"trafficGetError" = "Error getting traffics." -"getNewX25519CertError" = "Error while obtaining the X25519 certificate." -"getNewmldsa65Error" = "Error while obtaining mldsa65." -"getNewVlessEncError" = "Error while obtaining VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Request" -"response" = "Response" -"name" = "Name" -"value" = "Value" - -[pages.inbounds.stream.tcp] -"version" = "Version" -"method" = "Method" -"path" = "Path" -"status" = "Status" -"statusDescription" = "Status Desc" -"requestHeader" = "Request Header" -"responseHeader" = "Response Header" - -[pages.settings] -"title" = "Panel Settings" -"save" = "Save" -"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes." -"restartPanel" = "Restart Panel" -"restartPanelDesc" = "Are you sure you want to restart the panel? If you cannot access the panel after restarting, please view the panel log info on the server." -"restartPanelSuccess" = "The panel was successfully restarted." -"actions" = "Actions" -"resetDefaultConfig" = "Reset to Default" -"panelSettings" = "General" -"securitySettings" = "Authentication" -"TGBotSettings" = "Telegram Bot" -"panelListeningIP" = "Listen IP" -"panelListeningIPDesc" = "The IP address for the web panel. (leave blank to listen on all IPs)" -"panelListeningDomain" = "Listen Domain" -"panelListeningDomainDesc" = "The domain name for the web panel. (leave blank to listen on all domains and IPs)" -"panelPort" = "Listen Port" -"panelPortDesc" = "The port number for the web panel. (must be an unused port)" -"publicKeyPath" = "Public Key Path" -"publicKeyPathDesc" = "The public key file path for the web panel. (begins with ‘/‘)" -"privateKeyPath" = "Private Key Path" -"privateKeyPathDesc" = "The private key file path for the web panel. (begins with ‘/‘)" -"panelUrlPath" = "URI Path" -"panelUrlPathDesc" = "The URI path for the web panel. (begins with ‘/‘ and concludes with ‘/‘)" -"pageSize" = "Pagination Size" -"pageSizeDesc" = "Define page size for inbounds table. (0 = disable)" -"remarkModel" = "Remark Model & Separation Character" -"datepicker" = "Calendar Type" -"datepickerPlaceholder" = "Select date" -"datepickerDescription" = "Scheduled tasks will run based on this calendar." -"sampleRemark" = "Sample Remark" -"oldUsername" = "Current Username" -"currentPassword" = "Current Password" -"newUsername" = "New Username" -"newPassword" = "New Password" -"telegramBotEnable" = "Enable Telegram Bot" -"telegramBotEnableDesc" = "Enables the Telegram bot." -"telegramToken" = "Telegram Token" -"telegramTokenDesc" = "The Telegram bot token obtained from '@BotFather'." -"telegramProxy" = "SOCKS Proxy" -"telegramProxyDesc" = "Enables SOCKS5 proxy for connecting to Telegram. (adjust settings as per guide)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "The Telegram API server to use. Leave blank to use the default server." -"telegramChatId" = "Admin Chat ID" -"telegramChatIdDesc" = "The Telegram Admin Chat ID(s). (comma-separated)(get it here @userinfobot) or (use '/id' command in the bot)" -"telegramNotifyTime" = "Notification Time" -"telegramNotifyTimeDesc" = "The Telegram bot notification time set for periodic reports. (use the crontab time format)" -"tgNotifyBackup" = "Database Backup" -"tgNotifyBackupDesc" = "Send a database backup file with a report." -"tgNotifyLogin" = "Login Notification" -"tgNotifyLoginDesc" = "Get notified about the username, IP address, and time whenever someone attempts to log into your web panel." -"sessionMaxAge" = "Session Duration" -"sessionMaxAgeDesc" = "The duration for which you can stay logged in. (unit: minute)" -"expireTimeDiff" = "Expiration Date Notification" -"expireTimeDiffDesc" = "Get notified about expiration date when reaching this threshold. (unit: day)" -"trafficDiff" = "Traffic Cap Notification" -"trafficDiffDesc" = "Get notified about traffic cap when reaching this threshold. (unit: GB)" -"tgNotifyCpu" = "CPU Load Notification" -"tgNotifyCpuDesc" = "Get notified if CPU load exceeds this threshold. (unit: %)" -"timeZone" = "Time Zone" -"timeZoneDesc" = "Scheduled tasks will run based on this time zone." -"subSettings" = "Subscription" -"subEnable" = "Subscription Service" -"subEnableDesc" = "Enable/Disable the subscription service." -"subJsonEnable" = "Enable/Disable the JSON subscription endpoint independently." -"subTitle" = "Subscription Title" -"subTitleDesc" = "Title shown in VPN client" -"subSupportUrl" = "Support URL" -"subSupportUrlDesc" = "Technical support link shown in the VPN client" -"subProfileUrl" = "Profile URL" -"subProfileUrlDesc" = "A link to your website displayed in the VPN client" -"subAnnounce" = "Announce" -"subAnnounceDesc" = "The text of the announce displayed in the VPN client" -"subEnableRouting" = "Enable routing" -"subEnableRoutingDesc" = "Global setting to enable routing in the VPN client. (Only for Happ)" -"subRoutingRules" = "Routing rules" -"subRoutingRulesDesc" = "Global routing rules for the VPN client. (Only for Happ)" -"subListen" = "Listen IP" -"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)" -"subPort" = "Listen Port" -"subPortDesc" = "The port number for the subscription service. (must be an unused port)" -"subCertPath" = "Public Key Path" -"subCertPathDesc" = "The public key file path for the subscription service. (begins with ‘/‘)" -"subKeyPath" = "Private Key Path" -"subKeyPathDesc" = "The private key file path for the subscription service. (begins with ‘/‘)" -"subPath" = "URI Path" -"subPathDesc" = "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)" -"subDomain" = "Listen Domain" -"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)" -"subUpdates" = "Update Intervals" -"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)" -"subEncrypt" = "Encode" -"subEncryptDesc" = "The returned content of subscription service will be Base64 encoded." -"subShowInfo" = "Show Usage Info" -"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." -"subURI" = "Reverse Proxy URI" -"subURIDesc" = "The URI path of the subscription URL for use behind proxies." -"externalTrafficInformEnable" = "External Traffic Inform" -"externalTrafficInformEnableDesc" = "Inform external API on every traffic update." -"externalTrafficInformURI" = "External Traffic Inform URI" -"externalTrafficInformURIDesc" = "Traffic updates are sent to this URI." -"restartXrayOnClientDisable" = "Restart Xray After Auto Disable" -"restartXrayOnClientDisableDesc" = "When a client is automatically disabled due to expiration or traffic limit, restart Xray." -"fragment" = "Fragmentation" -"fragmentDesc" = "Enable fragmentation for TLS hello packet." -"fragmentSett" = "Fragmentation Settings" -"noisesDesc" = "Enable Noises." -"noisesSett" = "Noises Settings" -"mux" = "Mux" -"muxDesc" = "Transmit multiple independent data streams within an established data stream." -"muxSett" = "Mux Settings" -"direct" = "Direct Connection" -"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country." -"notifications" = "Notifications" -"certs" = "Certificaties" -"externalTraffic" = "External Traffic" -"dateAndTime" = "Date and Time" -"proxyAndServer" = "Proxy and Server" -"intervals" = "Intervals" -"information" = "Information" -"language" = "Language" -"telegramBotLanguage" = "Telegram Bot Language" - -[pages.xray] -"title" = "Xray Configs" -"save" = "Save" -"restart" = "Restart Xray" -"restartSuccess" = "Xray has been successfully relaunched." -"stopSuccess" = "Xray has been successfully stopped." -"restartError" = "There was an error when rebooting the Xray." -"stopError" = "There was an error when stopping the Xray." -"basicTemplate" = "Basics" -"advancedTemplate" = "Advanced" -"generalConfigs" = "General" -"generalConfigsDesc" = "These options will determine general adjustments." -"logConfigs" = "Log" -"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs" -"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites." -"basicRouting" = "Basic Routing" -"blockConnectionsConfigsDesc" = "These options will block traffic based on the specific requested country." -"directConnectionsConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server." -"blockips" = "Block IPs" -"blockdomains" = "Block Domains" -"directips" = "Direct IPs" -"directdomains" = "Direct Domains" -"ipv4Routing" = "IPv4 Routing" -"ipv4RoutingDesc" = "These options will route traffic based on a specific destination via IPv4." -"warpRouting" = "WARP Routing" -"warpRoutingDesc" = "These options will route traffic based on a specific destination via WARP." -"nordRouting" = "NordVPN Routing" -"nordRoutingDesc" = "These options will route traffic based on a specific destination via NordVPN." -"Template" = "Advanced Xray Configuration Template" -"TemplateDesc" = "The final Xray config file will be generated based on this template." -"FreedomStrategy" = "Freedom Protocol Strategy" -"FreedomStrategyDesc" = "Set the output strategy for the network in the Freedom Protocol." -"RoutingStrategy" = "Overall Routing Strategy" -"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests." -"outboundTestUrl" = "Outbound Test URL" -"outboundTestUrlDesc" = "URL used when testing outbound connectivity." -"Torrent" = "Block BitTorrent Protocol" -"Inbounds" = "Inbounds" -"InboundsDesc" = "Accepting the specific clients." -"Outbounds" = "Outbounds" -"Balancers" = "Balancers" -"OutboundsDesc" = "Set the outgoing traffic pathway." -"Routings" = "Routing Rules" -"RoutingsDesc" = "The priority of each rule is important!" -"completeTemplate" = "All" -"logLevel" = "Log Level" -"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded." -"accessLog" = "Access Log" -"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs" -"errorLog" = "Error Log" -"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs" -"dnsLog" = "DNS Log" -"dnsLogDesc" = "Whether to enable DNS query logs" -"maskAddress" = "Mask Address" -"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log." -"statistics" = "Statistics" -"statsInboundUplink" = "Inbound Upload Statistics" -"statsInboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all inbound proxies." -"statsInboundDownlink" = "Inbound Download Statistics" -"statsInboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all inbound proxies." -"statsOutboundUplink" = "Outbound Upload Statistics" -"statsOutboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all outbound proxies." -"statsOutboundDownlink" = "Outbound Download Statistics" -"statsOutboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all outbound proxies." - -[pages.xray.rules] -"first" = "First" -"last" = "Last" -"up" = "Up" -"down" = "Down" -"source" = "Source" -"dest" = "Destination" -"inbound" = "Inbound" -"outbound" = "Outbound" -"balancer" = "Balancer" -"info" = "Info" -"add" = "Add Rule" -"edit" = "Edit Rule" -"useComma" = "Comma-separated items" - -[pages.xray.outbound] -"addOutbound" = "Add Outbound" -"addReverse" = "Add Reverse" -"editOutbound" = "Edit Outbound" -"editReverse" = "Edit Reverse" -"reverseTag" = "Reverse Tag" -"reverseTagDesc" = "VLESS simple reverse proxy tag. Leave empty to disable." -"reverseTagPlaceholder" = "reverse tag (leave empty to disable)" -"tag" = "Tag" -"tagDesc" = "Unique Tag" -"address" = "Address" -"reverse" = "Reverse" -"domain" = "Domain" -"type" = "Type" -"bridge" = "Bridge" -"portal" = "Portal" -"link" = "Link" -"intercon" = "Interconnection" -"settings" = "Settings" -"accountInfo" = "Account Information" -"outboundStatus" = "Outbound Status" -"sendThrough" = "Send Through" -"test" = "Test" -"testResult" = "Test Result" -"testing" = "Testing connection..." -"testSuccess" = "Test successful" -"testFailed" = "Test failed" -"testError" = "Failed to test outbound" -"nordvpn" = "NordVPN" -"accessToken" = "Access Token" -"country" = "Country" -"server" = "Server" -"city" = "City" -"allCities" = "All Cities" -"privateKey" = "Private Key" -"load" = "Load" - -[pages.xray.balancer] -"addBalancer" = "Add Balancer" -"editBalancer" = "Edit Balancer" -"balancerStrategy" = "Strategy" -"balancerSelectors" = "Selectors" -"tag" = "Tag" -"tagDesc" = "Unique Tag" -"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work." - -[pages.xray.wireguard] -"secretKey" = "Secret Key" -"publicKey" = "Public Key" -"allowedIPs" = "Allowed IPs" -"endpoint" = "Endpoint" -"psk" = "PreShared Key" -"domainStrategy" = "Domain Strategy" - -[pages.xray.tun] -"nameDesc" = "The name of the TUN interface. Default is 'xray0'" -"mtuDesc" = "Maximum Transmission Unit. The maximum size of data packets. Default is 1500" -"userLevel" = "User Level" -"userLevelDesc" = "All connections made through this inbound will use this user level. Default is 0" - -[pages.xray.dns] -"enable" = "Enable DNS" -"enableDesc" = "Enable built-in DNS server" -"tag" = "DNS Inbound Tag" -"tagDesc" = "This tag will be available as an Inbound tag in routing rules." -"clientIp" = "Client IP" -"clientIpDesc" = "Used to notify the server of the specified IP location during DNS queries" -"disableCache" = "Disable cache" -"disableCacheDesc" = "Disables DNS caching" -"disableFallback" = "Disable Fallback" -"disableFallbackDesc" = "Disables fallback DNS queries" -"disableFallbackIfMatch" = "Disable Fallback If Match" -"disableFallbackIfMatchDesc" = "Disables fallback DNS queries when the matching domain list of the DNS server is hit" -"enableParallelQuery" = "Enable Parallel Query" -"enableParallelQueryDesc" = "Enable parallel DNS queries to multiple servers for faster resolution" -"strategy" = "Query Strategy" -"strategyDesc" = "Overall strategy to resolve domain names" -"add" = "Add Server" -"edit" = "Edit Server" -"domains" = "Domains" -"expectIPs" = "Expect IPs" -"unexpectIPs" = "Unexpect IPs" -"useSystemHosts" = "Use System Hosts" -"useSystemHostsDesc" = "Use the hosts file from an installed system" -"usePreset" = "Use Preset" -"dnsPresetTitle" = "DNS Presets" -"dnsPresetFamily" = "Family" - -[pages.xray.fakedns] -"add" = "Add Fake DNS" -"edit" = "Edit Fake DNS" -"ipPool" = "IP Pool Subnet" -"poolSize" = "Pool Size" - -[pages.settings.security] -"admin" = "Admin credentials" -"twoFactor" = "Two-factor authentication" -"twoFactorEnable" = "Enable 2FA" -"twoFactorEnableDesc" = "Adds an additional layer of authentication to provide more security." -"twoFactorModalSetTitle" = "Enable two-factor authentication" -"twoFactorModalDeleteTitle" = "Disable two-factor authentication" -"twoFactorModalSteps" = "To set up two-factor authentication, perform a few steps:" -"twoFactorModalFirstStep" = "1. Scan this QR code in the app for authentication or copy the token near the QR code and paste it into the app" -"twoFactorModalSecondStep" = "2. Enter the code from the app" -"twoFactorModalRemoveStep" = "Enter the code from the application to remove two-factor authentication." -"twoFactorModalChangeCredentialsTitle" = "Change credentials" -"twoFactorModalChangeCredentialsStep" = "Enter the code from the application to change administrator credentials." -"twoFactorModalSetSuccess" = "Two-factor authentication has been successfully established" -"twoFactorModalDeleteSuccess" = "Two-factor authentication has been successfully deleted" -"twoFactorModalError" = "Wrong code" - -[pages.settings.toasts] -"modifySettings" = "The parameters have been changed." -"getSettings" = "An error occurred while retrieving parameters." -"modifyUserError" = "An error occurred while changing administrator credentials." -"modifyUser" = "You have successfully changed the credentials of the administrator." -"originalUserPassIncorrect" = "The сurrent username or password is invalid" -"userPassMustBeNotEmpty" = "The new username and password is empty" -"getOutboundTrafficError" = "Error getting traffics" -"resetOutboundTrafficError" = "Error in reset outbound traffics" - -[tgbot] -"keyboardClosed" = "❌ Custom keyboard closed!" -"noResult" = "❗ No result!" -"noQuery" = "❌ Query not found! Please use the command again!" -"wentWrong" = "❌ Something went wrong!" -"noIpRecord" = "❗ No IP Record!" -"noInbounds" = "❗ No inbound found!" -"unlimited" = "♾ Unlimited(Reset)" -"add" = "Add" -"month" = "Month" -"months" = "Months" -"day" = "Day" -"days" = "Days" -"hours" = "Hours" -"minutes" = "Minutes" -"unknown" = "Unknown" -"inbounds" = "Inbounds" -"clients" = "Clients" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Unknown command." -"pleaseChoose" = "👇 Please choose:\r\n" -"help" = "🤖 Welcome to this bot! It's designed to offer specific data from the web panel and allows you to make modifications as needed.\r\n\r\n" -"start" = "👋 Hello {{ .Firstname }}.\r\n" -"welcome" = "🤖 Welcome to {{ .Hostname }} management bot.\r\n" -"status" = "✅ Bot is OK!" -"usage" = "❗ Please provide a text to search!" -"getID" = "🆔 Your ID: {{ .ID }}" -"helpAdminCommands" = "To restart Xray Core:\r\n/restart\r\n\r\nTo search for a client email:\r\n/usage [Email]\r\n\r\nTo search for inbounds (with client stats):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id" -"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Operation successful!" -"restartFailed" = "❗ Error in operation.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core is not running." -"startDesc" = "Show the main menu" -"helpDesc" = "Bot help" -"statusDesc" = "Check bot status" -"idDesc" = "Show your Telegram ID" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU Load {{ .Percent }}% exceeds the threshold of {{ .Threshold }}%" -"selectUserFailed" = "❌ Error in user selection!" -"userSaved" = "✅ Telegram User saved." -"loginSuccess" = "✅ Logged in to the panel successfully.\r\n" -"loginFailed" = "❗️Login attempt to the panel failed.\r\n" -"2faFailed" = "2FA Failed" -"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n" -"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Version: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Version: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Uptime: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 System Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Username: {{ .Username }}\r\n" -"reason" = "❗️ Reason: {{ .Reason }}\r\n" -"time" = "⏰ Time: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Expire Date: {{ .Time }}\r\n" -"expireIn" = "📅 Expire In: {{ .Time }}\r\n" -"active" = "💡 Active: {{ .Enable }}\r\n" -"enabled" = "🚨 Enabled: {{ .Enable }}\r\n" -"online" = "🌐 Connection status: {{ .Status }}\r\n" -"lastOnline" = "🔙 Last online: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n" -"onlinesCount" = "🌐 Online Clients: {{ .Count }}\r\n" -"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Deplete Soon: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n" -"yes" = "✅ Yes" -"no" = "❌ No" -"received_id" = "🔑📥 ID updated." -"received_password" = "🔑📥 Password updated." -"received_email" = "📧📥 Email updated." -"received_comment" = "💬📥 Comment updated." -"id_prompt" = "🔑 Default ID: {{ .ClientId }}\n\nEnter your id." -"pass_prompt" = "🔑 Default Password: {{ .ClientPassword }}\n\nEnter your password." -"email_prompt" = "📧 Default Email: {{ .ClientEmail }}\n\nEnter your email." -"comment_prompt" = "💬 Default Comment: {{ .ClientComment }}\n\nEnter your Comment." -"inbound_client_data_id" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!" -"inbound_client_data_pass" = "🔄 Inbound: {{ .InboundRemark }}\n\n🔑 Password: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Traffic: {{ .ClientTraffic }}\n📅 Expire Date: {{ .ClientExp }}\n🌐 IP Limit: {{ .IpLimit }}\n💬 Comment: {{ .ClientComment }}\n\nYou can add the client to inbound now!" -"cancel" = "❌ Process Canceled! \n\nYou can /start again anytime. 🔄" -"error_add_client" = "⚠️ Error:\n\n {{ .error }}" -"using_default_value" = "Okay, I'll stick with the default value. 😊" -"incorrect_input" = "Your input is not valid.\nThe phrases should be continuous without spaces.\nCorrect example: aaaaaa\nIncorrect example: aaa aaa 🚫" -"AreYouSure" = "Are you sure? 🤔" -"SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ✅ Success" -"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Traffic reset process finished for all clients." - -[tgbot.buttons] -"closeKeyboard" = "❌ Close Keyboard" -"cancel" = "❌ Cancel" -"cancelReset" = "❌ Cancel Reset" -"cancelIpLimit" = "❌ Cancel IP Limit" -"confirmResetTraffic" = "✅ Confirm Reset Traffic?" -"confirmClearIps" = "✅ Confirm Clear IPs?" -"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?" -"confirmToggle" = "✅ Confirm Enable/Disable User?" -"dbBackup" = "Get DB Backup" -"serverUsage" = "Server Usage" -"getInbounds" = "Get Inbounds" -"depleteSoon" = "Deplete Soon" -"clientUsage" = "Get Usage" -"onlines" = "Online Clients" -"commands" = "Commands" -"refresh" = "🔄 Refresh" -"clearIPs" = "❌ Clear IPs" -"removeTGUser" = "❌ Remove Telegram User" -"selectTGUser" = "👤 Select Telegram User" -"selectOneTGUser" = "👤 Select a Telegram User:" -"resetTraffic" = "📈 Reset Traffic" -"resetExpire" = "📅 Change Expiry Date" -"ipLog" = "🔢 IP Log" -"ipLimit" = "🔢 IP Limit" -"setTGUser" = "👤 Set Telegram User" -"toggle" = "🔘 Enable / Disable" -"custom" = "🔢 Custom" -"confirmNumber" = "✅ Confirm: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirm adding: {{ .Num }}" -"limitTraffic" = "🚧 Traffic Limit" -"getBanLogs" = "Get Ban Logs" -"allClients" = "All Clients" -"addClient" = "Add Client" -"submitDisable" = "Submit As Disable ☑️" -"submitEnable" = "Submit As Enable ✅" -"use_default" = "🏷️ Use default" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Password" -"change_email" = "⚙️📧 Email" -"change_comment" = "⚙️💬 Comment" -"ResetAllTraffics" = "Reset All Traffics" -"SortedTrafficUsageReport" = "Sorted Traffic Usage Report" - -[tgbot.answers] -"successfulOperation" = "✅ Operation successful!" -"errorOperation" = "❗ Error in operation." -"getInboundsFailed" = "❌ Failed to get inbounds." -"getClientsFailed" = "❌ Failed to get clients." -"canceled" = "❌ {{ .Email }}: Operation canceled." -"clientRefreshSuccess" = "✅ {{ .Email }}: Client refreshed successfully." -"IpRefreshSuccess" = "✅ {{ .Email }}: IPs refreshed successfully." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Client's Telegram User refreshed successfully." -"resetTrafficSuccess" = "✅ {{ .Email }}: Traffic reset successfully." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Traffic limit saved successfully." -"expireResetSuccess" = "✅ {{ .Email }}: Expire days reset successfully." -"resetIpSuccess" = "✅ {{ .Email }}: IP limit {{ .Count }} saved successfully." -"clearIpSuccess" = "✅ {{ .Email }}: IPs cleared successfully." -"getIpLog" = "✅ {{ .Email }}: Get IP Log." -"getUserInfo" = "✅ {{ .Email }}: Get Telegram User Info." -"removedTGUserSuccess" = "✅ {{ .Email }}: Telegram User removed successfully." -"enableSuccess" = "✅ {{ .Email }}: Enabled successfully." -"disableSuccess" = "✅ {{ .Email }}: Disabled successfully." -"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your admin to use your Telegram ChatID in your configuration(s).\r\n\r\nYour ChatID: {{ .TgUserID }}" -"chooseClient" = "Choose a Client for Inbound {{ .Inbound }}" -"chooseInbound" = "Choose an Inbound" diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml deleted file mode 100644 index 48d47ced..00000000 --- a/web/translation/translate.es_ES.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Nombre de Usuario" -"password" = "Contraseña" -"login" = "Acceder" -"confirm" = "Confirmar" -"cancel" = "Cancelar" -"close" = "Cerrar" -"save" = "Guardar" -"logout" = "Cerrar Sesión" -"create" = "Crear" -"update" = "Actualizar" -"copy" = "Copiar" -"copied" = "Copiado" -"download" = "Descargar" -"remark" = "Notas" -"enable" = "Habilitar" -"protocol" = "Protocolo" -"search" = "Buscar" -"filter" = "Filtrar" -"loading" = "Cargando..." -"second" = "Segundo" -"minute" = "Minuto" -"hour" = "Hora" -"day" = "Día" -"check" = "Verificar" -"indefinite" = "Indefinido" -"unlimited" = "Ilimitado" -"none" = "None" -"qrCode" = "Código QR" -"info" = "Más Información" -"edit" = "Editar" -"delete" = "Eliminar" -"reset" = "Restablecer" -"noData" = "Sin datos" -"copySuccess" = "Copiado exitosamente" -"sure" = "Seguro" -"encryption" = "Encriptación" -"useIPv4ForHost" = "Usar IPv4 para el host" -"transmission" = "Transmisión" -"host" = "Host" -"path" = "Path" -"camouflage" = "Camuflaje" -"status" = "Estado" -"enabled" = "Habilitado" -"disabled" = "Deshabilitado" -"depleted" = "Agotado" -"depletingSoon" = "Agotándose" -"offline" = "fuera de línea" -"online" = "en línea" -"domainName" = "Nombre de dominio" -"monitor" = "Listening IP" -"certificate" = "Certificado Digital" -"fail" = "Falló" -"comment" = "Comentario" -"success" = "Éxito" -"lastOnline" = "Última conexión" -"getVersion" = "Obtener versión" -"install" = "Instalar" -"clients" = "Clientes" -"usage" = "Uso" -"twoFactorCode" = "Código" -"remained" = "Restante" -"security" = "Seguridad" -"secAlertTitle" = "Alerta de Seguridad" -"secAlertSsl" = "Esta conexión no es segura. Por favor, evite ingresar información sensible hasta que se active TLS para la protección de datos." -"secAlertConf" = "Ciertas configuraciones son vulnerables a ataques. Se recomienda reforzar los protocolos de seguridad para prevenir posibles violaciones." -"secAlertSSL" = "El panel carece de una conexión segura. Por favor, instale un certificado TLS para la protección de datos." -"secAlertPanelPort" = "El puerto predeterminado del panel es vulnerable. Por favor, configure un puerto aleatorio o específico." -"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja." -"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." -"secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja." -"emptyDnsDesc" = "No hay servidores DNS añadidos." -"emptyFakeDnsDesc" = "No hay servidores Fake DNS añadidos." -"emptyBalancersDesc" = "No hay balanceadores añadidos." -"emptyReverseDesc" = "No hay proxies inversos añadidos." -"somethingWentWrong" = "Algo salió mal" - -[subscription] -"title" = "Información de suscripción" -"subId" = "ID de suscripción" -"status" = "Estado" -"downloaded" = "Descargado" -"uploaded" = "Subido" -"expiry" = "Caducidad" -"totalQuota" = "Cuota total" -"individualLinks" = "Enlaces individuales" -"active" = "Activo" -"inactive" = "Inactivo" -"unlimited" = "Ilimitado" -"noExpiry" = "Sin caducidad" - -[menu] -"theme" = "Tema" -"dark" = "Oscuro" -"ultraDark" = "Ultra Oscuro" -"dashboard" = "Estado del Sistema" -"inbounds" = "Entradas" -"settings" = "Configuraciones" -"xray" = "Ajustes Xray" -"logout" = "Cerrar Sesión" -"link" = "Gestionar" - -[pages.login] -"hello" = "Hola" -"title" = "Bienvenido" -"loginAgain" = "El límite de tiempo de inicio de sesión ha expirado. Por favor, inicia sesión nuevamente." - -[pages.login.toasts] -"invalidFormData" = "El formato de los datos de entrada es inválido." -"emptyUsername" = "Por favor ingresa el nombre de usuario." -"emptyPassword" = "Por favor ingresa la contraseña." -"wrongUsernameOrPassword" = "Nombre de usuario, contraseña o código de dos factores incorrecto." -"successLogin" = "Has iniciado sesión en tu cuenta correctamente." - -[pages.index] -"title" = "Estado del Sistema" -"cpu" = "CPU" -"logicalProcessors" = "Procesadores lógicos" -"frequency" = "Frecuencia" -"swap" = "Memoria Virtual" -"storage" = "Almacenamiento" -"memory" = "RAM" -"threads" = "Hilos" -"xrayStatus" = "Xray" -"stopXray" = "Detener" -"restartXray" = "Reiniciar" -"xraySwitch" = "Versión" -"xraySwitchClick" = "Elige la versión a la que deseas cambiar." -"xraySwitchClickDesk" = "Elige sabiamente, ya que las versiones anteriores pueden no ser compatibles con las configuraciones actuales." -"xrayUpdates" = "Actualizaciones de Xray" -"updatePanel" = "Actualizar panel" -"panelUpdateDesc" = "Esto actualizará 3X-UI a la última versión y reiniciará el servicio del panel." -"currentPanelVersion" = "Versión actual del panel" -"latestPanelVersion" = "Última versión del panel" -"panelUpToDate" = "El panel está actualizado" -"upToDate" = "Actualizado" -"xrayStatusUnknown" = "Desconocido" -"xrayStatusRunning" = "En ejecución" -"xrayStatusStop" = "Detenido" -"xrayStatusError" = "Error" -"xrayErrorPopoverTitle" = "Se produjo un error al ejecutar Xray" -"operationHours" = "Tiempo de Funcionamiento" -"systemLoad" = "Carga del Sistema" -"systemLoadDesc" = "promedio de carga del sistema en los últimos 1, 5 y 15 minutos" -"connectionCount" = "Número de Conexiones" -"ipAddresses" = "Direcciones IP" -"toggleIpVisibility" = "Alternar visibilidad de la IP" -"overallSpeed" = "Velocidad general" -"upload" = "Subida" -"download" = "Descarga" -"totalData" = "Datos totales" -"sent" = "Enviado" -"received" = "Recibido" -"documentation" = "Documentación" -"xraySwitchVersionDialog" = "¿Realmente deseas cambiar la versión de Xray?" -"xraySwitchVersionDialogDesc" = "Esto cambiará la versión de Xray a #version#." -"xraySwitchVersionPopover" = "Xray se actualizó correctamente" -"panelUpdateDialog" = "¿Deseas actualizar el panel?" -"panelUpdateDialogDesc" = "Esto actualizará 3X-UI a la versión #version# y reiniciará el servicio del panel." -"panelUpdateCheckPopover" = "Fallo al comprobar actualización del panel" -"panelUpdateStartedPopover" = "Actualización del panel iniciada" -"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?" -"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#." -"geofilesUpdateDialogDesc" = "Esto actualizará todos los archivos." -"geofilesUpdateAll" = "Actualizar todo" -"geofileUpdatePopover" = "Geofichero actualizado correctamente" -"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página." -"logs" = "Registros" -"config" = "Configuración" -"backup" = "Сopia de Seguridad" -"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos" -"exportDatabase" = "Copia de seguridad" -"exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo." -"importDatabase" = "Restaurar" -"importDatabaseDesc" = "Haz clic para seleccionar y cargar un archivo .db desde tu dispositivo para restaurar tu base de datos desde una copia de seguridad." -"importDatabaseSuccess" = "La base de datos se ha importado correctamente" -"importDatabaseError" = "Ocurrió un error al importar la base de datos" -"readDatabaseError" = "Ocurrió un error al leer la base de datos" -"getDatabaseError" = "Ocurrió un error al obtener la base de datos" -"getConfigError" = "Ocurrió un error al obtener el archivo de configuración" -"customGeoTitle" = "GeoSite / GeoIP personalizados" -"customGeoAdd" = "Añadir" -"customGeoType" = "Tipo" -"customGeoAlias" = "Alias" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Activado" -"customGeoLastUpdated" = "Última actualización" -"customGeoExtColumn" = "Enrutamiento (ext:…)" -"customGeoToastUpdateAll" = "Todas las fuentes personalizadas se actualizaron" -"customGeoActions" = "Acciones" -"customGeoEdit" = "Editar" -"customGeoDelete" = "Eliminar" -"customGeoDownload" = "Actualizar ahora" -"customGeoModalAdd" = "Añadir geo personalizado" -"customGeoModalEdit" = "Editar geo personalizado" -"customGeoModalSave" = "Guardar" -"customGeoDeleteConfirm" = "¿Eliminar esta fuente geo personalizada?" -"customGeoRoutingHint" = "En reglas de enrutamiento use la columna de valor como ext:archivo.dat:etiqueta (sustituya la etiqueta)." -"customGeoInvalidId" = "Id de recurso no válido" -"customGeoAliasesError" = "No se pudieron cargar los alias geo personalizados" -"customGeoValidationAlias" = "El alias solo puede contener letras minúsculas, dígitos, - y _" -"customGeoValidationUrl" = "La URL debe comenzar con http:// o https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (personalizado)" -"customGeoToastList" = "Lista de geo personalizado" -"customGeoToastAdd" = "Añadir geo personalizado" -"customGeoToastUpdate" = "Actualizar geo personalizado" -"customGeoToastDelete" = "Geofile personalizado «{{ .fileName }}» eliminado" -"customGeoToastDownload" = "Geofile «{{ .fileName }}» actualizado" -"customGeoErrInvalidType" = "El tipo debe ser geosite o geoip" -"customGeoErrAliasRequired" = "El alias es obligatorio" -"customGeoErrAliasPattern" = "El alias contiene caracteres no permitidos" -"customGeoErrAliasReserved" = "Este alias está reservado" -"customGeoErrUrlRequired" = "La URL es obligatoria" -"customGeoErrInvalidUrl" = "La URL no es válida" -"customGeoErrUrlScheme" = "La URL debe usar http o https" -"customGeoErrUrlHost" = "El host de la URL no es válido" -"customGeoErrDuplicateAlias" = "Este alias ya se usa para este tipo" -"customGeoErrNotFound" = "Fuente geo personalizada no encontrada" -"customGeoErrDownload" = "Error de descarga" -"customGeoErrUpdateAllIncomplete" = "No se pudieron actualizar una o más fuentes geo personalizadas" -"customGeoEmpty" = "Aún no hay fuentes geo personalizadas — haz clic en Añadir para crear una" - -[pages.inbounds] -"allTimeTraffic" = "Tráfico Total" -"allTimeTrafficUsage" = "Uso de datos histórico" -"title" = "Entradas" -"totalDownUp" = "Subidas/Descargas Totales" -"totalUsage" = "Uso Total" -"inboundCount" = "Número de Entradas" -"operate" = "Menú" -"enable" = "Habilitar" -"remark" = "Notas" -"protocol" = "Protocolo" -"port" = "Puerto" -"portMap" = "Puertos de Destino" -"traffic" = "Tráfico" -"details" = "Detalles" -"transportConfig" = "Transporte" -"expireDate" = "Fecha de Expiración" -"createdAt" = "Creado" -"updatedAt" = "Actualizado" -"resetTraffic" = "Restablecer Tráfico" -"addInbound" = "Agregar Entrada" -"generalActions" = "Acciones Generales" -"autoRefresh" = "Auto-actualizar" -"autoRefreshInterval" = "Intervalo" -"modifyInbound" = "Modificar Entrada" -"deleteInbound" = "Eliminar Entrada" -"deleteInboundContent" = "¿Confirmar eliminación de entrada?" -"deleteClient" = "Eliminar cliente" -"deleteClientContent" = "¿Está seguro de que desea eliminar el cliente?" -"resetTrafficContent" = "¿Confirmar restablecimiento de tráfico?" -"copyLink" = "Copiar Enlace" -"address" = "Dirección" -"network" = "Red" -"destinationPort" = "Puerto de Destino" -"targetAddress" = "Dirección de Destino" -"monitorDesc" = "Dejar en blanco por defecto" -"meansNoLimit" = " = illimitata. (unidad: GB)" -"totalFlow" = "Flujo Total" -"leaveBlankToNeverExpire" = "Dejar en Blanco para Nunca Expirar" -"noRecommendKeepDefault" = "No hay requisitos especiales para mantener la configuración predeterminada" -"certificatePath" = "Ruta Cert" -"certificateContent" = "Datos Cert" -"publicKey" = "Clave Pública" -"privatekey" = "Clave Privada" -"clickOnQRcode" = "Haz clic en el Código QR para Copiar" -"client" = "Cliente" -"export" = "Exportar Enlaces" -"clone" = "Clonar" -"cloneInbound" = "Clonar Entradas" -"cloneInboundContent" = "Se aplicarán todas las configuraciones de esta entrada, excepto el Puerto, la IP de Escucha y los Clientes, al clon." -"cloneInboundOk" = "Clonar" -"resetAllTraffic" = "Restablecer Tráfico de Todas las Entradas" -"resetAllTrafficTitle" = "Restablecer tráfico de todas las entradas" -"resetAllTrafficContent" = "¿Estás seguro de que deseas restablecer el tráfico de todas las entradas?" -"resetInboundClientTraffics" = "Restablecer Tráfico de Clientes" -"resetInboundClientTrafficTitle" = "Restablecer todo el tráfico de clientes" -"resetInboundClientTrafficContent" = "¿Estás seguro de que deseas restablecer todo el tráfico para los clientes de esta entrada?" -"resetAllClientTraffics" = "Restablecer Tráfico de Todos los Clientes" -"resetAllClientTrafficTitle" = "Restablecer todo el tráfico de clientes" -"resetAllClientTrafficContent" = "¿Estás seguro de que deseas restablecer todo el tráfico para todos los clientes?" -"delDepletedClients" = "Eliminar Clientes Agotados" -"delDepletedClientsTitle" = "Eliminar clientes agotados" -"delDepletedClientsContent" = "¿Estás seguro de que deseas eliminar todos los clientes agotados?" -"email" = "Email" -"emailDesc" = "Por favor proporciona una dirección de correo electrónico única." -"IPLimit" = "Límite de IP" -"IPLimitDesc" = "Desactiva la entrada si la cantidad supera el valor ingresado (ingresa 0 para desactivar el límite de IP)." -"IPLimitlog" = "Registro de IP" -"IPLimitlogDesc" = "Registro de historial de IPs (antes de habilitar la entrada después de que haya sido desactivada por el límite de IP, debes borrar el registro)." -"IPLimitlogclear" = "Limpiar el Registro" -"setDefaultCert" = "Establecer certificado desde el panel" -"telegramDesc" = "Por favor, proporciona el ID de Chat de Telegram. (usa el comando '/id' en el bot) o (@userinfobot)" -"subscriptionDesc" = "Puedes encontrar tu enlace de suscripción en Detalles, también puedes usar el mismo nombre para varias configuraciones." -"info" = "Info" -"same" = "misma" -"inboundData" = "Datos de entrada" -"exportInbound" = "Exportación entrante" -"import" = "Importar" -"importInbound" = "Importar un entrante" -"periodicTrafficResetTitle" = "Reset de Tráfico" -"periodicTrafficResetDesc" = "Reiniciar automáticamente el contador de tráfico en intervalos especificados" -"lastReset" = "Último reinicio" - -[pages.client] -"add" = "Agregar Cliente" -"edit" = "Editar Cliente" -"submitAdd" = "Agregar Cliente" -"submitEdit" = "Guardar Cambios" -"clientCount" = "Número de Clientes" -"bulk" = "Agregar en Lote" -"copyFromInbound" = "Copiar clientes desde entrada" -"copyToInbound" = "Copiar clientes a" -"copySelected" = "Copiar seleccionados" -"copySource" = "Origen" -"copyEmailPreview" = "Vista previa del email resultante" -"copySelectSourceFirst" = "Seleccione primero una entrada de origen." -"copyResult" = "Resultado de la copia" -"copyResultSuccess" = "Copiado correctamente" -"copyResultNone" = "Nada que copiar: ningún cliente seleccionado o el origen está vacío" -"copyResultErrors" = "Errores al copiar" -"copyFlowLabel" = "Flow para nuevos clientes (VLESS)" -"copyFlowHint" = "Se aplica a todos los clientes copiados. Déjelo vacío para omitir." -"selectAll" = "Seleccionar todo" -"clearAll" = "Limpiar todo" -"method" = "Método" -"first" = "Primero" -"last" = "Último" -"prefix" = "Prefijo" -"postfix" = "Sufijo" -"delayedStart" = "Iniciar después del primer uso" -"expireDays" = "Duración" -"days" = "Día(s)" -"renew" = "Renovación automática" -"renewDesc" = "Renovación automática después de la expiración. (0 = desactivar) (unidad: día)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Nunca" -"daily" = "Diariamente" -"weekly" = "Semanalmente" -"monthly" = "Mensualmente" -"hourly" = "Cada hora" - -[pages.inbounds.toasts] -"obtain" = "Recibir" -"updateSuccess" = "La actualización fue exitosa" -"logCleanSuccess" = "El registro ha sido limpiado" -"inboundsUpdateSuccess" = "Entradas actualizadas correctamente" -"inboundUpdateSuccess" = "Entrada actualizada correctamente" -"inboundCreateSuccess" = "Entrada creada correctamente" -"inboundDeleteSuccess" = "Entrada eliminada correctamente" -"inboundClientAddSuccess" = "Cliente(s) de entrada añadido(s)" -"inboundClientDeleteSuccess" = "Cliente de entrada eliminado" -"inboundClientUpdateSuccess" = "Cliente de entrada actualizado" -"delDepletedClientsSuccess" = "Todos los clientes con tráfico agotado fueron eliminados" -"resetAllClientTrafficSuccess" = "Todo el tráfico del cliente ha sido reiniciado" -"resetAllTrafficSuccess" = "Todo el tráfico ha sido reiniciado" -"resetInboundClientTrafficSuccess" = "El tráfico ha sido reiniciado" -"trafficGetError" = "Error al obtener los tráficos" -"getNewX25519CertError" = "Error al obtener el certificado X25519." -"getNewmldsa65Error" = "Error al obtener el certificado mldsa65." -"getNewVlessEncError" = "Error al obtener el certificado VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Pedido" -"response" = "Respuesta" -"name" = "Nombre" -"value" = "Valor" - -[pages.inbounds.stream.tcp] -"version" = "Versión" -"method" = "Método" -"path" = "Camino" -"status" = "Estado" -"statusDescription" = "Descripción de la Situación" -"requestHeader" = "Encabezado de solicitud" -"responseHeader" = "Encabezado de respuesta" - -[pages.settings] -"title" = "Configuraciones" -"save" = "Guardar" -"infoDesc" = "Cada cambio realizado aquí debe ser guardado. Por favor, reinicie el panel para aplicar los cambios." -"restartPanel" = "Reiniciar Panel" -"restartPanelDesc" = "¿Está seguro de que desea reiniciar el panel? Haga clic en Aceptar para reiniciar después de 3 segundos. Si no puede acceder al panel después de reiniciar, por favor, consulte la información de registro del panel en el servidor." -"restartPanelSuccess" = "El panel se reinició correctamente" -"actions" = "Acciones" -"resetDefaultConfig" = "Restablecer a Configuración Predeterminada" -"panelSettings" = "Configuraciones del Panel" -"securitySettings" = "Configuraciones de Seguridad" -"TGBotSettings" = "Configuraciones de Bot de Telegram" -"panelListeningIP" = "IP de Escucha del Panel" -"panelListeningIPDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." -"panelListeningDomain" = "Dominio de Escucha del Panel" -"panelListeningDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs." -"panelPort" = "Puerto del Panel" -"panelPortDesc" = "El puerto utilizado para mostrar este panel." -"publicKeyPath" = "Ruta del Archivo de Clave Pública del Certificado del Panel" -"publicKeyPathDesc" = "Complete con una ruta absoluta que comience con." -"privateKeyPath" = "Ruta del Archivo de Clave Privada del Certificado del Panel" -"privateKeyPathDesc" = "Complete con una ruta absoluta que comience con." -"panelUrlPath" = "Ruta Raíz de la URL del Panel" -"panelUrlPathDesc" = "Debe empezar con '/' y terminar con." -"pageSize" = "Tamaño de paginación" -"pageSizeDesc" = "Defina el tamaño de página para la tabla de entradas. Establezca 0 para desactivar" -"remarkModel" = "Modelo de observación y carácter de separación" -"datepicker" = "selector de fechas" -"datepickerPlaceholder" = "Seleccionar fecha" -"datepickerDescription" = "El tipo de calendario selector especifica la fecha de vencimiento" -"sampleRemark" = "Observación de muestra" -"oldUsername" = "Nombre de Usuario Actual" -"currentPassword" = "Contraseña Actual" -"newUsername" = "Nuevo Nombre de Usuario" -"newPassword" = "Nueva Contraseña" -"telegramBotEnable" = "Habilitar bot de Telegram" -"telegramBotEnableDesc" = "Conéctese a las funciones de este panel a través del bot de Telegram." -"telegramToken" = "Token de Telegram" -"telegramTokenDesc" = "Debe obtener el token del administrador de bots de Telegram @botfather." -"telegramProxy" = "Socks5 Proxy" -"telegramProxyDesc" = "Si necesita el proxy Socks5 para conectarse a Telegram. Ajuste su configuración según la guía." -"telegramAPIServer" = "API Server de Telegram" -"telegramAPIServerDesc" = "El servidor API de Telegram a utilizar. Déjelo en blanco para utilizar el servidor predeterminado." -"telegramChatId" = "IDs de Chat de Telegram para Administradores" -"telegramChatIdDesc" = "IDs de Chat múltiples separados por comas. Use @userinfobot o use el comando '/id' en el bot para obtener sus IDs de Chat." -"telegramNotifyTime" = "Hora de Notificación del Bot de Telegram" -"telegramNotifyTimeDesc" = "Usar el formato de tiempo de Crontab." -"tgNotifyBackup" = "Respaldo de Base de Datos" -"tgNotifyBackupDesc" = "Incluir archivo de respaldo de base de datos con notificación de informe." -"tgNotifyLogin" = "Notificación de Inicio de Sesión" -"tgNotifyLoginDesc" = "Muestra el nombre de usuario, dirección IP y hora cuando alguien intenta iniciar sesión en su panel." -"sessionMaxAge" = "Edad Máxima de Sesión" -"sessionMaxAgeDesc" = "La duración de una sesión de inicio de sesión (unidad: minutos)." -"expireTimeDiff" = "Umbral de Expiración para Notificación" -"expireTimeDiffDesc" = "Reciba notificaciones sobre la expiración de la cuenta antes del umbral (unidad: días)." -"trafficDiff" = "Umbral de Tráfico para Notificación" -"trafficDiffDesc" = "Reciba notificaciones sobre el agotamiento del tráfico antes de alcanzar el umbral (unidad: GB)." -"tgNotifyCpu" = "Umbral de Alerta de Porcentaje de CPU" -"tgNotifyCpuDesc" = "Reciba notificaciones si el uso de la CPU supera este umbral (unidad: %)." -"timeZone" = "Zona Horaria" -"timeZoneDesc" = "Las tareas programadas se ejecutan de acuerdo con la hora en esta zona horaria." -"subSettings" = "Suscripción" -"subEnable" = "Habilitar Servicio" -"subEnableDesc" = "Función de suscripción con configuración separada." -"subJsonEnable" = "Habilitar/Deshabilitar el endpoint de suscripción JSON de forma independiente." -"subTitle" = "Título de la Suscripción" -"subTitleDesc" = "Título mostrado en el cliente VPN" -"subSupportUrl" = "URL de soporte" -"subSupportUrlDesc" = "Enlace de soporte técnico mostrado en el cliente VPN" -"subProfileUrl" = "URL del perfil" -"subProfileUrlDesc" = "Un enlace a tu sitio web mostrado en el cliente VPN" -"subAnnounce" = "Anuncio" -"subAnnounceDesc" = "El texto del anuncio mostrado en el cliente VPN" -"subEnableRouting" = "Habilitar enrutamiento" -"subEnableRoutingDesc" = "Configuración global para habilitar el enrutamiento en el cliente VPN. (Solo para Happ)" -"subRoutingRules" = "Reglas de enrutamiento" -"subRoutingRulesDesc" = "Reglas de enrutamiento globales para el cliente VPN. (Solo para Happ)" -"subListen" = "Listening IP" -"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." -"subPort" = "Puerto de Suscripción" -"subPortDesc" = "El número de puerto para el servicio de suscripción debe estar sin usar en el servidor." -"subCertPath" = "Ruta del Archivo de Clave Pública del Certificado de Suscripción" -"subCertPathDesc" = "Complete con una ruta absoluta que comience con '/'" -"subKeyPath" = "Ruta del Archivo de Clave Privada del Certificado de Suscripción" -"subKeyPathDesc" = "Complete con una ruta absoluta que comience con '/'" -"subPath" = "Ruta Raíz de la URL de Suscripción" -"subPathDesc" = "Debe empezar con '/' y terminar con '/'" -"subDomain" = "Dominio de Escucha" -"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs." -"subUpdates" = "Intervalos de Actualización de Suscripción" -"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente." -"subEncrypt" = "Encriptar configuraciones" -"subEncryptDesc" = "Encriptar las configuraciones devueltas en la suscripción." -"subShowInfo" = "Mostrar información de uso" -"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." -"subURI" = "URI de proxy inverso" -"externalTrafficInformEnable" = "Informe de tráfico externo" -"externalTrafficInformEnableDesc" = "Informar a la API externa sobre cada actualización de tráfico." -"externalTrafficInformURI" = "URI de información de tráfico externo" -"externalTrafficInformURIDesc" = "Las actualizaciones de tráfico se envían a este URI." -"restartXrayOnClientDisable" = "Reiniciar Xray tras desactivación automática" -"restartXrayOnClientDisableDesc" = "Cuando un cliente se desactive automáticamente por vencimiento o límite de tráfico, reiniciar Xray." -"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy" -"fragment" = "Fragmentación" -"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS" -"fragmentSett" = "Configuración de Fragmentación" -"noisesDesc" = "Activar Sonidos" -"noisesSett" = "Configuración de Sonidos" -"mux" = "Mux" -"muxDesc" = "Transmite múltiples flujos de datos independientes dentro de un flujo de datos establecido." -"muxSett" = "Configuración Mux" -"direct" = "Conexión Directa" -"directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico." -"notifications" = "Notificaciones" -"certs" = "Certificados" -"externalTraffic" = "Tráfico Externo" -"dateAndTime" = "Fecha y Hora" -"proxyAndServer" = "Proxy y Servidor" -"intervals" = "Intervalos" -"information" = "Información" -"language" = "Idioma" -"telegramBotLanguage" = "Idioma del Bot de Telegram" - -[pages.xray] -"title" = "Xray Configuración" -"save" = "Guardar configuración" -"restart" = "Reiniciar Xray" -"restartSuccess" = "Xray se ha reiniciado correctamente" -"stopSuccess" = "Xray se ha detenido correctamente" -"restartError" = "Ocurrió un error al reiniciar Xray." -"stopError" = "Ocurrió un error al detener Xray." -"basicTemplate" = "Perfil Básico" -"advancedTemplate" = "Perfil Avanzado" -"generalConfigs" = "Configuraciones Generales" -"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales." -"logConfigs" = "Registro" -"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlos sabiamente solo en caso de sus necesidades." -"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos." -"basicRouting" = "Enrutamiento Básico" -"blockConnectionsConfigsDesc" = "Estas opciones bloquearán el tráfico según el país solicitado específico." -"directConnectionsConfigsDesc" = "Una conexión directa asegura que el tráfico específico no sea enrutado a través de otro servidor." -"blockips" = "Bloquear IPs" -"blockdomains" = "Bloquear Dominios" -"directips" = "IPs Directas" -"directdomains" = "Dominios Directos" -"ipv4Routing" = "Enrutamiento IPv4" -"ipv4RoutingDesc" = "Estas opciones solo enrutarán a los dominios objetivo a través de IPv4." -"warpRouting" = "Enrutamiento WARP" -"warpRoutingDesc" = "Precaución: Antes de usar estas opciones, instale WARP en modo de proxy socks5 en su servidor siguiendo los pasos en el GitHub del panel. WARP enrutará el tráfico a los sitios web a través de los servidores de Cloudflare." -"nordRouting" = "Enrutamiento NordVPN" -"nordRoutingDesc" = "Estas opciones enrutarán el tráfico basado en un destino específico a través de NordVPN." -"Template" = "Plantilla de Configuración de Xray" -"TemplateDesc" = "Genera el archivo de configuración final de Xray basado en esta plantilla." -"FreedomStrategy" = "Configurar Estrategia para el Protocolo Freedom" -"FreedomStrategyDesc" = "Establece la estrategia de salida de la red en el Protocolo Freedom." -"RoutingStrategy" = "Configurar Estrategia de Enrutamiento de Dominios" -"RoutingStrategyDesc" = "Establece la estrategia general de enrutamiento para la resolución de DNS." -"outboundTestUrl" = "URL de prueba de outbound" -"outboundTestUrlDesc" = "URL usada al probar la conectividad del outbound" -"Torrent" = "Prohibir Uso de BitTorrent" -"Inbounds" = "Entrante" -"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos." -"Outbounds" = "Salidas" -"Balancers" = "Equilibradores" -"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor." -"Routings" = "Reglas de enrutamiento" -"RoutingsDesc" = "¡La prioridad de cada regla es importante!" -"completeTemplate" = "Todos" -"logLevel" = "Nivel de registro" -"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse." -"accessLog" = "Registro de acceso" -"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso" -"errorLog" = "Registro de Errores" -"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores." -"dnsLog" = "Registro DNS" -"dnsLogDesc" = "Si habilitar los registros de consulta DNS" -"maskAddress" = "Enmascarar Dirección" -"maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro." -"statistics" = "Estadísticas" -"statsInboundUplink" = "Estadísticas de Subida de Entrada" -"statsInboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de entrada." -"statsInboundDownlink" = "Estadísticas de Bajada de Entrada" -"statsInboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de entrada." -"statsOutboundUplink" = "Estadísticas de Subida de Salida" -"statsOutboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de salida." -"statsOutboundDownlink" = "Estadísticas de Bajada de Salida" -"statsOutboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de salida." - -[pages.xray.rules] -"first" = "Primero" -"last" = "Último" -"up" = "Arriba" -"down" = "Abajo" -"source" = "Fuente" -"dest" = "Destino" -"inbound" = "Entrante" -"outbound" = "Saliente" -"balancer" = "Equilibrador" -"info" = "Información" -"add" = "Agregar Regla" -"edit" = "Editar Regla" -"useComma" = "Elementos separados por comas" - -[pages.xray.outbound] -"addOutbound" = "Agregar salida" -"addReverse" = "Agregar reverso" -"editOutbound" = "Editar salida" -"editReverse" = "Editar reverso" -"reverseTag" = "Etiqueta Reverso" -"reverseTagDesc" = "Etiqueta de salida del proxy inverso simple VLESS. Dejar vacío para deshabilitar. Cuando se establece, las conexiones de este cliente pueden usarse como túnel de proxy inverso." -"reverseTagPlaceholder" = "etiqueta de salida (vacío para deshabilitar)" -"tag" = "Etiqueta" -"tagDesc" = "etiqueta única" -"address" = "Dirección" -"reverse" = "Reverso" -"domain" = "Dominio" -"type" = "Tipo" -"bridge" = "puente" -"portal" = "portal" -"link" = "Enlace" -"intercon" = "Interconexión" -"settings" = "Configuración" -"accountInfo" = "Información de la Cuenta" -"outboundStatus" = "Estado de Salida" -"sendThrough" = "Enviar a través de" -"test" = "Probar" -"testResult" = "Resultado de la prueba" -"testing" = "Probando conexión..." -"testSuccess" = "Prueba exitosa" -"testFailed" = "Prueba fallida" -"testError" = "Error al probar la salida" -"nordvpn" = "NordVPN" -"accessToken" = "Token de acceso" -"country" = "País" -"server" = "Servidor" -"city" = "Ciudad" -"allCities" = "Todas las ciudades" -"privateKey" = "Clave privada" -"load" = "Carga" - -[pages.xray.balancer] -"addBalancer" = "Agregar equilibrador" -"editBalancer" = "Editar balanceador" -"balancerStrategy" = "Estrategia" -"balancerSelectors" = "Selectores" -"tag" = "Etiqueta" -"tagDesc" = "etiqueta única" -"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Llave secreta" -"publicKey" = "Llave pública" -"allowedIPs" = "IP permitidas" -"endpoint" = "Punto final" -"psk" = "Clave precompartida" -"domainStrategy" = "Estrategia de dominio" - -[pages.xray.tun] -"nameDesc" = "El nombre de la interfaz TUN. El valor predeterminado es 'xray0'" -"mtuDesc" = "Unidad Máxima de Transmisión. El tamaño máximo de los paquetes de datos. El valor predeterminado es 1500" -"userLevel" = "Nivel de Usuario" -"userLevelDesc" = "Todas las conexiones realizadas a través de este entrada utilizarán este nivel de usuario. El valor predeterminado es 0" - -[pages.xray.dns] -"enable" = "Habilitar DNS" -"enableDesc" = "Habilitar servidor DNS incorporado" -"tag" = "Etiqueta de Entrada DNS" -"tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento." -"clientIp" = "IP del cliente" -"clientIpDesc" = "Se utiliza para notificar al servidor la ubicación IP especificada durante las consultas DNS" -"disableCache" = "Desactivar caché" -"disableCacheDesc" = "Desactiva el almacenamiento en caché de DNS" -"disableFallback" = "Desactivar respaldo" -"disableFallbackDesc" = "Desactiva las consultas DNS de respaldo" -"disableFallbackIfMatch" = "Desactivar respaldo si coincide" -"disableFallbackIfMatchDesc" = "Desactiva las consultas DNS de respaldo cuando se acierta en la lista de dominios coincidentes del servidor DNS" -"enableParallelQuery" = "Habilitar consulta paralela" -"enableParallelQueryDesc" = "Habilitar consultas DNS paralelas a múltiples servidores para una resolución más rápida" -"strategy" = "Estrategia de Consulta" -"strategyDesc" = "Estrategia general para resolver nombres de dominio" -"add" = "Agregar Servidor" -"edit" = "Editar Servidor" -"domains" = "Dominios" -"expectIPs" = "IPs esperadas" -"unexpectIPs" = "IPs inesperadas" -"useSystemHosts" = "Usar Hosts del sistema" -"useSystemHostsDesc" = "Usar el archivo hosts de un sistema instalado" -"usePreset" = "Usar plantilla" -"dnsPresetTitle" = "Plantillas DNS" -"dnsPresetFamily" = "Familiar" - -[pages.xray.fakedns] -"add" = "Agregar DNS Falso" -"edit" = "Editar DNS Falso" -"ipPool" = "Subred del grupo de IP" -"poolSize" = "Tamaño del grupo" - -[pages.settings.security] -"admin" = "Credenciales de administrador" -"twoFactor" = "Autenticación de dos factores" -"twoFactorEnable" = "Habilitar 2FA" -"twoFactorEnableDesc" = "Añade una capa adicional de autenticación para mayor seguridad." -"twoFactorModalSetTitle" = "Activar autenticación de dos factores" -"twoFactorModalDeleteTitle" = "Desactivar autenticación de dos factores" -"twoFactorModalSteps" = "Para configurar la autenticación de dos factores, sigue estos pasos:" -"twoFactorModalFirstStep" = "1. Escanea este código QR en la aplicación de autenticación o copia el token cerca del código QR y pégalo en la aplicación" -"twoFactorModalSecondStep" = "2. Ingresa el código de la aplicación" -"twoFactorModalRemoveStep" = "Ingresa el código de la aplicación para eliminar la autenticación de dos factores." -"twoFactorModalChangeCredentialsTitle" = "Cambiar credenciales" -"twoFactorModalChangeCredentialsStep" = "Ingrese el código de la aplicación para cambiar las credenciales del administrador." -"twoFactorModalSetSuccess" = "La autenticación de dos factores se ha establecido con éxito" -"twoFactorModalDeleteSuccess" = "La autenticación de dos factores se ha eliminado con éxito" -"twoFactorModalError" = "Código incorrecto" - -[pages.settings.toasts] -"modifySettings" = "Los parámetros han sido modificados." -"getSettings" = "Ocurrió un error al obtener los parámetros." -"modifyUserError" = "Ocurrió un error al cambiar las credenciales del administrador." -"modifyUser" = "Has cambiado exitosamente las credenciales del administrador." -"originalUserPassIncorrect" = "Nombre de usuario o contraseña original incorrectos" -"userPassMustBeNotEmpty" = "El nuevo nombre de usuario y la nueva contraseña no pueden estar vacíos" -"getOutboundTrafficError" = "Error al obtener el tráfico saliente" -"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente" - -[tgbot] -"keyboardClosed" = "❌ Teclado cerrado!" -"noResult" = "❗ ¡Sin resultados!" -"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando nuevamente!" -"wentWrong" = "❌ ¡Algo salió mal!" -"noIpRecord" = "❗ ¡No hay registro de IP!" -"noInbounds" = "❗ ¡No se encontraron entradas!" -"unlimited" = "♾ Ilimitado (Restablecer)" -"add" = "Añadir" -"month" = "Mes" -"months" = "Meses" -"day" = "Día" -"days" = "Días" -"hours" = "Horas" -"minutes" = "Minutos" -"unknown" = "Desconocido" -"inbounds" = "Entradas" -"clients" = "Clientes" -"offline" = "🔴 Desconectado" -"online" = "🟢 En línea" - -[tgbot.commands] -"unknown" = "❗ Comando desconocido" -"pleaseChoose" = "👇 Por favor elige:\r\n" -"help" = "🤖 ¡Bienvenido a este bot! Está diseñado para ofrecerte datos específicos del servidor y te permite hacer modificaciones según sea necesario.\r\n\r\n" -"start" = "👋 Hola {{ .Firstname }}.\r\n" -"welcome" = "🤖 Bienvenido al bot de gestión de {{ .Hostname }}.\r\n" -"status" = "✅ ¡El bot está bien!" -"usage" = "❗ ¡Por favor proporciona un texto para buscar!" -"getID" = "🆔 Tu ID: {{ .ID }}" -"helpAdminCommands" = "Para reiniciar Xray Core:\r\n/restart\r\n\r\nPara buscar un correo electrónico de cliente:\r\n/usage [Correo electrónico]\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n/inbound [Observación]\r\n\r\nID de Chat de Telegram:\r\n/id" -"helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n/usage [Correo electrónico]\r\n\r\nID de Chat de Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ ¡Operación exitosa!" -"restartFailed" = "❗ Error en la operación.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core no está en ejecución." -"startDesc" = "Mostrar el menú principal" -"helpDesc" = "Ayuda del bot" -"statusDesc" = "Comprobar el estado del bot" -"idDesc" = "Mostrar tu ID de Telegram" - -[tgbot.messages] -"cpuThreshold" = "🔴 El uso de CPU {{ .Percent }}% es mayor que el umbral {{ .Threshold }}%" -"selectUserFailed" = "❌ ¡Error al seleccionar usuario!" -"userSaved" = "✅ Usuario de Telegram guardado." -"loginSuccess" = "✅ Has iniciado sesión en el panel con éxito.\r\n" -"loginFailed" = "❗️ Falló el inicio de sesión en el panel.\r\n" -"2faFailed" = "Error de 2FA" -"report" = "🕰 Informes programados: {{ .RunTime }}\r\n" -"datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n" -"hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versión de X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versión de Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Tiempo de actividad del servidor: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Carga del servidor: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 Memoria del servidor: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Conteo de TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 Conteo de UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Estado de Xray: {{ .State }}\r\n" -"username" = "👤 Nombre de usuario: {{ .Username }}\r\n" -"reason" = "❗️ Motivo: {{ .Reason }}\r\n" -"time" = "⏰ Hora: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Puerto: {{ .Port }}\r\n" -"expire" = "📅 Fecha de Vencimiento: {{ .Time }}\r\n" -"expireIn" = "📅 Vence en: {{ .Time }}\r\n" -"active" = "💡 Activo: {{ .Enable }}\r\n" -"enabled" = "🚨 Habilitado: {{ .Enable }}\r\n" -"online" = "🌐 Estado de conexión: {{ .Status }}\r\n" -"lastOnline" = "🔙 Última conexión: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Subida: ↑{{ .Upload }}\r\n" -"download" = "🔽 Bajada: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Usuario de Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Agotado {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Cantidad de Agotados {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Clientes en línea: {{ .Count }}\r\n" -"disabled" = "🛑 Desactivado: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Se agotará pronto: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Hora de la Copia de Seguridad: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n" -"yes" = "✅ Sí" -"no" = "❌ No" -"received_id" = "🔑📥 ID actualizado." -"received_password" = "🔑📥 Contraseña actualizada." -"received_email" = "📧📥 Correo electrónico actualizado." -"received_comment" = "💬📥 Comentario actualizado." -"id_prompt" = "🔑 ID predeterminado: {{ .ClientId }}\n\nIntroduce tu ID." -"pass_prompt" = "🔑 Contraseña predeterminada: {{ .ClientPassword }}\n\nIntroduce tu contraseña." -"email_prompt" = "📧 Correo electrónico predeterminado: {{ .ClientEmail }}\n\nIntroduce tu correo electrónico." -"comment_prompt" = "💬 Comentario predeterminado: {{ .ClientComment }}\n\nIntroduce tu comentario." -"inbound_client_data_id" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!" -"inbound_client_data_pass" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Contraseña: {{ .ClientPass }}\n📧 Correo: {{ .ClientEmail }}\n📊 Tráfico: {{ .ClientTraffic }}\n📅 Fecha de expiración: {{ .ClientExp }}\n🌐 Límite de IP: {{ .IpLimit }}\n💬 Comentario: {{ .ClientComment }}\n\n¡Ahora puedes agregar al cliente a la entrada!" -"cancel" = "❌ ¡Proceso cancelado! \n\nPuedes /start de nuevo en cualquier momento. 🔄" -"error_add_client" = "⚠️ Error:\n\n {{ .error }}" -"using_default_value" = "Está bien, me quedaré con el valor predeterminado. 😊" -"incorrect_input" = "Tu entrada no es válida.\nLas frases deben ser continuas sin espacios.\nEjemplo correcto: aaaaaa\nEjemplo incorrecto: aaa aaa 🚫" -"AreYouSure" = "¿Estás seguro? 🤔" -"SuccessResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ✅ Éxito" -"FailedResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ❌ Fallido \n\n🛠️ Error: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Proceso de reinicio de tráfico finalizado para todos los clientes." - -[tgbot.buttons] -"closeKeyboard" = "❌ Cerrar Teclado" -"cancel" = "❌ Cancelar" -"cancelReset" = "❌ Cancelar Reinicio" -"cancelIpLimit" = "❌ Cancelar Límite de IP" -"confirmResetTraffic" = "✅ ¿Confirmar Reinicio de Tráfico?" -"confirmClearIps" = "✅ ¿Confirmar Limpiar IPs?" -"confirmRemoveTGUser" = "✅ ¿Confirmar Eliminar Usuario de Telegram?" -"confirmToggle" = "✅ ¿Confirmar habilitar/deshabilitar usuario?" -"dbBackup" = "Obtener Copia de Seguridad de BD" -"serverUsage" = "Uso del Servidor" -"getInbounds" = "Obtener Entradas" -"depleteSoon" = "Pronto se Agotará" -"clientUsage" = "Obtener Uso" -"onlines" = "Clientes en línea" -"commands" = "Comandos" -"refresh" = "🔄 Actualizar" -"clearIPs" = "❌ Limpiar IPs" -"removeTGUser" = "❌ Eliminar Usuario de Telegram" -"selectTGUser" = "👤 Seleccionar Usuario de Telegram" -"selectOneTGUser" = "👤 Selecciona un usuario de telegram:" -"resetTraffic" = "📈 Reiniciar Tráfico" -"resetExpire" = "📅 Cambiar fecha de Vencimiento" -"ipLog" = "🔢 Registro de IP" -"ipLimit" = "🔢 Límite de IP" -"setTGUser" = "👤 Establecer Usuario de Telegram" -"toggle" = "🔘 Habilitar / Deshabilitar" -"custom" = "🔢 Costumbre" -"confirmNumber" = "✅ Confirmar: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirmar agregando: {{ .Num }}" -"limitTraffic" = "🚧 Límite de tráfico" -"getBanLogs" = "Registros de prohibición" -"allClients" = "Todos los Clientes" -"addClient" = "Añadir cliente" -"submitDisable" = "Enviar como deshabilitado ☑️" -"submitEnable" = "Enviar como habilitado ✅" -"use_default" = "🏷️ Usar por defecto" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Contraseña" -"change_email" = "⚙️📧 Correo electrónico" -"change_comment" = "⚙️💬 Comentario" -"ResetAllTraffics" = "Reiniciar todo el tráfico" -"SortedTrafficUsageReport" = "Informe de uso de tráfico ordenado" - -[tgbot.answers] -"successfulOperation" = "✅ ¡Exitosa!" -"errorOperation" = "❗ Error en la Operación." -"getInboundsFailed" = "❌ Error al obtener las entradas" -"getClientsFailed" = "❌ No se pudo obtener los clientes." -"canceled" = "❌ {{ .Email }} : Operación cancelada." -"clientRefreshSuccess" = "✅ {{ .Email }} : Cliente actualizado exitosamente." -"IpRefreshSuccess" = "✅ {{ .Email }} : IPs actualizadas exitosamente." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : Usuario de Telegram del cliente actualizado exitosamente." -"resetTrafficSuccess" = "✅ {{ .Email }} : Tráfico reiniciado exitosamente." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : Límite de Tráfico guardado exitosamente." -"expireResetSuccess" = "✅ {{ .Email }} : Días de vencimiento reiniciados exitosamente." -"resetIpSuccess" = "✅ {{ .Email }} : Límite de IP {{ .Count }} guardado exitosamente." -"clearIpSuccess" = "✅ {{ .Email }} : IPs limpiadas exitosamente." -"getIpLog" = "✅ {{ .Email }} : Obtener Registro de IP." -"getUserInfo" = "✅ {{ .Email }} : Obtener Información de Usuario de Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }} : Usuario de Telegram eliminado exitosamente." -"enableSuccess" = "✅ {{ .Email }} : Habilitado exitosamente." -"disableSuccess" = "✅ {{ .Email }} : Deshabilitado exitosamente." -"askToAddUserId" = "¡No se encuentra su configuración!\r\nPor favor, pídale a su administrador que use su ChatID de usuario de Telegram en su(s) configuración(es).\r\n\r\nSu ChatID de usuario: {{ .TgUserID }}" -"chooseClient" = "Elige un Cliente para Inbound {{ .Inbound }}" -"chooseInbound" = "Elige un Inbound" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml deleted file mode 100644 index 4d27cc43..00000000 --- a/web/translation/translate.fa_IR.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "نام‌کاربری" -"password" = "رمزعبور" -"login" = "ورود" -"confirm" = "تایید" -"cancel" = "انصراف" -"close" = "بستن" -"save" = "ذخیره" -"logout" = "خروج" -"create" = "ایجاد" -"update" = "به‌روزرسانی" -"copy" = "کپی" -"copied" = "کپی شد" -"download" = "دانلود" -"remark" = "نام" -"enable" = "فعال" -"protocol" = "پروتکل" -"search" = "جستجو" -"filter" = "فیلتر" -"loading" = "...در حال بارگذاری" -"second" = "ثانیه" -"minute" = "دقیقه" -"hour" = "ساعت" -"day" = "روز" -"check" = "چک کردن" -"indefinite" = "نامحدود" -"unlimited" = "نامحدود" -"none" = "هیچ" -"qrCode" = "QRکد" -"info" = "اطلاعات بیشتر" -"edit" = "ویرایش" -"delete" = "حذف" -"reset" = "ریست" -"noData" = "داده‌ای وجود ندارد." -"copySuccess" = "باموفقیت کپی‌شد" -"sure" = "مطمئن" -"encryption" = "رمزگذاری" -"useIPv4ForHost" = "از IPv4 برای میزبان استفاده کنید" -"transmission" = "راه‌اتصال" -"host" = "آدرس" -"path" = "مسیر" -"camouflage" = "مبهم‌سازی" -"status" = "وضعیت" -"enabled" = "فعال" -"disabled" = "غیرفعال" -"depleted" = "منقضی" -"depletingSoon" = "در‌حال‌انقضا" -"offline" = "آفلاین" -"online" = "آنلاین" -"domainName" = "آدرس دامنه" -"monitor" = "آی‌پی اتصال" -"certificate" = "گواهی دیجیتال" -"fail" = "ناموفق" -"comment" = "توضیحات" -"success" = "موفق" -"lastOnline" = "آخرین فعالیت" -"getVersion" = "دریافت نسخه" -"install" = "نصب" -"clients" = "کاربران" -"usage" = "استفاده" -"twoFactorCode" = "کد" -"remained" = "باقی‌مانده" -"security" = "امنیت" -"secAlertTitle" = "هشدار‌امنیتی" -"secAlertSsl" = "این‌اتصال‌امن نیست. لطفا‌ تازمانی‌که تی‌ال‌اس برای محافظت از‌ داده‌ها فعال نشده‌است، از وارد کردن اطلاعات حساس خودداری کنید" -"secAlertConf" = "تنظیمات خاصی در برابر حملات آسیب پذیر هستند. توصیه می‌شود پروتکل‌های امنیتی را برای جلوگیری از نفوذ احتمالی تقویت کنید" -"secAlertSSL" = "پنل فاقد ارتباط امن است. لطفاً یک گواهینامه تی‌ال‌اس برای محافظت از داده‌ها نصب کنید" -"secAlertPanelPort" = "استفاده از پورت پیش‌فرض پنل ناامن است. لطفاً یک پورت تصادفی یا خاص تنظیم کنید" -"secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" -"secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" -"secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" -"emptyDnsDesc" = "هیچ سرور DNS اضافه نشده است." -"emptyFakeDnsDesc" = "هیچ سرور Fake DNS اضافه نشده است." -"emptyBalancersDesc" = "هیچ بالانسر اضافه نشده است." -"emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است." -"somethingWentWrong" = "مشکلی پیش آمد" - -[subscription] -"title" = "اطلاعات سابسکریپشن" -"subId" = "شناسه اشتراک" -"status" = "وضعیت" -"downloaded" = "دانلود" -"uploaded" = "آپلود" -"expiry" = "تاریخ پایان" -"totalQuota" = "حجم کلی" -"individualLinks" = "لینک‌های تکی" -"active" = "فعال" -"inactive" = "غیرفعال" -"unlimited" = "نامحدود" -"noExpiry" = "بدون انقضا" - -[menu] -"theme" = "تم" -"dark" = "تیره" -"ultraDark" = "فوق تیره" -"dashboard" = "نمای کلی" -"inbounds" = "ورودی‌ها" -"settings" = "تنظیمات پنل" -"xray" = "پیکربندی ایکس‌ری" -"logout" = "خروج" -"link" = "مدیریت" - -[pages.login] -"hello" = "سلام" -"title" = "خوش‌آمدید" -"loginAgain" = "مدت زمان استفاده به‌اتمام‌رسیده، لطفا دوباره وارد شوید" - -[pages.login.toasts] -"invalidFormData" = "اطلاعات به‌درستی وارد نشده‌است" -"emptyUsername" = "لطفا یک نام‌کاربری وارد کنید‌" -"emptyPassword" = "لطفا یک رمزعبور وارد کنید" -"wrongUsernameOrPassword" = "نام کاربری، رمز عبور یا کد دو مرحله‌ای نامعتبر است." -"successLogin" = "شما با موفقیت به حساب کاربری خود وارد شدید." - -[pages.index] -"title" = "نمای کلی" -"cpu" = "پردازنده" -"logicalProcessors" = "پردازنده‌های منطقی" -"frequency" = "فرکانس" -"swap" = "سواپ" -"storage" = "ذخیره‌سازی" -"memory" = "حافظه رم" -"threads" = "رشته‌ها" -"xrayStatus" = "ایکس‌ری" -"stopXray" = "توقف" -"restartXray" = "شروع‌مجدد" -"xraySwitch" = "‌نسخه" -"xraySwitchClick" = "نسخه مورد نظر را انتخاب کنید" -"xraySwitchClickDesk" = "لطفا بادقت انتخاب کنید. درصورت انتخاب نسخه قدیمی‌تر، امکان ناهماهنگی با پیکربندی فعلی وجود دارد" -"xrayUpdates" = "به‌روزرسانی‌های Xray" -"updatePanel" = "به‌روزرسانی پنل" -"panelUpdateDesc" = "این عملیات 3X-UI را به آخرین نسخه به‌روزرسانی می‌کند و سرویس پنل را مجدداً راه‌اندازی می‌کند." -"currentPanelVersion" = "نسخه فعلی پنل" -"latestPanelVersion" = "آخرین نسخه پنل" -"panelUpToDate" = "پنل به‌روز است" -"upToDate" = "به‌روز" -"xrayStatusUnknown" = "ناشناخته" -"xrayStatusRunning" = "در حال اجرا" -"xrayStatusStop" = "متوقف" -"xrayStatusError" = "خطا" -"xrayErrorPopoverTitle" = "خطا در هنگام اجرای Xray رخ داد" -"operationHours" = "مدت‌کارکرد" -"systemLoad" = "بارسیستم" -"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته" -"connectionCount" = "تعداد کانکشن ها" -"ipAddresses" = "آدرس‌های IP" -"toggleIpVisibility" = "تغییر وضعیت نمایش IP" -"overallSpeed" = "سرعت کلی" -"upload" = "آپلود" -"download" = "دانلود" -"totalData" = "داده‌های کل" -"sent" = "ارسال شده" -"received" = "دریافت شده" -"documentation" = "مستندات" -"xraySwitchVersionDialog" = "آیا واقعاً می‌خواهید نسخه Xray را تغییر دهید؟" -"xraySwitchVersionDialogDesc" = "این کار نسخه Xray را به #version# تغییر می‌دهد." -"xraySwitchVersionPopover" = "Xray با موفقیت به‌روز شد" -"panelUpdateDialog" = "آیا مطمئن هستید که می‌خواهید پنل را به‌روزرسانی کنید؟" -"panelUpdateDialogDesc" = "این 3X-UI را به نسخه #version# به‌روزرسانی کرده و سرویس پنل را مجدداً راه‌اندازی می‌کند." -"panelUpdateCheckPopover" = "خطا در بررسی به‌روزرسانی پنل" -"panelUpdateStartedPopover" = "به‌روزرسانی پنل آغاز شد" -"geofileUpdateDialog" = "آیا واقعاً می‌خواهید فایل جغرافیایی را به‌روز کنید؟" -"geofileUpdateDialogDesc" = "این عمل فایل #filename# را به‌روز می‌کند." -"geofilesUpdateDialogDesc" = "با این کار همه فایل‌ها به‌روزرسانی می‌شوند." -"geofilesUpdateAll" = "همه را به‌روزرسانی کنید" -"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت به‌روز شد" -"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" -"logs" = "گزارش‌ها" -"config" = "پیکربندی" -"backup" = "پشتیبان‌گیری" -"backupTitle" = "پشتیبان‌گیری دیتابیس" -"exportDatabase" = "پشتیبان‌گیری" -"exportDatabaseDesc" = "برای دانلود یک فایل .db حاوی پشتیبان از پایگاه داده فعلی خود به دستگاهتان کلیک کنید." -"importDatabase" = "بازیابی" -"importDatabaseDesc" = "برای انتخاب و آپلود یک فایل .db از دستگاهتان و بازیابی پایگاه داده از یک پشتیبان کلیک کنید." -"importDatabaseSuccess" = "پایگاه داده با موفقیت وارد شد" -"importDatabaseError" = "خطا در وارد کردن پایگاه داده" -"readDatabaseError" = "خطا در خواندن پایگاه داده" -"getDatabaseError" = "خطا در دریافت پایگاه داده" -"getConfigError" = "خطا در دریافت فایل پیکربندی" -"customGeoTitle" = "GeoSite / GeoIP سفارشی" -"customGeoAdd" = "افزودن" -"customGeoType" = "نوع" -"customGeoAlias" = "نام مستعار" -"customGeoUrl" = "URL" -"customGeoEnabled" = "فعال" -"customGeoLastUpdated" = "آخرین به‌روزرسانی" -"customGeoExtColumn" = "مسیریابی (ext:…)" -"customGeoToastUpdateAll" = "همه منابع سفارشی به‌روزرسانی شدند" -"customGeoActions" = "اقدامات" -"customGeoEdit" = "ویرایش" -"customGeoDelete" = "حذف" -"customGeoDownload" = "به‌روزرسانی اکنون" -"customGeoModalAdd" = "افزودن geo سفارشی" -"customGeoModalEdit" = "ویرایش geo سفارشی" -"customGeoModalSave" = "ذخیره" -"customGeoDeleteConfirm" = "این منبع geo سفارشی حذف شود؟" -"customGeoRoutingHint" = "در قوانین مسیریابی مقدار را به صورت ext:file.dat:tag استفاده کنید (tag را جایگزین کنید)." -"customGeoInvalidId" = "شناسه منبع نامعتبر است" -"customGeoAliasesError" = "بارگذاری نام مستعارهای geo سفارشی ناموفق بود" -"customGeoValidationAlias" = "نام مستعار فقط حروف کوچک، اعداد، - و _" -"customGeoValidationUrl" = "URL باید با http:// یا https:// شروع شود" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (سفارشی)" -"customGeoToastList" = "فهرست geo سفارشی" -"customGeoToastAdd" = "افزودن geo سفارشی" -"customGeoToastUpdate" = "به‌روزرسانی geo سفارشی" -"customGeoToastDelete" = "geofile سفارشی «{{ .fileName }}» حذف شد" -"customGeoToastDownload" = "geofile «{{ .fileName }}» به‌روزرسانی شد" -"customGeoErrInvalidType" = "نوع باید geosite یا geoip باشد" -"customGeoErrAliasRequired" = "نام مستعار لازم است" -"customGeoErrAliasPattern" = "نام مستعار دارای نویسه نامجاز است" -"customGeoErrAliasReserved" = "این نام مستعار رزرو است" -"customGeoErrUrlRequired" = "URL لازم است" -"customGeoErrInvalidUrl" = "URL نامعتبر است" -"customGeoErrUrlScheme" = "URL باید http یا https باشد" -"customGeoErrUrlHost" = "میزبان URL نامعتبر است" -"customGeoErrDuplicateAlias" = "این نام مستعار برای این نوع قبلاً استفاده شده است" -"customGeoErrNotFound" = "منبع geo سفارشی یافت نشد" -"customGeoErrDownload" = "بارگیری ناموفق بود" -"customGeoErrUpdateAllIncomplete" = "به‌روزرسانی یک یا چند منبع geo سفارشی ناموفق بود" -"customGeoEmpty" = "هنوز منبع geo سفارشی‌ای ثبت نشده — برای ایجاد روی «افزودن» کلیک کنید" - -[pages.inbounds] -"allTimeTraffic" = "کل ترافیک" -"allTimeTrafficUsage" = "کل استفاده در تمام مدت" -"title" = "کاربران" -"totalDownUp" = "دریافت/ارسال کل" -"totalUsage" = "‌‌‌مصرف کل" -"inboundCount" = "کل ورودی‌ها" -"operate" = "عملیات" -"enable" = "فعال" -"remark" = "نام" -"protocol" = "پروتکل" -"port" = "پورت" -"portMap" = "پورت‌های نظیر" -"traffic" = "ترافیک" -"details" = "توضیحات" -"transportConfig" = "نحوه اتصال" -"expireDate" = "مدت زمان" -"createdAt" = "ایجاد" -"updatedAt" = "به‌روزرسانی" -"resetTraffic" = "ریست ترافیک" -"addInbound" = "افزودن ورودی" -"generalActions" = "عملیات کلی" -"autoRefresh" = "تازه‌سازی خودکار" -"autoRefreshInterval" = "فاصله" -"modifyInbound" = "ویرایش ورودی" -"deleteInbound" = "حذف ورودی" -"deleteInboundContent" = "آیا مطمئن به حذف ورودی هستید؟" -"deleteClient" = "حذف کاربر" -"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟" -"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟" -"copyLink" = "کپی لینک" -"address" = "آدرس" -"network" = "شبکه" -"destinationPort" = "پورت مقصد" -"targetAddress" = "آدرس مقصد" -"monitorDesc" = "به‌طور پیش‌فرض خالی‌بگذارید" -"meansNoLimit" = "0 = واحد: گیگابایت) نامحدود)" -"totalFlow" = "ترافیک کل" -"leaveBlankToNeverExpire" = "برای منقضی‌نشدن خالی‌بگذارید" -"noRecommendKeepDefault" = "توصیه‌می‌شود به‌طور پیش‌فرض حفظ‌شود" -"certificatePath" = "مسیر فایل" -"certificateContent" = "محتوای فایل" -"publicKey" = "کلید عمومی" -"privatekey" = "کلید خصوصی" -"clickOnQRcode" = "برای کپی بر روی کدتصویری کلیک کنید" -"client" = "کاربر" -"export" = "استخراج لینک‌ها" -"clone" = "شبیه‌سازی" -"cloneInbound" = "شبیه‌سازی ورودی" -"cloneInboundContent" = "همه موارد این ورودی بجز پورت، آی‌پی و کاربر‌ها شبیه‌سازی خواهند شد" -"cloneInboundOk" = "ساختن شبیه ساز" -"resetAllTraffic" = "ریست ترافیک کل ورودی‌ها" -"resetAllTrafficTitle" = "ریست ترافیک کل ورودی‌ها" -"resetAllTrafficContent" = "آیا مطمئن به ریست ترافیک تمام ورودی‌ها هستید؟" -"resetInboundClientTraffics" = "ریست ترافیک کاربران" -"resetInboundClientTrafficTitle" = "ریست ترافیک کاربران" -"resetInboundClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران این‌ ورودی هستید؟" -"resetAllClientTraffics" = "ریست ترافیک کل کاربران" -"resetAllClientTrafficTitle" = "ریست ترافیک کل کاربران" -"resetAllClientTrafficContent" = "آیا مطمئن به ریست ترافیک تمام کاربران هستید؟" -"delDepletedClients" = "حذف کاربران منقضی" -"delDepletedClientsTitle" = "حذف کاربران منقضی" -"delDepletedClientsContent" = "آیا مطمئن به حذف تمام کاربران منقضی‌شده ‌هستید؟" -"email" = "ایمیل" -"emailDesc" = "باید یک ایمیل یکتا باشد" -"IPLimit" = "محدودیت آی‌پی" -"IPLimitDesc" = "(اگر تعداد از مقدار تنظیم شده بیشتر شود، ورودی را غیرفعال می کند. (0 = غیرفعال" -"IPLimitlog" = "گزارش‌ها" -"IPLimitlogDesc" = "گزارش تاریخچه آی‌پی. برای فعال کردن ورودی پس از غیرفعال شدن، گزارش را پاک کنید" -"IPLimitlogclear" = "پاک کردن گزارش‌ها" -"setDefaultCert" = "استفاده از گواهی پنل" -"telegramDesc" = "لطفا شناسه گفتگوی تلگرام را وارد کنید. (از دستور '/id' در ربات استفاده کنید) یا (@userinfobot)" -"subscriptionDesc" = "شما می‌توانید لینک سابسکربپشن خودرا در 'جزئیات' پیدا کنید، همچنین می‌توانید از همین نام برای چندین کاربر استفاده‌کنید" -"info" = "اطلاعات" -"same" = "همسان" -"inboundData" = "داده‌های ورودی" -"exportInbound" = "استخراج ورودی" -"import" = "افزودن" -"importInbound" = "افزودن یک ورودی" -"periodicTrafficResetTitle" = "بازنشانی ترافیک" -"periodicTrafficResetDesc" = "بازنشانی خودکار شمارنده ترافیک در فواصل زمانی مشخص" -"lastReset" = "آخرین بازنشانی" - -[pages.client] -"add" = "کاربر جدید" -"edit" = "ویرایش کاربر" -"submitAdd" = "اضافه کردن" -"submitEdit" = "ذخیره تغییرات" -"clientCount" = "تعداد کاربران" -"bulk" = "انبوه‌سازی" -"copyFromInbound" = "کپی کاربران از اینباند" -"copyToInbound" = "کپی کاربران به" -"copySelected" = "کپی انتخاب‌شده‌ها" -"copySource" = "منبع" -"copyEmailPreview" = "پیش‌نمایش ایمیل نهایی" -"copySelectSourceFirst" = "ابتدا یک اینباند منبع انتخاب کنید." -"copyResult" = "نتیجه کپی" -"copyResultSuccess" = "با موفقیت کپی شد" -"copyResultNone" = "چیزی برای کپی نیست: هیچ کاربری انتخاب نشده یا منبع خالی است" -"copyResultErrors" = "خطاهای کپی" -"copyFlowLabel" = "Flow برای کاربران جدید (VLESS)" -"copyFlowHint" = "برای همه کاربران کپی‌شده اعمال می‌شود. برای نادیده گرفتن، خالی بگذارید." -"selectAll" = "انتخاب همه" -"clearAll" = "پاک کردن همه" -"method" = "روش" -"first" = "از" -"last" = "تا" -"prefix" = "پیشوند" -"postfix" = "پسوند" -"delayedStart" = "شروع‌پس‌از‌اولین‌استفاده" -"expireDays" = "مدت زمان" -"days" = "(روز)" -"renew" = "تمدید خودکار" -"renewDesc" = "تمدید خودکار پس‌از ‌انقضا. (0 = غیرفعال)(واحد: روز)" - -[pages.inbounds.periodicTrafficReset] -"never" = "هرگز" -"daily" = "روزانه" -"weekly" = "هفتگی" -"monthly" = "ماهانه" -"hourly" = "هر ساعت" - -[pages.inbounds.toasts] -"obtain" = "فراهم‌سازی" -"updateSuccess" = "بروزرسانی با موفقیت انجام شد" -"logCleanSuccess" = "لاگ پاکسازی شد" -"inboundsUpdateSuccess" = "ورودی‌ها با موفقیت به‌روزرسانی شدند" -"inboundUpdateSuccess" = "ورودی با موفقیت به‌روزرسانی شد" -"inboundCreateSuccess" = "ورودی با موفقیت ایجاد شد" -"inboundDeleteSuccess" = "ورودی با موفقیت حذف شد" -"inboundClientAddSuccess" = "کلاینت(های) ورودی اضافه شدند" -"inboundClientDeleteSuccess" = "کلاینت ورودی حذف شد" -"inboundClientUpdateSuccess" = "کلاینت ورودی به‌روزرسانی شد" -"delDepletedClientsSuccess" = "تمام کلاینت‌های مصرف شده حذف شدند" -"resetAllClientTrafficSuccess" = "تمام ترافیک کلاینت بازنشانی شد" -"resetAllTrafficSuccess" = "تمام ترافیک‌ها بازنشانی شدند" -"resetInboundClientTrafficSuccess" = "ترافیک بازنشانی شد" -"trafficGetError" = "خطا در دریافت ترافیک‌ها" -"getNewX25519CertError" = "خطا در دریافت گواهی X25519." -"getNewmldsa65Error" = "خطا در دریافت گواهی mldsa65." -"getNewVlessEncError" = "خطا در دریافت گواهی VlessEnc." - -[pages.inbounds.stream.general] -"request" = "درخواست" -"response" = "پاسخ" -"name" = "نام" -"value" = "مقدار" - -[pages.inbounds.stream.tcp] -"version" = "نسخه" -"method" = "متد" -"path" = "مسیر" -"status" = "وضعیت" -"statusDescription" = "توضیحات وضعیت" -"requestHeader" = "سربرگ درخواست" -"responseHeader" = "سربرگ پاسخ" - -[pages.settings] -"title" = "تنظیمات پنل" -"save" = "ذخیره" -"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید" -"restartPanel" = "ریستارت پنل" -"restartPanelDesc" = "آیا مطمئن به ریستارت پنل هستید؟ اگر پس‌از ریستارت نمی‌توانید به پنل دسترسی پیدا کنید، لطفاً گزارش‌های موجود در اسکریپت پنل را بررسی کنید" -"restartPanelSuccess" = "پنل با موفقیت راه‌اندازی مجدد شد" -"actions" = "عملیات ها" -"resetDefaultConfig" = "برگشت به پیش‌فرض" -"panelSettings" = "پیکربندی" -"securitySettings" = "احرازهویت" -"TGBotSettings" = "ربات تلگرام" -"panelListeningIP" = "آدرس آی‌پی" -"panelListeningIPDesc" = "آدرس آی‌پی برای وب پنل. برای گوش‌دادن به‌تمام آی‌پی‌ها خالی‌بگذارید" -"panelListeningDomain" = "نام دامنه" -"panelListeningDomainDesc" = "آدرس دامنه برای وب پنل. برای گوش دادن به‌تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید" -"panelPort" = "پورت" -"panelPortDesc" = "شماره پورت برای وب پنل. باید پورت استفاده نشده‌باشد" -"publicKeyPath" = "مسیر کلید عمومی" -"publicKeyPathDesc" = "مسیر فایل کلیدعمومی برای وب پنل. با '/' شروع‌می‌شود" -"privateKeyPath" = "مسیر کلید خصوصی" -"privateKeyPathDesc" = "مسیر فایل کلیدخصوصی برای وب پنل. با '/' شروع‌می‌شود" -"panelUrlPath" = "URI مسیر" -"panelUrlPathDesc" = "برای وب پنل. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر" -"pageSize" = "اندازه صفحه بندی جدول" -"pageSizeDesc" = "(اندازه صفحه برای جدول ورودی‌ها.(0 = غیرفعال" -"remarkModel" = "نام‌کانفیگ و جداکننده" -"datepicker" = "نوع تقویم" -"datepickerPlaceholder" = "انتخاب تاریخ" -"datepickerDescription" = "وظایف برنامه ریزی شده بر اساس این تقویم اجرا می‌شود" -"sampleRemark" = "نمونه‌نام" -"oldUsername" = "نام‌کاربری فعلی" -"currentPassword" = "رمز‌عبور فعلی" -"newUsername" = "نام‌کاربری جدید" -"newPassword" = "رمزعبور جدید" -"telegramBotEnable" = "فعال‌سازی ربات تلگرام" -"telegramBotEnableDesc" = "ربات تلگرام را فعال می‌کند" -"telegramToken" = "توکن تلگرام" -"telegramTokenDesc" = "دریافت کنید @botfather توکن را می‌توانید از" -"telegramProxy" = "SOCKS پراکسی" -"telegramProxyDesc" = "را برای اتصال به تلگرام فعال می کند SOCKS5 پراکسی" -"telegramAPIServer" = "سرور API تلگرام" -"telegramAPIServerDesc" = "API سرور تلگرام برای اتصال را تغییر میدهد. برای استفاده از سرور پیش فرض خالی بگذارید" -"telegramChatId" = "آی‌دی چت مدیر" -"telegramChatIdDesc" = "دریافت ‌کنید ('/id'یا (دستور (@userinfobot) آی‌دی(های) چت تلگرام مدیر، از" -"telegramNotifyTime" = "زمان نوتیفیکیشن" -"telegramNotifyTimeDesc" = "زمان‌اطلاع‌رسانی ربات تلگرام برای گزارش های دوره‌ای. از فرمت زمانبندی لینوکس استفاده‌کنید‌" -"tgNotifyBackup" = "پشتیبان‌گیری از دیتابیس" -"tgNotifyBackupDesc" = "فایل پشتیبان‌دیتابیس را به‌همراه گزارش ارسال می‌کند" -"tgNotifyLogin" = "اعلان ورود" -"tgNotifyLoginDesc" = "نام‌کاربری، آدرس آی‌پی، و زمان ورود، فردی که سعی می‌کند وارد پنل شود را نمایش می‌دهد" -"sessionMaxAge" = "بیشینه زمان جلسه وب" -"sessionMaxAgeDesc" = "(بیشینه زمانی که می‌توانید لاگین بمانید. (واحد: دقیقه" -"expireTimeDiff" = "آستانه زمان باقی مانده" -"expireTimeDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به زمان انقضا. (واحد: روز" -"trafficDiff" = "آستانه ترافیک باقی مانده" -"trafficDiffDesc" = "(فاصله زمانی هشدار تا رسیدن به اتمام ترافیک. (واحد: گیگابایت" -"tgNotifyCpu" = "آستانه هشدار بار پردازنده" -"tgNotifyCpuDesc" = "(اگر بار روی پردازنده ازاین آستانه فراتر رفت، برای شما پیام ارسال می‌شود. (واحد: درصد" -"timeZone" = "منطقه زمانی" -"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه‌زمانی اجرا می‌شود" -"subSettings" = "سابسکریپشن" -"subEnable" = "فعال‌سازی سرویس سابسکریپشن" -"subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند" -"subJsonEnable" = "فعال/غیرفعال‌سازی مستقل نقطه دسترسی سابسکریپشن JSON." -"subTitle" = "عنوان اشتراک" -"subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN" -"subSupportUrl" = "آدرس پشتیبانی" -"subSupportUrlDesc" = "لینک پشتیبانی فنی که در کلاینت VPN نمایش داده می‌شود" -"subProfileUrl" = "آدرس پروفایل" -"subProfileUrlDesc" = "لینک وب‌سایت شما که در کلاینت VPN نمایش داده می‌شود" -"subAnnounce" = "اعلان" -"subAnnounceDesc" = "متن اعلانی که در کلاینت VPN نمایش داده می‌شود" -"subEnableRouting" = "فعال‌سازی مسیریابی" -"subEnableRoutingDesc" = "تنظیمات سراسری برای فعال‌سازی مسیریابی در کلاینت VPN. (فقط برای Happ)" -"subRoutingRules" = "قوانین مسیریابی" -"subRoutingRulesDesc" = "قوانین مسیریابی سراسری برای کلاینت VPN. (فقط برای Happ)" -"subListen" = "آدرس آی‌پی" -"subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید" -"subPort" = "پورت" -"subPortDesc" = "شماره پورت برای سرویس سابسکریپشن. باید پورت استفاده نشده‌باشد" -"subCertPath" = "مسیر کلید عمومی" -"subCertPathDesc" = "مسیر فایل کلیدعمومی برای سرویس سابیکریپشن. با '/' شروع‌می‌شود" -"subKeyPath" = "مسیر کلید خصوصی" -"subKeyPathDesc" = "مسیر فایل کلیدخصوصی برای سرویس سابسکریپشن. با '/' شروع‌می‌شود" -"subPath" = "URI مسیر" -"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع‌ و با '/' خاتمه‌ می‌یابد URI مسیر" -"subDomain" = "نام دامنه" -"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌" -"subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن" -"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت" -"subEncrypt" = "کدگذاری" -"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه" -"subShowInfo" = "نمایش اطلاعات مصرف" -"subShowInfoDesc" = "ترافیک و زمان باقی‌مانده را در برنامه‌های کاربری نمایش می‌دهد" -"subURI" = "پروکسی معکوس URI مسیر" -"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسی‌ها تغییر می‌دهد URI مسیر" -"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک" -"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود" -"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک" -"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود" -"restartXrayOnClientDisable" = "ری‌استارت Xray بعد از غیرفعال‌سازی خودکار" -"restartXrayOnClientDisableDesc" = "وقتی کاربر به‌صورت خودکار به‌دلیل اتمام زمان یا ترافیک غیرفعال می‌شود، Xray ری‌استارت شود." -"fragment" = "فرگمنت" -"fragmentDesc" = "فعال کردن فرگمنت برای بسته‌ی نخست تی‌ال‌اس" -"fragmentSett" = "تنظیمات فرگمنت" -"noisesDesc" = "فعال کردن Noises." -"noisesSett" = "تنظیمات Noises" -"mux" = "ماکس" -"muxDesc" = "چندین جریان داده مستقل را در یک جریان داده ثابت منتقل می کند" -"muxSett" = "تنظیمات ماکس" -"direct" = "اتصال مستقیم" -"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند" -"notifications" = "اعلان‌ها" -"certs" = "گواهی‌ها" -"externalTraffic" = "ترافیک خارجی" -"dateAndTime" = "تاریخ و زمان" -"proxyAndServer" = "پراکسی و سرور" -"intervals" = "فواصل" -"information" = "اطلاعات" -"language" = "زبان" -"telegramBotLanguage" = "زبان ربات تلگرام" - -[pages.xray] -"title" = "پیکربندی ایکس‌ری" -"save" = "ذخیره" -"restart" = "ریستارت ایکس‌ری" -"restartSuccess" = "Xray با موفقیت راه‌اندازی مجدد شد" -"stopSuccess" = "Xray با موفقیت متوقف شد" -"restartError" = "خطا در راه‌اندازی مجدد Xray." -"stopError" = "خطا در توقف Xray." -"basicTemplate" = "پایه" -"advancedTemplate" = "پیشرفته" -"generalConfigs" = "استراتژی‌ کلی" -"generalConfigsDesc" = "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند" -"logConfigs" = "گزارش" -"logConfigsDesc" = "گزارش‌ها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید" -"blockConfigsDesc" = "این گزینه‌ها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود می‌کند" -"basicRouting" = "مسیریابی پایه" -"blockConnectionsConfigsDesc" = "این گزینه‌ها ترافیک را بر اساس کشور درخواست‌شده خاص مسدود می‌کنند." -"directConnectionsConfigsDesc" = "یک اتصال مستقیم تضمین می‌کند که ترافیک خاص از طریق سرور دیگری مسیریابی نشود." -"blockips" = "مسدود کردن آی‌پی‌ها" -"blockdomains" = "مسدود کردن دامنه‌ها" -"directips" = "آی‌پی‌های مستقیم" -"directdomains" = "دامنه‌های مستقیم" -"ipv4Routing" = "IPv4 مسیریابی" -"ipv4RoutingDesc" = "این گزینه‌ها ترافیک را از طریق آی‌پی نسخه4 سرور، به مقصد هدایت می‌کند" -"warpRouting" = "WARP مسیریابی" -"warpRoutingDesc" = "این گزینه‌ها ترافیک‌ را از طریق وارپ کلادفلر به مقصد هدایت می‌کند" -"nordRouting" = "مسیریابی NordVPN" -"nordRoutingDesc" = "این گزینه‌ها ترافیک را بر اساس مقصد خاص از طریق NordVPN مسیریابی می‌کنند." -"Template" = "‌پیکربندی پیشرفته الگو ایکس‌ری" -"TemplateDesc" = "فایل پیکربندی نهایی ایکس‌ری بر اساس این الگو ایجاد می‌شود" -"FreedomStrategy" = "Freedom استراتژی پروتکل" -"FreedomStrategyDesc" = "تعیین می‌کند Freedom استراتژی خروجی شبکه را برای پروتکل" -"RoutingStrategy" = "استراتژی کلی مسیریابی" -"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواست‌ها را تعیین می‌کند" -"outboundTestUrl" = "آدرس تست خروجی" -"outboundTestUrlDesc" = "آدرسی که برای تست اتصال خروجی استفاده می‌شود." -"Torrent" = "مسدودسازی پروتکل بیت‌تورنت" -"Inbounds" = "ورودی‌ها" -"InboundsDesc" = "پذیرش کلاینت خاص" -"Outbounds" = "خروجی‌ها" -"Balancers" = "بالانسرها" -"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید" -"Routings" = "قوانین مسیریابی" -"RoutingsDesc" = "اولویت هر قانون مهم است" -"completeTemplate" = "کامل" -"logLevel" = "سطح گزارش" -"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند." -"accessLog" = "مسیر گزارش" -"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند." -"errorLog" = "گزارش خطا" -"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند" -"dnsLog" = "گزارش DNS" -"dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید" -"maskAddress" = "پنهان کردن آدرس" -"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند." -"statistics" = "آمار" -"statsInboundUplink" = "آمار آپلود ورودی" -"statsInboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های ورودی را فعال می‌کند." -"statsInboundDownlink" = "آمار دانلود ورودی" -"statsInboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های ورودی را فعال می‌کند." -"statsOutboundUplink" = "آمار آپلود خروجی" -"statsOutboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های خروجی را فعال می‌کند." -"statsOutboundDownlink" = "آمار دانلود خروجی" -"statsOutboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های خروجی را فعال می‌کند." - -[pages.xray.rules] -"first" = "اولین" -"last" = "آخرین" -"up" = "بالا" -"down" = "پایین" -"source" = "مبدا" -"dest" = "مقصد" -"inbound" = "ورودی" -"outbound" = "خروجی" -"balancer" = "بالانسر" -"info" = "اطلاعات" -"add" = "افزودن قانون" -"edit" = "ویرایش قانون" -"useComma" = "موارد جدا شده با کاما" - -[pages.xray.outbound] -"addOutbound" = "افزودن خروجی" -"addReverse" = "افزودن معکوس" -"editOutbound" = "ویرایش خروجی" -"editReverse" = "ویرایش معکوس" -"reverseTag" = "تگ معکوس" -"reverseTagDesc" = "تگ خروجی پروکسی معکوس ساده VLESS. برای غیرفعال کردن خالی بگذارید. در صورت تنظیم، اتصالات این کلاینت می‌توانند به عنوان تونل پروکسی معکوس استفاده شوند." -"reverseTagPlaceholder" = "تگ خروجی (خالی = غیرفعال)" -"tag" = "برچسب" -"tagDesc" = "برچسب یگانه" -"address" = "آدرس" -"reverse" = "معکوس" -"domain" = "دامنه" -"type" = "نوع" -"bridge" = "پل" -"portal" = "پورتال" -"link" = "لینک" -"intercon" = "اتصال میانی" -"settings" = "تنظیمات" -"accountInfo" = "اطلاعات حساب" -"outboundStatus" = "وضعیت خروجی" -"sendThrough" = "ارسال با" -"test" = "تست" -"testResult" = "نتیجه تست" -"testing" = "در حال تست اتصال..." -"testSuccess" = "تست موفقیت‌آمیز" -"testFailed" = "تست ناموفق" -"testError" = "خطا در تست خروجی" -"nordvpn" = "NordVPN" -"accessToken" = "توکن دسترسی" -"country" = "کشور" -"server" = "سرور" -"privateKey" = "کلید خصوصی" -"city" = "شهر" -"allCities" = "همه شهرها" -"load" = "فشار سرور" - -[pages.xray.balancer] -"addBalancer" = "افزودن بالانسر" -"editBalancer" = "ویرایش بالانسر" -"balancerStrategy" = "استراتژی" -"balancerSelectors" = "انتخاب‌گرها" -"tag" = "برچسب" -"tagDesc" = "برچسب یگانه" -"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد." - -[pages.xray.wireguard] -"secretKey" = "کلید شخصی" -"publicKey" = "کلید عمومی" -"allowedIPs" = "آی‌پی‌های مجاز" -"endpoint" = "نقطه پایانی" -"psk" = "کلید مشترک" -"domainStrategy" = "استراتژی حل دامنه" - -[pages.xray.tun] -"nameDesc" = "نام رابط TUN. مقدار پیش‌فرض 'xray0' است" -"mtuDesc" = "واحد انتقال حداکثر. بیشترین اندازه بسته‌های داده. مقدار پیش‌فرض 1500 است" -"userLevel" = "سطح کاربر" -"userLevelDesc" = "تمام اتصالات انجام‌شده از طریق این ورودی از این سطح کاربری استفاده خواهند کرد. مقدار پیش‌فرض 0 است" - -[pages.xray.dns] -"enable" = "فعال کردن حل دامنه" -"enableDesc" = "سرور حل دامنه داخلی را فعال کنید" -"tag" = "برچسب" -"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود" -"clientIp" = "آی‌پی کلاینت" -"clientIpDesc" = "برای اطلاع‌رسانی به سرور درباره مکان IP مشخص‌شده در طول درخواست‌های DNS استفاده می‌شود" -"disableCache" = "غیرفعال‌سازی کش" -"disableCacheDesc" = "کش DNS را غیرفعال می‌کند" -"disableFallback" = "غیرفعال‌سازی Fallback" -"disableFallbackDesc" = "درخواست‌های DNS Fallback را غیرفعال می‌کند" -"disableFallbackIfMatch" = "غیرفعال‌سازی Fallback در صورت تطابق" -"disableFallbackIfMatchDesc" = "درخواست‌های DNS Fallback را زمانی که لیست دامنه‌های مطابقت‌یافته سرور DNS فعال است، غیرفعال می‌کند" -"enableParallelQuery" = "فعال‌سازی پرس‌وجوی موازی" -"enableParallelQueryDesc" = "فعال‌سازی پرس‌وجوهای DNS موازی به چندین سرور برای وضوح سریع‌تر" -"strategy" = "استراتژی پرس‌وجو" -"strategyDesc" = "استراتژی کلی برای حل نام دامنه" -"add" = "افزودن سرور" -"edit" = "ویرایش سرور" -"domains" = "دامنه‌ها" -"expectIPs" = "آی‌پی‌های مورد انتظار" -"unexpectIPs" = "آی‌پی‌های غیرمنتظره" -"useSystemHosts" = "استفاده از Hosts سیستم" -"useSystemHostsDesc" = "استفاده از فایل hosts یک سیستم نصب‌شده" -"usePreset" = "استفاده از پیش‌تنظیم" -"dnsPresetTitle" = "پیش‌تنظیم‌های DNS" -"dnsPresetFamily" = "خانوادگی" - -[pages.xray.fakedns] -"add" = "افزودن دی‌ان‌اس جعلی" -"edit" = "ویرایش دی‌ان‌اس جعلی" -"ipPool" = "زیرشبکه استخر آی‌پی" -"poolSize" = "اندازه استخر" - -[pages.settings.security] -"admin" = "اعتبارنامه‌های ادمین" -"twoFactor" = "احراز هویت دو مرحله‌ای" -"twoFactorEnable" = "فعال‌سازی 2FA" -"twoFactorEnableDesc" = "یک لایه اضافی امنیتی برای احراز هویت فراهم می‌کند." -"twoFactorModalSetTitle" = "فعال‌سازی احراز هویت دو مرحله‌ای" -"twoFactorModalDeleteTitle" = "غیرفعال‌سازی احراز هویت دو مرحله‌ای" -"twoFactorModalSteps" = "برای راه‌اندازی احراز هویت دو مرحله‌ای، مراحل زیر را انجام دهید:" -"twoFactorModalFirstStep" = "1. این کد QR را در برنامه احراز هویت اسکن کنید یا توکن کنار کد QR را کپی کرده و در برنامه بچسبانید" -"twoFactorModalSecondStep" = "2. کد را از برنامه وارد کنید" -"twoFactorModalRemoveStep" = "برای حذف احراز هویت دو مرحله‌ای، کد را از برنامه وارد کنید." -"twoFactorModalChangeCredentialsTitle" = "تغییر اعتبارنامه‌ها" -"twoFactorModalChangeCredentialsStep" = "برای تغییر اعتبارنامه‌های مدیر، کد را از برنامه وارد کنید." -"twoFactorModalSetSuccess" = "احراز هویت دو مرحله‌ای با موفقیت برقرار شد" -"twoFactorModalDeleteSuccess" = "احراز هویت دو مرحله‌ای با موفقیت حذف شد" -"twoFactorModalError" = "کد نادرست" - -[pages.settings.toasts] -"modifySettings" = "پارامترها تغییر کرده‌اند." -"getSettings" = "خطا در دریافت پارامترها" -"modifyUserError" = "خطا در تغییر اعتبارنامه‌های مدیر سیستم." -"modifyUser" = "شما با موفقیت اعتبارنامه‌های مدیر سیستم را تغییر دادید." -"originalUserPassIncorrect" = "نام‌کاربری یا رمزعبور فعلی اشتباه‌است" -"userPassMustBeNotEmpty" = "نام‌کاربری یا رمزعبور جدید خالی‌است" -"getOutboundTrafficError" = "خطا در دریافت ترافیک خروجی" -"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی" - -[tgbot] -"keyboardClosed" = "❌ صفحه کلید بسته شد!" -"noResult" = "❗ نتیجه ای یافت نشد!" -"noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!" -"wentWrong" = "❌ مشکلی پیش آمد!" -"noIpRecord" = "❗ رکورد آی پی وجود ندارد!" -"noInbounds" = "❗ هیچ ورودی یافت نشد!" -"unlimited" = "♾ نامحدود(ریست)" -"add" = "افزودن" -"month" = "ماه" -"months" = "ماه" -"day" = "روز" -"days" = "روز" -"hours" = "ساعت" -"minutes" = "دقیقه" -"unknown" = "نامشخص" -"inbounds" = "ورودی ها" -"clients" = "کاربران" -"offline" = "🔴 آفلاین" -"online" = "🟢 آنلاین" - -[tgbot.commands] -"unknown" = "❗ دستور ناشناخته" -"pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n" -"help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه داده‌های خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را می‌دهد.\r\n\r\n" -"start" = "👋 سلام {{ .Firstname }}.\r\n" -"welcome" = "🤖 به ربات مدیریت {{ .Hostname }} خوش آمدید.\r\n" -"status" = "✅ ربات در حالت عادی است!" -"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" -"getID" = "🆔 شناسه شما: {{ .ID }}" -"helpAdminCommands" = "برای راه‌اندازی مجدد Xray Core:\r\n/restart\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n/usage [ایمیل]\r\n\r\nبرای جستجوی ورودی‌ها (با آمار مشتری):\r\n/inbound [توضیحات]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id" -"helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n/usage [ایمیل]\r\n\r\nشناسه گفتگوی تلگرام:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ عملیات با موفقیت انجام شد!" -"restartFailed" = "❗ خطا در عملیات.\r\n\r\nخطا: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست." -"startDesc" = "نمایش منوی اصلی" -"helpDesc" = "راهنمای ربات" -"statusDesc" = "بررسی وضعیت ربات" -"idDesc" = "نمایش شناسه تلگرام شما" - -[tgbot.messages] -"cpuThreshold" = "🔴 بار ‌پردازنده {{ .Percent }}% بیشتر از آستانه است {{ .Threshold }}%" -"selectUserFailed" = "❌ خطا در انتخاب کاربر!" -"userSaved" = "✅ کاربر تلگرام ذخیره شد." -"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n" -"loginFailed" = "❗️ ورود به پنل ناموفق‌بود \r\n" -"2faFailed" = "خطای 2FA" -"report" = "🕰 گزارشات‌زمان‌بندی‌شده: {{ .RunTime }}\r\n" -"datetime" = "⏰ تاریخ‌وزمان: {{ .DateTime }}\r\n" -"hostname" = "💻 نام‌میزبان: {{ .Hostname }}\r\n" -"version" = "🚀 نسخه‌پنل: {{ .Version }}\r\n" -"xrayVersion" = "📡 نسخه‌هسته: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 آدرس‌آی‌پی: {{ .IP }}\r\n" -"ips" = "🔢 آدرس‌های آی‌پی:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ مدت‌کارکردسیستم: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 بارسیستم: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ وضعیت‌ایکس‌ری: {{ .State }}\r\n" -"username" = "👤 نام‌کاربری: {{ .Username }}\r\n" -"reason" = "❗️ دلیل: {{ .Reason }}\r\n" -"time" = "⏰ زمان: {{ .Time }}\r\n" -"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" -"port" = "🔌 پورت: {{ .Port }}\r\n" -"expire" = "📅 تاریخ‌انقضا: {{ .Time }}\r\n\r\n" -"expireIn" = "📅 باقی‌ مانده‌ تا انقضا: {{ .Time }}\r\n\r\n" -"active" = "💡 فعال: {{ .Enable }}\r\n" -"enabled" = "🚨 وضعیت: {{ .Enable }}\r\n" -"online" = "🌐 وضعیت اتصال: {{ .Status }}\r\n" -"lastOnline" = "🔙 آخرین فعالیت: {{ .Time }}\r\n" -"email" = "📧 ایمیل: {{ .Email }}\r\n" -"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n" -"download" = "🔽 دانلود↓: {{ .Download }}\r\n" -"total" = "🔄 کل: {{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 {{ .Type }} به‌اتمام‌رسیده‌است:\r\n" -"exhaustedCount" = "🚨 تعداد {{ .Type }} به‌اتمام‌رسیده‌است:\r\n" -"onlinesCount" = "🌐 کاربران‌آنلاین: {{ .Count }}\r\n" -"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 به‌زودی‌به‌پایان‌خواهدرسید: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 زمان‌پشتیبان‌گیری: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 تازه‌سازی شده در: {{ .Time }}\r\n\r\n" -"yes" = "✅ بله" -"no" = "❌ خیر" -"received_id" = "🔑📥 شناسه به‌روزرسانی شد." -"received_password" = "🔑📥 رمز عبور به‌روزرسانی شد." -"received_email" = "📧📥 ایمیل به‌روزرسانی شد." -"received_comment" = "💬📥 نظر به‌روزرسانی شد." -"id_prompt" = "🔑 شناسه پیش‌فرض: {{ .ClientId }}\n\nشناسه خود را وارد کنید." -"pass_prompt" = "🔑 رمز عبور پیش‌فرض: {{ .ClientPassword }}\n\nرمز عبور خود را وارد کنید." -"email_prompt" = "📧 ایمیل پیش‌فرض: {{ .ClientEmail }}\n\nایمیل خود را وارد کنید." -"comment_prompt" = "💬 نظر پیش‌فرض: {{ .ClientComment }}\n\nنظر خود را وارد کنید." -"inbound_client_data_id" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 شناسه: {{ .ClientId }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!" -"inbound_client_data_pass" = "🔄 ورودی: {{ .InboundRemark }}\n\n🔑 رمز عبور: {{ .ClientPass }}\n📧 ایمیل: {{ .ClientEmail }}\n📊 ترافیک: {{ .ClientTraffic }}\n📅 تاریخ انقضا: {{ .ClientExp }}\n🌐 محدودیت IP: {{ .IpLimit }}\n💬 توضیح: {{ .ClientComment }}\n\nاکنون می‌تونی مشتری را به ورودی اضافه کنی!" -"cancel" = "❌ فرآیند لغو شد! \n\nمی‌توانید هر زمان که خواستید /start را دوباره اجرا کنید. 🔄" -"error_add_client" = "⚠️ خطا:\n\n {{ .error }}" -"using_default_value" = "باشه، از مقدار پیش‌فرض استفاده می‌کنم. 😊" -"incorrect_input" = "ورودی شما معتبر نیست.\nعبارت‌ها باید بدون فاصله باشند.\nمثال صحیح: aaaaaa\nمثال نادرست: aaa aaa 🚫" -"AreYouSure" = "مطمئنی؟ 🤔" -"SuccessResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ✅ موفقیت‌آمیز" -"FailedResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ❌ ناموفق \n\n🛠️ خطا: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 فرآیند بازنشانی ترافیک برای همه مشتریان به پایان رسید." - -[tgbot.buttons] -"closeKeyboard" = "❌ بستن کیبورد" -"cancel" = "❌ لغو" -"cancelReset" = "❌ لغو تنظیم مجدد" -"cancelIpLimit" = "❌ لغو محدودیت آی‌پی" -"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟" -"confirmClearIps" = "✅ تأیید پاک‌سازی آدرس‌های آی‌پی؟" -"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟" -"confirmToggle" = "✅ تایید فعال/غیرفعال کردن کاربر؟" -"dbBackup" = "دریافت پشتیبان" -"serverUsage" = "استفاده از سیستم" -"getInbounds" = "دریافت ورودی‌ها" -"depleteSoon" = "به‌زودی به پایان خواهد رسید" -"clientUsage" = "دریافت آمار کاربر" -"onlines" = "کاربران آنلاین" -"commands" = "دستورات" -"refresh" = "🔄 تازه‌سازی" -"clearIPs" = "❌ پاک‌سازی آدرس‌ها" -"removeTGUser" = "❌ حذف کاربر تلگرام" -"selectTGUser" = "👤 انتخاب کاربر تلگرام" -"selectOneTGUser" = "👤 یک کاربر تلگرام را انتخاب کنید:" -"resetTraffic" = "📈 تنظیم مجدد ترافیک" -"resetExpire" = "📅 تنظیم مجدد تاریخ انقضا" -"ipLog" = "🔢 لاگ آدرس‌های IP" -"ipLimit" = "🔢 محدودیت IP" -"setTGUser" = "👤 تنظیم کاربر تلگرام" -"toggle" = "🔘 فعال / غیرفعال" -"custom" = "🔢 سفارشی" -"confirmNumber" = "✅ تایید: {{ .Num }}" -"confirmNumberAdd" = "✅ تایید اضافه کردن: {{ .Num }}" -"limitTraffic" = "🚧 محدودیت ترافیک" -"getBanLogs" = "گزارش های بلوک را دریافت کنید" -"allClients" = "همه مشتریان" -"addClient" = "افزودن مشتری" -"submitDisable" = "ارسال به عنوان غیرفعال ☑️" -"submitEnable" = "ارسال به عنوان فعال ✅" -"use_default" = "🏷️ استفاده از پیش‌فرض" -"change_id" = "⚙️🔑 شناسه" -"change_password" = "⚙️🔑 گذرواژه" -"change_email" = "⚙️📧 ایمیل" -"change_comment" = "⚙️💬 نظر" -"ResetAllTraffics" = "بازنشانی همه ترافیک‌ها" -"SortedTrafficUsageReport" = "گزارش استفاده از ترافیک مرتب‌شده" - -[tgbot.answers] -"successfulOperation" = "✅ انجام شد!" -"errorOperation" = "❗ خطا در عملیات." -"getInboundsFailed" = "❌ دریافت ورودی‌ها با خطا مواجه شد." -"getClientsFailed" = "❌ دریافت مشتریان با شکست مواجه شد." -"canceled" = "❌ {{ .Email }} : عملیات لغو شد." -"clientRefreshSuccess" = "✅ {{ .Email }} : کلاینت با موفقیت تازه‌سازی شد." -"IpRefreshSuccess" = "✅ {{ .Email }} : آدرس‌ها با موفقیت تازه‌سازی شدند." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازه‌سازی شد." -"resetTrafficSuccess" = "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : محدودیت ترافیک با موفقیت ذخیره شد." -"expireResetSuccess" = "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد." -"resetIpSuccess" = "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد." -"clearIpSuccess" = "✅ {{ .Email }} : آدرس‌ها با موفقیت پاک‌سازی شدند." -"getIpLog" = "✅ {{ .Email }} : دریافت لاگ آدرس‌های IP." -"getUserInfo" = "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام." -"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد." -"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد." -"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد." -"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: {{ .TgUserID }}" -"chooseClient" = "یک مشتری برای ورودی {{ .Inbound }} انتخاب کنید" -"chooseInbound" = "یک ورودی انتخاب کنید" diff --git a/web/translation/translate.id_ID.toml b/web/translation/translate.id_ID.toml deleted file mode 100644 index d8bce20b..00000000 --- a/web/translation/translate.id_ID.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Nama Pengguna" -"password" = "Kata Sandi" -"login" = "Masuk" -"confirm" = "Konfirmasi" -"cancel" = "Batal" -"close" = "Tutup" -"save" = "Simpan" -"logout" = "Keluar" -"create" = "Buat" -"update" = "Perbarui" -"copy" = "Salin" -"copied" = "Tersalin" -"download" = "Unduh" -"remark" = "Catatan" -"enable" = "Aktifkan" -"protocol" = "Protokol" -"search" = "Cari" -"filter" = "Filter" -"loading" = "Memuat..." -"second" = "Detik" -"minute" = "Menit" -"hour" = "Jam" -"day" = "Hari" -"check" = "Centang" -"indefinite" = "Tak Terbatas" -"unlimited" = "Tanpa Batas" -"none" = "None" -"qrCode" = "Kode QR" -"info" = "Informasi Lebih Lanjut" -"edit" = "Edit" -"delete" = "Hapus" -"reset" = "Reset" -"noData" = "Tidak ada data." -"copySuccess" = "Berhasil Disalin" -"sure" = "Yakin" -"encryption" = "Enkripsi" -"useIPv4ForHost" = "Gunakan IPv4 untuk host" -"transmission" = "Transmisi" -"host" = "Host" -"path" = "Jalur" -"camouflage" = "Obfuscation" -"status" = "Status" -"enabled" = "Aktif" -"disabled" = "Nonaktif" -"depleted" = "Habis" -"depletingSoon" = "Akan Habis" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Nama Domain" -"monitor" = "IP Pemantauan" -"certificate" = "Sertifikat Digital" -"fail" = "Gagal" -"comment" = "Komentar" -"success" = "Berhasil" -"lastOnline" = "Terakhir online" -"getVersion" = "Dapatkan Versi" -"install" = "Instal" -"clients" = "Klien" -"usage" = "Penggunaan" -"twoFactorCode" = "Kode" -"remained" = "Tersisa" -"security" = "Keamanan" -"secAlertTitle" = "Peringatan keamanan" -"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data." -"secAlertConf" = "Beberapa pengaturan rentan terhadap serangan. Disarankan untuk memperkuat protokol keamanan guna mencegah pelanggaran potensial." -"secAlertSSL" = "Panel kekurangan koneksi yang aman. Harap instal sertifikat TLS untuk perlindungan data." -"secAlertPanelPort" = "Port default panel rentan. Harap konfigurasi port acak atau tertentu." -"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks." -"secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks." -"secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks." -"emptyDnsDesc" = "Tidak ada server DNS yang ditambahkan." -"emptyFakeDnsDesc" = "Tidak ada server Fake DNS yang ditambahkan." -"emptyBalancersDesc" = "Tidak ada penyeimbang yang ditambahkan." -"emptyReverseDesc" = "Tidak ada proxy terbalik yang ditambahkan." -"somethingWentWrong" = "Terjadi kesalahan" - -[subscription] -"title" = "Info langganan" -"subId" = "ID langganan" -"status" = "Status" -"downloaded" = "Diunduh" -"uploaded" = "Diunggah" -"expiry" = "Kedaluwarsa" -"totalQuota" = "Kuota total" -"individualLinks" = "Tautan individual" -"active" = "Aktif" -"inactive" = "Nonaktif" -"unlimited" = "Tanpa batas" -"noExpiry" = "Tanpa kedaluwarsa" - -[menu] -"theme" = "Tema" -"dark" = "Gelap" -"ultraDark" = "Sangat Gelap" -"dashboard" = "Ikhtisar" -"inbounds" = "Masuk" -"settings" = "Pengaturan Panel" -"xray" = "Konfigurasi Xray" -"logout" = "Keluar" -"link" = "Kelola" - -[pages.login] -"hello" = "Halo" -"title" = "Selamat Datang" -"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali" - -[pages.login.toasts] -"invalidFormData" = "Format data input tidak valid." -"emptyUsername" = "Nama Pengguna diperlukan" -"emptyPassword" = "Kata Sandi diperlukan" -"wrongUsernameOrPassword" = "Username, kata sandi, atau kode dua faktor tidak valid." -"successLogin" = "Anda telah berhasil masuk ke akun Anda." - -[pages.index] -"title" = "Ikhtisar" -"cpu" = "CPU" -"logicalProcessors" = "Prosesor logis" -"frequency" = "Frekuensi" -"swap" = "Swap" -"storage" = "Penyimpanan" -"memory" = "RAM" -"threads" = "Thread" -"xrayStatus" = "Xray" -"stopXray" = "Stop" -"restartXray" = "Restart" -"xraySwitch" = "Versi" -"xraySwitchClick" = "Pilih versi yang ingin Anda pindah." -"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini." -"xrayUpdates" = "Pembaruan Xray" -"updatePanel" = "Perbarui Panel" -"panelUpdateDesc" = "Ini akan memperbarui 3X-UI ke rilis terbaru dan me-restart layanan panel." -"currentPanelVersion" = "Versi panel saat ini" -"latestPanelVersion" = "Versi panel terbaru" -"panelUpToDate" = "Panel sudah terbaru" -"upToDate" = "Terbaru" -"xrayStatusUnknown" = "Tidak diketahui" -"xrayStatusRunning" = "Berjalan" -"xrayStatusStop" = "Berhenti" -"xrayStatusError" = "Kesalahan" -"xrayErrorPopoverTitle" = "Terjadi kesalahan saat menjalankan Xray" -"operationHours" = "Waktu Aktif" -"systemLoad" = "Beban Sistem" -"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir" -"connectionCount" = "Statistik Koneksi" -"ipAddresses" = "Alamat IP" -"toggleIpVisibility" = "Alihkan visibilitas IP" -"overallSpeed" = "Kecepatan keseluruhan" -"upload" = "Unggah" -"download" = "Unduh" -"totalData" = "Total data" -"sent" = "Dikirim" -"received" = "Diterima" -"documentation" = "Dokumentasi" -"xraySwitchVersionDialog" = "Apakah Anda yakin ingin mengubah versi Xray?" -"xraySwitchVersionDialogDesc" = "Ini akan mengubah versi Xray ke #version#." -"xraySwitchVersionPopover" = "Xray berhasil diperbarui" -"panelUpdateDialog" = "Apakah Anda benar-benar ingin memperbarui panel?" -"panelUpdateDialogDesc" = "Ini akan memperbarui 3X-UI ke #version# dan me-restart layanan panel." -"panelUpdateCheckPopover" = "Pemeriksaan pembaruan panel gagal" -"panelUpdateStartedPopover" = "Pembaruan panel dimulai" -"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?" -"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#." -"geofilesUpdateDialogDesc" = "Ini akan memperbarui semua berkas." -"geofilesUpdateAll" = "Perbarui semua" -"geofileUpdatePopover" = "Geofile berhasil diperbarui" -"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" -"logs" = "Log" -"config" = "Konfigurasi" -"backup" = "Cadangan" -"backupTitle" = "Cadangan & Pulihkan Database" -"exportDatabase" = "Cadangkan" -"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda." -"importDatabase" = "Pulihkan" -"importDatabaseDesc" = "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan." -"importDatabaseSuccess" = "Database berhasil diimpor" -"importDatabaseError" = "Terjadi kesalahan saat mengimpor database" -"readDatabaseError" = "Terjadi kesalahan saat membaca database" -"getDatabaseError" = "Terjadi kesalahan saat mengambil database" -"getConfigError" = "Terjadi kesalahan saat mengambil file konfigurasi" -"customGeoTitle" = "GeoSite / GeoIP kustom" -"customGeoAdd" = "Tambah" -"customGeoType" = "Jenis" -"customGeoAlias" = "Alias" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Aktif" -"customGeoLastUpdated" = "Terakhir diperbarui" -"customGeoExtColumn" = "Routing (ext:…)" -"customGeoToastUpdateAll" = "Semua sumber kustom telah diperbarui" -"customGeoActions" = "Aksi" -"customGeoEdit" = "Edit" -"customGeoDelete" = "Hapus" -"customGeoDownload" = "Perbarui sekarang" -"customGeoModalAdd" = "Tambah geo kustom" -"customGeoModalEdit" = "Edit geo kustom" -"customGeoModalSave" = "Simpan" -"customGeoDeleteConfirm" = "Hapus sumber geo kustom ini?" -"customGeoRoutingHint" = "Pada aturan routing gunakan kolom nilai sebagai ext:file.dat:tag (ganti tag)." -"customGeoInvalidId" = "ID sumber tidak valid" -"customGeoAliasesError" = "Gagal memuat alias geo kustom" -"customGeoValidationAlias" = "Alias hanya huruf kecil, angka, - dan _" -"customGeoValidationUrl" = "URL harus diawali http:// atau https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (kustom)" -"customGeoToastList" = "Daftar geo kustom" -"customGeoToastAdd" = "Tambah geo kustom" -"customGeoToastUpdate" = "Perbarui geo kustom" -"customGeoToastDelete" = "Geofile kustom “{{ .fileName }}” dihapus" -"customGeoToastDownload" = "Geofile “{{ .fileName }}” diperbarui" -"customGeoErrInvalidType" = "Jenis harus geosite atau geoip" -"customGeoErrAliasRequired" = "Alias wajib diisi" -"customGeoErrAliasPattern" = "Alias berisi karakter yang tidak diizinkan" -"customGeoErrAliasReserved" = "Alias ini dicadangkan" -"customGeoErrUrlRequired" = "URL wajib diisi" -"customGeoErrInvalidUrl" = "URL tidak valid" -"customGeoErrUrlScheme" = "URL harus memakai http atau https" -"customGeoErrUrlHost" = "Host URL tidak valid" -"customGeoErrDuplicateAlias" = "Alias ini sudah dipakai untuk jenis ini" -"customGeoErrNotFound" = "Sumber geo kustom tidak ditemukan" -"customGeoErrDownload" = "Unduh gagal" -"customGeoErrUpdateAllIncomplete" = "Satu atau lebih sumber geo kustom gagal diperbarui" -"customGeoEmpty" = "Belum ada sumber geo kustom — klik Tambah untuk membuatnya" - -[pages.inbounds] -"allTimeTraffic" = "Total Lalu Lintas" -"allTimeTrafficUsage" = "Total Penggunaan Sepanjang Waktu" -"title" = "Masuk" -"totalDownUp" = "Total Terkirim/Diterima" -"totalUsage" = "Penggunaan Total" -"inboundCount" = "Total Masuk" -"operate" = "Menu" -"enable" = "Aktifkan" -"remark" = "Catatan" -"protocol" = "Protokol" -"port" = "Port" -"portMap" = "Port Mapping" -"traffic" = "Traffic" -"details" = "Rincian" -"transportConfig" = "Transport" -"expireDate" = "Durasi" -"createdAt" = "Dibuat" -"updatedAt" = "Diperbarui" -"resetTraffic" = "Reset Traffic" -"addInbound" = "Tambahkan Masuk" -"generalActions" = "Tindakan Umum" -"autoRefresh" = "Pembaruan otomatis" -"autoRefreshInterval" = "Interval" -"modifyInbound" = "Ubah Masuk" -"deleteInbound" = "Hapus Masuk" -"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?" -"deleteClient" = "Hapus Klien" -"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?" -"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?" -"copyLink" = "Salin URL" -"address" = "Alamat" -"network" = "Jaringan" -"destinationPort" = "Port Tujuan" -"targetAddress" = "Alamat Target" -"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP" -"meansNoLimit" = "= Unlimited. (unit: GB)" -"totalFlow" = "Total Aliran" -"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa" -"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default" -"certificatePath" = "Path Berkas" -"certificateContent" = "Konten Berkas" -"publicKey" = "Kunci Publik" -"privatekey" = "Kunci Pribadi" -"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin" -"client" = "Klien" -"export" = "Ekspor Semua URL" -"clone" = "Duplikat" -"cloneInbound" = "Duplikat" -"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat." -"cloneInboundOk" = "Duplikat" -"resetAllTraffic" = "Reset Semua Traffic Masuk" -"resetAllTrafficTitle" = "Reset Semua Traffic Masuk" -"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?" -"resetInboundClientTraffics" = "Reset Traffic Klien Masuk" -"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk" -"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?" -"resetAllClientTraffics" = "Reset Traffic Semua Klien" -"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien" -"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?" -"delDepletedClients" = "Hapus Klien Habis" -"delDepletedClientsTitle" = "Hapus Klien Habis" -"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?" -"email" = "Email" -"emailDesc" = "Harap berikan alamat email yang unik." -"IPLimit" = "Batas IP" -"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)" -"IPLimitlog" = "Log IP" -"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)" -"IPLimitlogclear" = "Hapus Log" -"setDefaultCert" = "Atur Sertifikat dari Panel" -"telegramDesc" = "Harap berikan ID Obrolan Telegram. (gunakan perintah '/id' di bot) atau (@userinfobot)" -"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien." -"info" = "Info" -"same" = "Sama" -"inboundData" = "Data Masuk" -"exportInbound" = "Ekspor Masuk" -"import" = "Impor" -"importInbound" = "Impor Masuk" -"periodicTrafficResetTitle" = "Reset Trafik Berkala" -"periodicTrafficResetDesc" = "Reset otomatis penghitung trafik pada interval tertentu" -"lastReset" = "Reset Terakhir" - -[pages.client] -"add" = "Tambah Klien" -"edit" = "Edit Klien" -"submitAdd" = "Tambah Klien" -"submitEdit" = "Simpan Perubahan" -"clientCount" = "Jumlah Klien" -"bulk" = "Tambahkan Massal" -"copyFromInbound" = "Salin klien dari inbound" -"copyToInbound" = "Salin klien ke" -"copySelected" = "Salin yang dipilih" -"copySource" = "Sumber" -"copyEmailPreview" = "Pratinjau email hasil" -"copySelectSourceFirst" = "Silakan pilih inbound sumber terlebih dahulu." -"copyResult" = "Hasil penyalinan" -"copyResultSuccess" = "Berhasil disalin" -"copyResultNone" = "Tidak ada yang disalin: tidak ada klien yang dipilih atau sumber kosong" -"copyResultErrors" = "Kesalahan penyalinan" -"copyFlowLabel" = "Flow untuk klien baru (VLESS)" -"copyFlowHint" = "Diterapkan ke semua klien yang disalin. Biarkan kosong untuk melewati." -"selectAll" = "Pilih semua" -"clearAll" = "Hapus semua" -"method" = "Metode" -"first" = "Pertama" -"last" = "Terakhir" -"prefix" = "Awalan" -"postfix" = "Akhiran" -"delayedStart" = "Mulai Awal" -"expireDays" = "Durasi" -"days" = "Hari" -"renew" = "Perpanjang Otomatis" -"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Tidak Pernah" -"daily" = "Harian" -"weekly" = "Mingguan" -"monthly" = "Bulanan" -"hourly" = "Setiap jam" - -[pages.inbounds.toasts] -"obtain" = "Dapatkan" -"updateSuccess" = "Pembaruan berhasil" -"logCleanSuccess" = "Log telah dibersihkan" -"inboundsUpdateSuccess" = "Inbound berhasil diperbarui" -"inboundUpdateSuccess" = "Inbound berhasil diperbarui" -"inboundCreateSuccess" = "Inbound berhasil dibuat" -"inboundDeleteSuccess" = "Inbound berhasil dihapus" -"inboundClientAddSuccess" = "Klien inbound telah ditambahkan" -"inboundClientDeleteSuccess" = "Klien inbound telah dihapus" -"inboundClientUpdateSuccess" = "Klien inbound telah diperbarui" -"delDepletedClientsSuccess" = "Semua klien yang habis telah dihapus" -"resetAllClientTrafficSuccess" = "Semua lalu lintas klien telah direset" -"resetAllTrafficSuccess" = "Semua lalu lintas telah direset" -"resetInboundClientTrafficSuccess" = "Lalu lintas telah direset" -"trafficGetError" = "Gagal mendapatkan data lalu lintas" -"getNewX25519CertError" = "Terjadi kesalahan saat mendapatkan sertifikat X25519." -"getNewmldsa65Error" = "Terjadi kesalahan saat mendapatkan sertifikat mldsa65." -"getNewVlessEncError" = "Terjadi kesalahan saat mendapatkan sertifikat VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Permintaan" -"response" = "Respons" -"name" = "Nama" -"value" = "Nilai" - -[pages.inbounds.stream.tcp] -"version" = "Versi" -"method" = "Metode" -"path" = "Path" -"status" = "Status" -"statusDescription" = "Deskripsi Status" -"requestHeader" = "Header Permintaan" -"responseHeader" = "Header Respons" - -[pages.settings] -"title" = "Pengaturan Panel" -"save" = "Simpan" -"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan." -"restartPanel" = "Restart Panel" -"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server." -"restartPanelSuccess" = "Panel berhasil dimulai ulang" -"actions" = "Tindakan" -"resetDefaultConfig" = "Reset ke Default" -"panelSettings" = "Umum" -"securitySettings" = "Otentikasi" -"TGBotSettings" = "Bot Telegram" -"panelListeningIP" = "IP Pendengar" -"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)" -"panelListeningDomain" = "Domain Pendengar" -"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)" -"panelPort" = "Port Pendengar" -"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)" -"publicKeyPath" = "Path Kunci Publik" -"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan ‘/‘)" -"privateKeyPath" = "Path Kunci Privat" -"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan ‘/‘)" -"panelUrlPath" = "URI Path" -"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" -"pageSize" = "Ukuran Halaman" -"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)" -"remarkModel" = "Model Catatan & Karakter Pemisah" -"datepicker" = "Jenis Kalender" -"datepickerPlaceholder" = "Pilih tanggal" -"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini." -"sampleRemark" = "Contoh Catatan" -"oldUsername" = "Username Saat Ini" -"currentPassword" = "Kata Sandi Saat Ini" -"newUsername" = "Username Baru" -"newPassword" = "Kata Sandi Baru" -"telegramBotEnable" = "Aktifkan Bot Telegram" -"telegramBotEnableDesc" = "Mengaktifkan bot Telegram." -"telegramToken" = "Token Telegram" -"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'." -"telegramProxy" = "Proxy SOCKS" -"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Server API Telegram yang akan digunakan. Biarkan kosong untuk menggunakan server default." -"telegramChatId" = "ID Obrolan Admin" -"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)" -"telegramNotifyTime" = "Waktu Notifikasi" -"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)" -"tgNotifyBackup" = "Cadangan Database" -"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan." -"tgNotifyLogin" = "Notifikasi Login" -"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda." -"sessionMaxAge" = "Durasi Sesi" -"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)" -"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa" -"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)" -"trafficDiff" = "Notifikasi Batas Traffic" -"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)" -"tgNotifyCpu" = "Notifikasi Beban CPU" -"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)" -"timeZone" = "Zone Waktu" -"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini." -"subSettings" = "Langganan" -"subEnable" = "Aktifkan Layanan Langganan" -"subEnableDesc" = "Mengaktifkan layanan langganan." -"subJsonEnable" = "Aktifkan/Nonaktifkan endpoint langganan JSON secara mandiri." -"subTitle" = "Judul Langganan" -"subTitleDesc" = "Judul yang ditampilkan di klien VPN" -"subSupportUrl" = "URL Dukungan" -"subSupportUrlDesc" = "Tautan dukungan teknis yang ditampilkan di klien VPN" -"subProfileUrl" = "URL Profil" -"subProfileUrlDesc" = "Tautan ke situs web Anda yang ditampilkan di klien VPN" -"subAnnounce" = "Pengumuman" -"subAnnounceDesc" = "Teks pengumuman yang ditampilkan di klien VPN" -"subEnableRouting" = "Aktifkan perutean" -"subEnableRoutingDesc" = "Pengaturan global untuk mengaktifkan perutean (routing) di klien VPN. (Hanya untuk Happ)" -"subRoutingRules" = "Aturan routing" -"subRoutingRulesDesc" = "Aturan routing global untuk klien VPN. (Hanya untuk Happ)" -"subListen" = "IP Pendengar" -"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)" -"subPort" = "Port Pendengar" -"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)" -"subCertPath" = "Path Kunci Publik" -"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan ‘/‘)" -"subKeyPath" = "Path Kunci Privat" -"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan ‘/‘)" -"subPath" = "URI Path" -"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)" -"subDomain" = "Domain Pendengar" -"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)" -"subUpdates" = "Interval Pembaruan" -"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)" -"subEncrypt" = "Encode" -"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64." -"subShowInfo" = "Tampilkan Info Penggunaan" -"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." -"subURI" = "URI Proxy Terbalik" -"subURIDesc" = "Path URI dari URL langganan untuk digunakan di belakang proxy." -"externalTrafficInformEnable" = "Informasikan API eksternal pada setiap pembaruan lalu lintas." -"externalTrafficInformEnableDesc" = "Inform external API on every traffic update." -"externalTrafficInformURI" = "Lalu Lintas Eksternal Menginformasikan URI" -"externalTrafficInformURIDesc" = "Pembaruan lalu lintas dikirim ke URI ini." -"restartXrayOnClientDisable" = "Nyalakan Ulang Xray Setelah Nonaktif Otomatis" -"restartXrayOnClientDisableDesc" = "Saat klien otomatis dinonaktifkan karena kedaluwarsa atau batas trafik, mulai ulang Xray." -"fragment" = "Fragmentasi" -"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS" -"fragmentSett" = "Pengaturan Fragmentasi" -"noisesDesc" = "Aktifkan Noises." -"noisesSett" = "Pengaturan Noises" -"mux" = "Mux" -"muxDesc" = "Mengirimkan beberapa aliran data independen dalam aliran data yang sudah ada." -"muxSett" = "Pengaturan Mux" -"direct" = "Koneksi langsung" -"directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu." -"notifications" = "Notifikasi" -"certs" = "Sertifikat" -"externalTraffic" = "Lalu Lintas Eksternal" -"dateAndTime" = "Tanggal dan Waktu" -"proxyAndServer" = "Proxy dan Server" -"intervals" = "Interval" -"information" = "Informasi" -"language" = "Bahasa" -"telegramBotLanguage" = "Bahasa Bot Telegram" - -[pages.xray] -"title" = "Konfigurasi Xray" -"save" = "Simpan" -"restart" = "Restart Xray" -"restartSuccess" = "Xray berhasil diluncurkan ulang" -"stopSuccess" = "Xray telah berhasil dihentikan" -"restartError" = "Terjadi kesalahan saat memulai ulang Xray." -"stopError" = "Terjadi kesalahan saat menghentikan Xray." -"basicTemplate" = "Dasar" -"advancedTemplate" = "Lanjutan" -"generalConfigs" = "Strategi Umum" -"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum." -"logConfigs" = "Catatan" -"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan" -"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta." -"basicRouting" = "Perutean Dasar" -"blockConnectionsConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta." -"directConnectionsConfigsDesc" = "Koneksi langsung memastikan bahwa lalu lintas tertentu tidak dialihkan melalui server lain." -"blockips" = "Blokir IP" -"blockdomains" = "Blokir Domain" -"directips" = "IP Langsung" -"directdomains" = "Domain Langsung" -"ipv4Routing" = "Perutean IPv4" -"ipv4RoutingDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4." -"warpRouting" = "Perutean WARP" -"warpRoutingDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP." -"nordRouting" = "Routing NordVPN" -"nordRoutingDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui NordVPN." -"Template" = "Template Konfigurasi Xray Lanjutan" -"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini." -"FreedomStrategy" = "Strategi Protokol Freedom" -"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom." -"RoutingStrategy" = "Strategi Pengalihan Keseluruhan" -"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan." -"outboundTestUrl" = "URL tes outbound" -"outboundTestUrlDesc" = "URL yang digunakan saat menguji konektivitas outbound" -"Torrent" = "Blokir Protokol BitTorrent" -"Inbounds" = "Masuk" -"InboundsDesc" = "Menerima klien tertentu." -"Outbounds" = "Keluar" -"Balancers" = "Penyeimbang" -"OutboundsDesc" = "Atur jalur lalu lintas keluar." -"Routings" = "Aturan Pengalihan" -"RoutingsDesc" = "Prioritas setiap aturan penting!" -"completeTemplate" = "Semua" -"logLevel" = "Tingkat Log" -"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat." -"accessLog" = "Log Akses" -"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses" -"errorLog" = "Catatan eror" -"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan" -"dnsLog" = "Log DNS" -"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS" -"maskAddress" = "Alamat Masker" -"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log." -"statistics" = "Statistik" -"statsInboundUplink" = "Statistik Unggah Masuk" -"statsInboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy masuk." -"statsInboundDownlink" = "Statistik Unduh Masuk" -"statsInboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy masuk." -"statsOutboundUplink" = "Statistik Unggah Keluar" -"statsOutboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy keluar." -"statsOutboundDownlink" = "Statistik Unduh Keluar" -"statsOutboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy keluar." - -[pages.xray.rules] -"first" = "Pertama" -"last" = "Terakhir" -"up" = "Naik" -"down" = "Turun" -"source" = "Sumber" -"dest" = "Tujuan" -"inbound" = "Masuk" -"outbound" = "Keluar" -"balancer" = "Pengimbang" -"info" = "Info" -"add" = "Tambahkan Aturan" -"edit" = "Edit Aturan" -"useComma" = "Item yang dipisahkan koma" - -[pages.xray.outbound] -"addOutbound" = "Tambahkan Keluar" -"addReverse" = "Tambahkan Revers" -"editOutbound" = "Edit Keluar" -"editReverse" = "Edit Revers" -"reverseTag" = "Tag Revers" -"reverseTagDesc" = "Tag outbound proxy revers sederhana VLESS. Kosongkan untuk menonaktifkan." -"reverseTagPlaceholder" = "tag outbound (kosong untuk menonaktifkan)" -"tag" = "Tag" -"tagDesc" = "Tag Unik" -"address" = "Alamat" -"reverse" = "Revers" -"domain" = "Domain" -"type" = "Tipe" -"bridge" = "Jembatan" -"portal" = "Portal" -"link" = "Tautan" -"intercon" = "Interkoneksi" -"settings" = "Pengaturan" -"accountInfo" = "Informasi Akun" -"outboundStatus" = "Status Keluar" -"sendThrough" = "Kirim Melalui" -"test" = "Tes" -"testResult" = "Hasil Tes" -"testing" = "Menguji koneksi..." -"testSuccess" = "Tes berhasil" -"testFailed" = "Tes gagal" -"testError" = "Gagal menguji outbound" -"nordvpn" = "NordVPN" -"accessToken" = "Token Akses" -"country" = "Negara" -"server" = "Server" -"city" = "Kota" -"allCities" = "Semua Kota" -"privateKey" = "Kunci Privat" -"load" = "Beban" - -[pages.xray.balancer] -"addBalancer" = "Tambahkan Penyeimbang" -"editBalancer" = "Sunting Penyeimbang" -"balancerStrategy" = "Strategi" -"balancerSelectors" = "Penyeleksi" -"tag" = "Menandai" -"tagDesc" = "Label Unik" -"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi." - -[pages.xray.wireguard] -"secretKey" = "Kunci Rahasia" -"publicKey" = "Kunci Publik" -"allowedIPs" = "IP yang Diizinkan" -"endpoint" = "Titik Akhir" -"psk" = "Kunci Pra-Bagi" -"domainStrategy" = "Strategi Domain" - -[pages.xray.tun] -"nameDesc" = "Nama antarmuka TUN. Standar adalah 'xray0'" -"mtuDesc" = "Unit Transmisi Maksimum. Ukuran maksimum paket data. Standar adalah 1500" -"userLevel" = "Level Pengguna" -"userLevelDesc" = "Semua koneksi yang dibuat melalui inbound ini akan menggunakan level pengguna ini. Standar adalah 0" - -[pages.xray.dns] -"enable" = "Aktifkan DNS" -"enableDesc" = "Aktifkan server DNS bawaan" -"tag" = "Tanda DNS Masuk" -"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan." -"clientIp" = "IP Klien" -"clientIpDesc" = "Digunakan untuk memberi tahu server tentang lokasi IP yang ditentukan selama kueri DNS" -"disableCache" = "Nonaktifkan cache" -"disableCacheDesc" = "Menonaktifkan caching DNS" -"disableFallback" = "Nonaktifkan Fallback" -"disableFallbackDesc" = "Menonaktifkan kueri DNS fallback" -"disableFallbackIfMatch" = "Nonaktifkan Fallback Jika Cocok" -"disableFallbackIfMatchDesc" = "Menonaktifkan kueri DNS fallback ketika daftar domain yang cocok dari server DNS terpenuhi" -"enableParallelQuery" = "Aktifkan Kueri Paralel" -"enableParallelQueryDesc" = "Aktifkan kueri DNS paralel ke beberapa server untuk resolusi yang lebih cepat" -"strategy" = "Strategi Kueri" -"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain" -"add" = "Tambahkan Server" -"edit" = "Sunting Server" -"domains" = "Domains" -"expectIPs" = "IP yang Diharapkan" -"unexpectIPs" = "IP tak terduga" -"useSystemHosts" = "Gunakan Hosts Sistem" -"useSystemHostsDesc" = "Gunakan file hosts dari sistem yang terinstal" -"usePreset" = "Gunakan templat" -"dnsPresetTitle" = "Templat DNS" -"dnsPresetFamily" = "Keluarga" - -[pages.xray.fakedns] -"add" = "Tambahkan DNS Palsu" -"edit" = "Edit DNS Palsu" -"ipPool" = "Subnet Kumpulan IP" -"poolSize" = "Ukuran Kolam" - -[pages.settings.security] -"admin" = "Kredensial admin" -"twoFactor" = "Autentikasi dua faktor" -"twoFactorEnable" = "Aktifkan 2FA" -"twoFactorEnableDesc" = "Menambahkan lapisan autentikasi tambahan untuk keamanan lebih." -"twoFactorModalSetTitle" = "Aktifkan autentikasi dua faktor" -"twoFactorModalDeleteTitle" = "Nonaktifkan autentikasi dua faktor" -"twoFactorModalSteps" = "Untuk menyiapkan autentikasi dua faktor, lakukan beberapa langkah:" -"twoFactorModalFirstStep" = "1. Pindai kode QR ini di aplikasi autentikasi atau salin token di dekat kode QR dan tempelkan ke aplikasi" -"twoFactorModalSecondStep" = "2. Masukkan kode dari aplikasi" -"twoFactorModalRemoveStep" = "Masukkan kode dari aplikasi untuk menghapus autentikasi dua faktor." -"twoFactorModalChangeCredentialsTitle" = "Ubah kredensial" -"twoFactorModalChangeCredentialsStep" = "Masukkan kode dari aplikasi untuk mengubah kredensial administrator." -"twoFactorModalSetSuccess" = "Autentikasi dua faktor telah berhasil dibuat" -"twoFactorModalDeleteSuccess" = "Autentikasi dua faktor telah berhasil dihapus" -"twoFactorModalError" = "Kode salah" - -[pages.settings.toasts] -"modifySettings" = "Parameter telah diubah." -"getSettings" = "Terjadi kesalahan saat mengambil parameter." -"modifyUserError" = "Terjadi kesalahan saat mengubah kredensial administrator." -"modifyUser" = "Anda telah berhasil mengubah kredensial administrator." -"originalUserPassIncorrect" = "Username atau password saat ini tidak valid" -"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong" -"getOutboundTrafficError" = "Gagal mendapatkan lalu lintas keluar" -"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar" - -[tgbot] -"keyboardClosed" = "❌ Keyboard ditutup!" -"noResult" = "❗ Tidak ada hasil!" -"noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!" -"wentWrong" = "❌ Terjadi kesalahan!" -"noIpRecord" = "❗ Tidak ada Catatan IP!" -"noInbounds" = "❗ Tidak ada inbound yang ditemukan!" -"unlimited" = "♾ Tidak terbatas (Reset)" -"add" = "Tambah" -"month" = "Bulan" -"months" = "Bulan" -"day" = "Hari" -"days" = "Hari" -"hours" = "Jam" -"minutes" = "Menit" -"unknown" = "Tidak diketahui" -"inbounds" = "Inbound" -"clients" = "Klien" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Perintah tidak dikenal." -"pleaseChoose" = "👇 Harap pilih:\r\n" -"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n" -"start" = "👋 Halo {{ .Firstname }}.\r\n" -"welcome" = "🤖 Selamat datang di {{.Hostname }} bot managemen.\r\n" -"status" = "✅ Bot dalam keadaan baik!" -"usage" = "❗ Harap berikan teks untuk mencari!" -"getID" = "🆔 ID Anda: {{ .ID }}" -"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n/restart\r\n\r\nUntuk mencari email klien:\r\n/usage [Email]\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n/inbound [Catatan]\r\n\r\nID Obrolan Telegram:\r\n/id" -"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n/usage [Email]\r\n\r\nID Obrolan Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Operasi berhasil!" -"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\nError: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core tidak berjalan." -"startDesc" = "Tampilkan menu utama" -"helpDesc" = "Bantuan bot" -"statusDesc" = "Periksa status bot" -"idDesc" = "Tampilkan ID Telegram Anda" - -[tgbot.messages] -"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%" -"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!" -"userSaved" = "✅ Pengguna Telegram tersimpan." -"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n" -"loginFailed" = "❗️ Gagal masuk ke panel.\r\n" -"2faFailed" = "2FA Gagal" -"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n" -"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versi Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Nama Pengguna: {{ .Username }}\r\n" -"reason" = "❗️ Alasan: {{ .Reason }}\r\n" -"time" = "⏰ Waktu: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n" -"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n" -"active" = "💡 Aktif: {{ .Enable }}\r\n" -"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n" -"online" = "🌐 Status Koneksi: {{ .Status }}\r\n" -"lastOnline" = "🔙 Terakhir online: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n" -"download" = "🔽 Unduh: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n" -"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n" -"yes" = "✅ Ya" -"no" = "❌ Tidak" -"received_id" = "🔑📥 ID diperbarui." -"received_password" = "🔑📥 Kata sandi diperbarui." -"received_email" = "📧📥 Email diperbarui." -"received_comment" = "💬📥 Komentar diperbarui." -"id_prompt" = "🔑 ID Default: {{ .ClientId }}\n\nMasukkan ID Anda." -"pass_prompt" = "🔑 Kata Sandi Default: {{ .ClientPassword }}\n\nMasukkan kata sandi Anda." -"email_prompt" = "📧 Email Default: {{ .ClientEmail }}\n\nMasukkan email Anda." -"comment_prompt" = "💬 Komentar Default: {{ .ClientComment }}\n\nMasukkan komentar Anda." -"inbound_client_data_id" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!" -"inbound_client_data_pass" = "🔄 Masuk: {{ .InboundRemark }}\n\n🔑 Kata sandi: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Lalu lintas: {{ .ClientTraffic }}\n📅 Tanggal Kedaluwarsa: {{ .ClientExp }}\n🌐 Batas IP: {{ .IpLimit }}\n💬 Komentar: {{ .ClientComment }}\n\nSekarang kamu bisa menambahkan klien ke inbound!" -"cancel" = "❌ Proses Dibatalkan! \n\nAnda dapat /start lagi kapan saja. 🔄" -"error_add_client" = "⚠️ Kesalahan:\n\n {{ .error }}" -"using_default_value" = "Oke, saya akan tetap menggunakan nilai default. 😊" -"incorrect_input" = "Masukan Anda tidak valid.\nFrasa harus berlanjut tanpa spasi.\nContoh benar: aaaaaa\nContoh salah: aaa aaa 🚫" -"AreYouSure" = "Apakah kamu yakin? 🤔" -"SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ✅ Berhasil" -"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ❌ Gagal \n\n🛠️ Kesalahan: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Proses reset traffic selesai untuk semua klien." - -[tgbot.buttons] -"closeKeyboard" = "❌ Tutup Papan Ketik" -"cancel" = "❌ Batal" -"cancelReset" = "❌ Batal Reset" -"cancelIpLimit" = "❌ Batal Batas IP" -"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?" -"confirmClearIps" = "✅ Konfirmasi Hapus IPs?" -"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?" -"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?" -"dbBackup" = "Dapatkan Cadangan DB" -"serverUsage" = "Penggunaan Server" -"getInbounds" = "Dapatkan Inbounds" -"depleteSoon" = "Habis Sebentar" -"clientUsage" = "Dapatkan Penggunaan" -"onlines" = "Klien Online" -"commands" = "Perintah" -"refresh" = "🔄 Perbarui" -"clearIPs" = "❌ Hapus IPs" -"removeTGUser" = "❌ Hapus Pengguna Telegram" -"selectTGUser" = "👤 Pilih Pengguna Telegram" -"selectOneTGUser" = "👤 Pilih Pengguna Telegram:" -"resetTraffic" = "📈 Reset Lalu Lintas" -"resetExpire" = "📅 Ubah Tanggal Kadaluarsa" -"ipLog" = "🔢 Log IP" -"ipLimit" = "🔢 Batas IP" -"setTGUser" = "👤 Set Pengguna Telegram" -"toggle" = "🔘 Aktifkan / Nonaktifkan" -"custom" = "🔢 Kustom" -"confirmNumber" = "✅ Konfirmasi: {{ .Num }}" -"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}" -"limitTraffic" = "🚧 Batas Lalu Lintas" -"getBanLogs" = "Dapatkan Log Pemblokiran" -"allClients" = "Semua Klien" -"addClient" = "Tambah Klien" -"submitDisable" = "Kirim Sebagai Nonaktif ☑️" -"submitEnable" = "Kirim Sebagai Aktif ✅" -"use_default" = "🏷️ Gunakan Default" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Kata Sandi" -"change_email" = "⚙️📧 Email" -"change_comment" = "⚙️💬 Komentar" -"ResetAllTraffics" = "Reset Semua Lalu Lintas" -"SortedTrafficUsageReport" = "Laporan Penggunaan Lalu Lintas yang Terurut" - -[tgbot.answers] -"successfulOperation" = "✅ Operasi berhasil!" -"errorOperation" = "❗ Kesalahan dalam operasi." -"getInboundsFailed" = "❌ Gagal mendapatkan inbounds." -"getClientsFailed" = "❌ Gagal mendapatkan klien." -"canceled" = "❌ {{ .Email }}: Operasi dibatalkan." -"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil." -"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil." -"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil." -"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil." -"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil." -"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP." -"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil." -"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil." -"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil." -"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ChatID Telegram Anda dalam konfigurasi Anda.\r\n\r\nChatID Pengguna Anda: {{ .TgUserID }}" -"chooseClient" = "Pilih Klien untuk Inbound {{ .Inbound }}" -"chooseInbound" = "Pilih Inbound" diff --git a/web/translation/translate.ja_JP.toml b/web/translation/translate.ja_JP.toml deleted file mode 100644 index eceabf8a..00000000 --- a/web/translation/translate.ja_JP.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "ユーザー名" -"password" = "パスワード" -"login" = "ログイン" -"confirm" = "確認" -"cancel" = "キャンセル" -"close" = "閉じる" -"save" = "保存" -"logout" = "ログアウト" -"create" = "作成" -"update" = "更新" -"copy" = "コピー" -"copied" = "コピー済み" -"download" = "ダウンロード" -"remark" = "備考" -"enable" = "有効化" -"protocol" = "プロトコル" -"search" = "検索" -"filter" = "フィルター" -"loading" = "読み込み中..." -"second" = "秒" -"minute" = "分" -"hour" = "時間" -"day" = "日" -"check" = "確認" -"indefinite" = "無期限" -"unlimited" = "無制限" -"none" = "なし" -"qrCode" = "QRコード" -"info" = "詳細情報" -"edit" = "編集" -"delete" = "削除" -"reset" = "リセット" -"noData" = "データなし。" -"copySuccess" = "コピー成功" -"sure" = "確定" -"encryption" = "暗号化" -"useIPv4ForHost" = "ホストにIPv4を使用" -"transmission" = "伝送" -"host" = "ホスト" -"path" = "パス" -"camouflage" = "偽装" -"status" = "ステータス" -"enabled" = "有効" -"disabled" = "無効" -"depleted" = "消耗済み" -"depletingSoon" = "間もなく消耗" -"offline" = "オフライン" -"online" = "オンライン" -"domainName" = "ドメイン名" -"monitor" = "監視" -"certificate" = "証明書" -"fail" = "失敗" -"comment" = "コメント" -"success" = "成功" -"lastOnline" = "最終オンライン" -"getVersion" = "バージョン取得" -"install" = "インストール" -"clients" = "クライアント" -"usage" = "利用状況" -"twoFactorCode" = "コード" -"remained" = "残り" -"security" = "セキュリティ" -"secAlertTitle" = "セキュリティアラート" -"secAlertSsl" = "この接続は安全ではありません。TLSを有効にしてデータ保護を行うまで、機密情報を入力しないでください。" -"secAlertConf" = "一部の設定は脆弱です。潜在的な脆弱性を防ぐために、セキュリティプロトコルを強化することをお勧めします。" -"secAlertSSL" = "セキュアな接続がありません。データ保護のためにTLS証明書をインストールしてください。" -"secAlertPanelPort" = "デフォルトのポートにはセキュリティリスクがあります。ランダムなポートまたは特定のポートを設定してください。" -"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。" -"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" -"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" -"emptyDnsDesc" = "追加されたDNSサーバーはありません。" -"emptyFakeDnsDesc" = "追加されたFake DNSサーバーはありません。" -"emptyBalancersDesc" = "追加されたバランサーはありません。" -"emptyReverseDesc" = "追加されたリバースプロキシはありません。" -"somethingWentWrong" = "エラーが発生しました" - -[subscription] -"title" = "サブスクリプション情報" -"subId" = "サブスクリプションID" -"status" = "ステータス" -"downloaded" = "ダウンロード" -"uploaded" = "アップロード" -"expiry" = "有効期限" -"totalQuota" = "合計クォータ" -"individualLinks" = "個別リンク" -"active" = "有効" -"inactive" = "無効" -"unlimited" = "無制限" -"noExpiry" = "期限なし" - -[menu] -"theme" = "テーマ" -"dark" = "ダーク" -"ultraDark" = "ウルトラダーク" -"dashboard" = "ダッシュボード" -"inbounds" = "インバウンド一覧" -"settings" = "パネル設定" -"xray" = "Xray設定" -"logout" = "ログアウト" -"link" = "リンク管理" - -[pages.login] -"hello" = "こんにちは" -"title" = "ようこそ" -"loginAgain" = "ログインセッションが切れました。再度ログインしてください。" - -[pages.login.toasts] -"invalidFormData" = "データ形式エラー" -"emptyUsername" = "ユーザー名を入力してください" -"emptyPassword" = "パスワードを入力してください" -"wrongUsernameOrPassword" = "ユーザー名、パスワード、または二段階認証コードが無効です。" -"successLogin" = "アカウントに正常にログインしました。" - -[pages.index] -"title" = "システムステータス" -"cpu" = "CPU" -"logicalProcessors" = "論理プロセッサ" -"frequency" = "周波数" -"swap" = "スワップ" -"storage" = "ストレージ" -"memory" = "RAM" -"threads" = "スレッド" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "再起動" -"xraySwitch" = "バージョン" -"xraySwitchClick" = "切り替えるバージョンを選択してください" -"xraySwitchClickDesk" = "慎重に選択してください。古いバージョンは現在の設定と互換性がない可能性があります。" -"xrayUpdates" = "Xrayの更新" -"updatePanel" = "パネルを更新" -"panelUpdateDesc" = "これにより3X-UIが最新リリースに更新され、パネルサービスが再起動されます。" -"currentPanelVersion" = "現在のパネルバージョン" -"latestPanelVersion" = "最新のパネルバージョン" -"panelUpToDate" = "パネルは最新です" -"upToDate" = "最新" -"xrayStatusUnknown" = "不明" -"xrayStatusRunning" = "実行中" -"xrayStatusStop" = "停止" -"xrayStatusError" = "エラー" -"xrayErrorPopoverTitle" = "Xrayの実行中にエラーが発生しました" -"operationHours" = "システム稼働時間" -"systemLoad" = "システム負荷" -"systemLoadDesc" = "過去1、5、15分間のシステム平均負荷" -"connectionCount" = "接続数" -"ipAddresses" = "IPアドレス" -"toggleIpVisibility" = "IPの表示を切り替える" -"overallSpeed" = "全体の速度" -"upload" = "アップロード" -"download" = "ダウンロード" -"totalData" = "総データ量" -"sent" = "送信" -"received" = "受信" -"documentation" = "ドキュメント" -"xraySwitchVersionDialog" = "Xrayのバージョンを本当に変更しますか?" -"xraySwitchVersionDialogDesc" = "Xrayのバージョンが#version#に変更されます。" -"xraySwitchVersionPopover" = "Xrayの更新が成功しました" -"panelUpdateDialog" = "本当にパネルを更新しますか?" -"panelUpdateDialogDesc" = "これにより3X-UIが#version#に更新され、パネルサービスが再起動されます。" -"panelUpdateCheckPopover" = "パネルの更新確認に失敗しました" -"panelUpdateStartedPopover" = "パネルの更新を開始しました" -"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?" -"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。" -"geofilesUpdateDialogDesc" = "これにより、すべてのファイルが更新されます。" -"geofilesUpdateAll" = "すべて更新" -"geofileUpdatePopover" = "ジオファイルの更新が成功しました" -"dontRefresh" = "インストール中、このページをリロードしないでください" -"logs" = "ログ" -"config" = "設定" -"backup" = "バックアップ" -"backupTitle" = "データベースのバックアップと復元" -"exportDatabase" = "バックアップ" -"exportDatabaseDesc" = "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。" -"importDatabase" = "復元" -"importDatabaseDesc" = "クリックして、デバイスから .db ファイルを選択し、アップロードしてバックアップからデータベースを復元します。" -"importDatabaseSuccess" = "データベースのインポートに成功しました" -"importDatabaseError" = "データベースのインポート中にエラーが発生しました" -"readDatabaseError" = "データベースの読み取り中にエラーが発生しました" -"getDatabaseError" = "データベースの取得中にエラーが発生しました" -"getConfigError" = "設定ファイルの取得中にエラーが発生しました" -"customGeoTitle" = "カスタム GeoSite / GeoIP" -"customGeoAdd" = "追加" -"customGeoType" = "種類" -"customGeoAlias" = "エイリアス" -"customGeoUrl" = "URL" -"customGeoEnabled" = "有効" -"customGeoLastUpdated" = "最終更新" -"customGeoExtColumn" = "ルーティング (ext:…)" -"customGeoToastUpdateAll" = "すべてのカスタムソースを更新しました" -"customGeoActions" = "操作" -"customGeoEdit" = "編集" -"customGeoDelete" = "削除" -"customGeoDownload" = "今すぐ更新" -"customGeoModalAdd" = "カスタム geo を追加" -"customGeoModalEdit" = "カスタム geo を編集" -"customGeoModalSave" = "保存" -"customGeoDeleteConfirm" = "このカスタム geo ソースを削除しますか?" -"customGeoRoutingHint" = "ルーティングでは値を ext:ファイル.dat:タグ(タグを置換)として使います。" -"customGeoInvalidId" = "無効なリソース ID" -"customGeoAliasesError" = "カスタム geo エイリアスの読み込みに失敗しました" -"customGeoValidationAlias" = "エイリアスは小文字・数字・- と _ のみ使用できます" -"customGeoValidationUrl" = "URL は http:// または https:// で始めてください" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = "(カスタム)" -"customGeoToastList" = "カスタム geo 一覧" -"customGeoToastAdd" = "カスタム geo を追加" -"customGeoToastUpdate" = "カスタム geo を更新" -"customGeoToastDelete" = "カスタム geofile「{{ .fileName }}」を削除しました" -"customGeoToastDownload" = "geofile「{{ .fileName }}」を更新しました" -"customGeoErrInvalidType" = "種類は geosite または geoip である必要があります" -"customGeoErrAliasRequired" = "エイリアスが必要です" -"customGeoErrAliasPattern" = "エイリアスに使用できない文字が含まれています" -"customGeoErrAliasReserved" = "このエイリアスは予約されています" -"customGeoErrUrlRequired" = "URL が必要です" -"customGeoErrInvalidUrl" = "URL が無効です" -"customGeoErrUrlScheme" = "URL は http または https を使用してください" -"customGeoErrUrlHost" = "URL のホストが無効です" -"customGeoErrDuplicateAlias" = "この種類ですでにこのエイリアスが使われています" -"customGeoErrNotFound" = "カスタム geo ソースが見つかりません" -"customGeoErrDownload" = "ダウンロードに失敗しました" -"customGeoErrUpdateAllIncomplete" = "カスタム geo ソースの 1 件以上を更新できませんでした" -"customGeoEmpty" = "カスタム geo ソースはまだありません — 「追加」をクリックして作成してください" - -[pages.inbounds] -"allTimeTraffic" = "総トラフィック" -"allTimeTrafficUsage" = "これまでの総使用量" -"title" = "インバウンド一覧" -"totalDownUp" = "総アップロード / ダウンロード" -"totalUsage" = "総使用量" -"inboundCount" = "インバウンド数" -"operate" = "メニュー" -"enable" = "有効化" -"remark" = "備考" -"protocol" = "プロトコル" -"port" = "ポート" -"portMap" = "ポートマッピング" -"traffic" = "トラフィック" -"details" = "詳細情報" -"transportConfig" = "トランスポート設定" -"expireDate" = "有効期限" -"createdAt" = "作成" -"updatedAt" = "更新" -"resetTraffic" = "トラフィックリセット" -"addInbound" = "インバウンド追加" -"generalActions" = "一般操作" -"autoRefresh" = "自動更新" -"autoRefreshInterval" = "間隔" -"modifyInbound" = "インバウンド修正" -"deleteInbound" = "インバウンド削除" -"deleteInboundContent" = "インバウンドを削除してもよろしいですか?" -"deleteClient" = "クライアント削除" -"deleteClientContent" = "クライアントを削除してもよろしいですか?" -"resetTrafficContent" = "トラフィックをリセットしてもよろしいですか?" -"copyLink" = "リンクをコピー" -"address" = "アドレス" -"network" = "ネットワーク" -"destinationPort" = "宛先ポート" -"targetAddress" = "宛先アドレス" -"monitorDesc" = "空白にするとすべてのIPを監視" -"meansNoLimit" = "= 無制限(単位:GB)" -"totalFlow" = "総トラフィック" -"leaveBlankToNeverExpire" = "空白にすると期限なし" -"noRecommendKeepDefault" = "デフォルト値を保持することをお勧めします" -"certificatePath" = "ファイルパス" -"certificateContent" = "ファイル内容" -"publicKey" = "公開鍵" -"privatekey" = "秘密鍵" -"clickOnQRcode" = "QRコードをクリックしてコピー" -"client" = "クライアント" -"export" = "リンクエクスポート" -"clone" = "複製" -"cloneInbound" = "複製" -"cloneInboundContent" = "このインバウンドルールは、ポート(Port)、リスニングIP(Listening IP)、クライアント(Clients)を除くすべての設定がクローンされます" -"cloneInboundOk" = "クローン作成" -"resetAllTraffic" = "すべてのインバウンドトラフィックをリセット" -"resetAllTrafficTitle" = "すべてのインバウンドトラフィックをリセット" -"resetAllTrafficContent" = "すべてのインバウンドトラフィックをリセットしてもよろしいですか?" -"resetInboundClientTraffics" = "クライアントトラフィックをリセット" -"resetInboundClientTrafficTitle" = "すべてのクライアントトラフィックをリセット" -"resetInboundClientTrafficContent" = "このインバウンドクライアントのすべてのトラフィックをリセットしてもよろしいですか?" -"resetAllClientTraffics" = "すべてのクライアントトラフィックをリセット" -"resetAllClientTrafficTitle" = "すべてのクライアントトラフィックをリセット" -"resetAllClientTrafficContent" = "すべてのクライアントのトラフィックをリセットしてもよろしいですか?" -"delDepletedClients" = "トラフィックが尽きたクライアントを削除" -"delDepletedClientsTitle" = "トラフィックが尽きたクライアントを削除" -"delDepletedClientsContent" = "トラフィックが尽きたすべてのクライアントを削除してもよろしいですか?" -"email" = "メールアドレス" -"emailDesc" = "メールアドレスは一意でなければなりません" -"IPLimit" = "IP制限" -"IPLimitDesc" = "設定値を超えるとインバウンドトラフィックが無効になります。(0 = 無効)" -"IPLimitlog" = "IPログ" -"IPLimitlogDesc" = "IP履歴ログ(無効なインバウンドトラフィックを有効にするには、ログをクリアしてください)" -"IPLimitlogclear" = "ログをクリア" -"setDefaultCert" = "パネル設定から証明書を設定" -"telegramDesc" = "TelegramチャットIDを提供してください。(ボットで'/id'コマンドを使用)または(@userinfobot)" -"subscriptionDesc" = "サブスクリプションURLを見つけるには、“詳細情報”に移動してください。また、複数のクライアントに同じ名前を使用することができます。" -"info" = "情報" -"same" = "同じ" -"inboundData" = "インバウンドデータ" -"exportInbound" = "インバウンドルールをエクスポート" -"import" = "インポート" -"importInbound" = "インバウンドルールをインポート" -"periodicTrafficResetTitle" = "トラフィックリセット" -"periodicTrafficResetDesc" = "指定された間隔でトラフィックカウンタを自動的にリセット" -"lastReset" = "最後のリセット" - -[pages.client] -"add" = "クライアント追加" -"edit" = "クライアント編集" -"submitAdd" = "クライアント追加" -"submitEdit" = "変更を保存" -"clientCount" = "クライアント数" -"bulk" = "一括作成" -"copyFromInbound" = "インバウンドからクライアントをコピー" -"copyToInbound" = "クライアントのコピー先" -"copySelected" = "選択項目をコピー" -"copySource" = "ソース" -"copyEmailPreview" = "結果メールのプレビュー" -"copySelectSourceFirst" = "先にソースインバウンドを選択してください。" -"copyResult" = "コピー結果" -"copyResultSuccess" = "正常にコピーされました" -"copyResultNone" = "コピーする項目がありません: クライアントが選択されていないかソースが空です" -"copyResultErrors" = "コピーエラー" -"copyFlowLabel" = "新規クライアントの Flow (VLESS)" -"copyFlowHint" = "すべてのコピー対象クライアントに適用されます。空のままにするとスキップします。" -"selectAll" = "すべて選択" -"clearAll" = "すべて解除" -"method" = "方法" -"first" = "最初" -"last" = "最後" -"prefix" = "プレフィックス" -"postfix" = "サフィックス" -"delayedStart" = "初回使用後に開始" -"expireDays" = "期間" -"days" = "日" -"renew" = "自動更新" -"renewDesc" = "期限が切れた後に自動更新。(0 = 無効)(単位:日)" - -[pages.inbounds.periodicTrafficReset] -"never" = "なし" -"daily" = "毎日" -"weekly" = "毎週" -"monthly" = "毎月" -"hourly" = "毎時" - -[pages.inbounds.toasts] -"obtain" = "取得" -"updateSuccess" = "更新が成功しました" -"logCleanSuccess" = "ログがクリアされました" -"inboundsUpdateSuccess" = "インバウンドが正常に更新されました" -"inboundUpdateSuccess" = "インバウンドが正常に更新されました" -"inboundCreateSuccess" = "インバウンドが正常に作成されました" -"inboundDeleteSuccess" = "インバウンドが正常に削除されました" -"inboundClientAddSuccess" = "インバウンドクライアントが追加されました" -"inboundClientDeleteSuccess" = "インバウンドクライアントが削除されました" -"inboundClientUpdateSuccess" = "インバウンドクライアントが更新されました" -"delDepletedClientsSuccess" = "すべての枯渇したクライアントが削除されました" -"resetAllClientTrafficSuccess" = "クライアントのすべてのトラフィックがリセットされました" -"resetAllTrafficSuccess" = "すべてのトラフィックがリセットされました" -"resetInboundClientTrafficSuccess" = "トラフィックがリセットされました" -"trafficGetError" = "トラフィックの取得中にエラーが発生しました" -"getNewX25519CertError" = "X25519証明書の取得中にエラーが発生しました。" -"getNewmldsa65Error" = "mldsa65証明書の取得中にエラーが発生しました。" -"getNewVlessEncError" = "VlessEnc証明書の取得中にエラーが発生しました。" - -[pages.inbounds.stream.general] -"request" = "リクエスト" -"response" = "レスポンス" -"name" = "名前" -"value" = "値" - -[pages.inbounds.stream.tcp] -"version" = "バージョン" -"method" = "方法" -"path" = "パス" -"status" = "ステータス" -"statusDescription" = "ステータス説明" -"requestHeader" = "リクエストヘッダー" -"responseHeader" = "レスポンスヘッダー" - -[pages.settings] -"title" = "パネル設定" -"save" = "保存" -"infoDesc" = "ここでのすべての変更は、保存してパネルを再起動する必要があります" -"restartPanel" = "パネル再起動" -"restartPanelDesc" = "パネルを再起動してもよろしいですか?再起動後にパネルにアクセスできない場合は、サーバーでパネルログを確認してください" -"restartPanelSuccess" = "パネルの再起動に成功しました" -"actions" = "操作" -"resetDefaultConfig" = "デフォルト設定にリセット" -"panelSettings" = "一般" -"securitySettings" = "セキュリティ設定" -"TGBotSettings" = "Telegramボット設定" -"panelListeningIP" = "パネル監視IP" -"panelListeningIPDesc" = "デフォルトではすべてのIPを監視する" -"panelListeningDomain" = "パネル監視ドメイン" -"panelListeningDomainDesc" = "デフォルトで空白の場合、すべてのドメインとIPアドレスを監視する" -"panelPort" = "パネル監視ポート" -"panelPortDesc" = "再起動で有効" -"publicKeyPath" = "パネル証明書公開鍵ファイルパス" -"publicKeyPathDesc" = "'/'で始まる絶対パスを入力" -"privateKeyPath" = "パネル証明書秘密鍵ファイルパス" -"privateKeyPathDesc" = "'/'で始まる絶対パスを入力" -"panelUrlPath" = "パネルURLルートパス" -"panelUrlPathDesc" = "'/'で始まり、'/'で終わる必要があります" -"pageSize" = "ページサイズ" -"pageSizeDesc" = "インバウンドテーブルのページサイズを定義します。0を設定すると無効化されます" -"remarkModel" = "備考モデルと区切り記号" -"datepicker" = "日付ピッカー" -"datepickerPlaceholder" = "日付を選択" -"datepickerDescription" = "日付選択カレンダーで有効期限を指定する" -"sampleRemark" = "備考の例" -"oldUsername" = "旧ユーザー名" -"currentPassword" = "旧パスワード" -"newUsername" = "新しいユーザー名" -"newPassword" = "新しいパスワード" -"telegramBotEnable" = "Telegramボットを有効にする" -"telegramBotEnableDesc" = "Telegramボット機能を有効にする" -"telegramToken" = "Telegramボットトークン" -"telegramTokenDesc" = "'@BotFather'から取得したTelegramボットトークン" -"telegramProxy" = "SOCKS5プロキシ" -"telegramProxyDesc" = "SOCKS5プロキシを有効にしてTelegramに接続する(ガイドに従って設定を調整)" -"telegramAPIServer" = "Telegram APIサーバー" -"telegramAPIServerDesc" = "使用するTelegram APIサーバー。空白の場合はデフォルトサーバーを使用する" -"telegramChatId" = "管理者チャットID" -"telegramChatIdDesc" = "Telegram管理者チャットID(複数の場合はカンマで区切る)@userinfobotで取得するか、ボットで'/id'コマンドを使用して取得する" -"telegramNotifyTime" = "通知時間" -"telegramNotifyTimeDesc" = "定期的なTelegramボット通知時間を設定する(crontab時間形式を使用)" -"tgNotifyBackup" = "データベースバックアップ" -"tgNotifyBackupDesc" = "レポート付きのデータベースバックアップファイルを送信" -"tgNotifyLogin" = "ログイン通知" -"tgNotifyLoginDesc" = "誰かがパネルにログインしようとしたときに、ユーザー名、IPアドレス、時間を表示する" -"sessionMaxAge" = "セッション期間" -"sessionMaxAgeDesc" = "ログイン状態を保持する期間(単位:分)" -"expireTimeDiff" = "有効期限通知のしきい値" -"expireTimeDiffDesc" = "このしきい値に達した場合、有効期限に関する通知を受け取る(単位:日)" -"trafficDiff" = "トラフィック消耗しきい値" -"trafficDiffDesc" = "このしきい値に達した場合、トラフィック消耗に関する通知を受け取る(単位:GB)" -"tgNotifyCpu" = "CPU負荷通知しきい値" -"tgNotifyCpuDesc" = "CPU負荷がこのしきい値を超えた場合、通知を受け取る(単位:%)" -"timeZone" = "タイムゾーン" -"timeZoneDesc" = "定時タスクはこのタイムゾーンの時間に従って実行される" -"subSettings" = "サブスクリプション設定" -"subEnable" = "サブスクリプションサービスを有効にする" -"subEnableDesc" = "サブスクリプションサービス機能を有効にする" -"subJsonEnable" = "JSON サブスクリプションのエンドポイントを個別に有効/無効にする。" -"subTitle" = "サブスクリプションタイトル" -"subTitleDesc" = "VPNクライアントに表示されるタイトル" -"subSupportUrl" = "サポートURL" -"subSupportUrlDesc" = "VPNクライアントに表示されるテクニカルサポートへのリンク" -"subProfileUrl" = "プロフィールURL" -"subProfileUrlDesc" = "VPNクライアントに表示されるWebサイトへのリンク" -"subAnnounce" = "お知らせ" -"subAnnounceDesc" = "VPNクライアントに表示されるお知らせのテキスト" -"subEnableRouting" = "ルーティングを有効化" -"subEnableRoutingDesc" = "VPNクライアントでルーティングを有効にするためのグローバル設定。(Happのみ)" -"subRoutingRules" = "ルーティングルール" -"subRoutingRulesDesc" = "VPNクライアントのグローバルルーティングルール。(Happのみ)" -"subListen" = "監視IP" -"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)" -"subPort" = "監視ポート" -"subPortDesc" = "サブスクリプションサービスが監視するポート番号(使用されていないポートである必要があります)" -"subCertPath" = "公開鍵パス" -"subCertPathDesc" = "サブスクリプションサービスで使用する公開鍵ファイルのパス('/'で始まる)" -"subKeyPath" = "秘密鍵パス" -"subKeyPathDesc" = "サブスクリプションサービスで使用する秘密鍵ファイルのパス('/'で始まる)" -"subPath" = "URIパス" -"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)" -"subDomain" = "監視ドメイン" -"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)" -"subUpdates" = "更新間隔" -"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)" -"subEncrypt" = "エンコード" -"subEncryptDesc" = "サブスクリプションサービスが返す内容をBase64エンコードする" -"subShowInfo" = "利用情報を表示" -"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する" -"subURI" = "リバースプロキシURI" -"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する" -"externalTrafficInformEnable" = "外部トラフィック情報" -"externalTrafficInformEnableDesc" = "トラフィックの更新ごとに外部 API に通知します。" -"externalTrafficInformURI" = "外部トラフィック通知 URI" -"externalTrafficInformURIDesc" = "トラフィックの更新ごとに外部 API に通知します。" -"restartXrayOnClientDisable" = "自動無効化後に Xray を再起動" -"restartXrayOnClientDisableDesc" = "有効期限切れまたはトラフィック上限でクライアントが自動的に無効化されたとき、Xray を再起動します。" -"fragment" = "フラグメント" -"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする" -"fragmentSett" = "設定" -"noisesDesc" = "Noisesを有効にする" -"noisesSett" = "Noises設定" -"mux" = "マルチプレクサ" -"muxDesc" = "確立されたストリーム内で複数の独立したストリームを伝送する" -"muxSett" = "マルチプレクサ設定" -"direct" = "直接接続" -"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する" -"notifications" = "通知" -"certs" = "証明書" -"externalTraffic" = "外部トラフィック" -"dateAndTime" = "日付と時刻" -"proxyAndServer" = "プロキシとサーバー" -"intervals" = "間隔" -"information" = "情報" -"language" = "言語" -"telegramBotLanguage" = "Telegram Botの言語" - -[pages.xray] -"title" = "Xray 設定" -"save" = "保存" -"restart" = "Xray 再起動" -"restartSuccess" = "Xrayの再起動に成功しました" -"stopSuccess" = "Xrayが正常に停止しました" -"restartError" = "Xrayの再起動中にエラーが発生しました。" -"stopError" = "Xrayの停止中にエラーが発生しました。" -"basicTemplate" = "基本設定" -"advancedTemplate" = "高度な設定" -"generalConfigs" = "一般設定" -"generalConfigsDesc" = "これらのオプションは一般設定を決定します" -"logConfigs" = "ログ" -"logConfigsDesc" = "ログはサーバーのパフォーマンスに影響を与える可能性があるため、必要な場合にのみ有効にすることをお勧めします" -"blockConfigsDesc" = "これらのオプションは、特定のプロトコルやウェブサイトへのユーザー接続をブロックします" -"basicRouting" = "基本ルーティング" -"blockConnectionsConfigsDesc" = "これらのオプションにより、特定のリクエスト元の国に基づいてトラフィックをブロックします。" -"directConnectionsConfigsDesc" = "直接接続により、特定のトラフィックが他のサーバーを経由しないようにします。" -"blockips" = "IPをブロック" -"blockdomains" = "ドメインをブロック" -"directips" = "直接IP" -"directdomains" = "直接ドメイン" -"ipv4Routing" = "IPv4 ルーティング" -"ipv4RoutingDesc" = "このオプションはIPv4のみを介してターゲットドメインへルーティングします" -"warpRouting" = "WARP ルーティング" -"warpRoutingDesc" = "注意:これらのオプションを使用する前に、パネルのGitHubの手順に従って、サーバーにsocks5プロキシモードでWARPをインストールしてください。WARPはCloudflareサーバー経由でトラフィックをウェブサイトにルーティングします。" -"nordRouting" = "NordVPN ルーティング" -"nordRoutingDesc" = "これらのオプションはNordVPN経由で特定の宛先にトラフィックをルーティングします。" -"Template" = "高度なXray設定テンプレート" -"TemplateDesc" = "最終的なXray設定ファイルはこのテンプレートに基づいて生成されます" -"FreedomStrategy" = "Freedom プロトコル戦略" -"FreedomStrategyDesc" = "Freedomプロトコル内のネットワークの出力戦略を設定する" -"RoutingStrategy" = "ルーティングドメイン戦略設定" -"RoutingStrategyDesc" = "DNS解決の全体的なルーティング戦略を設定する" -"outboundTestUrl" = "アウトバウンドテスト URL" -"outboundTestUrlDesc" = "アウトバウンド接続テストに使用する URL。既定値" -"Torrent" = "BitTorrent プロトコルをブロック" -"Inbounds" = "インバウンドルール" -"InboundsDesc" = "特定のクライアントからのトラフィックを受け入れる" -"Outbounds" = "アウトバウンドルール" -"Balancers" = "負荷分散" -"OutboundsDesc" = "アウトバウンドトラフィックの送信方法を設定する" -"Routings" = "ルーティングルール" -"RoutingsDesc" = "各ルールの優先順位が重要です" -"completeTemplate" = "すべて" -"logLevel" = "ログレベル" -"logLevelDesc" = "エラーログのレベルを指定し、記録する情報を示します" -"accessLog" = "アクセスログ" -"accessLogDesc" = "アクセスログのファイルパス。特殊値 'none' はアクセスログを無効にします" -"errorLog" = "エラーログ" -"errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします" -"dnsLog" = "DNS ログ" -"dnsLogDesc" = "DNSクエリのログを有効にするかどうか" -"maskAddress" = "アドレスをマスク" -"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます" -"statistics" = "統計" -"statsInboundUplink" = "インバウンドアップロード統計" -"statsInboundUplinkDesc" = "すべてのインバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。" -"statsInboundDownlink" = "インバウンドダウンロード統計" -"statsInboundDownlinkDesc" = "すべてのインバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。" -"statsOutboundUplink" = "アウトバウンドアップロード統計" -"statsOutboundUplinkDesc" = "すべてのアウトバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。" -"statsOutboundDownlink" = "アウトバウンドダウンロード統計" -"statsOutboundDownlinkDesc" = "すべてのアウトバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。" - -[pages.xray.rules] -"first" = "最初" -"last" = "最後" -"up" = "上へ" -"down" = "下へ" -"source" = "ソース" -"dest" = "宛先アドレス" -"inbound" = "インバウンド" -"outbound" = "アウトバウンド" -"balancer" = "負荷分散" -"info" = "情報" -"add" = "ルール追加" -"edit" = "ルール編集" -"useComma" = "カンマ区切りの項目" - -[pages.xray.outbound] -"addOutbound" = "アウトバウンド追加" -"addReverse" = "リバース追加" -"editOutbound" = "アウトバウンド編集" -"editReverse" = "リバース編集" -"reverseTag" = "リバースタグ" -"reverseTagDesc" = "VLESSシンプルリバースプロキシのアウトバウンドタグ。無効にするには空欄にしてください。" -"reverseTagPlaceholder" = "アウトバウンドタグ(空欄で無効)" -"tag" = "タグ" -"tagDesc" = "一意のタグ" -"address" = "アドレス" -"reverse" = "リバース" -"domain" = "ドメイン" -"type" = "タイプ" -"bridge" = "ブリッジ" -"portal" = "ポータル" -"link" = "リンク" -"intercon" = "インターコネクション" -"settings" = "設定" -"accountInfo" = "アカウント情報" -"outboundStatus" = "アウトバウンドステータス" -"sendThrough" = "送信経路" -"test" = "テスト" -"testResult" = "テスト結果" -"testing" = "接続をテスト中..." -"testSuccess" = "テスト成功" -"testFailed" = "テスト失敗" -"testError" = "アウトバウンドのテストに失敗しました" -"nordvpn" = "NordVPN" -"accessToken" = "アクセストークン" -"country" = "国" -"server" = "サーバー" -"city" = "都市" -"allCities" = "すべての都市" -"privateKey" = "秘密鍵" -"load" = "負荷" - -[pages.xray.balancer] -"addBalancer" = "負荷分散追加" -"editBalancer" = "負荷分散編集" -"balancerStrategy" = "戦略" -"balancerSelectors" = "セレクター" -"tag" = "タグ" -"tagDesc" = "一意のタグ" -"balancerDesc" = "balancerTagとoutboundTagは同時に使用できません。同時に使用された場合、outboundTagのみが有効になります。" - -[pages.xray.wireguard] -"secretKey" = "シークレットキー" -"publicKey" = "公開鍵" -"allowedIPs" = "許可されたIP" -"endpoint" = "エンドポイント" -"psk" = "共有キー" -"domainStrategy" = "ドメイン戦略" - -[pages.xray.tun] -"nameDesc" = "TUN インターフェースの名前。デフォルトは 'xray0' です" -"mtuDesc" = "最大伝送単位。データパケットの最大サイズ。デフォルトは 1500 です" -"userLevel" = "ユーザーレベル" -"userLevelDesc" = "このインバウンドを通じて確立されたすべての接続は、このユーザーレベルを使用します。デフォルトは 0 です" - -[pages.xray.dns] -"enable" = "DNSを有効にする" -"enableDesc" = "組み込みDNSサーバーを有効にする" -"tag" = "DNSインバウンドタグ" -"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます" -"clientIp" = "クライアントIP" -"clientIpDesc" = "DNSクエリ中に指定されたIPの位置をサーバーに通知するために使用されます" -"disableCache" = "キャッシュを無効にする" -"disableCacheDesc" = "DNSキャッシュを無効にします" -"disableFallback" = "フォールバックを無効にする" -"disableFallbackDesc" = "フォールバックDNSクエリを無効にします" -"disableFallbackIfMatch" = "一致した場合にフォールバックを無効にする" -"disableFallbackIfMatchDesc" = "DNSサーバーの一致するドメインリストにヒットした場合、フォールバックDNSクエリを無効にします" -"enableParallelQuery" = "並列クエリを有効にする" -"enableParallelQueryDesc" = "複数のサーバーへの並列DNSクエリを有効にして、より高速な解決を実現" -"strategy" = "クエリ戦略" -"strategyDesc" = "ドメイン名解決の全体的な戦略" -"add" = "サーバー追加" -"edit" = "サーバー編集" -"domains" = "ドメイン" -"expectIPs" = "期待されるIP" -"unexpectIPs" = "予期しないIP" -"useSystemHosts" = "システムのHostsを使用" -"useSystemHostsDesc" = "インストール済みシステムのhostsファイルを使用する" -"usePreset" = "テンプレートを使用" -"dnsPresetTitle" = "DNSテンプレート" -"dnsPresetFamily" = "ファミリー" - -[pages.xray.fakedns] -"add" = "フェイクDNS追加" -"edit" = "フェイクDNS編集" -"ipPool" = "IPプールサブネット" -"poolSize" = "プールサイズ" - -[pages.settings.security] -"admin" = "管理者の資格情報" -"twoFactor" = "二段階認証" -"twoFactorEnable" = "2FAを有効化" -"twoFactorEnableDesc" = "セキュリティを強化するために追加の認証層を追加します。" -"twoFactorModalSetTitle" = "二段階認証を有効にする" -"twoFactorModalDeleteTitle" = "二段階認証を無効にする" -"twoFactorModalSteps" = "二段階認証を設定するには、次の手順を実行してください:" -"twoFactorModalFirstStep" = "1. 認証アプリでこのQRコードをスキャンするか、QRコード近くのトークンをコピーしてアプリに貼り付けます" -"twoFactorModalSecondStep" = "2. アプリからコードを入力してください" -"twoFactorModalRemoveStep" = "二段階認証を削除するには、アプリからコードを入力してください。" -"twoFactorModalChangeCredentialsTitle" = "認証情報の変更" -"twoFactorModalChangeCredentialsStep" = "管理者の認証情報を変更するには、アプリケーションからコードを入力してください。" -"twoFactorModalSetSuccess" = "二要素認証が正常に設定されました" -"twoFactorModalDeleteSuccess" = "二要素認証が正常に削除されました" -"twoFactorModalError" = "コードが間違っています" - -[pages.settings.toasts] -"modifySettings" = "パラメーターが変更されました。" -"getSettings" = "パラメーターの取得中にエラーが発生しました" -"modifyUserError" = "管理者認証情報の変更中にエラーが発生しました。" -"modifyUser" = "管理者の認証情報を正常に変更しました。" -"originalUserPassIncorrect" = "旧ユーザー名または旧パスワードが間違っています" -"userPassMustBeNotEmpty" = "新しいユーザー名と新しいパスワードは空にできません" -"getOutboundTrafficError" = "送信トラフィックの取得エラー" -"resetOutboundTrafficError" = "送信トラフィックのリセットエラー" - -[tgbot] -"keyboardClosed" = "❌ キーボードを閉じました!" -"noResult" = "❗ 結果がありません!" -"noQuery" = "❌ クエリが見つかりません!コマンドを再利用してください!" -"wentWrong" = "❌ 何かがうまくいかなかった!" -"noIpRecord" = "❗ IPレコードがありません!" -"noInbounds" = "❗ インバウンドが見つかりません!" -"unlimited" = "♾ 無制限(リセット)" -"add" = "追加" -"month" = "月" -"months" = "ヶ月" -"day" = "日" -"days" = "日間" -"hours" = "時間" -"minutes" = "分" -"unknown" = "不明" -"inbounds" = "インバウンド" -"clients" = "クライアント" -"offline" = "🔴 オフライン" -"online" = "🟢 オンライン" - -[tgbot.commands] -"unknown" = "❗ 不明なコマンド" -"pleaseChoose" = "👇 選択してください:\r\n" -"help" = "🤖 このボットをご利用いただきありがとうございます!サーバーから特定のデータを提供し、必要な変更を行うことができます。\r\n\r\n" -"start" = "👋 こんにちは、{{ .Firstname }}。\r\n" -"welcome" = "🤖 {{ .Hostname }} 管理ボットへようこそ。\r\n" -"status" = "✅ ボットは正常に動作しています!" -"usage" = "❗ 検索するテキストを入力してください!" -"getID" = "🆔 あなたのIDは:{{ .ID }}" -"helpAdminCommands" = "Xray Coreを再起動するには:\r\n/restart\r\n\r\nクライアントの電子メールを検索するには:\r\n/usage [電子メール]\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n/inbound [備考]\r\n\r\nTelegramチャットID:\r\n/id" -"helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n/usage [電子メール]\r\n\r\nTelegramチャットID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作エラー。\r\n\r\nエラー: {{ .Error }}" -"xrayNotRunning" = "❗ Xray Core は動作していません。" -"startDesc" = "メインメニューを表示" -"helpDesc" = "ボットのヘルプ" -"statusDesc" = "ボットの状態を確認" -"idDesc" = "Telegram IDを表示" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU使用率は{{ .Percent }}%、しきい値{{ .Threshold }}%を超えました" -"selectUserFailed" = "❌ ユーザーの選択に失敗しました!" -"userSaved" = "✅ Telegramユーザーが保存されました。" -"loginSuccess" = "✅ パネルに正常にログインしました。\r\n" -"loginFailed" = "❗️ パネルのログインに失敗しました。\r\n" -"2faFailed" = "2FAエラー" -"report" = "🕰 定期報告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日時:{{ .DateTime }}\r\n" -"hostname" = "💻 ホスト名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI バージョン:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray バージョン: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IPアドレス:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ サーバー稼働時間:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 サーバー負荷:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 サーバーメモリ:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP接続数:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP接続数:{{ .Count }}\r\n" -"traffic" = "🚦 トラフィック:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xrayステータス:{{ .State }}\r\n" -"username" = "👤 ユーザー名:{{ .Username }}\r\n" -"reason" = "❗️ 理由:{{ .Reason }}\r\n" -"time" = "⏰ 時間:{{ .Time }}\r\n" -"inbound" = "📍 インバウンド:{{ .Remark }}\r\n" -"port" = "🔌 ポート:{{ .Port }}\r\n" -"expire" = "📅 有効期限:{{ .Time }}\r\n" -"expireIn" = "📅 残り時間:{{ .Time }}\r\n" -"active" = "💡 有効:{{ .Enable }}\r\n" -"enabled" = "🚨 有効化済み:{{ .Enable }}\r\n" -"online" = "🌐 接続ステータス:{{ .Status }}\r\n" -"lastOnline" = "🔙 最終オンライン: {{ .Time }}\r\n" -"email" = "📧 メール:{{ .Email }}\r\n" -"upload" = "🔼 アップロード↑:{{ .Upload }}\r\n" -"download" = "🔽 ダウンロード↓:{{ .Download }}\r\n" -"total" = "📊 合計:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegramユーザー:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 消耗済みの {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 消耗済みの {{ .Type }} 数量:\r\n" -"onlinesCount" = "🌐 オンラインクライアント:{{ .Count }}\r\n" -"disabled" = "🛑 無効化:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 間もなく消耗:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 バックアップ時間:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n" -"yes" = "✅ はい" -"no" = "❌ いいえ" -"received_id" = "🔑📥 IDが更新されました。" -"received_password" = "🔑📥 パスワードが更新されました。" -"received_email" = "📧📥 メールが更新されました。" -"received_comment" = "💬📥 コメントが更新されました。" -"id_prompt" = "🔑 デフォルトID: {{ .ClientId }}\n\nIDを入力してください。" -"pass_prompt" = "🔑 デフォルトパスワード: {{ .ClientPassword }}\n\nパスワードを入力してください。" -"email_prompt" = "📧 デフォルトメール: {{ .ClientEmail }}\n\nメールを入力してください。" -"comment_prompt" = "💬 デフォルトコメント: {{ .ClientComment }}\n\nコメントを入力してください。" -"inbound_client_data_id" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!" -"inbound_client_data_pass" = "🔄 インバウンド: {{ .InboundRemark }}\n\n🔑 パスワード: {{ .ClientPass }}\n📧 メール: {{ .ClientEmail }}\n📊 トラフィック: {{ .ClientTraffic }}\n📅 有効期限: {{ .ClientExp }}\n🌐 IP制限: {{ .IpLimit }}\n💬 コメント: {{ .ClientComment }}\n\n今すぐこのクライアントをインバウンドに追加できます!" -"cancel" = "❌ プロセスがキャンセルされました!\n\nいつでも /start で再開できます。 🔄" -"error_add_client" = "⚠️ エラー:\n\n {{ .error }}" -"using_default_value" = "わかりました、デフォルト値を使用します。 😊" -"incorrect_input" = "入力が無効です。\nフレーズはスペースなしで続けて入力してください。\n正しい例: aaaaaa\n間違った例: aaa aaa 🚫" -"AreYouSure" = "本当にいいですか?🤔" -"SuccessResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ✅ 成功" -"FailedResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ エラー: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 すべてのクライアントのトラフィックリセットが完了しました。" - -[tgbot.buttons] -"closeKeyboard" = "❌ キーボードを閉じる" -"cancel" = "❌ キャンセル" -"cancelReset" = "❌ リセットをキャンセル" -"cancelIpLimit" = "❌ IP制限をキャンセル" -"confirmResetTraffic" = "✅ トラフィックをリセットしますか?" -"confirmClearIps" = "✅ IPをクリアしますか?" -"confirmRemoveTGUser" = "✅ Telegramユーザーを削除しますか?" -"confirmToggle" = "✅ ユーザーを有効/無効にしますか?" -"dbBackup" = "データベースバックアップを取得" -"serverUsage" = "サーバーの使用状況" -"getInbounds" = "インバウンド情報を取得" -"depleteSoon" = "間もなく消耗" -"clientUsage" = "使用状況を取得" -"onlines" = "オンラインクライアント" -"commands" = "コマンド" -"refresh" = "🔄 更新" -"clearIPs" = "❌ IPをクリア" -"removeTGUser" = "❌ Telegramユーザーを削除" -"selectTGUser" = "👤 Telegramユーザーを選択" -"selectOneTGUser" = "👤 1人のTelegramユーザーを選択:" -"resetTraffic" = "📈 トラフィックをリセット" -"resetExpire" = "📅 有効期限を変更" -"ipLog" = "🔢 IPログ" -"ipLimit" = "🔢 IP制限" -"setTGUser" = "👤 Telegramユーザーを設定" -"toggle" = "🔘 有効/無効" -"custom" = "🔢 カスタム" -"confirmNumber" = "✅ 確認: {{ .Num }}" -"confirmNumberAdd" = "✅ 追加を確認:{{ .Num }}" -"limitTraffic" = "🚧 トラフィック制限" -"getBanLogs" = "禁止ログ" -"allClients" = "すべてのクライアント" -"addClient" = "クライアントを追加" -"submitDisable" = "無効として送信 ☑️" -"submitEnable" = "有効として送信 ✅" -"use_default" = "🏷️ デフォルトを使用" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 パスワード" -"change_email" = "⚙️📧 メールアドレス" -"change_comment" = "⚙️💬 コメント" -"ResetAllTraffics" = "すべてのトラフィックをリセット" -"SortedTrafficUsageReport" = "ソートされたトラフィック使用レポート" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作エラー。" -"getInboundsFailed" = "❌ インバウンド情報の取得に失敗しました。" -"getClientsFailed" = "❌ クライアントの取得に失敗しました。" -"canceled" = "❌ {{ .Email }}:操作がキャンセルされました。" -"clientRefreshSuccess" = "✅ {{ .Email }}:クライアントが正常に更新されました。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IPが正常に更新されました。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:クライアントのTelegramユーザーが正常に更新されました。" -"resetTrafficSuccess" = "✅ {{ .Email }}:トラフィックが正常にリセットされました。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}:トラフィック制限が正常に保存されました。" -"expireResetSuccess" = "✅ {{ .Email }}:有効期限の日数が正常にリセットされました。" -"resetIpSuccess" = "✅ {{ .Email }}:IP制限数が正常に保存されました:{{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IPが正常にクリアされました。" -"getIpLog" = "✅ {{ .Email }}:IPログの取得。" -"getUserInfo" = "✅ {{ .Email }}:Telegramユーザー情報の取得。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegramユーザーが正常に削除されました。" -"enableSuccess" = "✅ {{ .Email }}:正常に有効化されました。" -"disableSuccess" = "✅ {{ .Email }}:正常に無効化されました。" -"askToAddUserId" = "設定が見つかりませんでした!\r\n管理者に問い合わせて、設定にTelegramユーザーのChatIDを使用してください。\r\n\r\nあなたのユーザーChatID:{{ .TgUserID }}" -"chooseClient" = "インバウンド {{ .Inbound }} のクライアントを選択" -"chooseInbound" = "インバウンドを選択" diff --git a/web/translation/translate.pt_BR.toml b/web/translation/translate.pt_BR.toml deleted file mode 100644 index acfba765..00000000 --- a/web/translation/translate.pt_BR.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Nome de Usuário" -"password" = "Senha" -"login" = "Entrar" -"confirm" = "Confirmar" -"cancel" = "Cancelar" -"close" = "Fechar" -"save" = "Salvar" -"logout" = "Sair" -"create" = "Criar" -"update" = "Atualizar" -"copy" = "Copiar" -"copied" = "Copiado" -"download" = "Baixar" -"remark" = "Observação" -"enable" = "Ativado" -"protocol" = "Protocolo" -"search" = "Pesquisar" -"filter" = "Filtrar" -"loading" = "Carregando..." -"second" = "Segundo" -"minute" = "Minuto" -"hour" = "Hora" -"day" = "Dia" -"check" = "Verificar" -"indefinite" = "Indeterminado" -"unlimited" = "Ilimitado" -"none" = "Nada" -"qrCode" = "Código QR" -"info" = "Mais Informações" -"edit" = "Editar" -"delete" = "Excluir" -"reset" = "Redefinir" -"noData" = "Sem dados." -"copySuccess" = "Copiado com Sucesso" -"sure" = "Certo" -"encryption" = "Criptografia" -"useIPv4ForHost" = "Usar IPv4 para o host" -"transmission" = "Transmissão" -"host" = "Servidor" -"path" = "Caminho" -"camouflage" = "Ofuscação" -"status" = "Status" -"enabled" = "Ativado" -"disabled" = "Desativado" -"depleted" = "Encerrado" -"depletingSoon" = "Esgotando" -"offline" = "Offline" -"online" = "Online" -"domainName" = "Nome de Domínio" -"monitor" = "IP de Escuta" -"certificate" = "Certificado Digital" -"fail" = "Falhou" -"comment" = "Comentário" -"success" = "Com Sucesso" -"lastOnline" = "Última vez online" -"getVersion" = "Obter Versão" -"install" = "Instalar" -"clients" = "Clientes" -"usage" = "Uso" -"twoFactorCode" = "Código" -"remained" = "Restante" -"security" = "Segurança" -"secAlertTitle" = "Alerta de Segurança" -"secAlertSsl" = "Esta conexão não é segura. Evite inserir informações confidenciais até que o TLS seja ativado para proteção de dados." -"secAlertConf" = "Algumas configurações estão vulneráveis a ataques. Recomenda-se reforçar os protocolos de segurança para evitar possíveis violações." -"secAlertSSL" = "O painel não possui uma conexão segura. Instale o certificado TLS para proteção de dados." -"secAlertPanelPort" = "A porta padrão do painel é vulnerável. Configure uma porta aleatória ou específica." -"secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo." -"secAlertSubURI" = "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo." -"secAlertSubJsonURI" = "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo." -"emptyDnsDesc" = "Nenhum servidor DNS adicionado." -"emptyFakeDnsDesc" = "Nenhum servidor Fake DNS adicionado." -"emptyBalancersDesc" = "Nenhum balanceador adicionado." -"emptyReverseDesc" = "Nenhum proxy reverso adicionado." -"somethingWentWrong" = "Algo deu errado" - -[subscription] -"title" = "Informações da assinatura" -"subId" = "ID da assinatura" -"status" = "Status" -"downloaded" = "Baixado" -"uploaded" = "Enviado" -"expiry" = "Validade" -"totalQuota" = "Cota total" -"individualLinks" = "Links individuais" -"active" = "Ativo" -"inactive" = "Inativo" -"unlimited" = "Ilimitado" -"noExpiry" = "Sem validade" - -[menu] -"theme" = "Tema" -"dark" = "Escuro" -"ultraDark" = "Ultra Escuro" -"dashboard" = "Visão Geral" -"inbounds" = "Inbounds" -"settings" = "Panel Settings" -"xray" = "Xray Configs" -"logout" = "Sair" -"link" = "Gerenciar" - -[pages.login] -"hello" = "Olá" -"title" = "Bem-vindo" -"loginAgain" = "Sua sessão expirou, faça login novamente" - -[pages.login.toasts] -"invalidFormData" = "O formato dos dados de entrada é inválido." -"emptyUsername" = "Nome de usuário é obrigatório" -"emptyPassword" = "Senha é obrigatória" -"wrongUsernameOrPassword" = "Nome de usuário, senha ou código de dois fatores inválido." -"successLogin" = "Você entrou na sua conta com sucesso." - -[pages.index] -"title" = "Visão Geral" -"cpu" = "CPU" -"logicalProcessors" = "Processadores lógicos" -"frequency" = "Frequência" -"swap" = "Swap" -"storage" = "Armazenamento" -"memory" = "RAM" -"threads" = "Threads" -"xrayStatus" = "Xray" -"stopXray" = "Parar" -"restartXray" = "Reiniciar" -"xraySwitch" = "Versão" -"xraySwitchClick" = "Escolha a versão para a qual deseja alternar." -"xraySwitchClickDesk" = "Escolha com cuidado, pois versões mais antigas podem não ser compatíveis com as configurações atuais." -"xrayUpdates" = "Atualizações do Xray" -"updatePanel" = "Atualizar painel" -"panelUpdateDesc" = "Isso atualizará o 3X-UI para a versão mais recente e reiniciará o serviço do painel." -"currentPanelVersion" = "Versão atual do painel" -"latestPanelVersion" = "Última versão do painel" -"panelUpToDate" = "O painel está atualizado" -"upToDate" = "Atualizado" -"xrayStatusUnknown" = "Desconhecido" -"xrayStatusRunning" = "Em execução" -"xrayStatusStop" = "Parado" -"xrayStatusError" = "Erro" -"xrayErrorPopoverTitle" = "Ocorreu um erro ao executar o Xray" -"operationHours" = "Tempo de Atividade" -"systemLoad" = "Carga do Sistema" -"systemLoadDesc" = "Média de carga do sistema nos últimos 1, 5 e 15 minutos" -"connectionCount" = "Estatísticas de Conexão" -"ipAddresses" = "Endereços IP" -"toggleIpVisibility" = "Alternar visibilidade do IP" -"overallSpeed" = "Velocidade geral" -"upload" = "Upload" -"download" = "Download" -"totalData" = "Dados totais" -"sent" = "Enviado" -"received" = "Recebido" -"documentation" = "Documentação" -"xraySwitchVersionDialog" = "Você realmente deseja alterar a versão do Xray?" -"xraySwitchVersionDialogDesc" = "Isso mudará a versão do Xray para #version#." -"xraySwitchVersionPopover" = "Xray atualizado com sucesso" -"panelUpdateDialog" = "Deseja realmente atualizar o painel?" -"panelUpdateDialogDesc" = "Isso atualizará o 3X-UI para #version# e reiniciará o serviço do painel." -"panelUpdateCheckPopover" = "Falha na verificação de atualização do painel" -"panelUpdateStartedPopover" = "Atualização do painel iniciada" -"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?" -"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#." -"geofilesUpdateDialogDesc" = "Isso atualizará todos os arquivos." -"geofilesUpdateAll" = "Atualizar tudo" -"geofileUpdatePopover" = "Geofile atualizado com sucesso" -"dontRefresh" = "Instalação em andamento, por favor não atualize a página" -"logs" = "Logs" -"config" = "Configuração" -"backup" = "Backup" -"backupTitle" = "Backup e Restauração do Banco de Dados" -"exportDatabase" = "Backup" -"exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo." -"importDatabase" = "Restaurar" -"importDatabaseDesc" = "Clique para selecionar e enviar um arquivo .db do seu dispositivo para restaurar seu banco de dados a partir de um backup." -"importDatabaseSuccess" = "O banco de dados foi importado com sucesso" -"importDatabaseError" = "Ocorreu um erro ao importar o banco de dados" -"readDatabaseError" = "Ocorreu um erro ao ler o banco de dados" -"getDatabaseError" = "Ocorreu um erro ao recuperar o banco de dados" -"getConfigError" = "Ocorreu um erro ao recuperar o arquivo de configuração" -"customGeoTitle" = "GeoSite / GeoIP personalizados" -"customGeoAdd" = "Adicionar" -"customGeoType" = "Tipo" -"customGeoAlias" = "Alias" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Ativado" -"customGeoLastUpdated" = "Última atualização" -"customGeoExtColumn" = "Roteamento (ext:…)" -"customGeoToastUpdateAll" = "Todas as fontes personalizadas foram atualizadas" -"customGeoActions" = "Ações" -"customGeoEdit" = "Editar" -"customGeoDelete" = "Excluir" -"customGeoDownload" = "Atualizar agora" -"customGeoModalAdd" = "Adicionar geo personalizado" -"customGeoModalEdit" = "Editar geo personalizado" -"customGeoModalSave" = "Salvar" -"customGeoDeleteConfirm" = "Excluir esta fonte geo personalizada?" -"customGeoRoutingHint" = "Nas regras de roteamento use a coluna de valor como ext:arquivo.dat:tag (substitua a tag)." -"customGeoInvalidId" = "ID de recurso inválido" -"customGeoAliasesError" = "Falha ao carregar aliases geo personalizados" -"customGeoValidationAlias" = "O alias só pode conter letras minúsculas, dígitos, - e _" -"customGeoValidationUrl" = "A URL deve começar com http:// ou https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (personalizado)" -"customGeoToastList" = "Lista de geo personalizado" -"customGeoToastAdd" = "Adicionar geo personalizado" -"customGeoToastUpdate" = "Atualizar geo personalizado" -"customGeoToastDelete" = "Geofile personalizado “{{ .fileName }}” excluído" -"customGeoToastDownload" = "Geofile “{{ .fileName }}” atualizado" -"customGeoErrInvalidType" = "O tipo deve ser geosite ou geoip" -"customGeoErrAliasRequired" = "Alias é obrigatório" -"customGeoErrAliasPattern" = "O alias contém caracteres não permitidos" -"customGeoErrAliasReserved" = "Este alias é reservado" -"customGeoErrUrlRequired" = "URL é obrigatória" -"customGeoErrInvalidUrl" = "URL inválida" -"customGeoErrUrlScheme" = "A URL deve usar http ou https" -"customGeoErrUrlHost" = "Host da URL inválido" -"customGeoErrDuplicateAlias" = "Este alias já está em uso para este tipo" -"customGeoErrNotFound" = "Fonte geo personalizada não encontrada" -"customGeoErrDownload" = "Falha no download" -"customGeoErrUpdateAllIncomplete" = "Falha ao atualizar uma ou mais fontes geo personalizadas" -"customGeoEmpty" = "Ainda não há fontes geo personalizadas — clique em Adicionar para criar uma" - -[pages.inbounds] -"allTimeTraffic" = "Tráfego Total" -"allTimeTrafficUsage" = "Uso total de todos os tempos" -"title" = "Inbounds" -"totalDownUp" = "Total Enviado/Recebido" -"totalUsage" = "Uso Total" -"inboundCount" = "Total de Inbounds" -"operate" = "Menu" -"enable" = "Ativado" -"remark" = "Observação" -"protocol" = "Protocolo" -"port" = "Porta" -"portMap" = "Porta Mapeada" -"traffic" = "Tráfego" -"details" = "Detalhes" -"transportConfig" = "Transporte" -"expireDate" = "Duração" -"createdAt" = "Criado" -"updatedAt" = "Atualizado" -"resetTraffic" = "Redefinir Tráfego" -"addInbound" = "Adicionar Inbound" -"generalActions" = "Ações Gerais" -"autoRefresh" = "Atualização automática" -"autoRefreshInterval" = "Intervalo" -"modifyInbound" = "Modificar Inbound" -"deleteInbound" = "Excluir Inbound" -"deleteInboundContent" = "Tem certeza de que deseja excluir o inbound?" -"deleteClient" = "Excluir Cliente" -"deleteClientContent" = "Tem certeza de que deseja excluir o cliente?" -"resetTrafficContent" = "Tem certeza de que deseja redefinir o tráfego?" -"copyLink" = "Copiar URL" -"address" = "Endereço" -"network" = "Rede" -"destinationPort" = "Porta de Destino" -"targetAddress" = "Endereço de Destino" -"monitorDesc" = "Deixe em branco para ouvir todos os IPs" -"meansNoLimit" = "= Ilimitado. (unidade: GB)" -"totalFlow" = "Fluxo Total" -"leaveBlankToNeverExpire" = "Deixe em branco para nunca expirar" -"noRecommendKeepDefault" = "Recomenda-se manter o padrão" -"certificatePath" = "Caminho" -"certificateContent" = "Conteúdo" -"publicKey" = "Chave Pública" -"privatekey" = "Chave Privada" -"clickOnQRcode" = "Clique no Código QR para Copiar" -"client" = "Cliente" -"export" = "Exportar Todos os URLs" -"clone" = "Clonar" -"cloneInbound" = "Clonar" -"cloneInboundContent" = "Todas as configurações deste inbound, exceto Porta, IP de Escuta e Clientes, serão aplicadas ao clone." -"cloneInboundOk" = "Clonar" -"resetAllTraffic" = "Redefinir Tráfego de Todos os Inbounds" -"resetAllTrafficTitle" = "Redefinir Tráfego de Todos os Inbounds" -"resetAllTrafficContent" = "Tem certeza de que deseja redefinir o tráfego de todos os inbounds?" -"resetInboundClientTraffics" = "Redefinir Tráfego dos Clientes" -"resetInboundClientTrafficTitle" = "Redefinir Tráfego dos Clientes" -"resetInboundClientTrafficContent" = "Tem certeza de que deseja redefinir o tráfego dos clientes deste inbound?" -"resetAllClientTraffics" = "Redefinir Tráfego de Todos os Clientes" -"resetAllClientTrafficTitle" = "Redefinir Tráfego de Todos os Clientes" -"resetAllClientTrafficContent" = "Tem certeza de que deseja redefinir o tráfego de todos os clientes?" -"delDepletedClients" = "Excluir Clientes Esgotados" -"delDepletedClientsTitle" = "Excluir Clientes Esgotados" -"delDepletedClientsContent" = "Tem certeza de que deseja excluir todos os clientes esgotados?" -"email" = "Email" -"emailDesc" = "Por favor, forneça um endereço de e-mail único." -"IPLimit" = "Limite de IP" -"IPLimitDesc" = "Desativa o inbound se o número ultrapassar o valor definido. (0 = desativar)" -"IPLimitlog" = "Log de IP" -"IPLimitlogDesc" = "O histórico de IPs. (para ativar o inbound após a desativação, limpe o log)" -"IPLimitlogclear" = "Limpar o Log" -"setDefaultCert" = "Definir Certificado pelo Painel" -"telegramDesc" = "Por favor, forneça o ID do Chat do Telegram. (use o comando '/id' no bot) ou (@userinfobot)" -"subscriptionDesc" = "Para encontrar seu URL de assinatura, navegue até 'Detalhes'. Além disso, você pode usar o mesmo nome para vários clientes." -"info" = "Informações" -"same" = "Igual" -"inboundData" = "Dados do Inbound" -"exportInbound" = "Exportar Inbound" -"import" = "Importar" -"importInbound" = "Importar um Inbound" -"periodicTrafficResetTitle" = "Reset de Tráfego" -"periodicTrafficResetDesc" = "Reinicia automaticamente o contador de tráfego em intervalos especificados" -"lastReset" = "Último Reset" - -[pages.client] -"add" = "Adicionar Cliente" -"edit" = "Editar Cliente" -"submitAdd" = "Adicionar Cliente" -"submitEdit" = "Salvar Alterações" -"clientCount" = "Número de Clientes" -"bulk" = "Adicionar Vários" -"copyFromInbound" = "Copiar clientes da entrada" -"copyToInbound" = "Copiar clientes para" -"copySelected" = "Copiar selecionados" -"copySource" = "Origem" -"copyEmailPreview" = "Prévia do email resultante" -"copySelectSourceFirst" = "Selecione primeiro uma entrada de origem." -"copyResult" = "Resultado da cópia" -"copyResultSuccess" = "Copiado com sucesso" -"copyResultNone" = "Nada a copiar: nenhum cliente selecionado ou origem vazia" -"copyResultErrors" = "Erros ao copiar" -"copyFlowLabel" = "Flow para novos clientes (VLESS)" -"copyFlowHint" = "Aplicado a todos os clientes copiados. Deixe em branco para ignorar." -"selectAll" = "Selecionar tudo" -"clearAll" = "Limpar tudo" -"method" = "Método" -"first" = "Primeiro" -"last" = "Último" -"prefix" = "Prefixo" -"postfix" = "Sufixo" -"delayedStart" = "Iniciar Após Primeiro Uso" -"expireDays" = "Duração" -"days" = "Dia(s)" -"renew" = "Renovação Automática" -"renewDesc" = "Renovação automática após expiração. (0 = desativado)(unidade: dia)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Nunca" -"daily" = "Diariamente" -"weekly" = "Semanalmente" -"monthly" = "Mensalmente" -"hourly" = "A cada hora" - -[pages.inbounds.toasts] -"obtain" = "Obter" -"updateSuccess" = "A atualização foi bem-sucedida" -"logCleanSuccess" = "O log foi limpo" -"inboundsUpdateSuccess" = "Entradas atualizadas com sucesso" -"inboundUpdateSuccess" = "Entrada atualizada com sucesso" -"inboundCreateSuccess" = "Entrada criada com sucesso" -"inboundDeleteSuccess" = "Entrada excluída com sucesso" -"inboundClientAddSuccess" = "Cliente(s) de entrada adicionado(s)" -"inboundClientDeleteSuccess" = "Cliente de entrada excluído" -"inboundClientUpdateSuccess" = "Cliente de entrada atualizado" -"delDepletedClientsSuccess" = "Todos os clientes esgotados foram excluídos" -"resetAllClientTrafficSuccess" = "Todo o tráfego do cliente foi reiniciado" -"resetAllTrafficSuccess" = "Todo o tráfego foi reiniciado" -"resetInboundClientTrafficSuccess" = "O tráfego foi reiniciado" -"trafficGetError" = "Erro ao obter tráfegos" -"getNewX25519CertError" = "Erro ao obter o certificado X25519." -"getNewmldsa65Error" = "Erro ao obter o certificado mldsa65." -"getNewVlessEncError" = "Erro ao obter o certificado VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Requisição" -"response" = "Resposta" -"name" = "Nome" -"value" = "Valor" - -[pages.inbounds.stream.tcp] -"version" = "Versão" -"method" = "Método" -"path" = "Caminho" -"status" = "Status" -"statusDescription" = "Descrição do Status" -"requestHeader" = "Cabeçalho da Requisição" -"responseHeader" = "Cabeçalho da Resposta" - -[pages.settings] -"title" = "Configurações do Painel" -"save" = "Salvar" -"infoDesc" = "Toda alteração feita aqui precisa ser salva. Reinicie o painel para aplicar as alterações." -"restartPanel" = "Reiniciar Painel" -"restartPanelDesc" = "Tem certeza de que deseja reiniciar o painel? Se não conseguir acessar o painel após reiniciar, consulte os logs do painel no servidor." -"restartPanelSuccess" = "O painel foi reiniciado com sucesso" -"actions" = "Ações" -"resetDefaultConfig" = "Redefinir para Padrão" -"panelSettings" = "Geral" -"securitySettings" = "Autenticação" -"TGBotSettings" = "Bot do Telegram" -"panelListeningIP" = "IP de Escuta" -"panelListeningIPDesc" = "O endereço IP para o painel web. (deixe em branco para escutar em todos os IPs)" -"panelListeningDomain" = "Domínio de Escuta" -"panelListeningDomainDesc" = "O nome de domínio para o painel web. (deixe em branco para escutar em todos os domínios e IPs)" -"panelPort" = "Porta de Escuta" -"panelPortDesc" = "O número da porta para o painel web. (deve ser uma porta não usada)" -"publicKeyPath" = "Caminho da Chave Pública" -"publicKeyPathDesc" = "O caminho do arquivo de chave pública para o painel web. (começa com ‘/‘)" -"privateKeyPath" = "Caminho da Chave Privada" -"privateKeyPathDesc" = "O caminho do arquivo de chave privada para o painel web. (começa com ‘/‘)" -"panelUrlPath" = "Caminho URI" -"panelUrlPathDesc" = "O caminho URI para o painel web. (começa com ‘/‘ e termina com ‘/‘)" -"pageSize" = "Tamanho da Paginação" -"pageSizeDesc" = "Definir o tamanho da página para a tabela de entradas. (0 = desativado)" -"remarkModel" = "Modelo de Observação & Caractere de Separação" -"datepicker" = "Tipo de Calendário" -"datepickerPlaceholder" = "Selecionar data" -"datepickerDescription" = "Tarefas agendadas serão executadas com base neste calendário." -"sampleRemark" = "Exemplo de Observação" -"oldUsername" = "Nome de Usuário Atual" -"currentPassword" = "Senha Atual" -"newUsername" = "Novo Nome de Usuário" -"newPassword" = "Nova Senha" -"telegramBotEnable" = "Ativar Bot do Telegram" -"telegramBotEnableDesc" = "Ativa o bot do Telegram." -"telegramToken" = "Token do Telegram" -"telegramTokenDesc" = "O token do bot do Telegram obtido de '@BotFather'." -"telegramProxy" = "Proxy SOCKS" -"telegramProxyDesc" = "Ativa o proxy SOCKS5 para conectar ao Telegram. (ajuste as configurações conforme o guia)" -"telegramAPIServer" = "API Server do Telegram" -"telegramAPIServerDesc" = "O servidor API do Telegram a ser usado. Deixe em branco para usar o servidor padrão." -"telegramChatId" = "ID de Chat do Administrador" -"telegramChatIdDesc" = "O(s) ID(s) de Chat do Administrador no Telegram. (separado por vírgulas)(obtenha aqui @userinfobot) ou (use o comando '/id' no bot)" -"telegramNotifyTime" = "Hora da Notificação" -"telegramNotifyTimeDesc" = "O horário de notificação do bot do Telegram configurado para relatórios periódicos. (use o formato de tempo do crontab)" -"tgNotifyBackup" = "Backup do Banco de Dados" -"tgNotifyBackupDesc" = "Enviar arquivo de backup do banco de dados junto com o relatório." -"tgNotifyLogin" = "Notificação de Login" -"tgNotifyLoginDesc" = "Receba notificações sobre o nome de usuário, endereço IP e horário sempre que alguém tentar fazer login no seu painel web." -"sessionMaxAge" = "Duração da Sessão" -"sessionMaxAgeDesc" = "A duração pela qual você pode permanecer logado. (unidade: minuto)" -"expireTimeDiff" = "Notificação de Expiração" -"expireTimeDiffDesc" = "Receba notificações sobre a data de expiração ao atingir esse limite. (unidade: dia)" -"trafficDiff" = "Notificação de Limite de Tráfego" -"trafficDiffDesc" = "Receba notificações sobre o limite de tráfego ao atingir esse limite. (unidade: GB)" -"tgNotifyCpu" = "Notificação de Carga da CPU" -"tgNotifyCpuDesc" = "Receba notificações se a carga da CPU ultrapassar esse limite. (unidade: %)" -"timeZone" = "Fuso Horário" -"timeZoneDesc" = "As tarefas agendadas serão executadas com base nesse fuso horário." -"subSettings" = "Assinatura" -"subEnable" = "Ativar Serviço de Assinatura" -"subEnableDesc" = "Ativa o serviço de assinatura." -"subJsonEnable" = "Ativar/Desativar o endpoint de assinatura JSON de forma independente." -"subTitle" = "Título da Assinatura" -"subTitleDesc" = "Título exibido no cliente VPN" -"subSupportUrl" = "URL de Suporte" -"subSupportUrlDesc" = "Link de suporte técnico exibido no cliente VPN" -"subProfileUrl" = "URL de Perfil" -"subProfileUrlDesc" = "Um link para o seu site exibido no cliente VPN" -"subAnnounce" = "Anúncio" -"subAnnounceDesc" = "O texto do anúncio exibido no cliente VPN" -"subEnableRouting" = "Ativar roteamento" -"subEnableRoutingDesc" = "Configuração global para habilitar o roteamento no cliente VPN. (Apenas para Happ)" -"subRoutingRules" = "Regras de roteamento" -"subRoutingRulesDesc" = "Regras de roteamento globais para o cliente VPN. (Apenas para Happ)" -"subListen" = "IP de Escuta" -"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)" -"subPort" = "Porta de Escuta" -"subPortDesc" = "O número da porta para o serviço de assinatura. (deve ser uma porta não usada)" -"subCertPath" = "Caminho da Chave Pública" -"subCertPathDesc" = "O caminho do arquivo de chave pública para o serviço de assinatura. (começa com ‘/‘)" -"subKeyPath" = "Caminho da Chave Privada" -"subKeyPathDesc" = "O caminho do arquivo de chave privada para o serviço de assinatura. (começa com ‘/‘)" -"subPath" = "Caminho URI" -"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)" -"subDomain" = "Domínio de Escuta" -"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)" -"subUpdates" = "Intervalos de Atualização" -"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)" -"subEncrypt" = "Codificar" -"subEncryptDesc" = "O conteúdo retornado pelo serviço de assinatura será codificado em Base64." -"subShowInfo" = "Mostrar Informações de Uso" -"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente." -"subURI" = "URI de Proxy Reverso" -"subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies." -"externalTrafficInformEnable" = "Informações de tráfego externo" -"externalTrafficInformEnableDesc" = "Informar a API externa sobre cada atualização de tráfego." -"externalTrafficInformURI" = "URI de informação de tráfego externo" -"externalTrafficInformURIDesc" = "As atualizações de tráfego são enviadas para este URI." -"restartXrayOnClientDisable" = "Reiniciar Xray Após Desativação Automática" -"restartXrayOnClientDisableDesc" = "Quando um cliente for desativado automaticamente por expiração ou limite de tráfego, reinicie o Xray." -"fragment" = "Fragmentação" -"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello." -"fragmentSett" = "Configurações de Fragmentação" -"noisesDesc" = "Ativar Noises." -"noisesSett" = "Configurações de Noises" -"mux" = "Mux" -"muxDesc" = "Transmitir múltiplos fluxos de dados independentes dentro de um fluxo de dados estabelecido." -"muxSett" = "Configurações de Mux" -"direct" = "Conexão Direta" -"directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico." -"notifications" = "Notificações" -"certs" = "Certificados" -"externalTraffic" = "Tráfego Externo" -"dateAndTime" = "Data e Hora" -"proxyAndServer" = "Proxy e Servidor" -"intervals" = "Intervalos" -"information" = "Informação" -"language" = "Idioma" -"telegramBotLanguage" = "Idioma do Bot do Telegram" - -[pages.xray] -"title" = "Configurações Xray" -"save" = "Salvar" -"restart" = "Reiniciar Xray" -"restartSuccess" = "Xray foi reiniciado com sucesso" -"stopSuccess" = "Xray foi interrompido com sucesso" -"restartError" = "Ocorreu um erro ao reiniciar o Xray." -"stopError" = "Ocorreu um erro ao parar o Xray." -"basicTemplate" = "Básico" -"advancedTemplate" = "Avançado" -"generalConfigs" = "Geral" -"generalConfigsDesc" = "Essas opções determinam ajustes gerais." -"logConfigs" = "Log" -"logConfigsDesc" = "Os logs podem afetar a eficiência do servidor. É recomendável habilitá-los com sabedoria apenas se necessário." -"blockConfigsDesc" = "Essas opções bloqueiam tráfego com base em protocolos e sites específicos solicitados." -"basicRouting" = "Roteamento Básico" -"blockConnectionsConfigsDesc" = "Essas opções bloquearão o tráfego com base no país solicitado." -"directConnectionsConfigsDesc" = "Uma conexão direta garante que o tráfego específico não seja roteado por outro servidor." -"blockips" = "Bloquear IPs" -"blockdomains" = "Bloquear Domínios" -"directips" = "IPs Diretos" -"directdomains" = "Domínios Diretos" -"ipv4Routing" = "Roteamento IPv4" -"ipv4RoutingDesc" = "Essas opções roteam o tráfego para um destino específico via IPv4." -"warpRouting" = "Roteamento WARP" -"warpRoutingDesc" = "Essas opções roteam o tráfego para um destino específico via WARP." -"nordRouting" = "Roteamento NordVPN" -"nordRoutingDesc" = "Essas opções roteiam o tráfego para um destino específico via NordVPN." -"Template" = "Modelo de Configuração Avançada do Xray" -"TemplateDesc" = "O arquivo final de configuração do Xray será gerado com base neste modelo." -"FreedomStrategy" = "Estratégia do Protocolo Freedom" -"FreedomStrategyDesc" = "Definir a estratégia de saída para a rede no Protocolo Freedom." -"RoutingStrategy" = "Estratégia Geral de Roteamento" -"RoutingStrategyDesc" = "Definir a estratégia geral de roteamento de tráfego para resolver todas as solicitações." -"outboundTestUrl" = "URL de teste de outbound" -"outboundTestUrlDesc" = "URL usada ao testar conectividade do outbound" -"Torrent" = "Bloquear Protocolo BitTorrent" -"Inbounds" = "Inbounds" -"InboundsDesc" = "Aceitar clientes específicos." -"Outbounds" = "Outbounds" -"Balancers" = "Balanceadores" -"OutboundsDesc" = "Definir o caminho de saída do tráfego." -"Routings" = "Regras de Roteamento" -"RoutingsDesc" = "A prioridade de cada regra é importante!" -"completeTemplate" = "Todos" -"logLevel" = "Nível de Log" -"logLevelDesc" = "O nível de log para erros, indicando a informação que precisa ser registrada." -"accessLog" = "Log de Acesso" -"accessLogDesc" = "O caminho do arquivo para o log de acesso. O valor especial 'none' desativa os logs de acesso." -"errorLog" = "Log de Erros" -"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro." -"dnsLog" = "Log DNS" -"dnsLogDesc" = "Se ativar logs de consulta DNS" -"maskAddress" = "Mascarar Endereço" -"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log." -"statistics" = "Estatísticas" -"statsInboundUplink" = "Estatísticas de Upload de Entrada" -"statsInboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de entrada." -"statsInboundDownlink" = "Estatísticas de Download de Entrada" -"statsInboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de entrada." -"statsOutboundUplink" = "Estatísticas de Upload de Saída" -"statsOutboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de saída." -"statsOutboundDownlink" = "Estatísticas de Download de Saída" -"statsOutboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de saída." - -[pages.xray.rules] -"first" = "Primeiro" -"last" = "Último" -"up" = "Cima" -"down" = "Baixo" -"source" = "Fonte" -"dest" = "Destino" -"inbound" = "Entrada" -"outbound" = "Saída" -"balancer" = "Balanceador" -"info" = "Info" -"add" = "Adicionar Regra" -"edit" = "Editar Regra" -"useComma" = "Itens separados por vírgula" - -[pages.xray.outbound] -"addOutbound" = "Adicionar Saída" -"addReverse" = "Adicionar Reverso" -"editOutbound" = "Editar Saída" -"editReverse" = "Editar Reverso" -"reverseTag" = "Tag de Reverso" -"reverseTagDesc" = "Tag de saída do proxy reverso simples VLESS. Deixe vazio para desabilitar." -"reverseTagPlaceholder" = "tag de saída (vazio para desabilitar)" -"tag" = "Tag" -"tagDesc" = "Tag Única" -"address" = "Endereço" -"reverse" = "Reverso" -"domain" = "Domínio" -"type" = "Tipo" -"bridge" = "Ponte" -"portal" = "Portal" -"link" = "Link" -"intercon" = "Interconexão" -"settings" = "Configurações" -"accountInfo" = "Informações da Conta" -"outboundStatus" = "Status de Saída" -"sendThrough" = "Enviar Através de" -"test" = "Testar" -"testResult" = "Resultado do teste" -"testing" = "Testando conexão..." -"testSuccess" = "Teste bem-sucedido" -"testFailed" = "Teste falhou" -"testError" = "Falha ao testar saída" -"nordvpn" = "NordVPN" -"accessToken" = "Token de Acesso" -"country" = "País" -"server" = "Servidor" -"city" = "Cidade" -"allCities" = "Todas as Cidades" -"privateKey" = "Chave Privada" -"load" = "Carga" - -[pages.xray.balancer] -"addBalancer" = "Adicionar Balanceador" -"editBalancer" = "Editar Balanceador" -"balancerStrategy" = "Estratégia" -"balancerSelectors" = "Seletores" -"tag" = "Tag" -"tagDesc" = "Tag Única" -"balancerDesc" = "Não é possível usar balancerTag e outboundTag ao mesmo tempo. Se usados simultaneamente, apenas outboundTag funcionará." - -[pages.xray.wireguard] -"secretKey" = "Chave Secreta" -"publicKey" = "Chave Pública" -"allowedIPs" = "IPs Permitidos" -"endpoint" = "Ponto Final" -"psk" = "Chave Pré-Compartilhada" -"domainStrategy" = "Estratégia de Domínio" - -[pages.xray.tun] -"nameDesc" = "O nome da interface TUN. O padrão é 'xray0'" -"mtuDesc" = "Unidade Máxima de Transmissão. O tamanho máximo dos pacotes de dados. O padrão é 1500" -"userLevel" = "Nível do Usuário" -"userLevelDesc" = "Todas as conexões feitas através deste inbound usarão este nível de usuário. O padrão é 0" - -[pages.xray.dns] -"enable" = "Ativar DNS" -"enableDesc" = "Ativar o servidor DNS integrado" -"tag" = "Tag de Entrada DNS" -"tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento." -"clientIp" = "IP do Cliente" -"clientIpDesc" = "Usado para notificar o servidor sobre a localização IP especificada durante consultas DNS" -"disableCache" = "Desativar cache" -"disableCacheDesc" = "Desativa o cache de DNS" -"disableFallback" = "Desativar Fallback" -"disableFallbackDesc" = "Desativa consultas DNS de fallback" -"disableFallbackIfMatch" = "Desativar Fallback Se Corresponder" -"disableFallbackIfMatchDesc" = "Desativa consultas DNS de fallback quando a lista de domínios correspondentes do servidor DNS é atingida" -"enableParallelQuery" = "Habilitar Consulta Paralela" -"enableParallelQueryDesc" = "Habilitar consultas DNS paralelas para múltiplos servidores para resolução mais rápida" -"strategy" = "Estratégia de Consulta" -"strategyDesc" = "Estratégia geral para resolver nomes de domínio" -"add" = "Adicionar Servidor" -"edit" = "Editar Servidor" -"domains" = "Domínios" -"expectIPs" = "IPs Esperadas" -"unexpectIPs" = "IPs inesperados" -"useSystemHosts" = "Usar Hosts do sistema" -"useSystemHostsDesc" = "Usar o arquivo hosts de um sistema instalado" -"usePreset" = "Usar modelo" -"dnsPresetTitle" = "Modelos DNS" -"dnsPresetFamily" = "Familiar" - -[pages.xray.fakedns] -"add" = "Adicionar Fake DNS" -"edit" = "Editar Fake DNS" -"ipPool" = "Sub-rede do Pool de IP" -"poolSize" = "Tamanho do Pool" - -[pages.settings.security] -"admin" = "Credenciais de administrador" -"twoFactor" = "Autenticação de dois fatores" -"twoFactorEnable" = "Ativar 2FA" -"twoFactorEnableDesc" = "Adiciona uma camada extra de autenticação para mais segurança." -"twoFactorModalSetTitle" = "Ativar autenticação de dois fatores" -"twoFactorModalDeleteTitle" = "Desativar autenticação de dois fatores" -"twoFactorModalSteps" = "Para configurar a autenticação de dois fatores, siga alguns passos:" -"twoFactorModalFirstStep" = "1. Escaneie este QR code no aplicativo de autenticação ou copie o token próximo ao QR code e cole no aplicativo" -"twoFactorModalSecondStep" = "2. Digite o código do aplicativo" -"twoFactorModalRemoveStep" = "Digite o código do aplicativo para remover a autenticação de dois fatores." -"twoFactorModalChangeCredentialsTitle" = "Alterar credenciais" -"twoFactorModalChangeCredentialsStep" = "Insira o código do aplicativo para alterar as credenciais do administrador." -"twoFactorModalSetSuccess" = "A autenticação de dois fatores foi estabelecida com sucesso" -"twoFactorModalDeleteSuccess" = "A autenticação de dois fatores foi excluída com sucesso" -"twoFactorModalError" = "Código incorreto" - -[pages.settings.toasts] -"modifySettings" = "Os parâmetros foram alterados." -"getSettings" = "Ocorreu um erro ao recuperar os parâmetros." -"modifyUserError" = "Ocorreu um erro ao alterar as credenciais do administrador." -"modifyUser" = "Você alterou com sucesso as credenciais do administrador." -"originalUserPassIncorrect" = "O nome de usuário ou senha atual é inválido" -"userPassMustBeNotEmpty" = "O novo nome de usuário e senha não podem estar vazios" -"getOutboundTrafficError" = "Erro ao obter tráfego de saída" -"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída" - -[tgbot] -"keyboardClosed" = "❌ Teclado fechado!" -"noResult" = "❗ Nenhum resultado!" -"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!" -"wentWrong" = "❌ Algo deu errado!" -"noIpRecord" = "❗ Nenhum registro de IP!" -"noInbounds" = "❗ Nenhum inbound encontrado!" -"unlimited" = "♾ Ilimitado (Reset)" -"add" = "Adicionar" -"month" = "Mês" -"months" = "Meses" -"day" = "Dia" -"days" = "Dias" -"hours" = "Horas" -"minutes" = "Minutos" -"unknown" = "Desconhecido" -"inbounds" = "Inbounds" -"clients" = "Clientes" -"offline" = "🔴 Offline" -"online" = "🟢 Online" - -[tgbot.commands] -"unknown" = "❗ Comando desconhecido." -"pleaseChoose" = "👇 Escolha:\r\n" -"help" = "🤖 Bem-vindo a este bot! Ele foi projetado para oferecer dados específicos do painel da web e permite que você faça as modificações necessárias.\r\n\r\n" -"start" = "👋 Olá {{ .Firstname }}.\r\n" -"welcome" = "🤖 Bem-vindo ao bot de gerenciamento do {{ .Hostname }}.\r\n" -"status" = "✅ Bot está OK!" -"usage" = "❗ Por favor, forneça um texto para pesquisar!" -"getID" = "🆔 Seu ID: {{ .ID }}" -"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n/restart\r\n\r\nPara pesquisar por um email de cliente:\r\n/usage [Email]\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n/inbound [Remark]\r\n\r\nTelegram Chat ID:\r\n/id" -"helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n/usage [Email]\r\n\r\nTelegram Chat ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Operação bem-sucedida!" -"restartFailed" = "❗ Erro na operação.\r\n\r\nErro: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core não está em execução." -"startDesc" = "Mostrar menu principal" -"helpDesc" = "Ajuda do bot" -"statusDesc" = "Verificar status do bot" -"idDesc" = "Mostrar seu ID do Telegram" - -[tgbot.messages] -"cpuThreshold" = "🔴 A carga da CPU {{ .Percent }}% excede o limite de {{ .Threshold }}%" -"selectUserFailed" = "❌ Erro na seleção do usuário!" -"userSaved" = "✅ Usuário do Telegram salvo." -"loginSuccess" = "✅ Conectado ao painel com sucesso.\r\n" -"loginFailed" = "❗️Tentativa de login no painel falhou.\r\n" -"2faFailed" = "Falha no 2FA" -"report" = "🕰 Relatórios agendados: {{ .RunTime }}\r\n" -"datetime" = "⏰ Data&Hora: {{ .DateTime }}\r\n" -"hostname" = "💻 Host: {{ .Hostname }}\r\n" -"version" = "🚀 Versão 3X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Versão Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IPs:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Tempo de atividade: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Carga do sistema: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Tráfego: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n" -"username" = "👤 Nome de usuário: {{ .Username }}\r\n" -"reason" = "❗️ Motivo: {{ .Reason }}\r\n" -"time" = "⏰ Hora: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Porta: {{ .Port }}\r\n" -"expire" = "📅 Data de expiração: {{ .Time }}\r\n" -"expireIn" = "📅 Expira em: {{ .Time }}\r\n" -"active" = "💡 Ativo: {{ .Enable }}\r\n" -"enabled" = "🚨 Ativado: {{ .Enable }}\r\n" -"online" = "🌐 Status da conexão: {{ .Status }}\r\n" -"lastOnline" = "🔙 Última vez online: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Usuário do Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 {{ .Type }} esgotado:\r\n" -"exhaustedCount" = "🚨 Contagem de {{ .Type }} esgotado:\r\n" -"onlinesCount" = "🌐 Clientes online: {{ .Count }}\r\n" -"disabled" = "🛑 Desativado: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Esgotar em breve: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Hora do backup: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Atualizado em: {{ .Time }}\r\n\r\n" -"yes" = "✅ Sim" -"no" = "❌ Não" -"received_id" = "🔑📥 ID atualizado." -"received_password" = "🔑📥 Senha atualizada." -"received_email" = "📧📥 E-mail atualizado." -"received_comment" = "💬📥 Comentário atualizado." -"id_prompt" = "🔑 ID Padrão: {{ .ClientId }}\n\nDigite seu ID." -"pass_prompt" = "🔑 Senha Padrão: {{ .ClientPassword }}\n\nDigite sua senha." -"email_prompt" = "📧 E-mail Padrão: {{ .ClientEmail }}\n\nDigite seu e-mail." -"comment_prompt" = "💬 Comentário Padrão: {{ .ClientComment }}\n\nDigite seu comentário." -"inbound_client_data_id" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!" -"inbound_client_data_pass" = "🔄 Entrada: {{ .InboundRemark }}\n\n🔑 Senha: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Tráfego: {{ .ClientTraffic }}\n📅 Data de expiração: {{ .ClientExp }}\n🌐 Limite de IP: {{ .IpLimit }}\n💬 Comentário: {{ .ClientComment }}\n\nAgora você pode adicionar o cliente à entrada!" -"cancel" = "❌ Processo Cancelado! \n\nVocê pode iniciar novamente a qualquer momento com /start. 🔄" -"error_add_client" = "⚠️ Erro:\n\n {{ .error }}" -"using_default_value" = "Tudo bem, vou manter o valor padrão. 😊" -"incorrect_input" = "Sua entrada não é válida.\nAs frases devem ser contínuas, sem espaços.\nExemplo correto: aaaaaa\nExemplo incorreto: aaa aaa 🚫" -"AreYouSure" = "Você tem certeza? 🤔" -"SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ✅ Sucesso" -"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ❌ Falhou \n\n🛠️ Erro: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Processo de redefinição de tráfego concluído para todos os clientes." - -[tgbot.buttons] -"closeKeyboard" = "❌ Fechar teclado" -"cancel" = "❌ Cancelar" -"cancelReset" = "❌ Cancelar redefinição" -"cancelIpLimit" = "❌ Cancelar limite de IP" -"confirmResetTraffic" = "✅ Confirmar redefinição de tráfego?" -"confirmClearIps" = "✅ Confirmar limpar IPs?" -"confirmRemoveTGUser" = "✅ Confirmar remover usuário do Telegram?" -"confirmToggle" = "✅ Confirmar ativar/desativar usuário?" -"dbBackup" = "Obter backup do DB" -"serverUsage" = "Uso do servidor" -"getInbounds" = "Obter Inbounds" -"depleteSoon" = "Esgotar em breve" -"clientUsage" = "Obter uso" -"onlines" = "Clientes online" -"commands" = "Comandos" -"refresh" = "🔄 Atualizar" -"clearIPs" = "❌ Limpar IPs" -"removeTGUser" = "❌ Remover usuário do Telegram" -"selectTGUser" = "👤 Selecionar usuário do Telegram" -"selectOneTGUser" = "👤 Selecione um usuário do Telegram:" -"resetTraffic" = "📈 Redefinir tráfego" -"resetExpire" = "📅 Alterar data de expiração" -"ipLog" = "🔢 Log de IP" -"ipLimit" = "🔢 Limite de IP" -"setTGUser" = "👤 Definir usuário do Telegram" -"toggle" = "🔘 Ativar / Desativar" -"custom" = "🔢 Personalizado" -"confirmNumber" = "✅ Confirmar: {{ .Num }}" -"confirmNumberAdd" = "✅ Confirmar adicionar: {{ .Num }}" -"limitTraffic" = "🚧 Limite de tráfego" -"getBanLogs" = "Obter logs de banimento" -"allClients" = "Todos os clientes" -"addClient" = "Adicionar Cliente" -"submitDisable" = "Enviar como Desativado ☑️" -"submitEnable" = "Enviar como Ativado ✅" -"use_default" = "🏷️ Usar padrão" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Senha" -"change_email" = "⚙️📧 E-mail" -"change_comment" = "⚙️💬 Comentário" -"ResetAllTraffics" = "Redefinir Todo o Tráfego" -"SortedTrafficUsageReport" = "Relatório de Uso de Tráfego Ordenado" - -[tgbot.answers] -"successfulOperation" = "✅ Operação bem-sucedida!" -"errorOperation" = "❗ Erro na operação." -"getInboundsFailed" = "❌ Falha ao obter inbounds." -"getClientsFailed" = "❌ Falha ao obter clientes." -"canceled" = "❌ {{ .Email }}: Operação cancelada." -"clientRefreshSuccess" = "✅ {{ .Email }}: Cliente atualizado com sucesso." -"IpRefreshSuccess" = "✅ {{ .Email }}: IPs atualizados com sucesso." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Usuário do Telegram do cliente atualizado com sucesso." -"resetTrafficSuccess" = "✅ {{ .Email }}: Tráfego redefinido com sucesso." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Limite de tráfego salvo com sucesso." -"expireResetSuccess" = "✅ {{ .Email }}: Dias de expiração redefinidos com sucesso." -"resetIpSuccess" = "✅ {{ .Email }}: Limite de IP {{ .Count }} salvo com sucesso." -"clearIpSuccess" = "✅ {{ .Email }}: IPs limpos com sucesso." -"getIpLog" = "✅ {{ .Email }}: Obter log de IP." -"getUserInfo" = "✅ {{ .Email }}: Obter informações do usuário do Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Usuário do Telegram removido com sucesso." -"enableSuccess" = "✅ {{ .Email }}: Ativado com sucesso." -"disableSuccess" = "✅ {{ .Email }}: Desativado com sucesso." -"askToAddUserId" = "Sua configuração não foi encontrada!\r\nPeça ao seu administrador para usar seu Telegram ChatID em suas configurações.\r\n\r\nSeu ChatID: {{ .TgUserID }}" -"chooseClient" = "Escolha um cliente para Inbound {{ .Inbound }}" -"chooseInbound" = "Escolha um Inbound" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml deleted file mode 100644 index 3317cd53..00000000 --- a/web/translation/translate.ru_RU.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Имя пользователя" -"password" = "Пароль" -"login" = "Войти" -"confirm" = "Подтвердить" -"cancel" = "Отмена" -"close" = "Закрыть" -"save" = "Сохранить" -"logout" = "Выход" -"create" = "Создать" -"update" = "Обновить" -"copy" = "Копировать" -"copied" = "Скопировано" -"download" = "Скачать" -"remark" = "Примечание" -"enable" = "Включить" -"protocol" = "Протокол" -"search" = "Поиск" -"filter" = "Фильтр" -"loading" = "Загрузка..." -"second" = "Секунда" -"minute" = "Минута" -"hour" = "Час" -"day" = "День" -"check" = "Проверить" -"indefinite" = "Бесконечно" -"unlimited" = "Безлимит" -"none" = "Пусто" -"qrCode" = "QR-код" -"info" = "Информация" -"edit" = "Изменить" -"delete" = "Удалить" -"reset" = "Сбросить" -"noData" = "Нет данных." -"copySuccess" = "Скопировано" -"sure" = "Да" -"encryption" = "Шифрование" -"useIPv4ForHost" = "Использовать IPv4 для подключения к хосту" -"transmission" = "Транспорт" -"host" = "Хост" -"path" = "Путь" -"camouflage" = "Маскировка" -"status" = "Статус" -"enabled" = "Включено" -"disabled" = "Отключено" -"depleted" = "Исчерпано" -"depletingSoon" = "Почти исчерпано" -"offline" = "Офлайн" -"online" = "Онлайн" -"domainName" = "Домен" -"monitor" = "Мониторинг IP" -"certificate" = "SSL-сертификат" -"fail" = "Сбой" -"comment" = "Комментарий" -"success" = "Успешно" -"lastOnline" = "Был(а) в сети" -"getVersion" = "Узнать версию" -"install" = "Установка" -"clients" = "Клиенты" -"usage" = "Использование" -"twoFactorCode" = "Код 2FA" -"remained" = "Остаток" -"security" = "Безопасность" -"secAlertTitle" = "Предупреждение системы безопасности" -"secAlertSsl" = "Соединение не защищено. Не вводите конфиденциальные данные до установки SSL-сертификата." -"secAlertConf" = "Некоторые настройки уязвимы. Рекомендуется усилить защиту для предотвращения атак." -"secAlertSSL" = "Подключение к панели не защищено. Установите SSL-сертификат для защиты данных." -"secAlertPanelPort" = "Порт панели по умолчанию небезопасен. Установите нестандартный или случайный порт." -"secAlertPanelURI" = "Адрес панели по умолчанию небезопасен. Настройте уникальный и сложный URI." -"secAlertSubURI" = "URI подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес." -"secAlertSubJsonURI" = "URI JSON-подписки по умолчанию небезопасен. Настройте уникальный и сложный адрес." -"emptyDnsDesc" = "Нет добавленных DNS-серверов." -"emptyFakeDnsDesc" = "Нет добавленных Fake DNS-серверов." -"emptyBalancersDesc" = "Нет добавленных балансировщиков." -"emptyReverseDesc" = "Нет добавленных реверс-прокси." -"somethingWentWrong" = "Что-то пошло не так" - -[subscription] -"title" = "Информация о подписке" -"subId" = "ID подписки" -"status" = "Статус" -"downloaded" = "Загружено" -"uploaded" = "Отправлено" -"expiry" = "Срок действия" -"totalQuota" = "Общий лимит" -"individualLinks" = "Индивидуальные ссылки" -"active" = "Активна" -"inactive" = "Неактивна" -"unlimited" = "Неограниченно" -"noExpiry" = "Бессрочно" - -[menu] -"theme" = "Тема" -"dark" = "Темная" -"ultraDark" = "Очень темная" -"dashboard" = "Дашборд" -"inbounds" = "Подключения" -"settings" = "Настройки" -"xray" = "Настройки Xray" -"logout" = "Выход" -"link" = "Управление" - -[pages.login] -"hello" = "Привет!" -"title" = "Добро пожаловать!" -"loginAgain" = "Сессия истекла. Войдите в систему снова" - -[pages.login.toasts] -"invalidFormData" = "Недопустимый формат данных" -"emptyUsername" = "Введите имя пользователя" -"emptyPassword" = "Введите пароль" -"wrongUsernameOrPassword" = "Неверные данные учетной записи." -"successLogin" = "Вход выполнен успешно" - -[pages.index] -"title" = "Дашборд" -"cpu" = "ЦП" -"logicalProcessors" = "Логические процессоры" -"frequency" = "Частота" -"swap" = "Файл подкачки" -"storage" = "Диск" -"memory" = "ОЗУ" -"threads" = "Потоки" -"xrayStatus" = "Xray" -"stopXray" = "Остановить" -"restartXray" = "Перезапустить" -"xraySwitch" = "Выбор версии" -"xraySwitchClick" = "Выберите нужную версию" -"xraySwitchClickDesk" = "Важно: старые версии могут не поддерживать текущие настройки" -"xrayUpdates" = "Обновления Xray" -"updatePanel" = "Обновить панель" -"panelUpdateDesc" = "Это обновит 3X-UI до последнего релиза и перезапустит сервис панели." -"currentPanelVersion" = "Текущая версия панели" -"latestPanelVersion" = "Последняя версия панели" -"panelUpToDate" = "Панель обновлена" -"upToDate" = "Обновлено" -"xrayStatusUnknown" = "Неизвестно" -"xrayStatusRunning" = "Запущен" -"xrayStatusStop" = "Остановлен" -"xrayStatusError" = "Ошибка" -"xrayErrorPopoverTitle" = "Ошибка при запуске Xray" -"operationHours" = "Время работы системы" -"systemLoad" = "Нагрузка на систему" -"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут" -"connectionCount" = "Количество соединений" -"ipAddresses" = "IP-адреса сервера" -"toggleIpVisibility" = "Скрыть или показать IP-адреса сервера" -"overallSpeed" = "Общая скорость передачи трафика" -"upload" = "Отправка" -"download" = "Загрузка" -"totalData" = "Общий объем трафика" -"sent" = "Отправлено" -"received" = "Получено" -"documentation" = "Документация" -"xraySwitchVersionDialog" = "Переключить версию Xray" -"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" -"xraySwitchVersionPopover" = "Xray успешно обновлён" -"panelUpdateDialog" = "Вы действительно хотите обновить панель?" -"panelUpdateDialogDesc" = "Это обновит 3X-UI до версии #version# и перезапустит сервис панели." -"panelUpdateCheckPopover" = "Проверка обновления панели не удалась" -"panelUpdateStartedPopover" = "Обновление панели началось" -"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?" -"geofileUpdateDialogDesc" = "Это обновит файл #filename#." -"geofilesUpdateDialogDesc" = "Это обновит все геофайлы." -"geofilesUpdateAll" = "Обновить все" -"geofileUpdatePopover" = "Геофайлы успешно обновлены" -"customGeoTitle" = "Пользовательские GeoSite / GeoIP" -"customGeoAdd" = "Добавить" -"customGeoType" = "Тип" -"customGeoAlias" = "Псевдоним" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Включено" -"customGeoLastUpdated" = "Обновлено" -"customGeoExtColumn" = "Маршрутизация (ext:…)" -"customGeoToastUpdateAll" = "Все пользовательские источники обновлены" -"customGeoActions" = "Действия" -"customGeoEdit" = "Изменить" -"customGeoDelete" = "Удалить" -"customGeoDownload" = "Обновить сейчас" -"customGeoModalAdd" = "Добавить источник" -"customGeoModalEdit" = "Изменить источник" -"customGeoModalSave" = "Сохранить" -"customGeoDeleteConfirm" = "Удалить этот пользовательский источник?" -"customGeoRoutingHint" = "В правилах маршрутизации используйте значение как ext:файл.dat:тег (замените тег)." -"customGeoInvalidId" = "Некорректный идентификатор" -"customGeoAliasesError" = "Не удалось загрузить список пользовательских geo" -"customGeoValidationAlias" = "Псевдоним: только a-z, цифры, - и _" -"customGeoValidationUrl" = "URL должен начинаться с http:// или https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (свой)" -"customGeoToastList" = "Список пользовательских geo" -"customGeoToastAdd" = "Добавить пользовательский geo" -"customGeoToastUpdate" = "Изменить пользовательский geo" -"customGeoToastDelete" = "Пользовательский geo-файл «{{ .fileName }}» удалён" -"customGeoToastDownload" = "Geofile «{{ .fileName }}» обновлен" -"customGeoErrInvalidType" = "Тип должен быть geosite или geoip" -"customGeoErrAliasRequired" = "Укажите псевдоним" -"customGeoErrAliasPattern" = "Псевдоним содержит недопустимые символы" -"customGeoErrAliasReserved" = "Этот псевдоним зарезервирован" -"customGeoErrUrlRequired" = "Укажите URL" -"customGeoErrInvalidUrl" = "Некорректный URL" -"customGeoErrUrlScheme" = "URL должен использовать http или https" -"customGeoErrUrlHost" = "Некорректный хост URL" -"customGeoErrDuplicateAlias" = "Такой псевдоним уже используется для этого типа" -"customGeoErrNotFound" = "Источник не найден" -"customGeoErrDownload" = "Ошибка загрузки" -"customGeoErrUpdateAllIncomplete" = "Не удалось обновить один или несколько пользовательских источников" -"customGeoEmpty" = "Пользовательских источников geo пока нет — нажмите «Добавить», чтобы создать" -"dontRefresh" = "Установка в процессе. Не обновляйте страницу" -"logs" = "Журнал" -"config" = "Конфигурация" -"backup" = "Резервная копия" -"backupTitle" = "Резервная копия базы данных" -"exportDatabase" = "Экспорт базы данных" -"exportDatabaseDesc" = "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство." -"importDatabase" = "Импорт базы данных" -"importDatabaseDesc" = "Нажмите, чтобы выбрать и загрузить файл .db с вашего устройства для восстановления базы данных из резервной копии." -"importDatabaseSuccess" = "База данных успешно импортирована" -"importDatabaseError" = "Произошла ошибка при импорте базы данных" -"readDatabaseError" = "Произошла ошибка при чтении базы данных" -"getDatabaseError" = "Произошла ошибка при получении базы данных" -"getConfigError" = "Произошла ошибка при получении конфигурационного файла" - -[pages.inbounds] -"allTimeTraffic" = "Общий трафик" -"allTimeTrafficUsage" = "Общее использование за все время" -"title" = "Подключения" -"totalDownUp" = "Отправлено/получено" -"totalUsage" = "Всего трафика" -"inboundCount" = "Всего подключений" -"operate" = "Меню" -"enable" = "Включить" -"remark" = "Примечание" -"protocol" = "Протокол" -"port" = "Порт" -"portMap" = "Порт-маппинг" -"traffic" = "Трафик" -"details" = "Подробнее" -"transportConfig" = "Транспорт" -"expireDate" = "Дата окончания" -"createdAt" = "Создано" -"updatedAt" = "Обновлено" -"resetTraffic" = "Сброс трафика" -"addInbound" = "Создать подключение" -"generalActions" = "Общие действия" -"autoRefresh" = "Автообновление" -"autoRefreshInterval" = "Интервал" -"modifyInbound" = "Изменить подключение" -"deleteInbound" = "Удалить подключение" -"deleteInboundContent" = "Вы уверены, что хотите удалить подключение?" -"deleteClient" = "Удалить клиента" -"deleteClientContent" = "Вы уверены, что хотите удалить клиента?" -"resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?" -"copyLink" = "Копировать ссылку" -"address" = "Адрес" -"network" = "Сеть" -"destinationPort" = "Порт назначения" -"targetAddress" = "Целевой адрес" -"monitorDesc" = "Оставьте пустым для прослушивания всех IP-адресов" -"meansNoLimit" = "= Без ограничений (значение: ГБ)" -"totalFlow" = "Общий расход" -"leaveBlankToNeverExpire" = "Оставьте пустым, чтобы было бесконечным" -"noRecommendKeepDefault" = "Рекомендуется оставить настройки по умолчанию" -"certificatePath" = "Путь к сертификату" -"certificateContent" = "Содержимое сертификата" -"publicKey" = "Публичный ключ" -"privatekey" = "Приватный ключ" -"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать" -"client" = "Клиент" -"export" = "Экспорт ссылок" -"clone" = "Клонировать" -"cloneInbound" = "Клонировать" -"cloneInboundContent" = "Будут клонированы все настройки подключений, кроме списка клиентов, порта и IP-адреса прослушивания" -"cloneInboundOk" = "Клонировано" -"resetAllTraffic" = "Сброс трафика всех подключений" -"resetAllTrafficTitle" = "Сброс трафика всех подключений" -"resetAllTrafficContent" = "Вы уверены, что хотите сбросить трафик всех подключений?" -"resetInboundClientTraffics" = "Сброс трафика клиента" -"resetInboundClientTrafficTitle" = "Сброс трафика клиентов" -"resetInboundClientTrafficContent" = "Вы уверены, что хотите сбросить трафик для этих клиентов?" -"resetAllClientTraffics" = "Сброс трафика всех клиентов" -"resetAllClientTrafficTitle" = "Сброс трафика всех клиентов" -"resetAllClientTrafficContent" = "Вы уверены, что хотите сбросить трафик всех клиентов?" -"delDepletedClients" = "Удалить отключенных клиентов" -"delDepletedClientsTitle" = "Удаление отключенных клиентов" -"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных клиентов?" -"email" = "Email" -"emailDesc" = "Пожалуйста, укажите уникальный Email" -"IPLimit" = "Лимит по количеству IP" -"IPLimitDesc" = "Ограничение числа одновременных подключений с разных IP (0 – отключить)" -"IPLimitlog" = "Лог IP-адресов" -"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить лог)" -"IPLimitlogclear" = "Очистить лог" -"setDefaultCert" = "Установить сертификат панели" -"telegramDesc" = "Пожалуйста, укажите Chat ID Telegram. (используйте команду '/id' в боте) или (@userinfobot)" -"subscriptionDesc" = "Вы можете найти свою ссылку подписки в разделе 'Подробнее'" -"info" = "Информация" -"same" = "Тот же" -"inboundData" = "Данные подключений" -"exportInbound" = "Экспорт подключений" -"import" = "Импортировать" -"importInbound" = "Импорт подключений" -"periodicTrafficResetTitle" = "Сброс трафика" -"periodicTrafficResetDesc" = "Автоматический сброс счетчика трафика через указанные интервалы" -"lastReset" = "Последний сброс" - -[pages.client] -"add" = "Добавить клиента" -"edit" = "Редактировать клиента" -"submitAdd" = "Добавить" -"submitEdit" = "Сохранить изменения" -"clientCount" = "Количество клиентов" -"bulk" = "Добавить несколько" -"copyFromInbound" = "Скопировать клиентов из инбаунда" -"copyToInbound" = "Скопировать клиентов в" -"copySelected" = "Скопировать выбранных" -"copySource" = "Источник" -"copyEmailPreview" = "Предпросмотр итоговых email" -"copySelectSourceFirst" = "Сначала выберите источник." -"copyResult" = "Результат копирования" -"copyResultSuccess" = "Успешно скопировано" -"copyResultNone" = "Нечего копировать: ни одного клиента не выбрано или список источника пуст" -"copyResultErrors" = "Ошибки при копировании" -"copyFlowLabel" = "Flow для новых клиентов (VLESS)" -"copyFlowHint" = "Применится ко всем копируемым клиентам. Оставьте пустым, чтобы не задавать." -"selectAll" = "Выбрать всех" -"clearAll" = "Снять всё" -"method" = "Метод" -"first" = "Первый" -"last" = "Последний" -"prefix" = "Префикс" -"postfix" = "Постфикс" -"delayedStart" = "Начало использования" -"expireDays" = "Длительность" -"days" = "дней" -"renew" = "Автопродление" -"renewDesc" = "Автопродление после истечения срока действия. (0 = отключить)(единица: день)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Никогда" -"daily" = "Ежедневно" -"weekly" = "Еженедельно" -"monthly" = "Ежемесячно" -"hourly" = "Ежечасно" - -[pages.inbounds.toasts] -"obtain" = "Получить" -"updateSuccess" = "Обновление прошло успешно" -"logCleanSuccess" = "Лог был очищен" -"inboundsUpdateSuccess" = "Подключения успешно обновлены" -"inboundUpdateSuccess" = "Подключение успешно обновлено" -"inboundCreateSuccess" = "Подключение успешно создано" -"inboundDeleteSuccess" = "Подключение успешно удалено" -"inboundClientAddSuccess" = "Клиент(ы) подключения добавлен(ы)" -"inboundClientDeleteSuccess" = "Клиент подключения удалён" -"inboundClientUpdateSuccess" = "Клиент подключения обновлён" -"delDepletedClientsSuccess" = "Все исчерпанные клиенты удалены" -"resetAllClientTrafficSuccess" = "Весь трафик клиента сброшен" -"resetAllTrafficSuccess" = "Весь трафик сброшен" -"resetInboundClientTrafficSuccess" = "Трафик сброшен" -"trafficGetError" = "Ошибка получения данных о трафике" -"getNewX25519CertError" = "Ошибка при получении сертификата X25519." -"getNewmldsa65Error" = "Ошибка при получении сертификата mldsa65." -"getNewVlessEncError" = "Ошибка при получении сертификата VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Запрос" -"response" = "Ответ" -"name" = "Имя" -"value" = "Значение" - -[pages.inbounds.stream.tcp] -"version" = "Версия" -"method" = "Метод" -"path" = "Путь" -"status" = "Статус" -"statusDescription" = "Описание статуса" -"requestHeader" = "Заголовок запроса" -"responseHeader" = "Заголовок ответа" - -[pages.settings] -"title" = "Настройки" -"save" = "Сохранить" -"infoDesc" = "Сохраните изменения и перезапустите панель для их применения." -"restartPanel" = "Перезапуск панели" -"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель будет недоступна, проверьте лог сервера" -"restartPanelSuccess" = "Панель успешно перезапущена" -"actions" = "Действия" -"resetDefaultConfig" = "Восстановить настройки по умолчанию" -"panelSettings" = "Панель" -"securitySettings" = "Учетная запись" -"TGBotSettings" = "Telegram-Бот" -"panelListeningIP" = "IP-адрес для управления панелью" -"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" -"panelListeningDomain" = "Домен панели" -"panelListeningDomainDesc" = "Оставьте пустым для подключения с любых доменов и IP." -"panelPort" = "Порт панели" -"panelPortDesc" = "Порт, на котором работает панель" -"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" -"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели" -"privateKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"panelUrlPath" = "Корневой путь URL адреса панели" -"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'" -"pageSize" = "Размер нумерации страниц" -"pageSizeDesc" = "Определить размер страницы для таблицы подключений. Установите 0, чтобы отключить" -"remarkModel" = "Модель примечания и символ разделения" -"datepicker" = "Тип календаря" -"datepickerPlaceholder" = "Выберите дату" -"datepickerDescription" = "Запланированные задачи будут выполняться в соответствии с этим календарем." -"sampleRemark" = "Пример примечания" -"oldUsername" = "Текущий логин" -"currentPassword" = "Текущий пароль" -"newUsername" = "Новый логин" -"newPassword" = "Новый пароль" -"telegramBotEnable" = "Включить Telegram бота" -"telegramBotEnableDesc" = "Доступ к функциям панели через Telegram-бота" -"telegramToken" = "Токен Telegram бота" -"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" -"telegramProxy" = "Прокси-сервер Socks5" -"telegramProxyDesc" = "Если для подключения к Telegram вам нужен прокси Socks5, настройте его параметры согласно руководству." -"telegramAPIServer" = "API-сервер Telegram" -"telegramAPIServerDesc" = "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию." -"telegramChatId" = "User ID администратора бота" -"telegramChatIdDesc" = "Один или несколько User ID администратора(-ов) Telegram-бота. Для получения User ID используйте @userinfobot или команду '/id' в боте." -"telegramNotifyTime" = "Частота уведомлений для администраторов от бота" -"telegramNotifyTimeDesc" = "Укажите интервал уведомлений в формате Crontab" -"tgNotifyBackup" = "Резервное копирование базы данных" -"tgNotifyBackupDesc" = "Отправлять уведомление с файлом резервной копии базы данных" -"tgNotifyLogin" = "Уведомление о входе" -"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель." -"sessionMaxAge" = "Продолжительность сессии" -"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)" -"expireTimeDiff" = "Задержка уведомления об истечении сессии" -"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)" -"trafficDiff" = "Порог трафика для уведомления" -"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)" -"tgNotifyCpu" = "Порог нагрузки на ЦП для уведомления" -"tgNotifyCpuDesc" = "Уведомление администраторов в Telegram, если нагрузка на ЦП превышает этот порог (значение: %)" -"timeZone" = "Часовой пояс" -"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе" -"subSettings" = "Подписка" -"subEnable" = "Включить подписку" -"subEnableDesc" = "Функция подписки с отдельной конфигурацией" -"subJsonEnable" = "Включить/отключить JSON-эндпоинт подписки независимо." -"subTitle" = "Заголовок подписки" -"subTitleDesc" = "Название подписки, которое видит клиент в VPN-клиенте" -"subSupportUrl" = "URL поддержки" -"subSupportUrlDesc" = "Ссылка на техническую поддержку, отображаемая в VPN-клиенте" -"subProfileUrl" = "URL профиля" -"subProfileUrlDesc" = "Ссылка на ваш сайт, отображаемая в VPN-клиенте" -"subAnnounce" = "Объявление" -"subAnnounceDesc" = "Текст объявления, отображаемый в VPN-клиенте" -"subEnableRouting" = "Включить маршрутизацию" -"subEnableRoutingDesc" = "Глобальная настройка для включения маршрутизации в VPN-клиенте. (Только для Happ)" -"subRoutingRules" = "Правила маршрутизации" -"subRoutingRulesDesc" = "Глобальные правила маршрутизации для VPN-клиента. (Только для Happ)" -"subListen" = "Прослушивание IP" -"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса" -"subPort" = "Порт подписки" -"subPortDesc" = "Номер порта для обслуживания службы подписки не должен использоваться на сервере" -"subCertPath" = "Путь к файлу публичного ключа сертификата подписки" -"subCertPathDesc" = "Введите полный путь, начинающийся с '/'" -"subKeyPath" = "Путь к файлу приватного ключа сертификата подписки" -"subKeyPathDesc" = "Введите полный путь, начинающийся с '/'" -"subPath" = "Корневой путь URL-адреса подписки" -"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'" -"subDomain" = "Домен прослушивания" -"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса" -"subUpdates" = "Интервалы обновления подписки" -"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)" -"subEncrypt" = "Шифровать конфиги" -"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке" -"subShowInfo" = "Показать информацию об использовании" -"subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации" -"subURI" = "URI обратного прокси" -"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" -"externalTrafficInformEnable" = "Информация о внешнем трафике" -"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика" -"externalTrafficInformURI" = "URI информации о внешнем трафике" -"externalTrafficInformURIDesc" = "Обновления трафика отправляются на этот URI" -"restartXrayOnClientDisable" = "Перезапускать Xray после автоотключения" -"restartXrayOnClientDisableDesc" = "Когда клиент автоматически отключается из-за окончания срока действия или лимита трафика, перезапускать Xray." -"fragment" = "Фрагментация" -"fragmentDesc" = "Включить фрагментацию TLS-хэндшейка" -"fragmentSett" = "Настройки фрагментации" -"noisesDesc" = "Включить Noises." -"noisesSett" = "Настройки Noises" -"mux" = "Mux" -"muxDesc" = "Передача нескольких независимых потоков данных в одном соединении." -"muxSett" = "Настройки Mux" -"direct" = "Прямое подключение" -"directDesc" = "Устанавливает прямые соединения с доменами или IP-адресами определённой страны." -"notifications" = "Уведомления" -"certs" = "Сертификаты" -"externalTraffic" = "Внешний трафик" -"dateAndTime" = "Дата и время" -"proxyAndServer" = "Прокси и сервер" -"intervals" = "Интервалы" -"information" = "Информация" -"language" = "Язык интерфейса" -"telegramBotLanguage" = "Язык Telegram-бота" - -[pages.xray] -"title" = "Настройки Xray" -"save" = "Сохранить" -"restart" = "Перезапуск Xray" -"restartSuccess" = "Xray успешно перезапущен" -"stopSuccess" = "Xray успешно остановлен" -"restartError" = "Произошла ошибка при перезапуске Xray." -"stopError" = "Произошла ошибка при остановке Xray." -"basicTemplate" = "Основное" -"advancedTemplate" = "Расширенный шаблон" -"generalConfigs" = "Основные настройки" -"generalConfigsDesc" = "Эти параметры описывают общие настройки" -"logConfigs" = "Логи" -"logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!" -"blockConfigsDesc" = "Настройте, чтобы клиенты не имели доступа к определенным протоколам" -"basicRouting" = "Базовые соединения" -"blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от страны назначения." -"directConnectionsConfigsDesc" = "Прямое соединение означает, что определенный трафик не будет перенаправлен через другой сервер." -"blockips" = "Заблокированные IP-адреса" -"blockdomains" = "Заблокированные домены" -"directips" = "Прямые IP-адреса" -"directdomains" = "Прямые домены" -"ipv4Routing" = "Правила IPv4" -"ipv4RoutingDesc" = "Эти параметры позволят клиентам маршрутизироваться к целевым доменам только через IPv4" -"warpRouting" = "Правила WARP" -"warpRoutingDesc" = " Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через WARP." -"nordRouting" = "Маршрутизация NordVPN" -"nordRoutingDesc" = "Эти опции будут направлять трафик в зависимости от конкретного пункта назначения через NordVPN." -"Template" = "Шаблон конфигурации Xray" -"TemplateDesc" = "На основе шаблона создаётся конфигурационный файл Xray." -"FreedomStrategy" = "Настройка стратегии протокола Freedom" -"FreedomStrategyDesc" = "Установка стратегии вывода сети в протоколе Freedom" -"RoutingStrategy" = "Настройка маршрутизации доменов" -"RoutingStrategyDesc" = "Установка общей стратегии маршрутизации разрешения DNS" -"outboundTestUrl" = "URL для теста исходящего" -"outboundTestUrlDesc" = "URL для проверки подключения исходящего" -"Torrent" = "Заблокировать BitTorrent" -"Inbounds" = "Входящие подключения" -"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных клиентов" -"Outbounds" = "Исходящие подключения" -"Balancers" = "Балансировщик" -"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие подключения для этого сервера" -"Routings" = "Маршрутизация" -"RoutingsDesc" = "Важен приоритет каждого правила!" -"completeTemplate" = "Все" -"logLevel" = "Уровень логов" -"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать." -"accessLog" = "Логи доступа" -"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключает логи доступа." -"errorLog" = "Логи ошибок" -"errorLogDesc" = "Путь к файлу логов ошибок. Специальное значение «none» отключает логи ошибок." -"dnsLog" = "Логи DNS" -"dnsLogDesc" = "Включить логи запросов DNS" -"maskAddress" = "Маскировка адреса" -"maskAddressDesc" = "При активации реальный IP-адрес заменяется на маскировочный в логах." -"statistics" = "Статистика" -"statsInboundUplink" = "Статистика входящего аплинка" -"statsInboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех входящих прокси." -"statsInboundDownlink" = "Статистика входящего даунлинка" -"statsInboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех входящих прокси." -"statsOutboundUplink" = "Статистика исходящего аплинка" -"statsOutboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех исходящих прокси." -"statsOutboundDownlink" = "Статистика исходящего даунлинка" -"statsOutboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех исходящих прокси." - -[pages.xray.rules] -"first" = "Первый" -"last" = "Последний" -"up" = "Поднять вверх" -"down" = "Опустить вниз" -"source" = "Источник" -"dest" = "Пункт назначения" -"inbound" = "Входящее подключение" -"outbound" = "Исходящее подключение" -"balancer" = "Балансировщик" -"info" = "Информация" -"add" = "Создать правило" -"edit" = "Редактировать правило" -"useComma" = "Элементы, разделённые запятыми" - -[pages.xray.outbound] -"addOutbound" = "Создать исходящее подключение" -"addReverse" = "Создать реверс-прокси" -"editOutbound" = "Изменить исходящее подключение" -"editReverse" = "Редактировать реверс-прокси" -"reverseTag" = "Тег реверс-прокси" -"reverseTagDesc" = "Тег исходящего подключения для простого реверс-прокси VLESS. Оставьте пустым для отключения." -"reverseTagPlaceholder" = "тег исходящего (пусто = отключено)" -"tag" = "Тег" -"tagDesc" = "Уникальный тег" -"address" = "Адрес" -"reverse" = "Реверс-прокси" -"domain" = "Домен" -"type" = "Тип" -"bridge" = "Мост" -"portal" = "Портал" -"link" = "Ссылка" -"intercon" = "Соединение" -"settings" = "Настройки" -"accountInfo" = "Информация об учетной записи" -"outboundStatus" = "Статус исходящего подключения" -"sendThrough" = "Отправить через" -"test" = "Тест" -"testResult" = "Результат теста" -"testing" = "Тестирование соединения..." -"testSuccess" = "Тест успешен" -"testFailed" = "Тест не пройден" -"testError" = "Не удалось протестировать исходящее подключение" -"nordvpn" = "NordVPN" -"accessToken" = "Токен доступа" -"country" = "Страна" -"server" = "Сервер" -"city" = "Город" -"allCities" = "Все города" -"privateKey" = "Приватный ключ" -"load" = "Нагрузка" - -[pages.xray.balancer] -"addBalancer" = "Создать балансировщик" -"editBalancer" = "Редактировать балансировщик" -"balancerStrategy" = "Стратегия" -"balancerSelectors" = "Селекторы" -"tag" = "Тег" -"tagDesc" = "Уникальный тег" -"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Секретный ключ" -"publicKey" = "Публичный ключ" -"allowedIPs" = "Разрешенные IP-адреса" -"endpoint" = "Конечная точка" -"psk" = "Общий ключ" -"domainStrategy" = "Стратегия домена" - -[pages.xray.tun] -"nameDesc" = "Имя интерфейса TUN. Значение по умолчанию - 'xray0'" -"mtuDesc" = "Максимальная единица передачи. Максимальный размер пакетов данных. Значение по умолчанию - 1500" -"userLevel" = "Уровень пользователя" -"userLevelDesc" = "Все соединения, установленные через этот входящий поток, будут использовать этот уровень пользователя. Значение по умолчанию - 0" - -[pages.xray.dns] -"enable" = "Включить DNS" -"enableDesc" = "Включить встроенный DNS-сервер" -"tag" = "Название тега DNS" -"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации." -"clientIp" = "IP клиента" -"clientIpDesc" = "Используется для уведомления сервера о указанном местоположении IP во время DNS-запросов" -"disableCache" = "Отключить кэш" -"disableCacheDesc" = "Отключает кэширование DNS" -"disableFallback" = "Отключить резервный DNS" -"disableFallbackDesc" = "Отключает резервные DNS-запросы" -"disableFallbackIfMatch" = "Отключить резервный DNS при совпадении" -"disableFallbackIfMatchDesc" = "Отключает резервные DNS-запросы при совпадении списка доменов DNS-сервера" -"enableParallelQuery" = "Включить параллельные запросы" -"enableParallelQueryDesc" = "Включить параллельные DNS-запросы к нескольким серверам для более быстрого разрешения" -"strategy" = "Стратегия запроса" -"strategyDesc" = "Общая стратегия разрешения доменных имен" -"add" = "Создать DNS" -"edit" = "Редактировать DNS" -"domains" = "Домены" -"expectIPs" = "Ожидаемые IP" -"unexpectIPs" = "Неожидаемые IP" -"useSystemHosts" = "Использовать системные Hosts" -"useSystemHostsDesc" = "Использовать файл hosts из установленной системы" -"usePreset" = "Использовать шаблон" -"dnsPresetTitle" = "Шаблоны DNS" -"dnsPresetFamily" = "Семейный" - -[pages.xray.fakedns] -"add" = "Создать Fake DNS" -"edit" = "Редактировать Fake DNS" -"ipPool" = "Подсеть пула IP" -"poolSize" = "Размер пула" - -[pages.settings.security] -"admin" = "Учетные данные администратора" -"twoFactor" = "Двухфакторная аутентификация" -"twoFactorEnable" = "Включить 2FA" -"twoFactorEnableDesc" = "Добавляет дополнительный уровень аутентификации для повышения безопасности." -"twoFactorModalSetTitle" = "Включить двухфакторную аутентификацию" -"twoFactorModalDeleteTitle" = "Отключить двухфакторную аутентификацию" -"twoFactorModalSteps" = "Для настройки двухфакторной аутентификации выполните несколько шагов:" -"twoFactorModalFirstStep" = "1. Отсканируйте этот QR-код в приложении для аутентификации или скопируйте токен рядом с QR-кодом и вставьте его в приложение" -"twoFactorModalSecondStep" = "2. Введите код из приложения" -"twoFactorModalRemoveStep" = "Введите код из приложения, чтобы отключить двухфакторную аутентификацию." -"twoFactorModalChangeCredentialsTitle" = "Изменить учетные данные" -"twoFactorModalChangeCredentialsStep" = "Введите код из приложения, чтобы изменить учетные данные администратора." -"twoFactorModalSetSuccess" = "Двухфакторная аутентификация была успешно установлена" -"twoFactorModalDeleteSuccess" = "Двухфакторная аутентификация была успешно удалена" -"twoFactorModalError" = "Неверный код" - -[pages.settings.toasts] -"modifySettings" = "Настройки изменены" -"getSettings" = "Произошла ошибка при получении параметров." -"modifyUserError" = "Произошла ошибка при изменении учетных данных администратора." -"modifyUser" = "Вы успешно изменили учетные данные администратора." -"originalUserPassIncorrect" = "Неверное имя пользователя или пароль" -"userPassMustBeNotEmpty" = "Новое имя пользователя и новый пароль должны быть заполнены" -"getOutboundTrafficError" = "Ошибка получения трафика исходящего подключения" -"resetOutboundTrafficError" = "Ошибка сброса трафика исходящего подключения" - -[tgbot] -"keyboardClosed" = "❌ Клавиатура закрыта." -"noResult" = "❗ Нет результатов." -"noQuery" = "❌ Запрос не найден. Пожалуйста, повторите команду." -"wentWrong" = "❌ Что-то пошло не так..." -"noIpRecord" = "❗ Нет записей об IP-адресе." -"noInbounds" = "❗ У вас не настроено ни одного входящего подключения." -"unlimited" = "♾ Безлимит" -"add" = "Добавить" -"month" = "Месяц" -"months" = "Месяцев" -"day" = "День" -"days" = "Дней" -"hours" = "Часов" -"minutes" = "Минуты" -"unknown" = "Неизвестно" -"inbounds" = "Входящие подключения" -"clients" = "Клиенты" -"offline" = "🔴 Офлайн" -"online" = "🟢 Онлайн" - -[tgbot.commands] -"unknown" = "❗ Неизвестная команда" -"pleaseChoose" = "👇 Пожалуйста, выберите:\r\n" -"help" = "🤖 Добро пожаловать! Этот бот предназначен для предоставления вам данных с сервера и позволяет вносить изменения на него.\r\n\r\n" -"start" = "👋 Привет, {{ .Firstname }}.\r\n" -"welcome" = "🤖 Добро пожаловать в бота управления {{ .Hostname }}!\r\n" -"status" = "✅ Бот функционирует нормально." -"usage" = "❗ Пожалуйста, укажите email для поиска." -"getID" = "🆔 Ваш User ID: {{ .ID }}" -"helpAdminCommands" = "🔃 Для перезапуска Xray Core:\r\n/restart\r\n\r\n🔎 Для поиска клиента по email:\r\n/usage [Email]\r\n\r\n📊 Для поиска входящих подключений (со статистикой клиентов):\r\n/inbound [имя подключения]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id" -"helpClientCommands" = "💲 Для просмотра информации о вашей подписке используйте команду:\r\n/usage [Email]\r\n\r\n🆔 Ваш Telegram User ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Ядро Xray успешно перезапущено." -"restartFailed" = "❗ Ошибка при перезапуске Xray-core.\r\n\r\nОшибка: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core не запущен." -"startDesc" = "Показать главное меню" -"helpDesc" = "Справка по боту" -"statusDesc" = "Проверить статус бота" -"idDesc" = "Показать ваш Telegram ID" - -[tgbot.messages] -"cpuThreshold" = "🔴 Загрузка процессора составляет {{ .Percent }}%, что превышает пороговое значение {{ .Threshold }}%" -"selectUserFailed" = "❌ Ошибка при выборе пользователя." -"userSaved" = "✅ Пользователь Telegram сохранен." -"loginSuccess" = "✅ Успешный вход в панель.\r\n" -"loginFailed" = "❗️ Ошибка входа в панель.\r\n" -"2faFailed" = "Ошибка 2FA" -"report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n" -"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n" -"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n" -"version" = "🚀 Версия X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Версия Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP-адреса:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Время работы сервера: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Нагрузка сервера: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 ОЗУ сервера: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Количество TCP-соединений: {{ .Count }}\r\n" -"udpCount" = "🔸 Количество UDP-соединений: {{ .Count }}\r\n" -"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Состояние Xray: {{ .State }}\r\n" -"username" = "👤 Имя пользователя: {{ .Username }}\r\n" -"reason" = "❗️ Причина: {{ .Reason }}\r\n" -"time" = "⏰ Время: {{ .Time }}\r\n" -"inbound" = "📍 Входящее подключение: {{ .Remark }}\r\n" -"port" = "🔌 Порт: {{ .Port }}\r\n" -"expire" = "📅 Дата окончания: {{ .Time }}\r\n" -"expireIn" = "📅 Окончание через: {{ .Time }}\r\n" -"active" = "💡 Активен: {{ .Enable }}\r\n" -"enabled" = "🚨 Активен: {{ .Enable }}\r\n" -"online" = "🌐 Статус соединения: {{ .Status }}\r\n" -"lastOnline" = "🔙 Был(а) в сети: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Исходящий трафик: ↑{{ .Upload }}\r\n" -"download" = "🔽 Входящий трафик: ↓{{ .Download }}\r\n" -"total" = "📊 Всего: ↑↓{{ .UpDown }} из {{ .Total }}\r\n" -"TGUser" = "👤 Telegram User ID: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Исчерпаны {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Количество исчерпанных {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Клиентов онлайн: {{ .Count }}\r\n" -"disabled" = "🛑 Отключено: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Клиенты, у которых скоро исчерпание: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Время резервного копирования: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n" -"yes" = "✅ Да" -"no" = "❌ Нет" -"received_id" = "🔑📥 ID обновлён." -"received_password" = "🔑📥 Пароль обновлён." -"received_email" = "📧📥 Email обновлен." -"received_comment" = "💬📥 Комментарий обновлён." -"id_prompt" = "🔑 Стандартный ID: {{ .ClientId }}\n\nВведите ваш ID." -"pass_prompt" = "🔑 Стандартный пароль: {{ .ClientPassword }}\n\nВведите ваш пароль." -"email_prompt" = "📧 Стандартный email: {{ .ClientEmail }}\n\nВведите ваш email." -"comment_prompt" = "💬 Стандартный комментарий: {{ .ClientComment }}\n\nВведите ваш комментарий." -"inbound_client_data_id" = "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!" -"inbound_client_data_pass" = "🔄 Входящие подключения: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Трафик: {{ .ClientTraffic }}\n📅 Срок действия: {{ .ClientExp }}\n💬 Комментарий: {{ .ClientComment }}\n\nТеперь вы можете добавить клиента в входящее подключение!" -"cancel" = "❌ Процесс отменён! \n\nВы можете снова начать с /start в любое время. 🔄" -"error_add_client" = "⚠️ Ошибка:\n\n {{ .error }}" -"using_default_value" = "Используется значение по умолчанию👌" -"incorrect_input" = "Ваш ввод недействителен.\nФразы должны быть непрерывными без пробелов.\nПравильный пример: aaaaaa\nНеправильный пример: aaa aaa 🚫" -"AreYouSure" = "Вы уверены? 🤔" -"SuccessResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успешно" -"FailedResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ❌ Неудача \n\n🛠️ Ошибка: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Сброс трафика завершён для всех клиентов." - -[tgbot.buttons] -"closeKeyboard" = "❌ Закрыть клавиатуру" -"cancel" = "❌ Отмена" -"cancelReset" = "❌ Отменить сброс" -"cancelIpLimit" = "❌ Отменить лимит IP" -"confirmResetTraffic" = "✅ Подтвердить сброс трафика?" -"confirmClearIps" = "✅ Подтвердить очистку IP?" -"confirmRemoveTGUser" = "✅ Подтвердить удаление пользователя Telegram?" -"confirmToggle" = "✅ Подтвердить вкл/выкл пользователя?" -"dbBackup" = "📂 Бэкап БД" -"serverUsage" = "💻 Состояние сервера" -"getInbounds" = "🔌 Входящие подключения" -"depleteSoon" = "⚠️ Скоро конец" -"clientUsage" = "Статистика клиента" -"onlines" = "🟢 Онлайн" -"commands" = "🖱️ Команды" -"refresh" = "🔄 Обновить" -"clearIPs" = "❌ Очистить IP" -"removeTGUser" = "❌ Удалить пользователя Telegram" -"selectTGUser" = "👤 Выбрать пользователя Telegram" -"selectOneTGUser" = "👤 Выберите пользователя Telegram:" -"resetTraffic" = "📈 Сбросить трафик" -"resetExpire" = "📅 Изменить дату окончания" -"ipLog" = "🔢 Лог IP" -"ipLimit" = "🔢 Лимит IP" -"setTGUser" = "👤 Установить пользователя Telegram" -"toggle" = "🔘 Вкл./Выкл." -"custom" = "🔢 Свой" -"confirmNumber" = "✅ Подтвердить: {{ .Num }}" -"confirmNumberAdd" = "✅ Подтвердить добавление: {{ .Num }}" -"limitTraffic" = "🚧 Лимит трафика" -"getBanLogs" = "📄 Лог банов" -"allClients" = "👥 Все клиенты" -"addClient" = "➕ Новый клиент" -"submitDisable" = "Добавить отключенным ☑️" -"submitEnable" = "Добавить включенным ✅" -"use_default" = "🏷️ Использовать по умолчанию" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Пароль" -"change_email" = "⚙️📧 Email" -"change_comment" = "⚙️💬 Комментарий" -"ResetAllTraffics" = "Сбросить весь трафик" -"SortedTrafficUsageReport" = "Отсортированный отчет об использовании трафика" - -[tgbot.answers] -"successfulOperation" = "✅ Успешно!" -"errorOperation" = "❗ Ошибка в операции." -"getInboundsFailed" = "❌ Не удалось получить входящие подключения." -"getClientsFailed" = "❌ Не удалось получить клиентов." -"canceled" = "❌ {{ .Email }}: Операция отменена." -"clientRefreshSuccess" = "✅ {{ .Email }}: Клиент успешно обновлен." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен." -"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит трафика успешно установлен." -"expireResetSuccess" = "✅ {{ .Email }}: Срок действия успешно сброшен." -"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен." -"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены." -"getIpLog" = "✅ {{ .Email }}: Получен лог IP." -"getUserInfo" = "✅ {{ .Email }}: Получена информация о пользователе Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Пользователь Telegram успешно удален." -"enableSuccess" = "✅ {{ .Email }}: Включено успешно." -"disableSuccess" = "✅ {{ .Email }}: Отключено успешно." -"askToAddUserId" = "❌ Ваша конфигурация не найдена!\r\n💭 Пожалуйста, попросите администратора использовать ваш Telegram User ID в конфигурации.\r\n\r\n🆔 Ваш User ID: {{ .TgUserID }}" -"chooseClient" = "Выберите клиента для входящего подключения {{ .Inbound }}" -"chooseInbound" = "Выберите входящее подключение" diff --git a/web/translation/translate.tr_TR.toml b/web/translation/translate.tr_TR.toml deleted file mode 100644 index 32d468af..00000000 --- a/web/translation/translate.tr_TR.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Kullanıcı Adı" -"password" = "Şifre" -"login" = "Giriş Yap" -"confirm" = "Onayla" -"cancel" = "İptal" -"close" = "Kapat" -"save" = "Kaydet" -"logout" = "Çıkış Yap" -"create" = "Oluştur" -"update" = "Güncelle" -"copy" = "Kopyala" -"copied" = "Kopyalandı" -"download" = "İndir" -"remark" = "Açıklama" -"enable" = "Etkin" -"protocol" = "Protokol" -"search" = "Ara" -"filter" = "Filtrele" -"loading" = "Yükleniyor..." -"second" = "Saniye" -"minute" = "Dakika" -"hour" = "Saat" -"day" = "Gün" -"check" = "Kontrol Et" -"indefinite" = "Belirsiz" -"unlimited" = "Sınırsız" -"none" = "Hiçbiri" -"qrCode" = "QR Kod" -"info" = "Daha Fazla Bilgi" -"edit" = "Düzenle" -"delete" = "Sil" -"reset" = "Sıfırla" -"noData" = "Veri yok." -"copySuccess" = "Başarıyla Kopyalandı" -"sure" = "Emin misiniz" -"encryption" = "Şifreleme" -"useIPv4ForHost" = "Ana bilgisayar için IPv4 kullan" -"transmission" = "İletim" -"host" = "Sunucu" -"path" = "Yol" -"camouflage" = "Kandırma" -"status" = "Durum" -"enabled" = "Etkin" -"disabled" = "Devre Dışı" -"depleted" = "Bitti" -"depletingSoon" = "Bitmek Üzere" -"offline" = "Çevrimdışı" -"online" = "Çevrimiçi" -"domainName" = "Alan Adı" -"monitor" = "Dinleme IP" -"certificate" = "Dijital Sertifika" -"fail" = "Başarısız" -"comment" = "Yorum" -"success" = "Başarılı" -"lastOnline" = "Son çevrimiçi" -"getVersion" = "Sürümü Al" -"install" = "Yükle" -"clients" = "Müşteriler" -"usage" = "Kullanım" -"twoFactorCode" = "Kod" -"remained" = "Kalan" -"security" = "Güvenlik" -"secAlertTitle" = "Güvenlik Uyarısı" -"secAlertSsl" = "Bu bağlantı güvenli değil. Verilerin korunması için TLS etkinleştirilene kadar hassas bilgiler girmekten kaçının." -"secAlertConf" = "Bazı ayarlar saldırılara açıktır. Olası ihlalleri önlemek için güvenlik protokollerini güçlendirmeniz önerilir." -"secAlertSSL" = "Panelde güvenli bağlantı yok. Verilerin korunması için TLS sertifikası yükleyin." -"secAlertPanelPort" = "Panel varsayılan portu savunmasız. Rastgele veya belirli bir port yapılandırın." -"secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." -"secAlertSubURI" = "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." -"secAlertSubJsonURI" = "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." -"emptyDnsDesc" = "Eklenmiş DNS sunucusu yok." -"emptyFakeDnsDesc" = "Eklenmiş Fake DNS sunucusu yok." -"emptyBalancersDesc" = "Eklenmiş dengeleyici yok." -"emptyReverseDesc" = "Eklenmiş ters proxy yok." -"somethingWentWrong" = "Bir şeyler yanlış gitti" - -[subscription] -"title" = "Abonelik Bilgisi" -"subId" = "Abonelik Kimliği" -"status" = "Durum" -"downloaded" = "İndirilen" -"uploaded" = "Yüklenen" -"expiry" = "Son Kullanma" -"totalQuota" = "Toplam Kota" -"individualLinks" = "Bireysel Bağlantılar" -"active" = "Aktif" -"inactive" = "Pasif" -"unlimited" = "Sınırsız" -"noExpiry" = "Süresiz" - -[menu] -"theme" = "Tema" -"dark" = "Koyu" -"ultraDark" = "Ultra Koyu" -"dashboard" = "Genel Bakış" -"inbounds" = "Gelenler" -"settings" = "Panel Ayarları" -"xray" = "Xray Yapılandırmaları" -"logout" = "Çıkış Yap" -"link" = "Yönet" - -[pages.login] -"hello" = "Merhaba" -"title" = "Hoş Geldiniz" -"loginAgain" = "Oturum süreniz doldu, lütfen tekrar giriş yapın" - -[pages.login.toasts] -"invalidFormData" = "Girdi verisi formatı geçersiz." -"emptyUsername" = "Kullanıcı adı gerekli" -"emptyPassword" = "Şifre gerekli" -"wrongUsernameOrPassword" = "Geçersiz kullanıcı adı, şifre veya iki adımlı doğrulama kodu." -"successLogin" = "Hesabınıza başarıyla giriş yaptınız." - -[pages.index] -"title" = "Genel Bakış" -"cpu" = "İşlemci" -"logicalProcessors" = "Mantıksal işlemciler" -"frequency" = "Frekans" -"swap" = "Takas" -"storage" = "Depolama" -"memory" = "RAM" -"threads" = "İş parçacıkları" -"xrayStatus" = "Xray" -"stopXray" = "Durdur" -"restartXray" = "Yeniden Başlat" -"xraySwitch" = "Sürüm" -"xraySwitchClick" = "Geçiş yapmak istediğiniz sürümü seçin." -"xraySwitchClickDesk" = "Dikkatli seçin, eski sürümler mevcut yapılandırmalarla uyumlu olmayabilir." -"xrayUpdates" = "Xray Güncellemeleri" -"updatePanel" = "Paneli Güncelle" -"panelUpdateDesc" = "Bu, 3X-UI'yi en son sürüme güncelleyecek ve panel servisini yeniden başlatacaktır." -"currentPanelVersion" = "Mevcut panel sürümü" -"latestPanelVersion" = "Panelin en son sürümü" -"panelUpToDate" = "Panel güncel" -"upToDate" = "Güncel" -"xrayStatusUnknown" = "Bilinmiyor" -"xrayStatusRunning" = "Çalışıyor" -"xrayStatusStop" = "Durduruldu" -"xrayStatusError" = "Hata" -"xrayErrorPopoverTitle" = "Xray çalıştırılırken bir hata oluştu" -"operationHours" = "Çalışma Süresi" -"systemLoad" = "Sistem Yükü" -"systemLoadDesc" = "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması" -"connectionCount" = "Bağlantı İstatistikleri" -"ipAddresses" = "IP adresleri" -"toggleIpVisibility" = "IP görünürlüğünü değiştir" -"overallSpeed" = "Genel hız" -"upload" = "Yükleme" -"download" = "İndirme" -"totalData" = "Toplam veri" -"sent" = "Gönderilen" -"received" = "Alınan" -"documentation" = "Dokümantasyon" -"xraySwitchVersionDialog" = "Xray sürümünü gerçekten değiştirmek istiyor musunuz?" -"xraySwitchVersionDialogDesc" = "Bu işlem Xray sürümünü #version# olarak değiştirecektir." -"xraySwitchVersionPopover" = "Xray başarıyla güncellendi" -"panelUpdateDialog" = "Gerçekten paneli güncellemek istiyor musunuz?" -"panelUpdateDialogDesc" = "Bu, 3X-UI'yi #version# sürümüne güncelleyecek ve panel servisini yeniden başlatacaktır." -"panelUpdateCheckPopover" = "Panel güncelleme kontrolü başarısız oldu" -"panelUpdateStartedPopover" = "Panel güncellemesi başlatıldı" -"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?" -"geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir." -"geofilesUpdateDialogDesc" = "Bu, tüm dosyaları güncelleyecektir." -"geofilesUpdateAll" = "Tümünü güncelle" -"geofileUpdatePopover" = "Geofile başarıyla güncellendi" -"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin" -"logs" = "Günlükler" -"config" = "Yapılandırma" -"backup" = "Yedek" -"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme" -"exportDatabase" = "Yedekle" -"exportDatabaseDesc" = "Mevcut veritabanınızın yedeğini içeren bir .db dosyasını cihazınıza indirmek için tıklayın." -"importDatabase" = "Geri Yükle" -"importDatabaseDesc" = "Cihazınızdan bir .db dosyası seçip yükleyerek veritabanınızı yedekten geri yüklemek için tıklayın." -"importDatabaseSuccess" = "Veritabanı başarıyla içe aktarıldı" -"importDatabaseError" = "Veritabanı içe aktarılırken bir hata oluştu" -"readDatabaseError" = "Veritabanı okunurken bir hata oluştu" -"getDatabaseError" = "Veritabanı alınırken bir hata oluştu" -"getConfigError" = "Yapılandırma dosyası alınırken bir hata oluştu" -"customGeoTitle" = "Özel GeoSite / GeoIP" -"customGeoAdd" = "Ekle" -"customGeoType" = "Tür" -"customGeoAlias" = "Takma ad" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Etkin" -"customGeoLastUpdated" = "Son güncelleme" -"customGeoExtColumn" = "Yönlendirme (ext:…)" -"customGeoToastUpdateAll" = "Tüm özel kaynaklar güncellendi" -"customGeoActions" = "İşlemler" -"customGeoEdit" = "Düzenle" -"customGeoDelete" = "Sil" -"customGeoDownload" = "Şimdi güncelle" -"customGeoModalAdd" = "Özel geo ekle" -"customGeoModalEdit" = "Özel geo düzenle" -"customGeoModalSave" = "Kaydet" -"customGeoDeleteConfirm" = "Bu özel geo kaynağını silinsin mi?" -"customGeoRoutingHint" = "Yönlendirme kurallarında değer sütununu ext:dosya.dat:etiket olarak kullanın (etiketi değiştirin)." -"customGeoInvalidId" = "Geçersiz kaynak kimliği" -"customGeoAliasesError" = "Özel geo takma adları yüklenemedi" -"customGeoValidationAlias" = "Takma ad yalnızca küçük harf, rakam, - ve _ içerebilir" -"customGeoValidationUrl" = "URL http:// veya https:// ile başlamalıdır" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (özel)" -"customGeoToastList" = "Özel geo listesi" -"customGeoToastAdd" = "Özel geo ekle" -"customGeoToastUpdate" = "Özel geo güncelle" -"customGeoToastDelete" = "Özel geofile \"{{ .fileName }}\" silindi" -"customGeoToastDownload" = "\"{{ .fileName }}\" geofile güncellendi" -"customGeoErrInvalidType" = "Tür geosite veya geoip olmalıdır" -"customGeoErrAliasRequired" = "Takma ad gerekli" -"customGeoErrAliasPattern" = "Takma ad izin verilmeyen karakterler içeriyor" -"customGeoErrAliasReserved" = "Bu takma ad ayrılmış" -"customGeoErrUrlRequired" = "URL gerekli" -"customGeoErrInvalidUrl" = "URL geçersiz" -"customGeoErrUrlScheme" = "URL http veya https kullanmalıdır" -"customGeoErrUrlHost" = "URL ana bilgisayarı geçersiz" -"customGeoErrDuplicateAlias" = "Bu takma ad bu tür için zaten kullanılıyor" -"customGeoErrNotFound" = "Özel geo kaynağı bulunamadı" -"customGeoErrDownload" = "İndirme başarısız" -"customGeoErrUpdateAllIncomplete" = "Bir veya daha fazla özel geo kaynağı güncellenemedi" -"customGeoEmpty" = "Henüz özel geo kaynağı yok — oluşturmak için Ekle'ye tıklayın" - -[pages.inbounds] -"allTimeTraffic" = "Toplam Trafik" -"allTimeTrafficUsage" = "Tüm Zamanların Toplam Kullanımı" -"title" = "Gelenler" -"totalDownUp" = "Toplam Gönderilen/Alınan" -"totalUsage" = "Toplam Kullanım" -"inboundCount" = "Toplam Gelen" -"operate" = "Menü" -"enable" = "Etkin" -"remark" = "Açıklama" -"protocol" = "Protokol" -"port" = "Port" -"portMap" = "Port Atama" -"traffic" = "Trafik" -"details" = "Detaylar" -"transportConfig" = "Taşıma" -"expireDate" = "Süre" -"createdAt" = "Oluşturuldu" -"updatedAt" = "Güncellendi" -"resetTraffic" = "Trafiği Sıfırla" -"addInbound" = "Gelen Ekle" -"generalActions" = "Genel Eylemler" -"autoRefresh" = "Otomatik yenileme" -"autoRefreshInterval" = "Aralık" -"modifyInbound" = "Geleni Düzenle" -"deleteInbound" = "Geleni Sil" -"deleteInboundContent" = "Geleni silmek istediğinizden emin misiniz?" -"deleteClient" = "Müşteriyi Sil" -"deleteClientContent" = "Müşteriyi silmek istediğinizden emin misiniz?" -"resetTrafficContent" = "Trafiği sıfırlamak istediğinizden emin misiniz?" -"copyLink" = "URL'yi Kopyala" -"address" = "Adres" -"network" = "Ağ" -"destinationPort" = "Hedef Port" -"targetAddress" = "Hedef Adres" -"monitorDesc" = "Tüm IP'leri dinlemek için boş bırakın" -"meansNoLimit" = "= Sınırsız. (birim: GB)" -"totalFlow" = "Toplam Akış" -"leaveBlankToNeverExpire" = "Hiçbir zaman sona ermemesi için boş bırakın" -"noRecommendKeepDefault" = "Varsayılanı korumanız önerilir" -"certificatePath" = "Dosya Yolu" -"certificateContent" = "Dosya İçeriği" -"publicKey" = "Genel Anahtar" -"privatekey" = "Özel Anahtar" -"clickOnQRcode" = "Kopyalamak için QR Kodu Tıklayın" -"client" = "Müşteri" -"export" = "Tüm URL'leri Dışa Aktar" -"clone" = "Klonla" -"cloneInbound" = "Klonla" -"cloneInboundContent" = "Bu gelenin tüm ayarları, Port, Dinleme IP ve Müşteriler hariç, klona uygulanacaktır." -"cloneInboundOk" = "Klonla" -"resetAllTraffic" = "Tüm Gelen Trafiğini Sıfırla" -"resetAllTrafficTitle" = "Tüm Gelen Trafiğini Sıfırla" -"resetAllTrafficContent" = "Tüm gelenlerin trafiğini sıfırlamak istediğinizden emin misiniz?" -"resetInboundClientTraffics" = "Müşteri Trafiklerini Sıfırla" -"resetInboundClientTrafficTitle" = "Müşteri Trafiklerini Sıfırla" -"resetInboundClientTrafficContent" = "Bu gelenin müşterilerinin trafiğini sıfırlamak istediğinizden emin misiniz?" -"resetAllClientTraffics" = "Tüm Müşteri Trafiklerini Sıfırla" -"resetAllClientTrafficTitle" = "Tüm Müşteri Trafiklerini Sıfırla" -"resetAllClientTrafficContent" = "Tüm müşterilerin trafiğini sıfırlamak istediğinizden emin misiniz?" -"delDepletedClients" = "Bitmiş Müşterileri Sil" -"delDepletedClientsTitle" = "Bitmiş Müşterileri Sil" -"delDepletedClientsContent" = "Tüm bitmiş müşterileri silmek istediğinizden emin misiniz?" -"email" = "E-posta" -"emailDesc" = "Lütfen benzersiz bir e-posta adresi sağlayın." -"IPLimit" = "IP Limiti" -"IPLimitDesc" = "Sayının aşılması durumunda gelen devre dışı bırakılır. (0 = devre dışı)" -"IPLimitlog" = "IP Günlüğü" -"IPLimitlogDesc" = "IP geçmiş günlüğü. (devre dışı bırakıldıktan sonra gelini etkinleştirmek için günlüğü temizleyin)" -"IPLimitlogclear" = "Günlüğü Temizle" -"setDefaultCert" = "Panelden Sertifikayı Ayarla" -"telegramDesc" = "Lütfen Telegram Sohbet Kimliği sağlayın. (botta '/id' komutunu kullanın) veya (@userinfobot)" -"subscriptionDesc" = "Abonelik URL'inizi bulmak için 'Detaylar'a gidin. Ayrıca, aynı adı birden fazla müşteri için kullanabilirsiniz." -"info" = "Bilgi" -"same" = "Aynı" -"inboundData" = "Gelenin Verileri" -"exportInbound" = "Geleni Dışa Aktar" -"import" = "İçe Aktar" -"importInbound" = "Bir Gelen İçe Aktar" -"periodicTrafficResetTitle" = "Trafik Sıfırlama" -"periodicTrafficResetDesc" = "Belirtilen aralıklarla trafik sayacını otomatik olarak sıfırla" -"lastReset" = "Son Sıfırlama" - -[pages.client] -"add" = "Müşteri Ekle" -"edit" = "Müşteriyi Düzenle" -"submitAdd" = "Müşteri Ekle" -"submitEdit" = "Değişiklikleri Kaydet" -"clientCount" = "Müşteri Sayısı" -"bulk" = "Toplu Ekle" -"copyFromInbound" = "Gelen bağlantıdan istemcileri kopyala" -"copyToInbound" = "İstemcileri şuraya kopyala" -"copySelected" = "Seçilenleri kopyala" -"copySource" = "Kaynak" -"copyEmailPreview" = "Sonuç e-posta önizlemesi" -"copySelectSourceFirst" = "Önce bir kaynak gelen bağlantı seçin." -"copyResult" = "Kopyalama sonucu" -"copyResultSuccess" = "Başarıyla kopyalandı" -"copyResultNone" = "Kopyalanacak bir şey yok: istemci seçilmedi veya kaynak boş" -"copyResultErrors" = "Kopyalama hataları" -"copyFlowLabel" = "Yeni istemciler için Flow (VLESS)" -"copyFlowHint" = "Kopyalanan tüm istemcilere uygulanır. Boş bırakırsanız atlanır." -"selectAll" = "Tümünü seç" -"clearAll" = "Tümünü temizle" -"method" = "Yöntem" -"first" = "İlk" -"last" = "Son" -"prefix" = "Önek" -"postfix" = "Sonek" -"delayedStart" = "İlk Kullanımdan Sonra Başlat" -"expireDays" = "Süre" -"days" = "Gün" -"renew" = "Otomatik Yenile" -"renewDesc" = "Süresi dolduktan sonra otomatik yenileme. (0 = devre dışı)(birim: gün)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Asla" -"daily" = "Günlük" -"weekly" = "Haftalık" -"monthly" = "Aylık" -"hourly" = "Saatlik" - -[pages.inbounds.toasts] -"obtain" = "Elde Et" -"updateSuccess" = "Güncelleme başarılı oldu" -"logCleanSuccess" = "Günlük temizlendi" -"inboundsUpdateSuccess" = "Gelen bağlantılar başarıyla güncellendi" -"inboundUpdateSuccess" = "Gelen bağlantı başarıyla güncellendi" -"inboundCreateSuccess" = "Gelen bağlantı başarıyla oluşturuldu" -"inboundDeleteSuccess" = "Gelen bağlantı başarıyla silindi" -"inboundClientAddSuccess" = "Gelen bağlantı istemci(leri) eklendi" -"inboundClientDeleteSuccess" = "Gelen bağlantı istemcisi silindi" -"inboundClientUpdateSuccess" = "Gelen bağlantı istemcisi güncellendi" -"delDepletedClientsSuccess" = "Tüm tükenmiş istemciler silindi" -"resetAllClientTrafficSuccess" = "İstemcinin tüm trafiği sıfırlandı" -"resetAllTrafficSuccess" = "Tüm trafik sıfırlandı" -"resetInboundClientTrafficSuccess" = "Trafik sıfırlandı" -"trafficGetError" = "Trafik bilgisi alınırken hata oluştu" -"getNewX25519CertError" = "X25519 sertifikası alınırken hata oluştu." -"getNewmldsa65Error" = "mldsa65 sertifikası alınırken hata oluştu." -"getNewVlessEncError" = "VlessEnc sertifikası alınırken hata oluştu." - -[pages.inbounds.stream.general] -"request" = "İstek" -"response" = "Yanıt" -"name" = "Ad" -"value" = "Değer" - -[pages.inbounds.stream.tcp] -"version" = "Sürüm" -"method" = "Yöntem" -"path" = "Yol" -"status" = "Durum" -"statusDescription" = "Durum Açıklaması" -"requestHeader" = "İstek Başlığı" -"responseHeader" = "Yanıt Başlığı" - -[pages.settings] -"title" = "Panel Ayarları" -"save" = "Kaydet" -"infoDesc" = "Burada yapılan her değişikliğin kaydedilmesi gerekir. Değişikliklerin uygulanması için paneli yeniden başlatın." -"restartPanel" = "Paneli Yeniden Başlat" -"restartPanelDesc" = "Paneli yeniden başlatmak istediğinizden emin misiniz? Yeniden başlattıktan sonra panele erişemezseniz, sunucudaki panel günlük bilgilerini görüntüleyin." -"restartPanelSuccess" = "Panel başarıyla yeniden başlatıldı" -"actions" = "Eylemler" -"resetDefaultConfig" = "Varsayılana Sıfırla" -"panelSettings" = "Genel" -"securitySettings" = "Kimlik Doğrulama" -"TGBotSettings" = "Telegram Bot" -"panelListeningIP" = "Dinleme IP" -"panelListeningIPDesc" = "Web paneli için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" -"panelListeningDomain" = "Dinleme Alan Adı" -"panelListeningDomainDesc" = "Web paneli için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)" -"panelPort" = "Dinleme Portu" -"panelPortDesc" = "Web paneli için port numarası. (kullanılmayan bir port olmalıdır)" -"publicKeyPath" = "Genel Anahtar Yolu" -"publicKeyPathDesc" = "Web paneli için genel anahtar dosya yolu. ('/' ile başlar)" -"privateKeyPath" = "Özel Anahtar Yolu" -"privateKeyPathDesc" = "Web paneli için özel anahtar dosya yolu. ('/' ile başlar)" -"panelUrlPath" = "URI Yolu" -"panelUrlPathDesc" = "Web paneli için URI yolu. ('/' ile başlar ve '/' ile biter)" -"pageSize" = "Sayfa Boyutu" -"pageSizeDesc" = "Gelenler tablosu için sayfa boyutunu belirleyin. (0 = devre dışı)" -"remarkModel" = "Açıklama Modeli & Ayırma Karakteri" -"datepicker" = "Takvim Türü" -"datepickerPlaceholder" = "Tarih Seçin" -"datepickerDescription" = "Planlanmış görevler bu takvime göre çalışacaktır." -"sampleRemark" = "Örnek Açıklama" -"oldUsername" = "Mevcut Kullanıcı Adı" -"currentPassword" = "Mevcut Şifre" -"newUsername" = "Yeni Kullanıcı Adı" -"newPassword" = "Yeni Şifre" -"telegramBotEnable" = "Telegram Botunu Etkinleştir" -"telegramBotEnableDesc" = "Telegram botunu etkinleştirir." -"telegramToken" = "Telegram Token" -"telegramTokenDesc" = "'@BotFather'dan alınan Telegram bot token." -"telegramProxy" = "SOCKS Proxy" -"telegramProxyDesc" = "Telegram'a bağlanmak için SOCKS5 proxy'sini etkinleştirir. (ayarları kılavuzda belirtilen şekilde ayarlayın)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Kullanılacak Telegram API sunucusu. Varsayılan sunucuyu kullanmak için boş bırakın." -"telegramChatId" = "Yönetici Sohbet Kimliği" -"telegramChatIdDesc" = "Telegram Yönetici Sohbet Kimliği(leri). (virgülle ayrılmış)(buradan alın @userinfobot) veya (botta '/id' komutunu kullanın)" -"telegramNotifyTime" = "Bildirim Zamanı" -"telegramNotifyTimeDesc" = "Periyodik raporlar için ayarlanan Telegram bot bildirim zamanı. (crontab zaman formatını kullanın)" -"tgNotifyBackup" = "Veritabanı Yedeği" -"tgNotifyBackupDesc" = "Bir rapor ile birlikte veritabanı yedek dosyasını gönder." -"tgNotifyLogin" = "Giriş Bildirimi" -"tgNotifyLoginDesc" = "Birisi web panelinize giriş yapmaya çalıştığında kullanıcı adı, IP adresi ve zaman hakkında bildirim alın." -"sessionMaxAge" = "Oturum Süresi" -"sessionMaxAgeDesc" = "Giriş yaptıktan sonra oturum süresi. (birim: dakika)" -"expireTimeDiff" = "Son Kullanma Tarihi Bildirimi" -"expireTimeDiffDesc" = "Bu eşik seviyesine ulaşıldığında son kullanma tarihi hakkında bildirim alın. (birim: gün)" -"trafficDiff" = "Trafik Sınırı Bildirimi" -"trafficDiffDesc" = "Bu eşik seviyesine ulaşıldığında trafik sınırı hakkında bildirim alın. (birim: GB)" -"tgNotifyCpu" = "CPU Yükü Bildirimi" -"tgNotifyCpuDesc" = "CPU yükü bu eşik seviyesini aşarsa bildirim alın. (birim: %)" -"timeZone" = "Saat Dilimi" -"timeZoneDesc" = "Planlanmış görevler bu saat dilimine göre çalışacaktır." -"subSettings" = "Abonelik" -"subEnable" = "Abonelik Hizmetini Etkinleştir" -"subEnableDesc" = "Abonelik hizmetini etkinleştirir." -"subJsonEnable" = "JSON abonelik uç noktasını bağımsız olarak Etkinleştir/Devre Dışı bırak." -"subTitle" = "Abonelik Başlığı" -"subTitleDesc" = "VPN istemcisinde gösterilen başlık" -"subSupportUrl" = "Destek URL'si" -"subSupportUrlDesc" = "VPN istemcisinde gösterilen teknik destek bağlantısı" -"subProfileUrl" = "Profil URL'si" -"subProfileUrlDesc" = "VPN istemcisinde görüntülenen web sitenize giden bağlantı" -"subAnnounce" = "Duyuru" -"subAnnounceDesc" = "VPN istemcisinde görüntülenen duyuru metni" -"subEnableRouting" = "Yönlendirmeyi etkinleştir" -"subEnableRoutingDesc" = "VPN istemcisinde yönlendirmeyi etkinleştirmek için genel ayar. (Yalnızca Happ için)" -"subRoutingRules" = "Yönlendirme kuralları" -"subRoutingRulesDesc" = "VPN istemcisi için genel yönlendirme kuralları. (Yalnızca Happ için)" -"subListen" = "Dinleme IP" -"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" -"subPort" = "Dinleme Portu" -"subPortDesc" = "Abonelik hizmeti için port numarası. (kullanılmayan bir port olmalıdır)" -"subCertPath" = "Genel Anahtar Yolu" -"subCertPathDesc" = "Abonelik hizmeti için genel anahtar dosya yolu. ('/' ile başlar)" -"subKeyPath" = "Özel Anahtar Yolu" -"subKeyPathDesc" = "Abonelik hizmeti için özel anahtar dosya yolu. ('/' ile başlar)" -"subPath" = "URI Yolu" -"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)" -"subDomain" = "Dinleme Alan Adı" -"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)" -"subUpdates" = "Güncelleme Aralıkları" -"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)" -"subEncrypt" = "Şifrele" -"subEncryptDesc" = "Abonelik hizmetinin döndürülen içeriği Base64 ile şifrelenir." -"subShowInfo" = "Kullanım Bilgisini Göster" -"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir." -"subURI" = "Ters Proxy URI" -"subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu." -"externalTrafficInformEnable" = "Harici Trafik Bilgisi" -"externalTrafficInformEnableDesc" = "Her trafik güncellemesinde harici API'yi bilgilendirin." -"externalTrafficInformURI" = "Harici Trafik Bilgisi URI'si" -"externalTrafficInformURIDesc" = "Trafik güncellemeleri bu URI'ye gönderildi." -"restartXrayOnClientDisable" = "Otomatik Devre Dışı Sonrası Xray'i Yeniden Başlat" -"restartXrayOnClientDisableDesc" = "Bir istemci süre dolumu veya trafik limiti nedeniyle otomatik devre dışı bırakıldığında Xray'i yeniden başlat." -"fragment" = "Parçalama" -"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir." -"fragmentSett" = "Parçalama Ayarları" -"noisesDesc" = "Noises'i Etkinleştir." -"noisesSett" = "Noises Ayarları" -"mux" = "Mux" -"muxDesc" = "Kurulmuş bir veri akışında birden çok bağımsız veri akışını iletir." -"muxSett" = "Mux Ayarları" -"direct" = "Doğrudan Bağlantı" -"directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar." -"notifications" = "Bildirimler" -"certs" = "Sertifikalar" -"externalTraffic" = "Harici Trafik" -"dateAndTime" = "Tarih ve Saat" -"proxyAndServer" = "Proxy ve Sunucu" -"intervals" = "Aralıklar" -"information" = "Bilgi" -"language" = "Dil" -"telegramBotLanguage" = "Telegram Bot Dili" - -[pages.xray] -"title" = "Xray Yapılandırmaları" -"save" = "Kaydet" -"restart" = "Xray'i Yeniden Başlat" -"restartSuccess" = "Xray başarıyla yeniden başlatıldı" -"stopSuccess" = "Xray başarıyla durduruldu" -"restartError" = "Xray yeniden başlatılırken bir hata oluştu." -"stopError" = "Xray durdurulurken bir hata oluştu." -"basicTemplate" = "Temeller" -"advancedTemplate" = "Gelişmiş" -"generalConfigs" = "Genel" -"generalConfigsDesc" = "Bu seçenekler genel ayarlamaları belirler." -"logConfigs" = "Günlük" -"logConfigsDesc" = "Günlükler sunucunuzun verimliliğini etkileyebilir. Yalnızca ihtiyaç durumunda akıllıca etkinleştirmeniz önerilir" -"blockConfigsDesc" = "Bu seçenekler belirli istek protokolleri ve web siteleri temelinde trafiği engeller." -"basicRouting" = "Temel Yönlendirme" -"blockConnectionsConfigsDesc" = "Bu seçenekler belirli bir istenen ülkeye göre trafiği engelleyecektir." -"directConnectionsConfigsDesc" = "Doğrudan bağlantı, belirli bir trafiğin başka bir sunucu üzerinden yönlendirilmediğini sağlar." -"blockips" = "IP'leri Engelle" -"blockdomains" = "Alan Adlarını Engelle" -"directips" = "Doğrudan IP'ler" -"directdomains" = "Doğrudan Alan Adları" -"ipv4Routing" = "IPv4 Yönlendirme" -"ipv4RoutingDesc" = "Bu seçenekler belirli bir varış yerine IPv4 üzerinden trafiği yönlendirir." -"warpRouting" = "WARP Yönlendirme" -"warpRoutingDesc" = "Bu seçenekler belirli bir varış yerine WARP üzerinden trafiği yönlendirir." -"nordRouting" = "NordVPN Yönlendirme" -"nordRoutingDesc" = "Bu seçenekler belirli bir varış yerine NordVPN üzerinden trafiği yönlendirir." -"Template" = "Gelişmiş Xray Yapılandırma Şablonu" -"TemplateDesc" = "Nihai Xray yapılandırma dosyası bu şablona göre oluşturulacaktır." -"FreedomStrategy" = "Freedom Protokol Stratejisi" -"FreedomStrategyDesc" = "Freedom Protokolünde ağın çıkış stratejisini ayarlayın." -"RoutingStrategy" = "Genel Yönlendirme Stratejisi" -"RoutingStrategyDesc" = "Tüm istekleri çözmek için genel trafik yönlendirme stratejisini ayarlayın." -"outboundTestUrl" = "Outbound test URL" -"outboundTestUrlDesc" = "Outbound bağlantı testinde kullanılan URL" -"Torrent" = "BitTorrent Protokolünü Engelle" -"Inbounds" = "Gelenler" -"InboundsDesc" = "Belirli müşterileri kabul eder." -"Outbounds" = "Gidenler" -"Balancers" = "Dengeler" -"OutboundsDesc" = "Giden trafiğin yolunu ayarlayın." -"Routings" = "Yönlendirme Kuralları" -"RoutingsDesc" = "Her kuralın önceliği önemlidir!" -"completeTemplate" = "Tümü" -"logLevel" = "Günlük Seviyesi" -"logLevelDesc" = "Hata günlükleri için günlük seviyesi, kaydedilmesi gereken bilgileri belirtir." -"accessLog" = "Erişim Günlüğü" -"accessLogDesc" = "Erişim günlüğü için dosya yolu. 'none' özel değeri erişim günlüklerini devre dışı bırakır" -"errorLog" = "Hata Günlüğü" -"errorLogDesc" = "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır" -"dnsLog" = "DNS Günlüğü" -"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin" -"maskAddress" = "Adres Maskesi" -"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir." -"statistics" = "İstatistikler" -"statsInboundUplink" = "Gelen Yükleme İstatistikleri" -"statsInboundUplinkDesc" = "Tüm gelen proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir." -"statsInboundDownlink" = "Gelen İndirme İstatistikleri" -"statsInboundDownlinkDesc" = "Tüm gelen proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir." -"statsOutboundUplink" = "Giden Yükleme İstatistikleri" -"statsOutboundUplinkDesc" = "Tüm giden proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir." -"statsOutboundDownlink" = "Giden İndirme İstatistikleri" -"statsOutboundDownlinkDesc" = "Tüm giden proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir." - -[pages.xray.rules] -"first" = "İlk" -"last" = "Son" -"up" = "Yukarı" -"down" = "Aşağı" -"source" = "Kaynak" -"dest" = "Hedef" -"inbound" = "Gelen" -"outbound" = "Giden" -"balancer" = "Dengeler" -"info" = "Bilgi" -"add" = "Kural Ekle" -"edit" = "Kuralı Düzenle" -"useComma" = "Virgülle ayrılmış öğeler" - -[pages.xray.outbound] -"addOutbound" = "Giden Ekle" -"addReverse" = "Ters Ekle" -"editOutbound" = "Gideni Düzenle" -"editReverse" = "Tersi Düzenle" -"reverseTag" = "Ters Etiket" -"reverseTagDesc" = "VLESS basit ters proxy çıkış etiketi. Devre dışı bırakmak için boş bırakın." -"reverseTagPlaceholder" = "çıkış etiketi (boş = devre dışı)" -"tag" = "Etiket" -"tagDesc" = "Benzersiz Etiket" -"address" = "Adres" -"reverse" = "Ters" -"domain" = "Alan Adı" -"type" = "Tür" -"bridge" = "Köprü" -"portal" = "Portal" -"link" = "Bağlantı" -"intercon" = "Bağlantı" -"settings" = "Ayarlar" -"accountInfo" = "Hesap Bilgileri" -"outboundStatus" = "Giden Durumu" -"sendThrough" = "Üzerinden Gönder" -"test" = "Test" -"testResult" = "Test Sonucu" -"testing" = "Bağlantı test ediliyor..." -"testSuccess" = "Test başarılı" -"testFailed" = "Test başarısız" -"testError" = "Giden test edilemedi" -"nordvpn" = "NordVPN" -"accessToken" = "Erişim Jetonu" -"country" = "Ülke" -"server" = "Sunucu" -"city" = "Şehir" -"allCities" = "Tüm Şehirler" -"privateKey" = "Özel Anahtar" -"load" = "Yük" - -[pages.xray.balancer] -"addBalancer" = "Dengeleyici Ekle" -"editBalancer" = "Dengeleyiciyi Düzenle" -"balancerStrategy" = "Strateji" -"balancerSelectors" = "Seçiciler" -"tag" = "Etiket" -"tagDesc" = "Benzersiz Etiket" -"balancerDesc" = "Dengeleyici Etiketi ve Giden Etiketi aynı anda kullanılamaz. Aynı anda kullanıldığında yalnızca giden etiketi çalışır." - -[pages.xray.wireguard] -"secretKey" = "Gizli Anahtar" -"publicKey" = "Genel Anahtar" -"allowedIPs" = "İzin Verilen IP'ler" -"endpoint" = "Uç Nokta" -"psk" = "Ön Paylaşılan Anahtar" -"domainStrategy" = "Alan Adı Stratejisi" - -[pages.xray.tun] -"nameDesc" = "TUN arabiriminin adı. Varsayılan değer 'xray0'dir" -"mtuDesc" = "Maksimum İletim Birimi. Veri paketlerinin maksimum boyutu. Varsayılan değer 1500'dür" -"userLevel" = "Kullanıcı Seviyesi" -"userLevelDesc" = "Bu giriş yoluyla yapılan tüm bağlantılar bu kullanıcı seviyesini kullanacaktır. Varsayılan değer 0'dır" - -[pages.xray.dns] -"enable" = "DNS'yi Etkinleştir" -"enableDesc" = "Dahili DNS sunucusunu etkinleştir" -"tag" = "DNS Gelen Etiketi" -"tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir." -"clientIp" = "İstemci IP" -"clientIpDesc" = "DNS sorguları sırasında belirtilen IP konumunu sunucuya bildirmek için kullanılır" -"disableCache" = "Önbelleği devre dışı bırak" -"disableCacheDesc" = "DNS önbelleğini devre dışı bırakır" -"disableFallback" = "Yedeklemeyi devre dışı bırak" -"disableFallbackDesc" = "Yedek DNS sorgularını devre dışı bırakır" -"disableFallbackIfMatch" = "Eşleşirse Yedeklemeyi Devre Dışı Bırak" -"disableFallbackIfMatchDesc" = "DNS sunucusunun eşleşen alan adı listesi vurulduğunda yedek DNS sorgularını devre dışı bırakır" -"enableParallelQuery" = "Paralel Sorguyu Etkinleştir" -"enableParallelQueryDesc" = "Daha hızlı çözümleme için birden fazla sunucuya paralel DNS sorgularını etkinleştir" -"strategy" = "Sorgu Stratejisi" -"strategyDesc" = "Alan adlarını çözmek için genel strateji" -"add" = "Sunucu Ekle" -"edit" = "Sunucuyu Düzenle" -"domains" = "Alan Adları" -"expectIPs" = "Beklenen IP'ler" -"unexpectIPs" = "Beklenmeyen IP'ler" -"useSystemHosts" = "Sistem Hosts'larını Kullan" -"useSystemHostsDesc" = "Yüklü bir sistemden hosts dosyasını kullan" -"usePreset" = "Şablon kullan" -"dnsPresetTitle" = "DNS Şablonları" -"dnsPresetFamily" = "Aile" - -[pages.xray.fakedns] -"add" = "Sahte DNS Ekle" -"edit" = "Sahte DNS'i Düzenle" -"ipPool" = "IP Havuzu Alt Ağı" -"poolSize" = "Havuz Boyutu" - -[pages.settings.security] -"admin" = "Yönetici kimlik bilgileri" -"twoFactor" = "İki adımlı doğrulama" -"twoFactorEnable" = "2FA'yı Etkinleştir" -"twoFactorEnableDesc" = "Daha fazla güvenlik için ek bir doğrulama katmanı ekler." -"twoFactorModalSetTitle" = "İki adımlı doğrulamayı etkinleştir" -"twoFactorModalDeleteTitle" = "İki adımlı doğrulamayı devre dışı bırak" -"twoFactorModalSteps" = "İki adımlı doğrulamayı ayarlamak için şu adımları izleyin:" -"twoFactorModalFirstStep" = "1. Bu QR kodunu doğrulama uygulamasında tarayın veya QR kodunun yanındaki token'ı kopyalayıp uygulamaya yapıştırın" -"twoFactorModalSecondStep" = "2. Uygulamadaki kodu girin" -"twoFactorModalRemoveStep" = "İki adımlı doğrulamayı kaldırmak için uygulamadaki kodu girin." -"twoFactorModalChangeCredentialsTitle" = "Kimlik bilgilerini değiştir" -"twoFactorModalChangeCredentialsStep" = "Yönetici kimlik bilgilerini değiştirmek için uygulamadaki kodu girin." -"twoFactorModalSetSuccess" = "İki faktörlü kimlik doğrulama başarıyla kuruldu" -"twoFactorModalDeleteSuccess" = "İki faktörlü kimlik doğrulama başarıyla silindi" -"twoFactorModalError" = "Yanlış kod" - -[pages.settings.toasts] -"modifySettings" = "Parametreler değiştirildi." -"getSettings" = "Parametreler alınırken bir hata oluştu." -"modifyUserError" = "Yönetici kimlik bilgileri değiştirilirken bir hata oluştu." -"modifyUser" = "Yönetici kimlik bilgilerini başarıyla değiştirdiniz." -"originalUserPassIncorrect" = "Mevcut kullanıcı adı veya şifre geçersiz" -"userPassMustBeNotEmpty" = "Yeni kullanıcı adı ve şifre boş olamaz" -"getOutboundTrafficError" = "Giden trafik alınırken hata" -"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata" - -[tgbot] -"keyboardClosed" = "❌ Klavye kapatıldı!" -"noResult" = "❗ Sonuç yok!" -"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!" -"wentWrong" = "❌ Bir şeyler yanlış gitti!" -"noIpRecord" = "❗ IP Kaydı Yok!" -"noInbounds" = "❗ Gelen bağlantı bulunamadı!" -"unlimited" = "♾ Sınırsız (Sıfırla)" -"add" = "Ekle" -"month" = "Ay" -"months" = "Aylar" -"day" = "Gün" -"days" = "Günler" -"hours" = "Saatler" -"minutes" = "Dakika" -"unknown" = "Bilinmeyen" -"inbounds" = "Gelenler" -"clients" = "İstemciler" -"offline" = "🔴 Çevrimdışı" -"online" = "🟢 Çevrimiçi" - -[tgbot.commands] -"unknown" = "❗ Bilinmeyen komut." -"pleaseChoose" = "👇 Lütfen seçin:\r\n" -"help" = "🤖 Bu bota hoş geldiniz! Web panelinden belirli verileri sunmak ve gerektiğinde değişiklik yapmanıza olanak tanımak için tasarlanmıştır.\r\n\r\n" -"start" = "👋 Merhaba {{ .Firstname }}.\r\n" -"welcome" = "🤖 {{ .Hostname }} yönetim botuna hoş geldiniz.\r\n" -"status" = "✅ Bot çalışıyor!" -"usage" = "❗ Lütfen aramak için bir metin sağlayın!" -"getID" = "🆔 Kimliğiniz: {{ .ID }}" -"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n/restart\r\n\r\nBir müşteri e-postasını aramak için:\r\n/usage [E-posta]\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n/inbound [Açıklama]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id" -"helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n/usage [E-posta]\r\n\r\nTelegram Sohbet Kimliği:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ İşlem başarılı!" -"restartFailed" = "❗ İşlem hatası.\r\n\r\nHata: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core çalışmıyor." -"startDesc" = "Ana menüyü göster" -"helpDesc" = "Bot yardımı" -"statusDesc" = "Bot durumunu kontrol et" -"idDesc" = "Telegram ID'nizi göster" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU Yükü {{ .Percent }}% eşiği {{ .Threshold }}%'yi aşıyor" -"selectUserFailed" = "❌ Kullanıcı seçiminde hata!" -"userSaved" = "✅ Telegram Kullanıcısı kaydedildi." -"loginSuccess" = "✅ Panele başarıyla giriş yapıldı.\r\n" -"loginFailed" = "❗️Panele giriş denemesi başarısız oldu.\r\n" -"2faFailed" = "2FA Hatası" -"report" = "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n" -"datetime" = "⏰ Tarih&Zaman: {{ .DateTime }}\r\n" -"hostname" = "💻 Sunucu: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Sürümü: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Sürümü: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP'ler:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Çalışma Süresi: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Sistem Yükü: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Trafik: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Durum: {{ .State }}\r\n" -"username" = "👤 Kullanıcı Adı: {{ .Username }}\r\n" -"reason" = "❗️ Sebep: {{ .Reason }}\r\n" -"time" = "⏰ Zaman: {{ .Time }}\r\n" -"inbound" = "📍 Gelen: {{ .Remark }}\r\n" -"port" = "🔌 Port: {{ .Port }}\r\n" -"expire" = "📅 Son Kullanma Tarihi: {{ .Time }}\r\n" -"expireIn" = "📅 Sona Erecek: {{ .Time }}\r\n" -"active" = "💡 Aktif: {{ .Enable }}\r\n" -"enabled" = "🚨 Etkin: {{ .Enable }}\r\n" -"online" = "🌐 Bağlantı durumu: {{ .Status }}\r\n" -"lastOnline" = "🔙 Son çevrimiçi: {{ .Time }}\r\n" -"email" = "📧 E-posta: {{ .Email }}\r\n" -"upload" = "🔼 Yükleme: ↑{{ .Upload }}\r\n" -"download" = "🔽 İndirme: ↓{{ .Download }}\r\n" -"total" = "📊 Toplam: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Telegram Kullanıcısı: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Tükenmiş {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Tükenmiş {{ .Type }} sayısı:\r\n" -"onlinesCount" = "🌐 Çevrimiçi Müşteriler: {{ .Count }}\r\n" -"disabled" = "🛑 Devre Dışı: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Yakında Tükenecek: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Yedekleme Zamanı: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Yenilendi: {{ .Time }}\r\n\r\n" -"yes" = "✅ Evet" -"no" = "❌ Hayır" -"received_id" = "🔑📥 Kimlik güncellendi." -"received_password" = "🔑📥 Şifre güncellendi." -"received_email" = "📧📥 E-posta güncellendi." -"received_comment" = "💬📥 Yorum güncellendi." -"id_prompt" = "🔑 Varsayılan Kimlik: {{ .ClientId }}\n\nKimliğinizi girin." -"pass_prompt" = "🔑 Varsayılan Şifre: {{ .ClientPassword }}\n\nŞifrenizi girin." -"email_prompt" = "📧 Varsayılan E-posta: {{ .ClientEmail }}\n\nE-postanızı girin." -"comment_prompt" = "💬 Varsayılan Yorum: {{ .ClientComment }}\n\nYorumunuzu girin." -"inbound_client_data_id" = "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Kimlik: {{ .ClientId }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!" -"inbound_client_data_pass" = "🔄 Giriş: {{ .InboundRemark }}\n\n🔑 Şifre: {{ .ClientPass }}\n📧 E-posta: {{ .ClientEmail }}\n📊 Trafik: {{ .ClientTraffic }}\n📅 Bitiş Tarihi: {{ .ClientExp }}\n🌐 IP Sınırı: {{ .IpLimit }}\n💬 Yorum: {{ .ClientComment }}\n\nArtık bu müşteriyi girişe ekleyebilirsin!" -"cancel" = "❌ İşlem iptal edildi! \n\nİstediğiniz zaman /start ile yeniden başlayabilirsiniz. 🔄" -"error_add_client" = "⚠️ Hata:\n\n {{ .error }}" -"using_default_value" = "Tamam, varsayılan değeri kullanacağım. 😊" -"incorrect_input" = "Girdiğiniz değer geçerli değil.\nKelime öbekleri boşluk olmadan devam etmelidir.\nDoğru örnek: aaaaaa\nYanlış örnek: aaa aaa 🚫" -"AreYouSure" = "Emin misin? 🤔" -"SuccessResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ✅ Başarılı" -"FailedResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ❌ Başarısız \n\n🛠️ Hata: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Tüm müşteriler için trafik sıfırlama işlemi tamamlandı." - -[tgbot.buttons] -"closeKeyboard" = "❌ Klavyeyi Kapat" -"cancel" = "❌ İptal" -"cancelReset" = "❌ Sıfırlamayı İptal Et" -"cancelIpLimit" = "❌ IP Limitini İptal Et" -"confirmResetTraffic" = "✅ Trafiği Sıfırlamayı Onayla?" -"confirmClearIps" = "✅ IP'leri Temizlemeyi Onayla?" -"confirmRemoveTGUser" = "✅ Telegram Kullanıcısını Kaldırmayı Onayla?" -"confirmToggle" = "✅ Kullanıcıyı Etkinleştirme/Devre Dışı Bırakmayı Onayla?" -"dbBackup" = "Veritabanı Yedeği Al" -"serverUsage" = "Sunucu Kullanımı" -"getInbounds" = "Gelenleri Al" -"depleteSoon" = "Yakında Tükenecek" -"clientUsage" = "Kullanımı Al" -"onlines" = "Çevrimiçi Müşteriler" -"commands" = "Komutlar" -"refresh" = "🔄 Yenile" -"clearIPs" = "❌ IP'leri Temizle" -"removeTGUser" = "❌ Telegram Kullanıcısını Kaldır" -"selectTGUser" = "👤 Telegram Kullanıcısını Seç" -"selectOneTGUser" = "👤 Bir Telegram Kullanıcısını Seçin:" -"resetTraffic" = "📈 Trafiği Sıfırla" -"resetExpire" = "📅 Son Kullanma Tarihini Değiştir" -"ipLog" = "🔢 IP Günlüğü" -"ipLimit" = "🔢 IP Limiti" -"setTGUser" = "👤 Telegram Kullanıcısını Ayarla" -"toggle" = "🔘 Etkinleştir / Devre Dışı Bırak" -"custom" = "🔢 Özel" -"confirmNumber" = "✅ Onayla: {{ .Num }}" -"confirmNumberAdd" = "✅ Ekleme onayı: {{ .Num }}" -"limitTraffic" = "🚧 Trafik Sınırı" -"getBanLogs" = "Yasak Günlüklerini Al" -"allClients" = "Tüm Müşteriler" -"addClient" = "Müşteri Ekle" -"submitDisable" = "Devre Dışı Olarak Gönder ☑️" -"submitEnable" = "Etkin Olarak Gönder ✅" -"use_default" = "🏷️ Varsayılanı Kullan" -"change_id" = "⚙️🔑 Kimlik" -"change_password" = "⚙️🔑 Şifre" -"change_email" = "⚙️📧 E-posta" -"change_comment" = "⚙️💬 Yorum" -"ResetAllTraffics" = "Tüm Trafikleri Sıfırla" -"SortedTrafficUsageReport" = "Sıralı Trafik Kullanım Raporu" - -[tgbot.answers] -"successfulOperation" = "✅ İşlem başarılı!" -"errorOperation" = "❗ İşlemde hata." -"getInboundsFailed" = "❌ Gelenler alınamadı." -"getClientsFailed" = "❌ Müşteriler alınamadı." -"canceled" = "❌ {{ .Email }}: İşlem iptal edildi." -"clientRefreshSuccess" = "✅ {{ .Email }}: Müşteri başarıyla yenilendi." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP'ler başarıyla yenilendi." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Müşterinin Telegram Kullanıcısı başarıyla yenilendi." -"resetTrafficSuccess" = "✅ {{ .Email }}: Trafik başarıyla sıfırlandı." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Trafik limiti başarıyla kaydedildi." -"expireResetSuccess" = "✅ {{ .Email }}: Son kullanma günleri başarıyla sıfırlandı." -"resetIpSuccess" = "✅ {{ .Email }}: IP limiti {{ .Count }} başarıyla kaydedildi." -"clearIpSuccess" = "✅ {{ .Email }}: IP'ler başarıyla temizlendi." -"getIpLog" = "✅ {{ .Email }}: IP Günlüğü alındı." -"getUserInfo" = "✅ {{ .Email }}: Telegram Kullanıcı Bilgisi alındı." -"removedTGUserSuccess" = "✅ {{ .Email }}: Telegram Kullanıcısı başarıyla kaldırıldı." -"enableSuccess" = "✅ {{ .Email }}: Başarıyla etkinleştirildi." -"disableSuccess" = "✅ {{ .Email }}: Başarıyla devre dışı bırakıldı." -"askToAddUserId" = "Yapılandırmanız bulunamadı!\r\nLütfen yöneticinizden yapılandırmalarınıza Telegram ChatID'nizi eklemesini isteyin.\r\n\r\nKullanıcı ChatID'niz: {{ .TgUserID }}" -"chooseClient" = "Gelen {{ .Inbound }} için bir Müşteri Seçin" -"chooseInbound" = "Bir Gelen Seçin" diff --git a/web/translation/translate.uk_UA.toml b/web/translation/translate.uk_UA.toml deleted file mode 100644 index d259d5a1..00000000 --- a/web/translation/translate.uk_UA.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Ім'я користувача" -"password" = "Пароль" -"login" = "Увійти" -"confirm" = "Підтвердити" -"cancel" = "Скасувати" -"close" = "Закрити" -"save" = "Зберегти" -"logout" = "Вийти" -"create" = "Створити" -"update" = "Оновити" -"copy" = "Копіювати" -"copied" = "Скопійовано" -"download" = "Завантажити" -"remark" = "Примітка" -"enable" = "Увімкнути" -"protocol" = "Протокол" -"search" = "Пошук" -"filter" = "Фільтр" -"loading" = "Завантаження..." -"second" = "Секунда" -"minute" = "Хвилина" -"hour" = "Година" -"day" = "День" -"check" = "Перевірка" -"indefinite" = "Безстроково" -"unlimited" = "Безлімітний" -"none" = "Немає" -"qrCode" = "QR-Код" -"info" = "Більше інформації" -"edit" = "Редагувати" -"delete" = "Видалити" -"reset" = "Скидання" -"noData" = "Немає даних." -"copySuccess" = "Скопійовано успішно" -"sure" = "Звичайно" -"encryption" = "Шифрування" -"useIPv4ForHost" = "Використовувати IPv4 для хоста" -"transmission" = "Протокол передачи" -"host" = "Хост" -"path" = "Шлях" -"camouflage" = "Маскування" -"status" = "Статус" -"enabled" = "Увімкнено" -"disabled" = "Вимкнено" -"depleted" = "Вичерпано" -"depletingSoon" = "Вичерпується" -"offline" = "Офлайн" -"online" = "Онлайн" -"domainName" = "Доменне ім`я" -"monitor" = "Слухати IP" -"certificate" = "Цифровий сертифікат" -"fail" = "Помилка" -"comment" = "Коментар" -"success" = "Успішно" -"lastOnline" = "Був(ла) онлайн" -"getVersion" = "Отримати версію" -"install" = "Встановити" -"clients" = "Клієнти" -"usage" = "Використання" -"twoFactorCode" = "Код" -"remained" = "Залишилося" -"security" = "Беспека" -"secAlertTitle" = "Попередження системи безпеки" -"secAlertSsl" = "Це з'єднання не є безпечним. Будь ласка, уникайте введення конфіденційної інформації, поки TLS не буде активовано для захисту даних." -"secAlertConf" = "Деякі налаштування вразливі до атак. Рекомендується посилити протоколи безпеки, щоб запобігти можливим порушенням." -"secAlertSSL" = "Панель не має безпечного з'єднання. Будь ласка, встановіть сертифікат TLS для захисту даних." -"secAlertPanelPort" = "Стандартний порт панелі вразливий. Будь ласка, сконфігуруйте випадковий або конкретний порт." -"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." -"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." -"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." -"emptyDnsDesc" = "Немає доданих DNS-серверів." -"emptyFakeDnsDesc" = "Немає доданих Fake DNS-серверів." -"emptyBalancersDesc" = "Немає доданих балансувальників." -"emptyReverseDesc" = "Немає доданих зворотних проксі." -"somethingWentWrong" = "Щось пішло не так" - -[subscription] -"title" = "Інформація про підписку" -"subId" = "ID підписки" -"status" = "Статус" -"downloaded" = "Завантажено" -"uploaded" = "Відвантажено" -"expiry" = "Термін дії" -"totalQuota" = "Загальна квота" -"individualLinks" = "Окремі посилання" -"active" = "Активна" -"inactive" = "Неактивна" -"unlimited" = "Безліміт" -"noExpiry" = "Без строку" - -[menu] -"theme" = "Тема" -"dark" = "Темна" -"ultraDark" = "Ультра темна" -"dashboard" = "Огляд" -"inbounds" = "Вхідні" -"settings" = "Параметри панелі" -"xray" = "Конфігурації Xray" -"logout" = "Вийти" -"link" = "Керувати" - -[pages.login] -"hello" = "Привіт" -"title" = "Привітання!" -"loginAgain" = "Ваш сеанс закінчився, увійдіть знову" - -[pages.login.toasts] -"invalidFormData" = "Формат вхідних даних недійсний." -"emptyUsername" = "Потрібне ім'я користувача" -"emptyPassword" = "Потрібен пароль" -"wrongUsernameOrPassword" = "Невірне ім’я користувача, пароль або код двофакторної аутентифікації." -"successLogin" = "Ви успішно увійшли до свого облікового запису." - -[pages.index] -"title" = "Огляд" -"cpu" = "ЦП" -"logicalProcessors" = "Логічні процесори" -"frequency" = "Частота" -"swap" = "Своп" -"storage" = "Сховище" -"memory" = "ОЗП" -"threads" = "Потоки" -"xrayStatus" = "Xray" -"stopXray" = "Зупинити" -"restartXray" = "Перезапустити" -"xraySwitch" = "Версія" -"xraySwitchClick" = "Виберіть версію, на яку ви хочете перейти." -"xraySwitchClickDesk" = "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями." -"xrayUpdates" = "Оновлення Xray" -"updatePanel" = "Оновити панель" -"panelUpdateDesc" = "Це оновить 3X-UI до останнього релізу та перезапустить сервіс панелі." -"currentPanelVersion" = "Поточна версія панелі" -"latestPanelVersion" = "Остання версія панелі" -"panelUpToDate" = "Панель оновлено" -"upToDate" = "Оновлено" -"xrayStatusUnknown" = "Невідомо" -"xrayStatusRunning" = "Запущено" -"xrayStatusStop" = "Зупинено" -"xrayStatusError" = "Помилка" -"xrayErrorPopoverTitle" = "Під час роботи Xray сталася помилка" -"operationHours" = "Час роботи" -"systemLoad" = "Завантаження системи" -"systemLoadDesc" = "Середнє завантаження системи за останні 1, 5 і 15 хвилин" -"connectionCount" = "Статистика з'єднання" -"ipAddresses" = "IP-адреси" -"toggleIpVisibility" = "Перемкнути видимість IP" -"overallSpeed" = "Загальна швидкість" -"upload" = "Відправка" -"download" = "Завантаження" -"totalData" = "Загальний обсяг даних" -"sent" = "Відправлено" -"received" = "Отримано" -"documentation" = "Документація" -"xraySwitchVersionDialog" = "Ви дійсно хочете змінити версію Xray?" -"xraySwitchVersionDialogDesc" = "Це змінить версію Xray на #version#." -"xraySwitchVersionPopover" = "Xray успішно оновлено" -"panelUpdateDialog" = "Ви дійсно хочете оновити панель?" -"panelUpdateDialogDesc" = "Це оновить 3X-UI до #version# та перезапустить сервіс панелі." -"panelUpdateCheckPopover" = "Перевірка оновлення панелі не вдалася" -"panelUpdateStartedPopover" = "Розпочато оновлення панелі" -"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?" -"geofileUpdateDialogDesc" = "Це оновить файл #filename#." -"geofilesUpdateDialogDesc" = "Це оновить усі геофайли." -"geofilesUpdateAll" = "Оновити все" -"geofileUpdatePopover" = "Геофайл успішно оновлено" -"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку" -"logs" = "Журнали" -"config" = "Конфігурація" -"backup" = "Резервна копія" -"backupTitle" = "Резервне копіювання та відновлення бази даних" -"exportDatabase" = "Резервна копія" -"exportDatabaseDesc" = "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій." -"importDatabase" = "Відновити" -"importDatabaseDesc" = "Натисніть, щоб вибрати та завантажити файл .db з вашого пристрою для відновлення бази даних з резервної копії." -"importDatabaseSuccess" = "Базу даних успішно імпортовано" -"importDatabaseError" = "Виникла помилка під час імпорту бази даних" -"readDatabaseError" = "Виникла помилка під час читання бази даних" -"getDatabaseError" = "Виникла помилка під час отримання бази даних" -"getConfigError" = "Виникла помилка під час отримання файлу конфігурації" -"customGeoTitle" = "Користувацькі GeoSite / GeoIP" -"customGeoAdd" = "Додати" -"customGeoType" = "Тип" -"customGeoAlias" = "Псевдонім" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Увімкнено" -"customGeoLastUpdated" = "Оновлено" -"customGeoExtColumn" = "Маршрутизація (ext:…)" -"customGeoToastUpdateAll" = "Усі користувацькі джерела оновлено" -"customGeoActions" = "Дії" -"customGeoEdit" = "Змінити" -"customGeoDelete" = "Видалити" -"customGeoDownload" = "Оновити зараз" -"customGeoModalAdd" = "Додати користувацький geo" -"customGeoModalEdit" = "Змінити користувацький geo" -"customGeoModalSave" = "Зберегти" -"customGeoDeleteConfirm" = "Видалити це джерело geo?" -"customGeoRoutingHint" = "У правилах маршрутизації використовуйте значення як ext:файл.dat:тег (замініть тег)." -"customGeoInvalidId" = "Некоректний ідентифікатор ресурсу" -"customGeoAliasesError" = "Не вдалося завантажити псевдоніми geo" -"customGeoValidationAlias" = "Псевдонім: лише a-z, цифри, - і _" -"customGeoValidationUrl" = "URL має починатися з http:// або https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (власний)" -"customGeoToastList" = "Список користувацьких geo" -"customGeoToastAdd" = "Додати користувацький geo" -"customGeoToastUpdate" = "Оновити користувацький geo" -"customGeoToastDelete" = "Користувацький geofile «{{ .fileName }}» видалено" -"customGeoToastDownload" = "Geofile «{{ .fileName }}» оновлено" -"customGeoErrInvalidType" = "Тип має бути geosite або geoip" -"customGeoErrAliasRequired" = "Потрібен псевдонім" -"customGeoErrAliasPattern" = "Псевдонім містить недопустимі символи" -"customGeoErrAliasReserved" = "Цей псевдонім зарезервовано" -"customGeoErrUrlRequired" = "Потрібен URL" -"customGeoErrInvalidUrl" = "Некоректний URL" -"customGeoErrUrlScheme" = "URL має використовувати http або https" -"customGeoErrUrlHost" = "Некоректний хост URL" -"customGeoErrDuplicateAlias" = "Цей псевдонім уже використовується для цього типу" -"customGeoErrNotFound" = "Джерело geo не знайдено" -"customGeoErrDownload" = "Помилка завантаження" -"customGeoErrUpdateAllIncomplete" = "Не вдалося оновити один або кілька користувацьких джерел" -"customGeoEmpty" = "Користувацьких джерел geo поки немає — натисніть «Додати», щоб створити" - -[pages.inbounds] -"allTimeTraffic" = "Загальний трафік" -"allTimeTrafficUsage" = "Загальне використання за весь час" -"title" = "Вхідні" -"totalDownUp" = "Всього надісланих/отриманих" -"totalUsage" = "Всього використанно" -"inboundCount" = "Загальна кількість вхідних" -"operate" = "Меню" -"enable" = "Увімкнено" -"remark" = "Примітка" -"protocol" = "Протокол" -"port" = "Порт" -"portMap" = "Порт-перехід" -"traffic" = "Трафік" -"details" = "Деталі" -"transportConfig" = "Транспорт" -"expireDate" = "Тривалість" -"createdAt" = "Створено" -"updatedAt" = "Оновлено" -"resetTraffic" = "Скинути трафік" -"addInbound" = "Додати вхідний" -"generalActions" = "Загальні дії" -"autoRefresh" = "Автооновлення" -"autoRefreshInterval" = "Інтервал" -"modifyInbound" = "Змінити вхідний" -"deleteInbound" = "Видалити вхідні" -"deleteInboundContent" = "Ви впевнені, що хочете видалити вхідні?" -"deleteClient" = "Видалити клієнта" -"deleteClientContent" = "Ви впевнені, що хочете видалити клієнт?" -"resetTrafficContent" = "Ви впевнені, що хочете скинути трафік?" -"copyLink" = "Копіювати URL" -"address" = "Адреса" -"network" = "Мережа" -"destinationPort" = "Порт призначення" -"targetAddress" = "Цільова адреса" -"monitorDesc" = "Залиште порожнім, щоб слухати всі IP-адреси" -"meansNoLimit" = "= Необмежено. (одиниця: ГБ)" -"totalFlow" = "Загальна витрата" -"leaveBlankToNeverExpire" = "Залиште порожнім, щоб ніколи не закінчувався" -"noRecommendKeepDefault" = "Рекомендується зберегти значення за замовчуванням" -"certificatePath" = "Шлях до файлу" -"certificateContent" = "Вміст файлу" -"publicKey" = "Публічний ключ" -"privatekey" = "Закритий ключ" -"clickOnQRcode" = "Натисніть QR-код, щоб скопіювати" -"client" = "Клієнт" -"export" = "Експортувати всі URL-адреси" -"clone" = "Клон" -"cloneInbound" = "Клонувати" -"cloneInboundContent" = "Усі налаштування цього вхідного потоку, крім порту, IP-адреси прослуховування та клієнтів, будуть застосовані до клону." -"cloneInboundOk" = "Клонувати" -"resetAllTraffic" = "Скинути весь вхідний трафік" -"resetAllTrafficTitle" = "Скинути весь вхідний трафік" -"resetAllTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх вхідних?" -"resetInboundClientTraffics" = "Скинути трафік клієнтів" -"resetInboundClientTrafficTitle" = "Скинути трафік клієнтів" -"resetInboundClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік клієнтів цього вхідного потоку?" -"resetAllClientTraffics" = "Скинути весь трафік клієнтів" -"resetAllClientTrafficTitle" = "Скинути весь трафік клієнтів" -"resetAllClientTrafficContent" = "Ви впевнені, що бажаєте скинути трафік усіх клієнтів?" -"delDepletedClients" = "Видалити вичерпані клієнти" -"delDepletedClientsTitle" = "Видалити вичерпані клієнти" -"delDepletedClientsContent" = "Ви впевнені, що хочете видалити всі вичерпані клієнти?" -"email" = "Електронна пошта" -"emailDesc" = "Будь ласка, надайте унікальну адресу електронної пошти." -"IPLimit" = "Обмеження IP" -"IPLimitDesc" = "Вимикає вхідний, якщо кількість перевищує встановлене значення. (0 = вимкнено)" -"IPLimitlog" = "Журнал IP" -"IPLimitlogDesc" = "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)" -"IPLimitlogclear" = "Очистити журнал" -"setDefaultCert" = "Установити сертифікат з панелі" -"telegramDesc" = "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або (@userinfobot)" -"subscriptionDesc" = "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів." -"info" = "Інформація" -"same" = "Те саме" -"inboundData" = "Вхідні дані" -"exportInbound" = "Експортувати вхідні" -"import" = "Імпорт" -"importInbound" = "Імпортувати вхідний" -"periodicTrafficResetTitle" = "Скидання трафіку" -"periodicTrafficResetDesc" = "Автоматично скидати лічильник трафіку через певні проміжки часу" -"lastReset" = "Останнє скидання" - -[pages.client] -"add" = "Додати клієнта" -"edit" = "Редагувати клієнта" -"submitAdd" = "Додати клієнта" -"submitEdit" = "Зберегти зміни" -"clientCount" = "Кількість клієнтів" -"bulk" = "Додати групу" -"copyFromInbound" = "Скопіювати клієнтів з інбаунда" -"copyToInbound" = "Скопіювати клієнтів у" -"copySelected" = "Скопіювати вибраних" -"copySource" = "Джерело" -"copyEmailPreview" = "Попередній перегляд підсумкових email" -"copySelectSourceFirst" = "Спочатку виберіть джерело." -"copyResult" = "Результат копіювання" -"copyResultSuccess" = "Успішно скопійовано" -"copyResultNone" = "Нічого копіювати: жодного клієнта не вибрано або список джерела порожній" -"copyResultErrors" = "Помилки під час копіювання" -"copyFlowLabel" = "Flow для нових клієнтів (VLESS)" -"copyFlowHint" = "Застосується до всіх скопійованих клієнтів. Залиште порожнім, щоб не задавати." -"selectAll" = "Вибрати всіх" -"clearAll" = "Зняти все" -"method" = "Метод" -"first" = "Перший" -"last" = "Останній" -"prefix" = "Префікс" -"postfix" = "Постфікс" -"delayedStart" = "Початок використання" -"expireDays" = "Тривалість" -"days" = "Дні(в)" -"renew" = "Автоматичне оновлення" -"renewDesc" = "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Ніколи" -"daily" = "Щодня" -"weekly" = "Щотижня" -"monthly" = "Щомісяця" -"hourly" = "Щогодини" - -[pages.inbounds.toasts] -"obtain" = "Отримати" -"updateSuccess" = "Оновлення пройшло успішно" -"logCleanSuccess" = "Журнал очищено" -"inboundsUpdateSuccess" = "Вхідні підключення успішно оновлено" -"inboundUpdateSuccess" = "Вхідне підключення успішно оновлено" -"inboundCreateSuccess" = "Вхідне підключення успішно створено" -"inboundDeleteSuccess" = "Вхідне підключення успішно видалено" -"inboundClientAddSuccess" = "Клієнт(и) вхідного підключення додано" -"inboundClientDeleteSuccess" = "Клієнта вхідного підключення видалено" -"inboundClientUpdateSuccess" = "Клієнта вхідного підключення оновлено" -"delDepletedClientsSuccess" = "Усі вичерпані клієнти видалені" -"resetAllClientTrafficSuccess" = "Весь трафік клієнта скинуто" -"resetAllTrafficSuccess" = "Весь трафік скинуто" -"resetInboundClientTrafficSuccess" = "Трафік скинуто" -"trafficGetError" = "Помилка отримання даних про трафік" -"getNewX25519CertError" = "Помилка при отриманні сертифіката X25519." -"getNewmldsa65Error" = "Помилка при отриманні сертифіката mldsa65." -"getNewVlessEncError" = "Помилка при отриманні сертифіката VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Запит" -"response" = "Відповідь" -"name" = "Ім'я" -"value" = "Значення" - -[pages.inbounds.stream.tcp] -"version" = "Версія" -"method" = "Метод" -"path" = "Шлях" -"status" = "Статус" -"statusDescription" = "Опис стану" -"requestHeader" = "Заголовок запиту" -"responseHeader" = "Заголовок відповіді" - -[pages.settings] -"title" = "Параметри панелі" -"save" = "Зберегти" -"infoDesc" = "Кожна внесена тут зміна повинна бути збережена. Перезапустіть панель, щоб застосувати зміни." -"restartPanel" = "Перезапустити панель" -"restartPanelDesc" = "Ви впевнені, що бажаєте перезапустити панель? Якщо ви не можете отримати доступ до панелі після перезапуску, будь ласка, перегляньте інформацію журналу панелі на сервері." -"restartPanelSuccess" = "Панель успішно перезапущено" -"actions" = "Дії" -"resetDefaultConfig" = "Відновити значення за замовчуванням" -"panelSettings" = "Загальні" -"securitySettings" = "Автентифікація" -"TGBotSettings" = "Telegram Бот" -"panelListeningIP" = "Слухати IP" -"panelListeningIPDesc" = "IP-адреса для веб-панелі. (залиште порожнім, щоб слухати всі IP-адреси)" -"panelListeningDomain" = "Домен прослуховування" -"panelListeningDomainDesc" = "Доменне ім'я для веб-панелі. (залиште порожнім, щоб слухати всі домени та IP-адреси)" -"panelPort" = "Порт прослуховування" -"panelPortDesc" = "Номер порту для веб-панелі. (має бути невикористаний порт)" -"publicKeyPath" = "Шлях відкритого ключа" -"publicKeyPathDesc" = "Шлях до файлу відкритого ключа для веб-панелі. (починається з ‘/‘)" -"privateKeyPath" = "Шлях приватного ключа" -"privateKeyPathDesc" = "Шлях до файлу приватного ключа для веб-панелі. (починається з ‘/‘)" -"panelUrlPath" = "Шлях URL" -"panelUrlPathDesc" = "Шлях URL для веб-панелі. (починається з ‘/‘ і закінчується ‘/‘)" -"pageSize" = "Розмір сторінки" -"pageSizeDesc" = "Визначити розмір сторінки для вхідної таблиці. (0 = вимкнено)" -"remarkModel" = "Модель зауваження та роздільний символ" -"datepicker" = "Тип календаря" -"datepickerPlaceholder" = "Виберіть дату" -"datepickerDescription" = "Заплановані завдання виконуватимуться на основі цього календаря." -"sampleRemark" = "Зразок зауваження" -"oldUsername" = "Поточне ім'я користувача" -"currentPassword" = "Поточний пароль" -"newUsername" = "Нове ім'я користувача" -"newPassword" = "Новий пароль" -"telegramBotEnable" = "Увімкнути Telegram Bot" -"telegramBotEnableDesc" = "Вмикає бота Telegram." -"telegramToken" = "Telegram Токен" -"telegramTokenDesc" = "Токен бота Telegram, отриманий від '@BotFather'." -"telegramProxy" = "SOCKS Проксі" -"telegramProxyDesc" = "Вмикає проксі-сервер SOCKS5 для підключення до Telegram. (відкоригуйте параметри відповідно до посібника)" -"telegramAPIServer" = "Сервер Telegram API" -"telegramAPIServerDesc" = "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням." -"telegramChatId" = "Ідентифікатор чату адміністратора" -"telegramChatIdDesc" = "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут @userinfobot) або (використовуйте команду '/id' у боті)" -"telegramNotifyTime" = "Час сповіщення" -"telegramNotifyTimeDesc" = "Час повідомлення бота Telegram, встановлений для періодичних звітів. (використовуйте формат часу crontab)" -"tgNotifyBackup" = "Резервне копіювання бази даних" -"tgNotifyBackupDesc" = "Надіслати файл резервної копії бази даних зі звітом." -"tgNotifyLogin" = "Сповіщення про вхід" -"tgNotifyLoginDesc" = "Отримувати сповіщення про ім'я користувача, IP-адресу та час щоразу, коли хтось намагається увійти у вашу веб-панель." -"sessionMaxAge" = "Тривалість сеансу" -"sessionMaxAgeDesc" = "Тривалість, протягом якої ви можете залишатися в системі. (одиниця: хвилина)" -"expireTimeDiff" = "Повідомлення про дату закінчення" -"expireTimeDiffDesc" = "Отримувати сповіщення про термін дії при досягненні цього порогу. (одиниця: день)" -"trafficDiff" = "Повідомлення про обмеження трафіку" -"trafficDiffDesc" = "Отримувати сповіщення про обмеження трафіку при досягненні цього порогу. (одиниця: ГБ)" -"tgNotifyCpu" = "Сповіщення про завантаження ЦП" -"tgNotifyCpuDesc" = "Отримувати сповіщення, якщо навантаження ЦП перевищує це порогове значення. (одиниця: %)" -"timeZone" = "Часовий пояс" -"timeZoneDesc" = "Заплановані завдання виконуватимуться на основі цього часового поясу." -"subSettings" = "Підписка" -"subEnable" = "Увімкнути службу підписки" -"subEnableDesc" = "Вмикає службу підписки." -"subJsonEnable" = "Увімкнути/вимкнути JSON-кінець підписки незалежно." -"subTitle" = "Назва Підписки" -"subTitleDesc" = "Назва, яка відображається у VPN-клієнті" -"subSupportUrl" = "URL підтримки" -"subSupportUrlDesc" = "Посилання на технічну підтримку, що відображається у VPN-клієнті" -"subProfileUrl" = "URL профілю" -"subProfileUrlDesc" = "Посилання на ваш вебсайт, що відображається у VPN-клієнті" -"subAnnounce" = "Оголошення" -"subAnnounceDesc" = "Текст оголошення, що відображається у VPN-клієнті" -"subEnableRouting" = "Увімкнути маршрутизацію" -"subEnableRoutingDesc" = "Глобальне налаштування для увімкнення маршрутизації у VPN-клієнті. (Тільки для Happ)" -"subRoutingRules" = "Правила маршрутизації" -"subRoutingRulesDesc" = "Глобальні правила маршрутизації для VPN-клієнта. (Тільки для Happ)" -"subListen" = "Слухати IP" -"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)" -"subPort" = "Слухати порт" -"subPortDesc" = "Номер порту для служби підписки. (має бути невикористаний порт)" -"subCertPath" = "Шлях відкритого ключа" -"subCertPathDesc" = "Шлях до файлу відкритого ключа для служби підписки. (починається з ‘/‘)" -"subKeyPath" = "Шлях приватного ключа" -"subKeyPathDesc" = "Шлях до файлу приватного ключа для служби підписки. (починається з ‘/‘)" -"subPath" = "Шлях URI" -"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)" -"subDomain" = "Домен прослуховування" -"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)" -"subUpdates" = "Інтервали оновлення" -"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)" -"subEncrypt" = "Закодувати" -"subEncryptDesc" = "Повернений вміст послуги підписки матиме кодування Base64." -"subShowInfo" = "Показати інформацію про використання" -"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах." -"subURI" = "URI зворотного проксі" -"subURIDesc" = "URI до URL-адреси підписки для використання за проксі." -"externalTrafficInformEnable" = "Інформація про зовнішній трафік" -"externalTrafficInformEnableDesc" = "Інформувати зовнішній API про кожне оновлення трафіку." -"externalTrafficInformURI" = "Інформаційний URI зовнішнього трафіку" -"externalTrafficInformURIDesc" = "Оновлення трафіку надсилаються на цей URI." -"restartXrayOnClientDisable" = "Перезапускати Xray після авто-вимкнення" -"restartXrayOnClientDisableDesc" = "Коли клієнт автоматично вимикається через закінчення терміну дії або ліміт трафіку, перезапускати Xray." -"fragment" = "Фрагментація" -"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS" -"fragmentSett" = "Параметри фрагментації" -"noisesDesc" = "Увімкнути Noises." -"noisesSett" = "Налаштування Noises" -"mux" = "Mux" -"muxDesc" = "Передавати кілька незалежних потоків даних у межах встановленого потоку даних." -"muxSett" = "Налаштування Mux" -"direct" = "Пряме підключення" -"directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни." -"notifications" = "Сповіщення" -"certs" = "Сертифікати" -"externalTraffic" = "Зовнішній трафік" -"dateAndTime" = "Дата та час" -"proxyAndServer" = "Проксі та сервер" -"intervals" = "Інтервали" -"information" = "Інформація" -"language" = "Мова" -"telegramBotLanguage" = "Мова Telegram-бота" - -[pages.xray] -"title" = "Xray конфігурації" -"save" = "Зберегти" -"restart" = "Перезапустити Xray" -"restartSuccess" = "Xray успішно перезапущено" -"stopSuccess" = "Xray успішно зупинено" -"restartError" = "Виникла помилка під час перезапуску Xray." -"stopError" = "Виникла помилка під час зупинки Xray." -"basicTemplate" = "Базовий шаблон" -"advancedTemplate" = "Додатково" -"generalConfigs" = "Загальні конфігурації" -"generalConfigsDesc" = "Ці параметри визначатимуть загальні налаштування." -"logConfigs" = "Журнал" -"logConfigsDesc" = "Журнали можуть вплинути на ефективність вашого сервера. Рекомендується вмикати його з розумом лише у випадку ваших потреб" -"blockConfigsDesc" = "Ці параметри блокуватимуть трафік на основі конкретних запитуваних протоколів і веб-сайтів." -"basicRouting" = "Основна Маршрутизація" -"blockConnectionsConfigsDesc" = "Ці параметри блокуватимуть трафік на основі запитаних країн." -"directConnectionsConfigsDesc" = "Пряме з'єднання гарантує, що певний трафік не буде маршрутизовано через інший сервер." -"blockips" = "Блокувати IP" -"blockdomains" = "Блокувати домени" -"directips" = "Прямі IP" -"directdomains" = "Прямі домени" -"ipv4Routing" = "Маршрутизація IPv4" -"ipv4RoutingDesc" = "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4." -"warpRouting" = "WARP Маршрутизація" -"warpRoutingDesc" = "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через WARP." -"nordRouting" = "Маршрутизація NordVPN" -"nordRoutingDesc" = "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через NordVPN." -"Template" = "Шаблон розширеної конфігурації Xray" -"TemplateDesc" = "Остаточний конфігураційний файл Xray буде створено на основі цього шаблону." -"FreedomStrategy" = "Стратегія протоколу свободи" -"FreedomStrategyDesc" = "Установити стратегію виведення для мережі в протоколі свободи." -"RoutingStrategy" = "Загальна стратегія маршрутизації" -"RoutingStrategyDesc" = "Установити загальну стратегію маршрутизації трафіку для вирішення всіх запитів." -"outboundTestUrl" = "URL тесту outbound" -"outboundTestUrlDesc" = "URL для перевірки з'єднання outbound" -"Torrent" = "Блокувати протокол BitTorrent" -"Inbounds" = "Вхідні" -"InboundsDesc" = "Прийняття певних клієнтів." -"Outbounds" = "Вихід" -"Balancers" = "Балансери" -"OutboundsDesc" = "Встановити шлях вихідного трафіку." -"Routings" = "Правила маршрутизації" -"RoutingsDesc" = "Пріоритет кожного правила важливий!" -"completeTemplate" = "Усі" -"logLevel" = "Рівень журналу" -"logLevelDesc" = "Рівень журналу для журналів помилок із зазначенням інформації, яку потрібно записати." -"accessLog" = "Журнал доступу" -"accessLogDesc" = "Шлях до файлу журналу доступу. Спеціальне значення 'none' вимикає журнали доступу" -"errorLog" = "Журнал помилок" -"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок" -"dnsLog" = "Журнал DNS" -"dnsLogDesc" = "Чи включити журнали запитів DNS" -"maskAddress" = "Маскувати Адресу" -"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі." -"statistics" = "Статистика" -"statsInboundUplink" = "Статистика вхідного аплінку" -"statsInboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вхідних проксі." -"statsInboundDownlink" = "Статистика вхідного даунлінку" -"statsInboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вхідних проксі." -"statsOutboundUplink" = "Статистика вихідного аплінку" -"statsOutboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вихідних проксі." -"statsOutboundDownlink" = "Статистика вихідного даунлінку" -"statsOutboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вихідних проксі." - -[pages.xray.rules] -"first" = "Перший" -"last" = "Останній" -"up" = "Вгору" -"down" = "Вниз" -"source" = "Джерело" -"dest" = "Пункт призначення" -"inbound" = "Вхідний" -"outbound" = "Вихідний" -"balancer" = "Балансувальник" -"info" = "Інформація" -"add" = "Додати правило" -"edit" = "Редагувати правило" -"useComma" = "Елементи, розділені комами" - -[pages.xray.outbound] -"addOutbound" = "Додати вихідний" -"addReverse" = "Додати реверс" -"editOutbound" = "Редагувати вихідні" -"editReverse" = "Редагувати реверс" -"reverseTag" = "Тег реверс-проксі" -"reverseTagDesc" = "Тег вихідного з'єднання для простого реверс-проксі VLESS. Залиште порожнім для вимкнення." -"reverseTagPlaceholder" = "тег вихідного (порожнє = вимкнено)" -"tag" = "Тег" -"tagDesc" = "Унікальний тег" -"address" = "Адреса" -"reverse" = "Зворотний" -"domain" = "Домен" -"type" = "Тип" -"bridge" = "Міст" -"portal" = "Портал" -"link" = "Посилання" -"intercon" = "Взаємозв'язок" -"settings" = "Налаштування" -"accountInfo" = "Інформація про обліковий запис" -"outboundStatus" = "Статус виходу" -"sendThrough" = "Надіслати через" -"test" = "Тест" -"testResult" = "Результат тесту" -"testing" = "Тестування з'єднання..." -"testSuccess" = "Тест успішний" -"testFailed" = "Тест не пройдено" -"testError" = "Не вдалося протестувати вихідне з'єднання" -"nordvpn" = "NordVPN" -"accessToken" = "Токен доступу" -"country" = "Країна" -"server" = "Сервер" -"city" = "Місто" -"allCities" = "Усі міста" -"privateKey" = "Приватний ключ" -"load" = "Навантаження" - -[pages.xray.balancer] -"addBalancer" = "Додати балансир" -"editBalancer" = "Редагувати балансир" -"balancerStrategy" = "Стратегія" -"balancerSelectors" = "Селектори" -"tag" = "Тег" -"tagDesc" = "Унікальний тег" -"balancerDesc" = "Неможливо використовувати balancerTag і outboundTag одночасно. Якщо використовувати одночасно, працюватиме лише outboundTag." - -[pages.xray.wireguard] -"secretKey" = "Приватний ключ" -"publicKey" = "Публічний ключ" -"allowedIPs" = "Дозволені IP-адреси" -"endpoint" = "Кінцева точка" -"psk" = "Спільний ключ" -"domainStrategy" = "Стратегія домену" - -[pages.xray.tun] -"nameDesc" = "Назва інтерфейсу TUN. Значення за замовчуванням - 'xray0'" -"mtuDesc" = "Максимальна одиниця передачі. Максимальний розмір пакетів даних. Значення за замовчуванням - 1500" -"userLevel" = "Рівень користувача" -"userLevelDesc" = "Всі з'єднання, встановлені через цей вхід, використовуватимуть цей рівень користувача. Значення за замовчуванням - 0" - -[pages.xray.dns] -"enable" = "Увімкнути DNS" -"enableDesc" = "Увімкнути вбудований DNS-сервер" -"tag" = "Мітка вхідного DNS" -"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації." -"clientIp" = "IP клієнта" -"clientIpDesc" = "Використовується для повідомлення серверу про вказане місцезнаходження IP під час DNS-запитів" -"disableCache" = "Вимкнути кеш" -"disableCacheDesc" = "Вимкнути кешування DNS" -"disableFallback" = "Вимкнути резервний DNS" -"disableFallbackDesc" = "Вимкнути резервні DNS-запити" -"disableFallbackIfMatch" = "Вимкнути резервний DNS при збігу" -"disableFallbackIfMatchDesc" = "Вимкнути резервні DNS-запити при збігу списку доменів DNS-сервера" -"enableParallelQuery" = "Увімкнути паралельні запити" -"enableParallelQueryDesc" = "Увімкнути паралельні DNS-запити до кількох серверів для швидшого вирішення" -"strategy" = "Стратегія запиту" -"strategyDesc" = "Загальна стратегія вирішення доменних імен" -"add" = "Додати сервер" -"edit" = "Редагувати сервер" -"domains" = "Домени" -"expectIPs" = "Очікувані IP" -"unexpectIPs" = "Неочікувані IP" -"useSystemHosts" = "Використовувати системні Hosts" -"useSystemHostsDesc" = "Використовувати файл hosts з встановленої системи" -"usePreset" = "Використати шаблон" -"dnsPresetTitle" = "Шаблони DNS" -"dnsPresetFamily" = "Сімейний" - -[pages.xray.fakedns] -"add" = "Додати підроблений DNS" -"edit" = "Редагувати підроблений DNS" -"ipPool" = "Підмережа IP-пулу" -"poolSize" = "Розмір пулу" - -[pages.settings.security] -"admin" = "Облікові дані адміністратора" -"twoFactor" = "Двофакторна аутентифікація" -"twoFactorEnable" = "Увімкнути 2FA" -"twoFactorEnableDesc" = "Додає додатковий рівень аутентифікації для підвищення безпеки." -"twoFactorModalSetTitle" = "Увімкнути двофакторну аутентифікацію" -"twoFactorModalDeleteTitle" = "Вимкнути двофакторну аутентифікацію" -"twoFactorModalSteps" = "Щоб налаштувати двофакторну аутентифікацію, виконайте кілька кроків:" -"twoFactorModalFirstStep" = "1. Відскануйте цей QR-код у програмі для аутентифікації або скопіюйте токен біля QR-коду та вставте його в програму" -"twoFactorModalSecondStep" = "2. Введіть код з програми" -"twoFactorModalRemoveStep" = "Введіть код з програми, щоб вимкнути двофакторну аутентифікацію." -"twoFactorModalChangeCredentialsTitle" = "Змінити облікові дані" -"twoFactorModalChangeCredentialsStep" = "Введіть код з додатку, щоб змінити облікові дані адміністратора." -"twoFactorModalSetSuccess" = "Двофакторна аутентифікація була успішно встановлена" -"twoFactorModalDeleteSuccess" = "Двофакторна аутентифікація була успішно видалена" -"twoFactorModalError" = "Невірний код" - -[pages.settings.toasts] -"modifySettings" = "Параметри було змінено." -"getSettings" = "Виникла помилка під час отримання параметрів." -"modifyUserError" = "Виникла помилка під час зміни облікових даних адміністратора." -"modifyUser" = "Ви успішно змінили облікові дані адміністратора." -"originalUserPassIncorrect" = "Поточне ім'я користувача або пароль недійсні" -"userPassMustBeNotEmpty" = "Нове ім'я користувача та пароль порожні" -"getOutboundTrafficError" = "Помилка отримання вихідного трафіку" -"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку" - -[tgbot] -"keyboardClosed" = "❌ Клавіатуру закрито!" -"noResult" = "❗ Немає результату!" -"noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!" -"wentWrong" = "❌ Щось пішло не так!" -"noIpRecord" = "❗ Немає запису IP!" -"noInbounds" = "❗ Вхідні не знайдені!" -"unlimited" = "♾ Необмежено (Скинути)" -"add" = "Додати" -"month" = "Місяць" -"months" = "Місяці" -"day" = "День" -"days" = "Дні" -"hours" = "Години" -"minutes" = "Хвилини" -"unknown" = "Невідомо" -"inbounds" = "Вхідні" -"clients" = "Клієнти" -"offline" = "🔴 Офлайн" -"online" = "🟢 Онлайн" - -[tgbot.commands] -"unknown" = "❗ Невідома команда." -"pleaseChoose" = "👇 Будь ласка, виберіть:\r\n" -"help" = "🤖 Ласкаво просимо до цього бота! Він розроблений, щоб надавати певні дані з веб-панелі та дозволяє вносити зміни за потреби.\r\n\r\n" -"start" = "👋 Привіт {{ .Firstname }}.\r\n" -"welcome" = "🤖 Ласкаво просимо до {{ .Hostname }} бота керування.\r\n" -"status" = "✅ Бот в порядку!" -"usage" = "❗ Введіть текст для пошуку!" -"getID" = "🆔 Ваш ідентифікатор: {{ .ID }}" -"helpAdminCommands" = "Для перезапуску Xray Core:\r\n/restart\r\n\r\nДля пошуку електронної пошти клієнта:\r\n/usage [Електронна пошта]\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n/inbound [Примітка]\r\n\r\nID чату Telegram:\r\n/id" -"helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n/usage [Електронна пошта]\r\n\r\nID чату Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Операція успішна!" -"restartFailed" = "❗ Помилка в операції.\r\n\r\nПомилка: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core не запущений." -"startDesc" = "Показати головне меню" -"helpDesc" = "Довідка по боту" -"statusDesc" = "Перевірити статус бота" -"idDesc" = "Показати ваш Telegram ID" - -[tgbot.messages] -"cpuThreshold" = "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%" -"selectUserFailed" = "❌ Помилка під час вибору користувача!" -"userSaved" = "✅ Користувача Telegram збережено." -"loginSuccess" = "✅ Успішно ввійшли в панель\r\n" -"loginFailed" = "❗️ Помилка входу в панель.\r\n" -"2faFailed" = "Помилка 2FA" -"report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n" -"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n" -"hostname" = "💻 Хост: {{ .Hostname }}\r\n" -"version" = "🚀 3X-UI Версія: {{ .Version }}\r\n" -"xrayVersion" = "📡 Xray Версія: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 IP-адреси:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Час роботи: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Завантаження системи: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Статус: {{ .State }}\r\n" -"username" = "👤 Ім'я користувача: {{ .Username }}\r\n" -"reason" = "❗️ Причина: {{ .Reason }}\r\n" -"time" = "⏰ Час: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Порт: {{ .Port }}\r\n" -"expire" = "📅 Дата закінчення: {{ .Time }}\r\n" -"expireIn" = "📅 Термін дії: {{ .Time }}\r\n" -"active" = "💡 Активний: {{ .Enable }}\r\n" -"enabled" = "🚨 Увімкнено: {{ .Enable }}\r\n" -"online" = "🌐 Стан підключення: {{ .Status }}\r\n" -"lastOnline" = "🔙 Був(ла) онлайн: {{ .Time }}\r\n" -"email" = "📧 Електронна пошта: {{ .Email }}\r\n" -"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n" -"download" = "🔽 Download: ↓{{ .Download }}\r\n" -"total" = "📊 Всього: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Користувач Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Вичерпано {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Вичерпано кількість {{ .Type }} count:\r\n" -"onlinesCount" = "🌐 Онлайн-клієнти: {{ .Count }}\r\n" -"disabled" = "🛑 Вимкнено: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Скоро вичерпається: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Час резервного копіювання: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n" -"yes" = "✅ Так" -"no" = "❌ Ні" -"received_id" = "🔑📥 ID оновлено." -"received_password" = "🔑📥 Пароль оновлено." -"received_email" = "📧📥 Електронна пошта оновлена." -"received_comment" = "💬📥 Коментар оновлено." -"id_prompt" = "🔑 Стандартний ID: {{ .ClientId }}\n\nВведіть ваш ID." -"pass_prompt" = "🔑 Стандартний пароль: {{ .ClientPassword }}\n\nВведіть ваш пароль." -"email_prompt" = "📧 Стандартний email: {{ .ClientEmail }}\n\nВведіть ваш email." -"comment_prompt" = "💬 Стандартний коментар: {{ .ClientComment }}\n\nВведіть ваш коментар." -"inbound_client_data_id" = "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!" -"inbound_client_data_pass" = "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!" -"cancel" = "❌ Процес скасовано! \n\nВи можете знову розпочати, використовуючи /start у будь-який час. 🔄" -"error_add_client" = "⚠️ Помилка:\n\n {{ .error }}" -"using_default_value" = "Гаразд, залишу значення за замовчуванням. 😊" -"incorrect_input" = "Ваш ввід невірний.\nФрази повинні бути без пробілів.\nПравильний приклад: aaaaaa\nНеправильний приклад: aaa aaa 🚫" -"AreYouSure" = "Ви впевнені? 🤔" -"SuccessResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успішно" -"FailedResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ❌ Невдача \n\n🛠️ Помилка: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Процес скидання трафіку завершено для всіх клієнтів." - -[tgbot.buttons] -"closeKeyboard" = "❌ Закрити клавіатуру" -"cancel" = "❌ Скасувати" -"cancelReset" = "❌ Скасувати скидання" -"cancelIpLimit" = "❌ Скасувати обмеження IP" -"confirmResetTraffic" = "✅ Підтвердити скидання трафіку?" -"confirmClearIps" = "✅ Підтвердити очищення IP-адрес?" -"confirmRemoveTGUser" = "✅ Підтвердити видалення користувача Telegram?" -"confirmToggle" = "✅ Підтвердити ввімкнути/вимкнути користувача?" -"dbBackup" = "Отримати резервну копію БД" -"serverUsage" = "Використання сервера" -"getInbounds" = "Отримати вхідні" -"depleteSoon" = "Скоро вичерпати" -"clientUsage" = "Отримати використання" -"onlines" = "Онлайн-клієнти" -"commands" = "Команди" -"refresh" = "🔄 Оновити" -"clearIPs" = "❌ Очистити IP-адреси" -"removeTGUser" = "❌ Видалити користувача Telegram" -"selectTGUser" = "👤 Виберіть користувача Telegram" -"selectOneTGUser" = "👤 Виберіть користувача Telegram:" -"resetTraffic" = "📈 Скинути трафік" -"resetExpire" = "📅 Змінити термін дії" -"ipLog" = "🔢 IP журнал" -"ipLimit" = "🔢 IP Ліміт" -"setTGUser" = "👤 Встановити користувача Telegram" -"toggle" = "🔘 Увімкнути / Вимкнути" -"custom" = "🔢 Custom" -"confirmNumber" = "✅ Підтвердити: {{ .Num }}" -"confirmNumberAdd" = "✅ Підтвердити додавання: {{ .Num }}" -"limitTraffic" = "🚧 Ліміт трафіку" -"getBanLogs" = "Отримати журнали заборон" -"allClients" = "Всі Клієнти" -"addClient" = "Додати клієнта" -"submitDisable" = "Надіслати як вимкнено ☑️" -"submitEnable" = "Надіслати як увімкнено ✅" -"use_default" = "🏷️ Використати типове" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Пароль" -"change_email" = "⚙️📧 Електронна пошта" -"change_comment" = "⚙️💬 Коментар" -"ResetAllTraffics" = "Скинути весь трафік" -"SortedTrafficUsageReport" = "Відсортований звіт про використання трафіку" - -[tgbot.answers] -"successfulOperation" = "✅ Операція успішна!" -"errorOperation" = "❗ Помилка в роботі." -"getInboundsFailed" = "❌ Не вдалося отримати вхідні повідомлення." -"getClientsFailed" = "❌ Не вдалося отримати клієнтів." -"canceled" = "❌ {{ .Email }}: Операцію скасовано." -"clientRefreshSuccess" = "✅ {{ .Email }}: Клієнт успішно оновлено." -"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреси успішно оновлено." -"TGIdRefreshSuccess" = "✅ {{ .Email }}: Користувач Telegram клієнта успішно оновлено." -"resetTrafficSuccess" = "✅ {{ .Email }}: Трафік скинуто успішно." -"setTrafficLimitSuccess" = "✅ {{ .Email }}: Ліміт трафіку успішно збережено." -"expireResetSuccess" = "✅ {{ .Email }}: Успішно скинуто дні закінчення терміну дії." -"resetIpSuccess" = "✅ {{ .Email }}: IP обмеження {{ .Count }} успішно збережено." -"clearIpSuccess" = "✅ {{ .Email }}: IP успішно очищено." -"getIpLog" = "✅ {{ .Email }}: Отримати IP-журнал." -"getUserInfo" = "✅ {{ .Email }}: Отримати інформацію про користувача Telegram." -"removedTGUserSuccess" = "✅ {{ .Email }}: Користувача Telegram видалено успішно." -"enableSuccess" = "✅ {{ .Email }}: Увімкнути успішно." -"disableSuccess" = "✅ {{ .Email }}: Успішно вимкнено." -"askToAddUserId" = "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: {{ .TgUserID }}" -"chooseClient" = "Виберіть клієнта для Вхідного {{ .Inbound }}" -"chooseInbound" = "Виберіть Вхідний" diff --git a/web/translation/translate.vi_VN.toml b/web/translation/translate.vi_VN.toml deleted file mode 100644 index f065eca7..00000000 --- a/web/translation/translate.vi_VN.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "Tên người dùng" -"password" = "Mật khẩu" -"login" = "Đăng nhập" -"confirm" = "Xác nhận" -"cancel" = "Hủy bỏ" -"close" = "Đóng" -"save" = "Lưu" -"logout" = "Đăng xuất" -"create" = "Tạo" -"update" = "Cập nhật" -"copy" = "Sao chép" -"copied" = "Đã sao chép" -"download" = "Tải xuống" -"remark" = "Ghi chú" -"enable" = "Kích hoạt" -"protocol" = "Giao thức" -"search" = "Tìm kiếm" -"filter" = "Bộ lọc" -"loading" = "Đang tải" -"second" = "Giây" -"minute" = "Phút" -"hour" = "Giờ" -"day" = "Ngày" -"check" = "Kiểm tra" -"indefinite" = "Không xác định" -"unlimited" = "Không giới hạn" -"none" = "None" -"qrCode" = "Mã QR" -"info" = "Thông tin thêm" -"edit" = "Chỉnh sửa" -"delete" = "Xóa" -"reset" = "Đặt lại" -"noData" = "Không có dữ liệu." -"copySuccess" = "Đã sao chép thành công" -"sure" = "Chắc chắn" -"encryption" = "Mã hóa" -"useIPv4ForHost" = "Sử dụng IPv4 cho máy chủ" -"transmission" = "Truyền tải" -"host" = "Máy chủ" -"path" = "Đường dẫn" -"camouflage" = "Ngụy trang" -"status" = "Trạng thái" -"enabled" = "Đã kích hoạt" -"disabled" = "Đã tắt" -"depleted" = "Depleted" -"depletingSoon" = "Depleting..." -"offline" = "Ngoại tuyến" -"online" = "Trực tuyến" -"domainName" = "Tên miền" -"monitor" = "Listening IP" -"certificate" = "Chứng chỉ số" -"fail" = "Thất bại" -"comment" = "Bình luận" -"success" = "Thành công" -"lastOnline" = "Lần online gần nhất" -"getVersion" = "Lấy phiên bản" -"install" = "Cài đặt" -"clients" = "Các khách hàng" -"usage" = "Sử dụng" -"twoFactorCode" = "Mã" -"remained" = "Còn lại" -"security" = "Bảo vệ" -"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7" -"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn" -"secAlertConf" = "Một số cài đặt có thể dễ bị tấn công. Đề xuất tăng cường các giao thức bảo mật để ngăn chặn các vi phạm tiềm ẩn." -"secAlertSSL" = "Bảng điều khiển thiếu kết nối an toàn. Vui lòng cài đặt chứng chỉ TLS để bảo vệ dữ liệu." -"secAlertPanelPort" = "Cổng mặc định của bảng điều khiển có thể dễ bị tấn công. Vui lòng cấu hình một cổng ngẫu nhiên hoặc cụ thể." -"secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." -"secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." -"secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp." -"emptyDnsDesc" = "Không có máy chủ DNS nào được thêm." -"emptyFakeDnsDesc" = "Không có máy chủ Fake DNS nào được thêm." -"emptyBalancersDesc" = "Không có bộ cân bằng tải nào được thêm." -"emptyReverseDesc" = "Không có proxy ngược nào được thêm." -"somethingWentWrong" = "Đã xảy ra lỗi" - -[subscription] -"title" = "Thông tin đăng ký" -"subId" = "ID đăng ký" -"status" = "Trạng thái" -"downloaded" = "Đã tải xuống" -"uploaded" = "Đã tải lên" -"expiry" = "Hết hạn" -"totalQuota" = "Tổng hạn mức" -"individualLinks" = "Liên kết riêng lẻ" -"active" = "Hoạt động" -"inactive" = "Không hoạt động" -"unlimited" = "Không giới hạn" -"noExpiry" = "Không hết hạn" - -[menu] -"theme" = "Chủ đề" -"dark" = "Tối" -"ultraDark" = "Siêu tối" -"dashboard" = "Trạng thái hệ thống" -"inbounds" = "Đầu vào khách hàng" -"settings" = "Cài đặt bảng điều khiển" -"logout" = "Đăng xuất" -"xray" = "Cài đặt Xray" -"link" = "Quản lý" - -[pages.login] -"hello" = "Xin chào" -"title" = "Chào mừng" -"loginAgain" = "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại." - -[pages.login.toasts] -"invalidFormData" = "Dạng dữ liệu nhập không hợp lệ." -"emptyUsername" = "Vui lòng nhập tên người dùng." -"emptyPassword" = "Vui lòng nhập mật khẩu." -"wrongUsernameOrPassword" = "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ." -"successLogin" = "Bạn đã đăng nhập vào tài khoản thành công." - -[pages.index] -"title" = "Trạng thái hệ thống" -"cpu" = "CPU" -"logicalProcessors" = "Bộ xử lý logic" -"frequency" = "Tần số" -"swap" = "Swap" -"storage" = "Lưu trữ" -"memory" = "RAM" -"threads" = "Luồng" -"xrayStatus" = "Xray" -"stopXray" = "Dừng lại" -"restartXray" = "Khởi động lại" -"xraySwitch" = "Phiên bản" -"xraySwitchClick" = "Chọn phiên bản mà bạn muốn chuyển đổi sang." -"xraySwitchClickDesk" = "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại." -"xrayUpdates" = "Cập nhật Xray" -"updatePanel" = "Cập nhật Panel" -"panelUpdateDesc" = "Điều này sẽ cập nhật 3X-UI lên bản phát hành mới nhất và khởi động lại dịch vụ panel." -"currentPanelVersion" = "Phiên bản panel hiện tại" -"latestPanelVersion" = "Phiên bản panel mới nhất" -"panelUpToDate" = "Panel đã được cập nhật" -"upToDate" = "Đã cập nhật" -"xrayStatusUnknown" = "Không xác định" -"xrayStatusRunning" = "Đang chạy" -"xrayStatusStop" = "Dừng" -"xrayStatusError" = "Lỗi" -"xrayErrorPopoverTitle" = "Đã xảy ra lỗi khi chạy Xray" -"operationHours" = "Thời gian hoạt động" -"systemLoad" = "Tải hệ thống" -"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua" -"connectionCount" = "Số lượng kết nối" -"ipAddresses" = "Địa chỉ IP" -"toggleIpVisibility" = "Chuyển đổi hiển thị IP" -"overallSpeed" = "Tốc độ tổng thể" -"upload" = "Tải lên" -"download" = "Tải xuống" -"totalData" = "Tổng dữ liệu" -"sent" = "Đã gửi" -"received" = "Đã nhận" -"documentation" = "Tài liệu" -"xraySwitchVersionDialog" = "Bạn có chắc chắn muốn thay đổi phiên bản Xray không?" -"xraySwitchVersionDialogDesc" = "Hành động này sẽ thay đổi phiên bản Xray thành #version#." -"xraySwitchVersionPopover" = "Xray đã được cập nhật thành công" -"panelUpdateDialog" = "Bạn có chắc muốn cập nhật panel không?" -"panelUpdateDialogDesc" = "Điều này sẽ cập nhật 3X-UI lên #version# và khởi động lại dịch vụ panel." -"panelUpdateCheckPopover" = "Kiểm tra cập nhật panel thất bại" -"panelUpdateStartedPopover" = "Bắt đầu cập nhật panel" -"geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?" -"geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#." -"geofilesUpdateDialogDesc" = "Thao tác này sẽ cập nhật tất cả các tập tin." -"geofilesUpdateAll" = "Cập nhật tất cả" -"geofileUpdatePopover" = "Geofile đã được cập nhật thành công" -"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này." -"logs" = "Nhật ký" -"config" = "Cấu hình" -"backup" = "Sao lưu" -"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu" -"exportDatabase" = "Sao lưu" -"exportDatabaseDesc" = "Nhấp để tải xuống tệp .db chứa bản sao lưu cơ sở dữ liệu hiện tại của bạn vào thiết bị." -"importDatabase" = "Khôi phục" -"importDatabaseDesc" = "Nhấp để chọn và tải lên tệp .db từ thiết bị của bạn để khôi phục cơ sở dữ liệu từ bản sao lưu." -"importDatabaseSuccess" = "Đã nhập cơ sở dữ liệu thành công" -"importDatabaseError" = "Lỗi xảy ra khi nhập cơ sở dữ liệu" -"readDatabaseError" = "Lỗi xảy ra khi đọc cơ sở dữ liệu" -"getDatabaseError" = "Lỗi xảy ra khi truy xuất cơ sở dữ liệu" -"getConfigError" = "Lỗi xảy ra khi truy xuất tệp cấu hình" -"customGeoTitle" = "GeoSite / GeoIP tùy chỉnh" -"customGeoAdd" = "Thêm" -"customGeoType" = "Loại" -"customGeoAlias" = "Bí danh" -"customGeoUrl" = "URL" -"customGeoEnabled" = "Bật" -"customGeoLastUpdated" = "Cập nhật lần cuối" -"customGeoExtColumn" = "Định tuyến (ext:…)" -"customGeoToastUpdateAll" = "Đã cập nhật tất cả nguồn tùy chỉnh" -"customGeoActions" = "Thao tác" -"customGeoEdit" = "Sửa" -"customGeoDelete" = "Xóa" -"customGeoDownload" = "Cập nhật ngay" -"customGeoModalAdd" = "Thêm geo tùy chỉnh" -"customGeoModalEdit" = "Sửa geo tùy chỉnh" -"customGeoModalSave" = "Lưu" -"customGeoDeleteConfirm" = "Xóa nguồn geo tùy chỉnh này?" -"customGeoRoutingHint" = "Trong quy tắc định tuyến dùng cột giá trị dạng ext:file.dat:tag (thay tag)." -"customGeoInvalidId" = "ID tài nguyên không hợp lệ" -"customGeoAliasesError" = "Không tải được bí danh geo tùy chỉnh" -"customGeoValidationAlias" = "Bí danh chỉ gồm chữ thường, số, - và _" -"customGeoValidationUrl" = "URL phải bắt đầu bằng http:// hoặc https://" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = " (tùy chỉnh)" -"customGeoToastList" = "Danh sách geo tùy chỉnh" -"customGeoToastAdd" = "Thêm geo tùy chỉnh" -"customGeoToastUpdate" = "Cập nhật geo tùy chỉnh" -"customGeoToastDelete" = "Đã xóa geofile tùy chỉnh “{{ .fileName }}”" -"customGeoToastDownload" = "Đã cập nhật geofile “{{ .fileName }}”" -"customGeoErrInvalidType" = "Loại phải là geosite hoặc geoip" -"customGeoErrAliasRequired" = "Cần bí danh" -"customGeoErrAliasPattern" = "Bí danh có ký tự không hợp lệ" -"customGeoErrAliasReserved" = "Bí danh này được dành riêng" -"customGeoErrUrlRequired" = "Cần URL" -"customGeoErrInvalidUrl" = "URL không hợp lệ" -"customGeoErrUrlScheme" = "URL phải dùng http hoặc https" -"customGeoErrUrlHost" = "Máy chủ URL không hợp lệ" -"customGeoErrDuplicateAlias" = "Bí danh này đã dùng cho loại này" -"customGeoErrNotFound" = "Không tìm thấy nguồn geo tùy chỉnh" -"customGeoErrDownload" = "Tải xuống thất bại" -"customGeoErrUpdateAllIncomplete" = "Một hoặc nhiều nguồn geo tùy chỉnh không cập nhật được" -"customGeoEmpty" = "Chưa có nguồn geo tùy chỉnh nào — nhấp Thêm để tạo" - -[pages.inbounds] -"allTimeTraffic" = "Tổng Lưu Lượng" -"allTimeTrafficUsage" = "Tổng mức sử dụng mọi lúc" -"title" = "Điểm vào (Inbounds)" -"totalDownUp" = "Tổng tải lên/tải xuống" -"totalUsage" = "Tổng sử dụng" -"inboundCount" = "Số lượng điểm vào" -"operate" = "Thao tác" -"enable" = "Kích hoạt" -"remark" = "Chú thích" -"protocol" = "Giao thức" -"port" = "Cổng" -"portMap" = "Cổng tạo" -"traffic" = "Lưu lượng" -"details" = "Chi tiết" -"transportConfig" = "Giao vận" -"expireDate" = "Ngày hết hạn" -"createdAt" = "Tạo lúc" -"updatedAt" = "Cập nhật" -"resetTraffic" = "Đặt lại lưu lượng" -"addInbound" = "Thêm điểm vào" -"generalActions" = "Hành động chung" -"autoRefresh" = "Tự động làm mới" -"autoRefreshInterval" = "Khoảng thời gian" -"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)" -"deleteInbound" = "Xóa điểm vào (Inbound)" -"deleteInboundContent" = "Xác nhận xóa điểm vào? (Inbound)" -"deleteClient" = "Xóa người dùng" -"deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?" -"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?" -"copyLink" = "Sao chép liên kết" -"address" = "Địa chỉ" -"network" = "Mạng" -"destinationPort" = "Cổng đích" -"targetAddress" = "Địa chỉ mục tiêu" -"monitorDesc" = "Mặc định để trống" -"meansNoLimit" = "= Không giới hạn (đơn vị: GB)" -"totalFlow" = "Tổng lưu lượng" -"leaveBlankToNeverExpire" = "Để trống để không bao giờ hết hạn" -"noRecommendKeepDefault" = "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định" -"certificatePath" = "Đường dẫn tập" -"certificateContent" = "Nội dung tập" -"publicKey" = "Khóa công khai" -"privatekey" = "Khóa cá nhân" -"clickOnQRcode" = "Nhấn vào Mã QR để sao chép" -"client" = "Người dùng" -"export" = "Xuất liên kết" -"clone" = "Sao chép" -"cloneInbound" = "Sao chép điểm vào (Inbound)" -"cloneInboundContent" = "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và máy khách, sẽ được áp dụng cho bản sao." -"cloneInboundOk" = "Sao chép" -"resetAllTraffic" = "Đặt lại lưu lượng cho tất cả điểm vào" -"resetAllTrafficTitle" = "Đặt lại lưu lượng cho tất cả điểm vào" -"resetAllTrafficContent" = "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?" -"resetInboundClientTraffics" = "Đặt lại lưu lượng toàn bộ người dùng của điểm vào" -"resetInboundClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào" -"resetInboundClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?" -"resetAllClientTraffics" = "Đặt lại lưu lượng cho toàn bộ người dùng" -"resetAllClientTrafficTitle" = "Đặt lại lưu lượng cho toàn bộ người dùng" -"resetAllClientTrafficContent" = "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?" -"delDepletedClients" = "Xóa các người dùng đã cạn kiệt" -"delDepletedClientsTitle" = "Xóa các người dùng đã cạn kiệt" -"delDepletedClientsContent" = "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?" -"email" = "Email" -"emailDesc" = "Vui lòng cung cấp một địa chỉ email duy nhất." -"IPLimit" = "Giới hạn IP" -"IPLimitDesc" = "Vô hiệu hóa điểm vào nếu số lượng vượt quá giá trị đã nhập (nhập 0 để vô hiệu hóa giới hạn IP)." -"IPLimitlog" = "Lịch sử IP" -"IPLimitlogDesc" = "Lịch sử đăng nhập IP (trước khi kích hoạt điểm vào sau khi bị vô hiệu hóa bởi giới hạn IP, bạn nên xóa lịch sử)." -"IPLimitlogclear" = "Xóa Lịch sử" -"setDefaultCert" = "Đặt chứng chỉ từ bảng điều khiển" -"telegramDesc" = "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc (@userinfobot)" -"subscriptionDesc" = "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau" -"info" = "Thông tin" -"same" = "Giống nhau" -"inboundData" = "Dữ liệu gửi đến" -"exportInbound" = "Xuất nhập khẩu" -"import" = "Nhập" -"importInbound" = "Nhập inbound" -"periodicTrafficResetTitle" = "Đặt lại lưu lượng" -"periodicTrafficResetDesc" = "Tự động đặt lại bộ đếm lưu lượng theo khoảng thời gian xác định" -"lastReset" = "Đặt lại lần cuối" - -[pages.client] -"add" = "Thêm người dùng" -"edit" = "Chỉnh sửa người dùng" -"submitAdd" = "Thêm" -"submitEdit" = "Lưu thay đổi" -"clientCount" = "Số lượng người dùng" -"bulk" = "Thêm hàng loạt" -"copyFromInbound" = "Sao chép người dùng từ Inbound" -"copyToInbound" = "Sao chép người dùng đến" -"copySelected" = "Sao chép đã chọn" -"copySource" = "Nguồn" -"copyEmailPreview" = "Xem trước email kết quả" -"copySelectSourceFirst" = "Vui lòng chọn Inbound nguồn trước." -"copyResult" = "Kết quả sao chép" -"copyResultSuccess" = "Đã sao chép thành công" -"copyResultNone" = "Không có gì để sao chép: chưa chọn người dùng hoặc nguồn trống" -"copyResultErrors" = "Lỗi sao chép" -"copyFlowLabel" = "Flow cho người dùng mới (VLESS)" -"copyFlowHint" = "Áp dụng cho tất cả người dùng được sao chép. Để trống để bỏ qua." -"selectAll" = "Chọn tất cả" -"clearAll" = "Bỏ chọn tất cả" -"method" = "Phương pháp" -"first" = "Đầu tiên" -"last" = "Cuối cùng" -"prefix" = "Tiền tố" -"postfix" = "Hậu tố" -"delayedStart" = "Bắt đầu ở Lần Đầu" -"expireDays" = "Khoảng thời gian" -"days" = "ngày" -"renew" = "Tự động gia hạn" -"renewDesc" = "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)" - -[pages.inbounds.periodicTrafficReset] -"never" = "Không bao giờ" -"daily" = "Hàng ngày" -"weekly" = "Hàng tuần" -"monthly" = "Hàng tháng" -"hourly" = "Hàng giờ" - -[pages.inbounds.toasts] -"obtain" = "Nhận" -"updateSuccess" = "Cập nhật thành công" -"logCleanSuccess" = "Đã xóa nhật ký" -"inboundsUpdateSuccess" = "Đã cập nhật thành công các kết nối inbound" -"inboundUpdateSuccess" = "Đã cập nhật thành công kết nối inbound" -"inboundCreateSuccess" = "Đã tạo thành công kết nối inbound" -"inboundDeleteSuccess" = "Đã xóa thành công kết nối inbound" -"inboundClientAddSuccess" = "Đã thêm client inbound" -"inboundClientDeleteSuccess" = "Đã xóa client inbound" -"inboundClientUpdateSuccess" = "Đã cập nhật client inbound" -"delDepletedClientsSuccess" = "Đã xóa tất cả client hết hạn" -"resetAllClientTrafficSuccess" = "Đã đặt lại toàn bộ lưu lượng client" -"resetAllTrafficSuccess" = "Đã đặt lại toàn bộ lưu lượng" -"resetInboundClientTrafficSuccess" = "Đã đặt lại lưu lượng" -"trafficGetError" = "Lỗi khi lấy thông tin lưu lượng" -"getNewX25519CertError" = "Lỗi khi lấy chứng chỉ X25519." -"getNewmldsa65Error" = "Lỗi khi lấy chứng chỉ mldsa65." -"getNewVlessEncError" = "Lỗi khi lấy chứng chỉ VlessEnc." - -[pages.inbounds.stream.general] -"request" = "Lời yêu cầu" -"response" = "Phản ứng" -"name" = "Tên" -"value" = "Giá trị" - -[pages.inbounds.stream.tcp] -"version" = "Phiên bản" -"method" = "Phương pháp" -"path" = "Đường dẫn" -"status" = "Trạng thái" -"statusDescription" = "Tình trạng Mô tả" -"requestHeader" = "Header yêu cầu" -"responseHeader" = "Header phản hồi" - -[pages.settings] -"title" = "Cài đặt" -"save" = "Lưu" -"infoDesc" = "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi." -"restartPanel" = "Khởi động lại bảng điều khiển" -"restartPanelDesc" = "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ." -"restartPanelSuccess" = "Đã khởi động lại bảng điều khiển thành công" -"actions" = "Hành động" -"resetDefaultConfig" = "Đặt lại cấu hình mặc định" -"panelSettings" = "Bảng điều khiển" -"securitySettings" = "Bảo mật" -"TGBotSettings" = "Bot Telegram" -"panelListeningIP" = "IP Nghe của bảng điều khiển" -"panelListeningIPDesc" = "Mặc định để trống để nghe tất cả các IP." -"panelListeningDomain" = "Tên miền của nghe bảng điều khiển" -"panelListeningDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" -"panelPort" = "Cổng bảng điều khiển" -"panelPortDesc" = "Cổng được sử dụng để kết nối với bảng điều khiển này" -"publicKeyPath" = "Đường dẫn file chứng chỉ bảng điều khiển" -"publicKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')" -"privateKeyPath" = "Đường dẫn file khóa của chứng chỉ bảng điều khiển" -"privateKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')" -"panelUrlPath" = "Đường dẫn gốc URL bảng điều khiển" -"panelUrlPathDesc" = "Phải bắt đầu và kết thúc bằng '/'" -"pageSize" = "Kích thước phân trang" -"pageSizeDesc" = "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt" -"remarkModel" = "Ghi chú mô hình và ký tự phân tách" -"datepicker" = "Kiểu lịch" -"datepickerPlaceholder" = "Chọn ngày" -"datepickerDescription" = "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này." -"sampleRemark" = "Nhận xét mẫu" -"oldUsername" = "Tên người dùng hiện tại" -"currentPassword" = "Mật khẩu hiện tại" -"newUsername" = "Tên người dùng mới" -"newPassword" = "Mật khẩu mới" -"telegramBotEnable" = "Bật Bot Telegram" -"telegramBotEnableDesc" = "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram" -"telegramToken" = "Token Telegram" -"telegramTokenDesc" = "Bạn phải nhận token từ quản lý bot Telegram @botfather" -"telegramProxy" = "Socks5 Proxy" -"telegramProxyDesc" = "Nếu bạn cần socks5 proxy để kết nối với Telegram. Điều chỉnh cài đặt của nó theo hướng dẫn." -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định." -"telegramChatId" = "Chat ID Telegram của quản trị viên" -"telegramChatIdDesc" = "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng @userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn." -"telegramNotifyTime" = "Thời gian thông báo của bot Telegram" -"telegramNotifyTimeDesc" = "Sử dụng định dạng thời gian Crontab." -"tgNotifyBackup" = "Sao lưu Cơ sở dữ liệu" -"tgNotifyBackupDesc" = "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo." -"tgNotifyLogin" = "Thông báo Đăng nhập" -"tgNotifyLoginDesc" = "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn." -"sessionMaxAge" = "Thời gian tối đa của phiên" -"sessionMaxAgeDesc" = "Thời gian của phiên đăng nhập (đơn vị: phút)" -"expireTimeDiff" = "Ngưỡng hết hạn cho thông báo" -"expireTimeDiffDesc" = "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)" -"trafficDiff" = "Ngưỡng lưu lượng cho thông báo" -"trafficDiffDesc" = "Nhận thông báo về việc cạn kiệt lưu lượng trước khi đạt đến ngưỡng này (đơn vị: GB)" -"tgNotifyCpu" = "Ngưỡng cảnh báo tỷ lệ CPU" -"tgNotifyCpuDesc" = "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)" -"timeZone" = "Múi giờ" -"timeZoneDesc" = "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này." -"subSettings" = "Gói đăng ký" -"subEnable" = "Bật dịch vụ" -"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng" -"subJsonEnable" = "Bật/Tắt điểm cuối đăng ký JSON độc lập." -"subTitle" = "Tiêu đề Đăng ký" -"subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN" -"subSupportUrl" = "URL Hỗ trợ" -"subSupportUrlDesc" = "Liên kết hỗ trợ kỹ thuật hiển thị trong ứng dụng VPN" -"subProfileUrl" = "URL Hồ sơ" -"subProfileUrlDesc" = "Liên kết đến trang web của bạn hiển thị trong ứng dụng VPN" -"subAnnounce" = "Thông báo" -"subAnnounceDesc" = "Văn bản thông báo hiển thị trong ứng dụng VPN" -"subEnableRouting" = "Bật định tuyến" -"subEnableRoutingDesc" = "Cài đặt toàn cục để bật định tuyến trong ứng dụng khách VPN. (Chỉ dành cho Happ)" -"subRoutingRules" = "Quy tắc định tuyến" -"subRoutingRulesDesc" = "Quy tắc định tuyến toàn cầu cho client VPN. (Chỉ dành cho Happ)" -"subListen" = "Listening IP" -"subListenDesc" = "Mặc định để trống để nghe tất cả các IP" -"subPort" = "Cổng gói đăng ký" -"subPortDesc" = "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ" -"subCertPath" = "Đường dẫn file chứng chỉ gói đăng ký" -"subCertPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')" -"subKeyPath" = "Đường dẫn file khóa của chứng chỉ gói đăng ký" -"subKeyPathDesc" = "Điền vào đường dẫn đầy đủ (bắt đầu với '/')" -"subPath" = "Đường dẫn gốc URL gói đăng ký" -"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'" -"subDomain" = "Tên miền con" -"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP" -"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký" -"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách" -"subEncrypt" = "Mã hóa cấu hình" -"subEncryptDesc" = "Mã hóa các cấu hình được trả về trong gói đăng ký" -"subShowInfo" = "Hiển thị thông tin sử dụng" -"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình" -"subURI" = "URI proxy trung gian" -"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian" -"externalTrafficInformEnable" = "Thông báo giao thông bên ngoài" -"externalTrafficInformEnableDesc" = "Thông báo cho API bên ngoài về mọi cập nhật lưu lượng truy cập." -"externalTrafficInformURI" = "URI thông báo lưu lượng truy cập bên ngoài" -"externalTrafficInformURIDesc" = "Cập nhật lưu lượng truy cập được gửi tới URI này." -"restartXrayOnClientDisable" = "Khởi Động Lại Xray Sau Khi Tự Động Vô Hiệu Hóa" -"restartXrayOnClientDisableDesc" = "Khi người dùng bị vô hiệu hóa tự động do hết hạn hoặc chạm giới hạn lưu lượng, hãy khởi động lại Xray." -"fragment" = "Sự phân mảnh" -"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello" -"fragmentSett" = "Cài đặt phân mảnh" -"noisesDesc" = "Bật Noises." -"noisesSett" = "Cài đặt Noises" -"mux" = "Mux" -"muxDesc" = "Truyền nhiều luồng dữ liệu độc lập trong luồng dữ liệu đã thiết lập." -"muxSett" = "Mux Cài đặt" -"direct" = "Kết nối trực tiếp" -"directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể." -"notifications" = "Thông báo" -"certs" = "Chứng chỉ" -"externalTraffic" = "Lưu lượng bên ngoài" -"dateAndTime" = "Ngày và giờ" -"proxyAndServer" = "Proxy và máy chủ" -"intervals" = "Khoảng thời gian" -"information" = "Thông tin" -"language" = "Ngôn ngữ" -"telegramBotLanguage" = "Ngôn ngữ của Bot Telegram" - -[pages.xray] -"title" = "Cài đặt Xray" -"save" = "Lưu cài đặt" -"restart" = "Khởi động lại Xray" -"restartSuccess" = "Đã khởi động lại Xray thành công" -"stopSuccess" = "Xray đã được dừng thành công" -"restartError" = "Đã xảy ra lỗi khi khởi động lại Xray." -"stopError" = "Đã xảy ra lỗi khi dừng Xray." -"basicTemplate" = "Mẫu Cơ bản" -"advancedTemplate" = "Mẫu Nâng cao" -"generalConfigs" = "Cấu hình Chung" -"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát." -"logConfigs" = "Nhật ký" -"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần" -"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể." -"basicRouting" = "Định tuyến Cơ bản" -"blockConnectionsConfigsDesc" = "Các tùy chọn này sẽ chặn lưu lượng truy cập dựa trên quốc gia được yêu cầu cụ thể." -"directConnectionsConfigsDesc" = "Kết nối trực tiếp đảm bảo rằng lưu lượng truy cập cụ thể không được định tuyến qua máy chủ khác." -"blockips" = "Chặn IP" -"blockdomains" = "Chặn Tên Miền" -"directips" = "IP Trực Tiếp" -"directdomains" = "Tên Miền Trực Tiếp" -"ipv4Routing" = "Định tuyến IPv4" -"ipv4RoutingDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4." -"warpRouting" = "Định tuyến WARP" -"warpRoutingDesc" = "Cảnh báo: Trước khi sử dụng những tùy chọn này, hãy cài đặt WARP ở chế độ proxy socks5 trên máy chủ của bạn bằng cách làm theo các bước trên GitHub của bảng điều khiển. WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare." -"nordRouting" = "Định tuyến NordVPN" -"nordRoutingDesc" = "Các tùy chọn này sẽ định tuyến lưu lượng dựa trên đích cụ thể qua NordVPN." -"Template" = "Mẫu Cấu hình Xray" -"TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này." -"FreedomStrategy" = "Cấu hình Chiến lược cho Giao thức Freedom" -"FreedomStrategyDesc" = "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom." -"RoutingStrategy" = "Cấu hình Chiến lược Định tuyến Tên miền" -"RoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS." -"outboundTestUrl" = "URL kiểm tra outbound" -"outboundTestUrlDesc" = "URL dùng khi kiểm tra kết nối outbound" -"Torrent" = "Cấu hình sử dụng BitTorrent" -"Inbounds" = "Đầu vào" -"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể." -"Outbounds" = "Đầu ra" -"Balancers" = "Cân bằng" -"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này." -"Routings" = "Quy tắc định tuyến" -"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!" -"completeTemplate" = "All" -"logLevel" = "Mức đăng nhập" -"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại." -"accessLog" = "Nhật ký truy cập" -"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'" -"errorLog" = "Nhật ký lỗi" -"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'" -"dnsLog" = "Nhật ký DNS" -"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không" -"maskAddress" = "Ẩn Địa Chỉ" -"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký." -"statistics" = "Thống kê" -"statsInboundUplink" = "Thống kê tải lên đầu vào" -"statsInboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu vào." -"statsInboundDownlink" = "Thống kê tải xuống đầu vào" -"statsInboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu vào." -"statsOutboundUplink" = "Thống kê tải lên đầu ra" -"statsOutboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu ra." -"statsOutboundDownlink" = "Thống kê tải xuống đầu ra" -"statsOutboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu ra." - -[pages.xray.rules] -"first" = "Đầu tiên" -"last" = "Cuối cùng" -"up" = "Lên" -"down" = "Xuống" -"source" = "Nguồn" -"dest" = "Đích" -"inbound" = "Vào" -"outbound" = "Ra" -"balancer" = "Cân bằng" -"info" = "Thông tin" -"add" = "Thêm quy tắc" -"edit" = "Chỉnh sửa quy tắc" -"useComma" = "Các mục được phân tách bằng dấu phẩy" - -[pages.xray.outbound] -"addOutbound" = "Thêm thư đi" -"addReverse" = "Thêm đảo ngược" -"editOutbound" = "Chỉnh sửa gửi đi" -"editReverse" = "Chỉnh sửa ngược lại" -"reverseTag" = "Thẻ Ngược" -"reverseTagDesc" = "Thẻ outbound của proxy ngược đơn giản VLESS. Để trống để vô hiệu hóa." -"reverseTagPlaceholder" = "thẻ outbound (để trống để vô hiệu hóa)" -"tag" = "Thẻ" -"tagDesc" = "thẻ duy nhất" -"address" = "Địa chỉ" -"reverse" = "Đảo ngược" -"domain" = "Miền" -"type" = "Loại" -"bridge" = "Cầu" -"portal" = "Cổng thông tin" -"link" = "Liên kết" -"intercon" = "Kết nối" -"settings" = "cài đặt" -"accountInfo" = "Thông tin tài khoản" -"outboundStatus" = "Trạng thái đầu ra" -"sendThrough" = "Gửi qua" -"test" = "Kiểm tra" -"testResult" = "Kết quả kiểm tra" -"testing" = "Đang kiểm tra kết nối..." -"testSuccess" = "Kiểm tra thành công" -"testFailed" = "Kiểm tra thất bại" -"testError" = "Không thể kiểm tra đầu ra" -"nordvpn" = "NordVPN" -"accessToken" = "Mã truy cập" -"country" = "Quốc gia" -"server" = "Máy chủ" -"city" = "Thành phố" -"allCities" = "Tất cả thành phố" -"privateKey" = "Khóa riêng" -"load" = "Tải" - -[pages.xray.balancer] -"addBalancer" = "Thêm cân bằng" -"editBalancer" = "Chỉnh sửa cân bằng" -"balancerStrategy" = "Chiến lược" -"balancerSelectors" = "Bộ chọn" -"tag" = "Thẻ" -"tagDesc" = "thẻ duy nhất" -"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động." - -[pages.xray.wireguard] -"secretKey" = "Khoá bí mật" -"publicKey" = "Khóa công khai" -"allowedIPs" = "IP được phép" -"endpoint" = "Điểm cuối" -"psk" = "Khóa chia sẻ" -"domainStrategy" = "Chiến lược tên miền" - -[pages.xray.tun] -"nameDesc" = "Tên của giao diện TUN. Giá trị mặc định là 'xray0'" -"mtuDesc" = "Đơn vị Truyền Tối đa. Kích thước tối đa của các gói dữ liệu. Giá trị mặc định là 1500" -"userLevel" = "Mức Người Dùng" -"userLevelDesc" = "Tất cả các kết nối được thực hiện thông qua inbound này sẽ sử dụng mức người dùng này. Giá trị mặc định là 0" - -[pages.xray.dns] -"enable" = "Kích hoạt DNS" -"enableDesc" = "Kích hoạt máy chủ DNS tích hợp" -"tag" = "Thẻ gửi đến DNS" -"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến." -"clientIp" = "IP khách hàng" -"clientIpDesc" = "Được sử dụng để thông báo cho máy chủ về vị trí IP được chỉ định trong các truy vấn DNS" -"disableCache" = "Tắt bộ nhớ đệm" -"disableCacheDesc" = "Tắt bộ nhớ đệm DNS" -"disableFallback" = "Tắt Fallback" -"disableFallbackDesc" = "Tắt các truy vấn DNS Fallback" -"disableFallbackIfMatch" = "Tắt Fallback Nếu Khớp" -"disableFallbackIfMatchDesc" = "Tắt các truy vấn DNS Fallback khi danh sách tên miền khớp của máy chủ DNS được kích hoạt" -"enableParallelQuery" = "Bật Truy vấn Song song" -"enableParallelQueryDesc" = "Bật truy vấn DNS song song đến nhiều máy chủ để phân giải nhanh hơn" -"strategy" = "Chiến lược truy vấn" -"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền" -"add" = "Thêm máy chủ" -"edit" = "Chỉnh sửa máy chủ" -"domains" = "Tên miền" -"expectIPs" = "Các IP Dự Kiến" -"unexpectIPs" = "IP không mong muốn" -"useSystemHosts" = "Sử dụng Hosts hệ thống" -"useSystemHostsDesc" = "Sử dụng file hosts từ hệ thống đã cài đặt" -"usePreset" = "Dùng mẫu" -"dnsPresetTitle" = "Mẫu DNS" -"dnsPresetFamily" = "Gia đình" - -[pages.xray.fakedns] -"add" = "Thêm DNS giả" -"edit" = "Chỉnh sửa DNS giả" -"ipPool" = "Mạng con nhóm IP" -"poolSize" = "Kích thước bể bơi" - -[pages.settings.security] -"admin" = "Thông tin đăng nhập quản trị viên" -"twoFactor" = "Xác thực hai yếu tố" -"twoFactorEnable" = "Bật 2FA" -"twoFactorEnableDesc" = "Thêm một lớp bảo mật bổ sung để tăng cường an toàn." -"twoFactorModalSetTitle" = "Bật xác thực hai yếu tố" -"twoFactorModalDeleteTitle" = "Tắt xác thực hai yếu tố" -"twoFactorModalSteps" = "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:" -"twoFactorModalFirstStep" = "1. Quét mã QR này trong ứng dụng xác thực hoặc sao chép mã token gần mã QR và dán vào ứng dụng" -"twoFactorModalSecondStep" = "2. Nhập mã từ ứng dụng" -"twoFactorModalRemoveStep" = "Nhập mã từ ứng dụng để xóa xác thực hai yếu tố." -"twoFactorModalChangeCredentialsTitle" = "Thay đổi thông tin xác thực" -"twoFactorModalChangeCredentialsStep" = "Nhập mã từ ứng dụng để thay đổi thông tin xác thực quản trị viên." -"twoFactorModalSetSuccess" = "Xác thực hai yếu tố đã được thiết lập thành công" -"twoFactorModalDeleteSuccess" = "Xác thực hai yếu tố đã được xóa thành công" -"twoFactorModalError" = "Mã sai" - -[pages.settings.toasts] -"modifySettings" = "Các tham số đã được thay đổi." -"getSettings" = "Lỗi xảy ra khi truy xuất tham số." -"modifyUserError" = "Đã xảy ra lỗi khi thay đổi thông tin đăng nhập quản trị viên." -"modifyUser" = "Bạn đã thay đổi thông tin đăng nhập quản trị viên thành công." -"originalUserPassIncorrect" = "Tên người dùng hoặc mật khẩu gốc không đúng" -"userPassMustBeNotEmpty" = "Tên người dùng mới và mật khẩu mới không thể để trống" -"getOutboundTrafficError" = "Lỗi khi lấy lưu lượng truy cập đi" -"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi" - -[tgbot] -"keyboardClosed" = "❌ Bàn phím đã đóng!" -"noResult" = "❗ Không có kết quả!" -"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!" -"wentWrong" = "❌ Đã xảy ra lỗi!" -"noIpRecord" = "❗ Không có bản ghi IP!" -"noInbounds" = "❗ Không tìm thấy inbound!" -"unlimited" = "♾ Không giới hạn (Đặt lại)" -"add" = "Thêm" -"month" = "Tháng" -"months" = "Tháng" -"day" = "Ngày" -"days" = "Ngày" -"hours" = "Giờ" -"minutes" = "Phút" -"unknown" = "Không xác định" -"inbounds" = "Inbound" -"clients" = "Client" -"offline" = "🔴 Ngoại tuyến" -"online" = "🟢 Trực tuyến" - -[tgbot.commands] -"unknown" = "❗ Lệnh không rõ" -"pleaseChoose" = "👇 Vui lòng chọn:\r\n" -"help" = "🤖 Chào mừng bạn đến với bot này! Bot được thiết kế để cung cấp cho bạn dữ liệu cụ thể từ máy chủ và cho phép bạn thực hiện các thay đổi cần thiết.\r\n\r\n" -"start" = "👋 Xin chào {{ .Firstname }}.\r\n" -"welcome" = "🤖 Chào mừng đến với bot quản lý của {{ .Hostname }}.\r\n" -"status" = "✅ Bot hoạt động bình thường!" -"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!" -"getID" = "🆔 ID của bạn: {{ .ID }}" -"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n/restart\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n/usage [Email]\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n/inbound [Ghi chú]\r\n\r\nID Trò chuyện Telegram:\r\n/id" -"helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n/usage [Email]\r\n\r\nID Trò chuyện Telegram:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ Hoạt động thành công!" -"restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\nLỗi: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core không chạy." -"startDesc" = "Hiển thị menu chính" -"helpDesc" = "Trợ giúp bot" -"statusDesc" = "Kiểm tra trạng thái bot" -"idDesc" = "Hiển thị ID Telegram của bạn" - -[tgbot.messages] -"cpuThreshold" = "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%" -"selectUserFailed" = "❌ Lỗi khi chọn người dùng!" -"userSaved" = "✅ Người dùng Telegram đã được lưu." -"loginSuccess" = "✅ Đăng nhập thành công vào bảng điều khiển.\r\n" -"loginFailed" = "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n" -"2faFailed" = "Lỗi 2FA" -"report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n" -"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n" -"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n" -"version" = "🚀 Phiên bản X-UI: {{ .Version }}\r\n" -"xrayVersion" = "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n" -"ip" = "🌐 IP: {{ .IP }}\r\n" -"ips" = "🔢 Các IP:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 Số lượng kết nối TCP: {{ .Count }}\r\n" -"udpCount" = "🔸 Số lượng kết nối UDP: {{ .Count }}\r\n" -"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Trạng thái Xray: {{ .State }}\r\n" -"username" = "👤 Tên người dùng: {{ .Username }}\r\n" -"reason" = "❗️ Lý do: {{ .Reason }}\r\n" -"time" = "⏰ Thời gian: {{ .Time }}\r\n" -"inbound" = "📍 Inbound: {{ .Remark }}\r\n" -"port" = "🔌 Cổng: {{ .Port }}\r\n" -"expire" = "📅 Ngày hết hạn: {{ .Time }}\r\n" -"expireIn" = "📅 Hết hạn sau: {{ .Time }}\r\n" -"active" = "💡 Đang hoạt động: {{ .Enable }}\r\n" -"enabled" = "🚨 Đã bật: {{ .Enable }}\r\n" -"online" = "🌐 Trạng thái kết nối: {{ .Status }}\r\n" -"lastOnline" = "🔙 Lần online gần nhất: {{ .Time }}\r\n" -"email" = "📧 Email: {{ .Email }}\r\n" -"upload" = "🔼 Tải lên: ↑{{ .Upload }}\r\n" -"download" = "🔽 Tải xuống: ↓{{ .Download }}\r\n" -"total" = "📊 Tổng cộng: ↑↓{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 Người dùng Telegram: {{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 Sự cạn kiệt {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 Số lần cạn kiệt {{ .Type }}:\r\n" -"onlinesCount" = "🌐 Khách hàng trực tuyến: {{ .Count }}\r\n" -"disabled" = "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n" -"depleteSoon" = "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 Thời gian sao lưu: {{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n" -"yes" = "✅ Có" -"no" = "❌ Không" -"received_id" = "🔑📥 ID đã được cập nhật." -"received_password" = "🔑📥 Mật khẩu đã được cập nhật." -"received_email" = "📧📥 Email đã được cập nhật." -"received_comment" = "💬📥 Bình luận đã được cập nhật." -"id_prompt" = "🔑 ID mặc định: {{ .ClientId }}\n\nVui lòng nhập ID của bạn." -"pass_prompt" = "🔑 Mật khẩu mặc định: {{ .ClientPassword }}\n\nVui lòng nhập mật khẩu của bạn." -"email_prompt" = "📧 Email mặc định: {{ .ClientEmail }}\n\nVui lòng nhập email của bạn." -"comment_prompt" = "💬 Bình luận mặc định: {{ .ClientComment }}\n\nVui lòng nhập bình luận của bạn." -"inbound_client_data_id" = "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!" -"inbound_client_data_pass" = "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 Mật khẩu: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!" -"cancel" = "❌ Quá trình đã bị hủy! \n\nBạn có thể bắt đầu lại bất cứ lúc nào bằng cách nhập /start. 🔄" -"error_add_client" = "⚠️ Lỗi:\n\n {{ .error }}" -"using_default_value" = "Được rồi, tôi sẽ sử dụng giá trị mặc định. 😊" -"incorrect_input" = "Dữ liệu bạn nhập không hợp lệ.\nCác chuỗi phải liền mạch và không có dấu cách.\nVí dụ đúng: aaaaaa\nVí dụ sai: aaa aaa 🚫" -"AreYouSure" = "Bạn có chắc không? 🤔" -"SuccessResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ✅ Thành công" -"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 Quá trình đặt lại lưu lượng đã hoàn tất cho tất cả khách hàng." - -[tgbot.buttons] -"closeKeyboard" = "❌ Đóng Bàn Phím" -"cancel" = "❌ Hủy" -"cancelReset" = "❌ Hủy Đặt Lại" -"cancelIpLimit" = "❌ Hủy Giới Hạn IP" -"confirmResetTraffic" = "✅ Xác Nhận Đặt Lại Lưu Lượng?" -"confirmClearIps" = "✅ Xác Nhận Xóa Các IP?" -"confirmRemoveTGUser" = "✅ Xác Nhận Xóa Người Dùng Telegram?" -"confirmToggle" = "✅ Xác nhận Bật/Tắt người dùng?" -"dbBackup" = "Tải bản sao lưu cơ sở dữ liệu" -"serverUsage" = "Sử Dụng Máy Chủ" -"getInbounds" = "Lấy cổng vào" -"depleteSoon" = "Depleted Soon" -"clientUsage" = "Lấy Sử Dụng" -"onlines" = "Khách hàng trực tuyến" -"commands" = "Lệnh" -"refresh" = "🔄 Cập Nhật" -"clearIPs" = "❌ Xóa IP" -"removeTGUser" = "❌ Xóa Người Dùng Telegram" -"selectTGUser" = "👤 Chọn Người Dùng Telegram" -"selectOneTGUser" = "👤 Chọn một người dùng telegram:" -"resetTraffic" = "📈 Đặt Lại Lưu Lượng" -"resetExpire" = "📅 Thay đổi ngày hết hạn" -"ipLog" = "🔢 Nhật ký địa chỉ IP" -"ipLimit" = "🔢 Giới Hạn địa chỉ IP" -"setTGUser" = "👤 Đặt Người Dùng Telegram" -"toggle" = "🔘 Bật / Tắt" -"custom" = "🔢 Tùy chỉnh" -"confirmNumber" = "✅ Xác nhận: {{ .Num }}" -"confirmNumberAdd" = "✅ Xác nhận thêm: {{ .Num }}" -"limitTraffic" = "🚧 Giới hạn lưu lượng" -"getBanLogs" = "Cấm nhật ký" -"allClients" = "Tất cả Khách hàng" -"addClient" = "Thêm Khách Hàng" -"submitDisable" = "Gửi Dưới Dạng Vô Hiệu ☑️" -"submitEnable" = "Gửi Dưới Dạng Kích Hoạt ✅" -"use_default" = "🏷️ Sử Dụng Mặc Định" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 Mật Khẩu" -"change_email" = "⚙️📧 Email" -"change_comment" = "⚙️💬 Bình Luận" -"ResetAllTraffics" = "Đặt lại tất cả lưu lượng" -"SortedTrafficUsageReport" = "Báo cáo sử dụng lưu lượng đã sắp xếp" - -[tgbot.answers] -"successfulOperation" = "✅ Thành công!" -"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện." -"getInboundsFailed" = "❌ Không Thể Lấy Được Inbounds" -"getClientsFailed" = "❌ Không thể lấy khách hàng." -"canceled" = "❌ {{ .Email }} : Thao Tác Đã Bị Hủy." -"clientRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Khách Hàng." -"IpRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho IPs." -"TGIdRefreshSuccess" = "✅ {{ .Email }} : Cập Nhật Thành Công Cho Người Dùng Telegram." -"resetTrafficSuccess" = "✅ {{ .Email }} : Đặt Lại Lưu Lượng Thành Công." -"setTrafficLimitSuccess" = "✅ {{ .Email }} : Đã lưu thành công giới hạn lưu lượng." -"expireResetSuccess" = "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công." -"resetIpSuccess" = "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công." -"clearIpSuccess" = "✅ {{ .Email }} : IP Đã Được Xóa Thành Công." -"getIpLog" = "✅ {{ .Email }} : Lấy nhật ký IP Thành Công." -"getUserInfo" = "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram Thành Công." -"removedTGUserSuccess" = "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công." -"enableSuccess" = "✅ {{ .Email }} : Đã Bật Thành Công." -"disableSuccess" = "✅ {{ .Email }} : Đã Tắt Thành Công." -"askToAddUserId" = "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: {{ .TgUserID }}" -"chooseClient" = "Chọn một Khách hàng cho Inbound {{ .Inbound }}" -"chooseInbound" = "Chọn một Inbound" diff --git a/web/translation/translate.zh_CN.toml b/web/translation/translate.zh_CN.toml deleted file mode 100644 index 8675a531..00000000 --- a/web/translation/translate.zh_CN.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "用户名" -"password" = "密码" -"login" = "登录" -"confirm" = "确定" -"cancel" = "取消" -"close" = "关闭" -"save" = "保存" -"logout" = "登出" -"create" = "创建" -"update" = "更新" -"copy" = "复制" -"copied" = "已复制" -"download" = "下载" -"remark" = "备注" -"enable" = "启用" -"protocol" = "协议" -"search" = "搜索" -"filter" = "筛选" -"loading" = "加载中..." -"second" = "秒" -"minute" = "分钟" -"hour" = "小时" -"day" = "天" -"check" = "查看" -"indefinite" = "无限期" -"unlimited" = "无限制" -"none" = "无" -"qrCode" = "二维码" -"info" = "更多信息" -"edit" = "编辑" -"delete" = "删除" -"reset" = "重置" -"noData" = "无数据。" -"copySuccess" = "复制成功" -"sure" = "确定" -"encryption" = "加密" -"useIPv4ForHost" = "使用 IPv4 连接主机" -"transmission" = "传输" -"host" = "主机" -"path" = "路径" -"camouflage" = "伪装" -"status" = "状态" -"enabled" = "开启" -"disabled" = "关闭" -"depleted" = "耗尽" -"depletingSoon" = "即将耗尽" -"offline" = "离线" -"online" = "在线" -"domainName" = "域名" -"monitor" = "监听" -"certificate" = "数字证书" -"fail" = "失败" -"comment" = "评论" -"success" = "成功" -"lastOnline" = "上次在线" -"getVersion" = "获取版本" -"install" = "安装" -"clients" = "客户端" -"usage" = "使用情况" -"twoFactorCode" = "代码" -"remained" = "剩余" -"security" = "安全" -"secAlertTitle" = "安全警报" -"secAlertSsl" = "此连接不安全。在激活 TLS 进行数据保护之前,请勿输入敏感信息。" -"secAlertConf" = "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。" -"secAlertSSL" = "面板缺少安全连接。请安装 TLS 证书以保护数据安全。" -"secAlertPanelPort" = "面板默认端口存在安全风险。请配置随机端口或特定端口。" -"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" -"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" -"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" -"emptyDnsDesc" = "未添加DNS服务器。" -"emptyFakeDnsDesc" = "未添加Fake DNS服务器。" -"emptyBalancersDesc" = "未添加负载均衡器。" -"emptyReverseDesc" = "未添加反向代理。" -"somethingWentWrong" = "出了点问题" - -[subscription] -"title" = "订阅信息" -"subId" = "订阅 ID" -"status" = "状态" -"downloaded" = "已下载" -"uploaded" = "已上传" -"expiry" = "到期" -"totalQuota" = "总配额" -"individualLinks" = "单独链接" -"active" = "启用" -"inactive" = "停用" -"unlimited" = "无限制" -"noExpiry" = "无到期" - -[menu] -"theme" = "主题" -"dark" = "暗色" -"ultraDark" = "超暗色" -"dashboard" = "系统状态" -"inbounds" = "入站列表" -"settings" = "面板设置" -"xray" = "Xray 设置" -"logout" = "退出登录" -"link" = "管理" - -[pages.login] -"hello" = "你好" -"title" = "欢迎" -"loginAgain" = "登录时效已过,请重新登录" - -[pages.login.toasts] -"invalidFormData" = "数据格式错误" -"emptyUsername" = "请输入用户名" -"emptyPassword" = "请输入密码" -"wrongUsernameOrPassword" = "用户名、密码或双重验证码无效。" -"successLogin" = "您已成功登录您的账户。" - -[pages.index] -"title" = "系统状态" -"cpu" = "CPU" -"logicalProcessors" = "逻辑处理器" -"frequency" = "频率" -"swap" = "交换分区" -"storage" = "存储" -"memory" = "内存" -"threads" = "线程" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "重启" -"xraySwitch" = "版本" -"xraySwitchClick" = "选择你要切换到的版本" -"xraySwitchClickDesk" = "请谨慎选择,因为较旧版本可能与当前配置不兼容" -"xrayUpdates" = "Xray 更新" -"updatePanel" = "更新面板" -"panelUpdateDesc" = "这将把 3X-UI 更新到最新版本并重启面板服务。" -"currentPanelVersion" = "当前面板版本" -"latestPanelVersion" = "最新面板版本" -"panelUpToDate" = "面板已是最新" -"upToDate" = "已是最新" -"xrayStatusUnknown" = "未知" -"xrayStatusRunning" = "运行中" -"xrayStatusStop" = "停止" -"xrayStatusError" = "错误" -"xrayErrorPopoverTitle" = "运行Xray时发生错误" -"operationHours" = "系统正常运行时间" -"systemLoad" = "系统负载" -"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载" -"connectionCount" = "连接数" -"ipAddresses" = "IP地址" -"toggleIpVisibility" = "切换IP可见性" -"overallSpeed" = "整体速度" -"upload" = "上传" -"download" = "下载" -"totalData" = "总数据" -"sent" = "已发送" -"received" = "已接收" -"documentation" = "文档" -"xraySwitchVersionDialog" = "您确定要更改Xray版本吗?" -"xraySwitchVersionDialogDesc" = "这将把Xray版本更改为#version#。" -"xraySwitchVersionPopover" = "Xray 更新成功" -"panelUpdateDialog" = "您确定要更新面板吗?" -"panelUpdateDialogDesc" = "这将把 3X-UI 更新到 #version# 并重启面板服务。" -"panelUpdateCheckPopover" = "面板更新检查失败" -"panelUpdateStartedPopover" = "已开始更新面板" -"geofileUpdateDialog" = "您确定要更新地理文件吗?" -"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。" -"geofilesUpdateDialogDesc" = "这将更新所有文件。" -"geofilesUpdateAll" = "全部更新" -"geofileUpdatePopover" = "地理文件更新成功" -"dontRefresh" = "安装中,请勿刷新此页面" -"logs" = "日志" -"config" = "配置" -"backup" = "备份" -"backupTitle" = "备份和恢复数据库" -"exportDatabase" = "备份" -"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。" -"importDatabase" = "恢复" -"importDatabaseDesc" = "点击选择并上传设备中的 .db 文件以从备份恢复数据库。" -"importDatabaseSuccess" = "数据库导入成功" -"importDatabaseError" = "导入数据库时出错" -"readDatabaseError" = "读取数据库时出错" -"getDatabaseError" = "检索数据库时出错" -"getConfigError" = "检索配置文件时出错" -"customGeoTitle" = "自定义 GeoSite / GeoIP" -"customGeoAdd" = "添加" -"customGeoType" = "类型" -"customGeoAlias" = "别名" -"customGeoUrl" = "URL" -"customGeoEnabled" = "启用" -"customGeoLastUpdated" = "上次更新" -"customGeoExtColumn" = "路由 (ext:…)" -"customGeoToastUpdateAll" = "所有自定义来源已更新" -"customGeoActions" = "操作" -"customGeoEdit" = "编辑" -"customGeoDelete" = "删除" -"customGeoDownload" = "立即更新" -"customGeoModalAdd" = "添加自定义 geo" -"customGeoModalEdit" = "编辑自定义 geo" -"customGeoModalSave" = "保存" -"customGeoDeleteConfirm" = "删除此自定义 geo 源?" -"customGeoRoutingHint" = "在路由规则中将值列写为 ext:文件.dat:标签(替换标签)。" -"customGeoInvalidId" = "无效的资源 ID" -"customGeoAliasesError" = "加载自定义 geo 别名失败" -"customGeoValidationAlias" = "别名只能包含小写字母、数字、- 和 _" -"customGeoValidationUrl" = "URL 必须以 http:// 或 https:// 开头" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = "(自定义)" -"customGeoToastList" = "自定义 geo 列表" -"customGeoToastAdd" = "添加自定义 geo" -"customGeoToastUpdate" = "更新自定义 geo" -"customGeoToastDelete" = "自定义 geofile「{{ .fileName }}」已删除" -"customGeoToastDownload" = "geofile「{{ .fileName }}」已更新" -"customGeoErrInvalidType" = "类型必须是 geosite 或 geoip" -"customGeoErrAliasRequired" = "请填写别名" -"customGeoErrAliasPattern" = "别名包含不允许的字符" -"customGeoErrAliasReserved" = "该别名已保留" -"customGeoErrUrlRequired" = "请填写 URL" -"customGeoErrInvalidUrl" = "URL 无效" -"customGeoErrUrlScheme" = "URL 必须使用 http 或 https" -"customGeoErrUrlHost" = "URL 主机无效" -"customGeoErrDuplicateAlias" = "此类型下已使用该别名" -"customGeoErrNotFound" = "未找到自定义 geo 源" -"customGeoErrDownload" = "下载失败" -"customGeoErrUpdateAllIncomplete" = "有一个或多个自定义 geo 源更新失败" -"customGeoEmpty" = "暂无自定义 geo 源 — 点击「添加」以创建" - -[pages.inbounds] -"allTimeTraffic" = "累计总流量" -"allTimeTrafficUsage" = "所有时间总使用量" -"title" = "入站列表" -"totalDownUp" = "总上传 / 下载" -"totalUsage" = "总用量" -"inboundCount" = "入站数量" -"operate" = "菜单" -"enable" = "启用" -"remark" = "备注" -"protocol" = "协议" -"port" = "端口" -"portMap" = "端口映射" -"traffic" = "流量" -"details" = "详细信息" -"transportConfig" = "传输配置" -"expireDate" = "到期时间" -"createdAt" = "创建时间" -"updatedAt" = "更新时间" -"resetTraffic" = "重置流量" -"addInbound" = "添加入站" -"generalActions" = "通用操作" -"autoRefresh" = "自动刷新" -"autoRefreshInterval" = "间隔" -"modifyInbound" = "修改入站" -"deleteInbound" = "删除入站" -"deleteInboundContent" = "确定要删除入站吗?" -"deleteClient" = "删除客户端" -"deleteClientContent" = "确定要删除客户端吗?" -"resetTrafficContent" = "确定要重置流量吗?" -"copyLink" = "复制链接" -"address" = "地址" -"network" = "网络" -"destinationPort" = "目标端口" -"targetAddress" = "目标地址" -"monitorDesc" = "留空表示监听所有 IP" -"meansNoLimit" = "= 无限制(单位:GB)" -"totalFlow" = "总流量" -"leaveBlankToNeverExpire" = "留空表示永不过期" -"noRecommendKeepDefault" = "建议保留默认值" -"certificatePath" = "文件路径" -"certificateContent" = "文件内容" -"publicKey" = "公钥" -"privatekey" = "私钥" -"clickOnQRcode" = "点击二维码复制" -"client" = "客户" -"export" = "导出链接" -"clone" = "克隆" -"cloneInbound" = "克隆" -"cloneInboundContent" = "此入站规则除端口(Port)、监听 IP(Listening IP)和客户端(Clients)以外的所有配置都将应用于克隆" -"cloneInboundOk" = "创建克隆" -"resetAllTraffic" = "重置所有入站流量" -"resetAllTrafficTitle" = "重置所有入站流量" -"resetAllTrafficContent" = "确定要重置所有入站流量吗?" -"resetInboundClientTraffics" = "重置客户端流量" -"resetInboundClientTrafficTitle" = "重置所有客户端流量" -"resetInboundClientTrafficContent" = "确定要重置此入站客户端的所有流量吗?" -"resetAllClientTraffics" = "重置所有客户端流量" -"resetAllClientTrafficTitle" = "重置所有客户端流量" -"resetAllClientTrafficContent" = "确定要重置所有客户端的所有流量吗?" -"delDepletedClients" = "删除流量耗尽的客户端" -"delDepletedClientsTitle" = "删除流量耗尽的客户端" -"delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?" -"email" = "电子邮件" -"emailDesc" = "电子邮件必须完全唯一" -"IPLimit" = "IP 限制" -"IPLimitDesc" = "如果数量超过设置值,则禁用入站流量。(0 = 禁用)" -"IPLimitlog" = "IP 日志" -"IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)" -"IPLimitlogclear" = "清除日志" -"setDefaultCert" = "从面板设置证书" -"telegramDesc" = "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或(@userinfobot" -"subscriptionDesc" = "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。" -"info" = "信息" -"same" = "相同" -"inboundData" = "入站数据" -"exportInbound" = "导出入站规则" -"import" = "导入" -"importInbound" = "导入入站规则" -"periodicTrafficResetTitle" = "流量重置" -"periodicTrafficResetDesc" = "按指定间隔自动重置流量计数器" -"lastReset" = "上次重置" - -[pages.client] -"add" = "添加客户端" -"edit" = "编辑客户端" -"submitAdd" = "添加客户端" -"submitEdit" = "保存修改" -"clientCount" = "客户端数量" -"bulk" = "批量创建" -"copyFromInbound" = "从入站复制客户端" -"copyToInbound" = "复制客户端到" -"copySelected" = "复制所选" -"copySource" = "来源" -"copyEmailPreview" = "最终邮箱预览" -"copySelectSourceFirst" = "请先选择来源入站。" -"copyResult" = "复制结果" -"copyResultSuccess" = "复制成功" -"copyResultNone" = "没有可复制的内容:未选择客户端或来源为空" -"copyResultErrors" = "复制错误" -"copyFlowLabel" = "新客户端的 Flow (VLESS)" -"copyFlowHint" = "应用于所有复制的客户端。留空则跳过。" -"selectAll" = "全选" -"clearAll" = "全不选" -"method" = "方法" -"first" = "置顶" -"last" = "置底" -"prefix" = "前缀" -"postfix" = "后缀" -"delayedStart" = "首次使用后开始" -"expireDays" = "期间" -"days" = "天" -"renew" = "自动续订" -"renewDesc" = "到期后自动续订。(0 = 禁用)(单位: 天)" - -[pages.inbounds.periodicTrafficReset] -"never" = "从不" -"daily" = "每日" -"weekly" = "每周" -"monthly" = "每月" -"hourly" = "每小时" - -[pages.inbounds.toasts] -"obtain" = "获取" -"updateSuccess" = "更新成功" -"logCleanSuccess" = "日志已清除" -"inboundsUpdateSuccess" = "入站连接已成功更新" -"inboundUpdateSuccess" = "入站连接已成功更新" -"inboundCreateSuccess" = "入站连接已成功创建" -"inboundDeleteSuccess" = "入站连接已成功删除" -"inboundClientAddSuccess" = "已添加入站客户端" -"inboundClientDeleteSuccess" = "入站客户端已删除" -"inboundClientUpdateSuccess" = "入站客户端已更新" -"delDepletedClientsSuccess" = "所有耗尽客户端已删除" -"resetAllClientTrafficSuccess" = "客户端所有流量已重置" -"resetAllTrafficSuccess" = "所有流量已重置" -"resetInboundClientTrafficSuccess" = "流量已重置" -"trafficGetError" = "获取流量数据时出错" -"getNewX25519CertError" = "获取X25519证书时出错。" -"getNewmldsa65Error" = "获取mldsa65证书时出错。" -"getNewVlessEncError" = "获取VlessEnc证书时出错。" - -[pages.inbounds.stream.general] -"request" = "请求" -"response" = "响应" -"name" = "名称" -"value" = "值" - -[pages.inbounds.stream.tcp] -"version" = "版本" -"method" = "方法" -"path" = "路径" -"status" = "状态" -"statusDescription" = "状态说明" -"requestHeader" = "请求头" -"responseHeader" = "响应头" - -[pages.settings] -"title" = "面板设置" -"save" = "保存" -"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效" -"restartPanel" = "重启面板" -"restartPanelDesc" = "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息" -"restartPanelSuccess" = "面板已成功重启" -"actions" = "操作" -"resetDefaultConfig" = "重置为默认配置" -"panelSettings" = "常规" -"securitySettings" = "安全设定" -"TGBotSettings" = "Telegram 机器人配置" -"panelListeningIP" = "面板监听 IP" -"panelListeningIPDesc" = "默认留空监听所有 IP" -"panelListeningDomain" = "面板监听域名" -"panelListeningDomainDesc" = "默认情况下留空以监视所有域名和 IP 地址" -"panelPort" = "面板监听端口" -"panelPortDesc" = "重启面板生效" -"publicKeyPath" = "面板证书公钥文件路径" -"publicKeyPathDesc" = "填写一个 '/' 开头的绝对路径" -"privateKeyPath" = "面板证书密钥文件路径" -"privateKeyPathDesc" = "填写一个 '/' 开头的绝对路径" -"panelUrlPath" = "面板 url 根路径" -"panelUrlPathDesc" = "必须以 '/' 开头,以 '/' 结尾" -"pageSize" = "分页大小" -"pageSizeDesc" = "定义入站表的页面大小。设置 0 表示禁用" -"remarkModel" = "备注模型和分隔符" -"datepicker" = "日期选择器" -"datepickerPlaceholder" = "选择日期" -"datepickerDescription" = "选择器日历类型指定到期日期" -"sampleRemark" = "备注示例" -"oldUsername" = "原用户名" -"currentPassword" = "原密码" -"newUsername" = "新用户名" -"newPassword" = "新密码" -"telegramBotEnable" = "启用 Telegram 机器人" -"telegramBotEnableDesc" = "启用 Telegram 机器人功能" -"telegramToken" = "Telegram 机器人令牌(token)" -"telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌" -"telegramProxy" = "SOCKS5 Proxy" -"telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "要使用的 Telegram API 服务器。留空以使用默认服务器。" -"telegramChatId" = "管理员聊天 ID" -"telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)" -"telegramNotifyTime" = "通知时间" -"telegramNotifyTimeDesc" = "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)" -"tgNotifyBackup" = "数据库备份" -"tgNotifyBackupDesc" = "发送带有报告的数据库备份文件" -"tgNotifyLogin" = "登录通知" -"tgNotifyLoginDesc" = "当有人试图登录你的面板时显示用户名、IP 地址和时间" -"sessionMaxAge" = "会话时长" -"sessionMaxAgeDesc" = "保持登录状态的时长(单位:分钟)" -"expireTimeDiff" = "到期通知阈值" -"expireTimeDiffDesc" = "达到此阈值时,将收到有关到期时间的通知(单位:天)" -"trafficDiff" = "流量耗尽阈值" -"trafficDiffDesc" = "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)" -"tgNotifyCpu" = "CPU 负载通知阈值" -"tgNotifyCpuDesc" = "CPU 负载超过此阈值时,将收到通知(单位:%)" -"timeZone" = "时区" -"timeZoneDesc" = "定时任务将按照该时区的时间运行" -"subSettings" = "订阅设置" -"subEnable" = "启用订阅服务" -"subEnableDesc" = "启用订阅服务功能" -"subJsonEnable" = "单独启用/禁用 JSON 订阅端点。" -"subTitle" = "订阅标题" -"subTitleDesc" = "在VPN客户端中显示的标题" -"subSupportUrl" = "支持链接" -"subSupportUrlDesc" = "VPN 客户端中显示的技术支持链接" -"subProfileUrl" = "个人资料链接" -"subProfileUrlDesc" = "VPN 客户端中显示的网站链接" -"subAnnounce" = "公告" -"subAnnounceDesc" = "VPN 客户端中显示的公告文本" -"subEnableRouting" = "启用路由" -"subEnableRoutingDesc" = "在 VPN 客户端中启用路由的全局设置。(僅限 Happ)" -"subRoutingRules" = "路由規則" -"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)" -"subListen" = "监听 IP" -"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)" -"subPort" = "监听端口" -"subPortDesc" = "订阅服务监听的端口号(必须是未使用的端口)" -"subCertPath" = "公钥路径" -"subCertPathDesc" = "订阅服务使用的公钥文件路径(以 '/' 开头)" -"subKeyPath" = "私钥路径" -"subKeyPathDesc" = "订阅服务使用的私钥文件路径(以 '/' 开头)" -"subPath" = "URI 路径" -"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)" -"subDomain" = "监听域名" -"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)" -"subUpdates" = "更新间隔" -"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)" -"subEncrypt" = "编码" -"subEncryptDesc" = "订阅服务返回的内容将采用 Base64 编码" -"subShowInfo" = "显示使用信息" -"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息" -"subURI" = "反向代理 URI" -"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径" -"externalTrafficInformEnable" = "外部交通通知" -"externalTrafficInformEnableDesc" = "每次流量更新时通知外部 API" -"externalTrafficInformURI" = "外部流量通知 URI" -"externalTrafficInformURIDesc" = "流量更新将发送到此 URI" -"restartXrayOnClientDisable" = "客户端自动禁用后重启 Xray" -"restartXrayOnClientDisableDesc" = "当客户端因到期或流量超限被自动禁用时,重启 Xray。" -"fragment" = "分片" -"fragmentDesc" = "启用 TLS hello 数据包分片" -"fragmentSett" = "设置" -"noisesDesc" = "启用 Noises." -"noisesSett" = "Noises 设置" -"mux" = "多路复用器" -"muxDesc" = "在已建立的数据流内传输多个独立的数据流" -"muxSett" = "复用器设置" -"direct" = "直接连接" -"directDesc" = "直接与特定国家的域或IP范围建立连接" -"notifications" = "通知" -"certs" = "证书" -"externalTraffic" = "外部流量" -"dateAndTime" = "日期和时间" -"proxyAndServer" = "代理和服务器" -"intervals" = "间隔" -"information" = "信息" -"language" = "语言" -"telegramBotLanguage" = "Telegram 机器人语言" - -[pages.xray] -"title" = "Xray 配置" -"save" = "保存" -"restart" = "重新启动 Xray" -"restartSuccess" = "Xray 已成功重新启动" -"stopSuccess" = "Xray 已成功停止" -"restartError" = "重启Xray时发生错误。" -"stopError" = "停止Xray时发生错误。" -"basicTemplate" = "基础配置" -"advancedTemplate" = "高级配置" -"generalConfigs" = "常规配置" -"generalConfigsDesc" = "这些选项将决定常规配置" -"logConfigs" = "日志" -"logConfigsDesc" = "日志可能会影响服务器的性能,建议仅在需要时启用" -"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" -"basicRouting" = "基本路由" -"blockConnectionsConfigsDesc" = "这些选项将根据特定的请求国家阻止流量。" -"directConnectionsConfigsDesc" = "直接连接确保特定的流量不会通过其他服务器路由。" -"blockips" = "阻止IP" -"blockdomains" = "阻止域名" -"directips" = "直接IP" -"directdomains" = "直接域名" -"ipv4Routing" = "IPv4 路由" -"ipv4RoutingDesc" = "此选项将仅通过 IPv4 路由到目标域" -"warpRouting" = "WARP 路由" -"warpRoutingDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在你的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。" -"nordRouting" = "NordVPN 路由" -"nordRoutingDesc" = "这些选项将根据特定目的地通过 NordVPN 路由流量。" -"Template" = "高级 Xray 配置模板" -"TemplateDesc" = "最终的 Xray 配置文件将基于此模板生成" -"FreedomStrategy" = "Freedom 协议策略" -"FreedomStrategyDesc" = "设置 Freedom 协议中网络的输出策略" -"RoutingStrategy" = "配置路由域策略" -"RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略" -"outboundTestUrl" = "出站测试 URL" -"outboundTestUrlDesc" = "测试出站连接时使用的 URL" -"Torrent" = "屏蔽 BitTorrent 协议" -"Inbounds" = "入站规则" -"InboundsDesc" = "接受来自特定客户端的流量" -"Outbounds" = "出站规则" -"Balancers" = "负载均衡" -"OutboundsDesc" = "设置出站流量传出方式" -"Routings" = "路由规则" -"RoutingsDesc" = "每条规则的优先级都很重要" -"completeTemplate" = "全部" -"logLevel" = "日志级别" -"logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息" -"accessLog" = "访问日志" -"accessLogDesc" = "访问日志的文件路径。特殊值 'none' 禁用访问日志" -"errorLog" = "错误日志" -"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志" -"dnsLog" = "DNS 日志" -"dnsLogDesc" = "是否启用 DNS 查询日志" -"maskAddress" = "隐藏地址" -"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。" -"statistics" = "统计" -"statsInboundUplink" = "入站上传统计" -"statsInboundUplinkDesc" = "启用所有入站代理的上行流量统计收集。" -"statsInboundDownlink" = "入站下载统计" -"statsInboundDownlinkDesc" = "启用所有入站代理的下行流量统计收集。" -"statsOutboundUplink" = "出站上传统计" -"statsOutboundUplinkDesc" = "启用所有出站代理的上行流量统计收集。" -"statsOutboundDownlink" = "出站下载统计" -"statsOutboundDownlinkDesc" = "启用所有出站代理的下行流量统计收集。" - -[pages.xray.rules] -"first" = "置顶" -"last" = "置底" -"up" = "向上" -"down" = "向下" -"source" = "来源" -"dest" = "目的地址" -"inbound" = "入站" -"outbound" = "出站" -"balancer" = "负载均衡" -"info" = "信息" -"add" = "添加规则" -"edit" = "编辑规则" -"useComma" = "逗号分隔的项目" - -[pages.xray.outbound] -"addOutbound" = "添加出站" -"addReverse" = "添加反向" -"editOutbound" = "编辑出站" -"editReverse" = "编辑反向" -"reverseTag" = "反向标签" -"reverseTagDesc" = "VLESS 简易反向代理出站标签。留空则禁用。设置后,此客户端的连接可用作反向代理隧道。" -"reverseTagPlaceholder" = "出站标签(留空则禁用)" -"tag" = "标签" -"tagDesc" = "唯一标签" -"address" = "地址" -"reverse" = "反向" -"domain" = "域名" -"type" = "类型" -"bridge" = "Bridge" -"portal" = "Portal" -"link" = "链接" -"intercon" = "互连" -"settings" = "设置" -"accountInfo" = "帐户信息" -"outboundStatus" = "出站状态" -"sendThrough" = "发送通过" -"test" = "测试" -"testResult" = "测试结果" -"testing" = "正在测试连接..." -"testSuccess" = "测试成功" -"testFailed" = "测试失败" -"testError" = "测试出站失败" -"nordvpn" = "NordVPN" -"accessToken" = "访问令牌" -"country" = "国家" -"server" = "服务器" -"city" = "城市" -"allCities" = "所有城市" -"privateKey" = "私钥" -"load" = "负载" - -[pages.xray.balancer] -"addBalancer" = "添加负载均衡" -"editBalancer" = "编辑负载均衡" -"balancerStrategy" = "策略" -"balancerSelectors" = "选择器" -"tag" = "标签" -"tagDesc" = "唯一标签" -"balancerDesc" = "无法同时使用 balancerTag 和 outboundTag。如果同时使用,则只有 outboundTag 会生效。" - -[pages.xray.wireguard] -"secretKey" = "密钥" -"publicKey" = "公钥" -"allowedIPs" = "允许的 IP" -"endpoint" = "端点" -"psk" = "共享密钥" -"domainStrategy" = "域策略" - -[pages.xray.tun] -"nameDesc" = "TUN 接口的名称。默认值为 'xray0'" -"mtuDesc" = "最大传输单元。数据包的最大大小。默认值为 1500" -"userLevel" = "用户级别" -"userLevelDesc" = "通过此入站的所有连接都将使用此用户级别。默认值为 0" - -[pages.xray.dns] -"enable" = "启用 DNS" -"enableDesc" = "启用内置 DNS 服务器" -"tag" = "DNS 入站标签" -"tagDesc" = "此标签将在路由规则中可用作入站标签" -"clientIp" = "客户端IP" -"clientIpDesc" = "用于在DNS查询期间通知服务器指定的IP位置" -"disableCache" = "禁用缓存" -"disableCacheDesc" = "禁用DNS缓存" -"disableFallback" = "禁用回退" -"disableFallbackDesc" = "禁用回退DNS查询" -"disableFallbackIfMatch" = "匹配时禁用回退" -"disableFallbackIfMatchDesc" = "当DNS服务器的匹配域名列表命中时,禁用回退DNS查询" -"enableParallelQuery" = "启用并行查询" -"enableParallelQueryDesc" = "启用并行DNS查询到多个服务器以实现更快的解析" -"strategy" = "查询策略" -"strategyDesc" = "解析域名的总体策略" -"add" = "添加服务器" -"edit" = "编辑服务器" -"domains" = "域" -"expectIPs" = "预期 IP" -"unexpectIPs" = "意外IP" -"useSystemHosts" = "使用系统Hosts" -"useSystemHostsDesc" = "使用已安装系统的hosts文件" -"usePreset" = "使用模板" -"dnsPresetTitle" = "DNS模板" -"dnsPresetFamily" = "家庭" - -[pages.xray.fakedns] -"add" = "添加假 DNS" -"edit" = "编辑假 DNS" -"ipPool" = "IP 池子网" -"poolSize" = "池大小" - -[pages.settings.security] -"admin" = "管理员凭据" -"twoFactor" = "双重验证" -"twoFactorEnable" = "启用2FA" -"twoFactorEnableDesc" = "增加额外的验证层以提高安全性。" -"twoFactorModalSetTitle" = "启用双重认证" -"twoFactorModalDeleteTitle" = "停用双重认证" -"twoFactorModalSteps" = "要设定双重认证,请执行以下步骤:" -"twoFactorModalFirstStep" = "1. 在认证应用程序中扫描此QR码,或复制QR码附近的令牌并粘贴到应用程序中" -"twoFactorModalSecondStep" = "2. 输入应用程序中的验证码" -"twoFactorModalRemoveStep" = "输入应用程序中的验证码以移除双重认证。" -"twoFactorModalChangeCredentialsTitle" = "更改凭据" -"twoFactorModalChangeCredentialsStep" = "输入应用程序中的代码以更改管理员凭据。" -"twoFactorModalSetSuccess" = "双因素认证已成功建立" -"twoFactorModalDeleteSuccess" = "双因素认证已成功删除" -"twoFactorModalError" = "验证码错误" - -[pages.settings.toasts] -"modifySettings" = "参数已更改。" -"getSettings" = "获取参数时发生错误" -"modifyUserError" = "更改管理员凭据时发生错误。" -"modifyUser" = "您已成功更改管理员凭据。" -"originalUserPassIncorrect" = "原用户名或原密码错误" -"userPassMustBeNotEmpty" = "新用户名和新密码不能为空" -"getOutboundTrafficError" = "获取出站流量错误" -"resetOutboundTrafficError" = "重置出站流量错误" - -[tgbot] -"keyboardClosed" = "❌ 自定义键盘已关闭!" -"noResult" = "❗ 没有结果!" -"noQuery" = "❌ 未找到查询!请再次使用该命令!" -"wentWrong" = "❌ 出了点问题!" -"noIpRecord" = "❗ 没有IP记录!" -"noInbounds" = "❗ 未找到入站!" -"unlimited" = "♾ 无限(重置)" -"add" = "添加" -"month" = "月" -"months" = "月" -"day" = "天" -"days" = "天" -"hours" = "小时" -"minutes" = "分钟" -"unknown" = "未知" -"inbounds" = "入站" -"clients" = "客户端" -"offline" = "🔴 离线" -"online" = "🟢 在线" - -[tgbot.commands] -"unknown" = "❗ 未知命令" -"pleaseChoose" = "👇 请选择:\r\n" -"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n" -"start" = "👋 你好,{{ .Firstname }}。\r\n" -"welcome" = "🤖 欢迎来到 {{ .Hostname }} 管理机器人。\r\n" -"status" = "✅ 机器人正常运行!" -"usage" = "❗ 请输入要搜索的文本!" -"getID" = "🆔 您的 ID 为:{{ .ID }}" -"helpAdminCommands" = "要重新启动 Xray Core:\r\n/restart\r\n\r\n要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\nTelegram聊天ID:\r\n/id" -"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n/usage [电子邮件]\r\n\r\nTelegram聊天ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作错误。\r\n\r\n错误: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core 未运行。" -"startDesc" = "显示主菜单" -"helpDesc" = "机器人帮助" -"statusDesc" = "检查机器人状态" -"idDesc" = "显示您的 Telegram ID" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%" -"selectUserFailed" = "❌ 用户选择错误!" -"userSaved" = "✅ 电报用户已保存。" -"loginSuccess" = "✅ 成功登录到面板。\r\n" -"loginFailed" = "❗️ 面板登录失败。\r\n" -"2faFailed" = "2FA 失败" -"report" = "🕰 定时报告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n" -"hostname" = "💻 主机名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 服务器内存:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP 连接数:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP 连接数:{{ .Count }}\r\n" -"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n" -"username" = "👤 用户名:{{ .Username }}\r\n" -"reason" = "❗️ 原因:{{ .Reason }}\r\n" -"time" = "⏰ 时间:{{ .Time }}\r\n" -"inbound" = "📍 入站:{{ .Remark }}\r\n" -"port" = "🔌 端口:{{ .Port }}\r\n" -"expire" = "📅 过期日期:{{ .Time }}\r\n" -"expireIn" = "📅 剩余时间:{{ .Time }}\r\n" -"active" = "💡 激活:{{ .Enable }}\r\n" -"enabled" = "🚨 已启用:{{ .Enable }}\r\n" -"online" = "🌐 连接状态:{{ .Status }}\r\n" -"lastOnline" = "🔙 上次在线: {{ .Time }}\r\n" -"email" = "📧 邮箱:{{ .Email }}\r\n" -"upload" = "🔼 上传↑:{{ .Upload }}\r\n" -"download" = "🔽 下载↓:{{ .Download }}\r\n" -"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 耗尽的 {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 耗尽的 {{ .Type }} 数量:\r\n" -"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n" -"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 备份时间:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n" -"yes" = "✅ 是的" -"no" = "❌ 没有" -"received_id" = "🔑📥 ID 已更新。" -"received_password" = "🔑📥 密码已更新。" -"received_email" = "📧📥 邮箱已更新。" -"received_comment" = "💬📥 评论已更新。" -"id_prompt" = "🔑 默认 ID: {{ .ClientId }}\n\n请输入您的 ID。" -"pass_prompt" = "🔑 默认密码: {{ .ClientPassword }}\n\n请输入您的密码。" -"email_prompt" = "📧 默认邮箱: {{ .ClientEmail }}\n\n请输入您的邮箱。" -"comment_prompt" = "💬 默认评论: {{ .ClientComment }}\n\n请输入您的评论。" -"inbound_client_data_id" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!" -"inbound_client_data_pass" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 密码: {{ .ClientPass }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!" -"cancel" = "❌ 进程已取消!\n\n您可以随时使用 /start 重新开始。 🔄" -"error_add_client" = "⚠️ 错误:\n\n {{ .error }}" -"using_default_value" = "好的,我会使用默认值。 😊" -"incorrect_input" = "您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫" -"AreYouSure" = "你确定吗?🤔" -"SuccessResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ✅ 成功" -"FailedResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 所有客户的流量重置已完成。" - -[tgbot.buttons] -"closeKeyboard" = "❌ 关闭键盘" -"cancel" = "❌ 取消" -"cancelReset" = "❌ 取消重置" -"cancelIpLimit" = "❌ 取消 IP 限制" -"confirmResetTraffic" = "✅ 确认重置流量?" -"confirmClearIps" = "✅ 确认清除 IP?" -"confirmRemoveTGUser" = "✅ 确认移除 Telegram 用户?" -"confirmToggle" = "✅ 确认启用/禁用用户?" -"dbBackup" = "获取数据库备份" -"serverUsage" = "服务器使用情况" -"getInbounds" = "获取入站信息" -"depleteSoon" = "即将耗尽" -"clientUsage" = "获取使用情况" -"onlines" = "在线客户端" -"commands" = "命令" -"refresh" = "🔄 刷新" -"clearIPs" = "❌ 清除 IP" -"removeTGUser" = "❌ 移除 Telegram 用户" -"selectTGUser" = "👤 选择 Telegram 用户" -"selectOneTGUser" = "👤 选择一个 Telegram 用户:" -"resetTraffic" = "📈 重置流量" -"resetExpire" = "📅 更改到期日期" -"ipLog" = "🔢 IP 日志" -"ipLimit" = "🔢 IP 限制" -"setTGUser" = "👤 设置 Telegram 用户" -"toggle" = "🔘 启用/禁用" -"custom" = "🔢 风俗" -"confirmNumber" = "✅ 确认: {{ .Num }}" -"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}" -"limitTraffic" = "🚧 流量限制" -"getBanLogs" = "禁止日志" -"allClients" = "所有客户" -"addClient" = "添加客户" -"submitDisable" = "提交为禁用 ☑️" -"submitEnable" = "提交为启用 ✅" -"use_default" = "🏷️ 使用默认" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 密码" -"change_email" = "⚙️📧 邮箱" -"change_comment" = "⚙️💬 评论" -"ResetAllTraffics" = "重置所有流量" -"SortedTrafficUsageReport" = "排序的流量使用报告" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作错误。" -"getInboundsFailed" = "❌ 获取入站信息失败。" -"getClientsFailed" = "❌ 获取客户失败。" -"canceled" = "❌ {{ .Email }}:操作已取消。" -"clientRefreshSuccess" = "✅ {{ .Email }}:客户端刷新成功。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IP 刷新成功。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。" -"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制保存成功。" -"expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。" -"resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。" -"getIpLog" = "✅ {{ .Email }}:获取 IP 日志。" -"getUserInfo" = "✅ {{ .Email }}:获取 Telegram 用户信息。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 用户已成功移除。" -"enableSuccess" = "✅ {{ .Email }}:已成功启用。" -"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" -"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:{{ .TgUserID }}" -"chooseClient" = "为入站 {{ .Inbound }} 选择一个客户" -"chooseInbound" = "选择一个入站" diff --git a/web/translation/translate.zh_TW.toml b/web/translation/translate.zh_TW.toml deleted file mode 100644 index a00cea72..00000000 --- a/web/translation/translate.zh_TW.toml +++ /dev/null @@ -1,877 +0,0 @@ -"username" = "使用者名稱" -"password" = "密碼" -"login" = "登入" -"confirm" = "確定" -"cancel" = "取消" -"close" = "關閉" -"save" = "儲存" -"logout" = "登出" -"create" = "建立" -"update" = "更新" -"copy" = "複製" -"copied" = "已複製" -"download" = "下載" -"remark" = "備註" -"enable" = "啟用" -"protocol" = "協議" -"search" = "搜尋" -"filter" = "篩選" -"loading" = "載入中..." -"second" = "秒" -"minute" = "分鐘" -"hour" = "小時" -"day" = "天" -"check" = "檢視" -"indefinite" = "無限期" -"unlimited" = "無限制" -"none" = "無" -"qrCode" = "二維碼" -"info" = "更多資訊" -"edit" = "編輯" -"delete" = "刪除" -"reset" = "重置" -"noData" = "無數據。" -"copySuccess" = "複製成功" -"sure" = "確定" -"encryption" = "加密" -"useIPv4ForHost" = "使用 IPv4 連接主機" -"transmission" = "傳輸" -"host" = "主機" -"path" = "路徑" -"camouflage" = "偽裝" -"status" = "狀態" -"enabled" = "開啟" -"disabled" = "關閉" -"depleted" = "耗盡" -"depletingSoon" = "即將耗盡" -"offline" = "離線" -"online" = "線上" -"domainName" = "域名" -"monitor" = "監聽" -"certificate" = "憑證" -"fail" = "失敗" -"comment" = "評論" -"success" = "成功" -"lastOnline" = "上次上線" -"getVersion" = "獲取版本" -"install" = "安裝" -"clients" = "客戶端" -"usage" = "使用情況" -"twoFactorCode" = "代碼" -"remained" = "剩餘" -"security" = "安全" -"secAlertTitle" = "安全警報" -"secAlertSsl" = "此連線不安全。在啟用 TLS 進行資料保護之前,請勿輸入敏感資訊。" -"secAlertConf" = "某些設定易受攻擊。建議加強安全協議以防止潛在漏洞。" -"secAlertSSL" = "面板缺少安全連線。請安裝 TLS 證書以保護資料安全。" -"secAlertPanelPort" = "面板預設埠存在安全風險。請配置隨機埠或特定埠。" -"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。" -"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。" -"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。" -"emptyDnsDesc" = "未添加DNS伺服器。" -"emptyFakeDnsDesc" = "未添加Fake DNS伺服器。" -"emptyBalancersDesc" = "未添加負載平衡器。" -"emptyReverseDesc" = "未添加反向代理。" -"somethingWentWrong" = "發生錯誤" - -[subscription] -"title" = "訂閱資訊" -"subId" = "訂閱 ID" -"status" = "狀態" -"downloaded" = "已下載" -"uploaded" = "已上傳" -"expiry" = "到期" -"totalQuota" = "總配額" -"individualLinks" = "個別連結" -"active" = "啟用" -"inactive" = "停用" -"unlimited" = "無限制" -"noExpiry" = "無到期" - -[menu] -"theme" = "主題" -"dark" = "深色" -"ultraDark" = "超深色" -"dashboard" = "系統狀態" -"inbounds" = "入站列表" -"settings" = "面板設定" -"xray" = "Xray 設定" -"logout" = "退出登入" -"link" = "管理" - -[pages.login] -"hello" = "你好" -"title" = "歡迎" -"loginAgain" = "登入時效已過,請重新登入" - -[pages.login.toasts] -"invalidFormData" = "資料格式錯誤" -"emptyUsername" = "請輸入使用者名稱" -"emptyPassword" = "請輸入密碼" -"wrongUsernameOrPassword" = "用戶名、密碼或雙重驗證碼無效。" -"successLogin" = "您已成功登入您的帳戶。" - -[pages.index] -"title" = "系統狀態" -"cpu" = "CPU" -"logicalProcessors" = "邏輯處理器" -"frequency" = "頻率" -"swap" = "交換空間" -"storage" = "儲存" -"memory" = "記憶體" -"threads" = "執行緒" -"xrayStatus" = "Xray" -"stopXray" = "停止" -"restartXray" = "重啟" -"xraySwitch" = "版本" -"xraySwitchClick" = "選擇你要切換到的版本" -"xraySwitchClickDesk" = "請謹慎選擇,因為較舊版本可能與當前配置不相容" -"xrayUpdates" = "Xray 更新" -"updatePanel" = "更新面板" -"panelUpdateDesc" = "這將把 3X-UI 更新到最新版本並重新啟動面板服務。" -"currentPanelVersion" = "目前面板版本" -"latestPanelVersion" = "最新面板版本" -"panelUpToDate" = "面板已是最新" -"upToDate" = "已是最新" -"xrayStatusUnknown" = "未知" -"xrayStatusRunning" = "運行中" -"xrayStatusStop" = "停止" -"xrayStatusError" = "錯誤" -"xrayErrorPopoverTitle" = "執行Xray時發生錯誤" -"operationHours" = "系統正常執行時間" -"systemLoad" = "系統負載" -"systemLoadDesc" = "過去 1、5 和 15 分鐘的系統平均負載" -"connectionCount" = "連線數" -"ipAddresses" = "IP地址" -"toggleIpVisibility" = "切換IP可見性" -"overallSpeed" = "整體速度" -"upload" = "上傳" -"download" = "下載" -"totalData" = "總數據" -"sent" = "已發送" -"received" = "已接收" -"documentation" = "文件" -"xraySwitchVersionDialog" = "您確定要變更Xray版本嗎?" -"xraySwitchVersionDialogDesc" = "這將會把Xray版本變更為#version#。" -"xraySwitchVersionPopover" = "Xray 更新成功" -"panelUpdateDialog" = "您確定要更新面板嗎?" -"panelUpdateDialogDesc" = "這將把 3X-UI 更新到 #version# 並重新啟動面板服務。" -"panelUpdateCheckPopover" = "面板更新檢查失敗" -"panelUpdateStartedPopover" = "面板更新已開始" -"geofileUpdateDialog" = "您確定要更新地理檔案嗎?" -"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。" -"geofilesUpdateDialogDesc" = "這將更新所有文件。" -"geofilesUpdateAll" = "全部更新" -"geofileUpdatePopover" = "地理檔案更新成功" -"dontRefresh" = "安裝中,請勿重新整理此頁面" -"logs" = "日誌" -"config" = "配置" -"backup" = "備份和恢復" -"backupTitle" = "備份和恢復資料庫" -"exportDatabase" = "備份" -"exportDatabaseDesc" = "點擊下載包含當前資料庫備份的 .db 文件到您的設備。" -"importDatabase" = "恢復" -"importDatabaseDesc" = "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。" -"importDatabaseSuccess" = "資料庫匯入成功" -"importDatabaseError" = "匯入資料庫時發生錯誤" -"readDatabaseError" = "讀取資料庫時發生錯誤" -"getDatabaseError" = "檢索資料庫時發生錯誤" -"getConfigError" = "檢索設定檔時發生錯誤" -"customGeoTitle" = "自訂 GeoSite / GeoIP" -"customGeoAdd" = "新增" -"customGeoType" = "類型" -"customGeoAlias" = "別名" -"customGeoUrl" = "URL" -"customGeoEnabled" = "啟用" -"customGeoLastUpdated" = "上次更新" -"customGeoExtColumn" = "路由 (ext:…)" -"customGeoToastUpdateAll" = "所有自訂來源已更新" -"customGeoActions" = "操作" -"customGeoEdit" = "編輯" -"customGeoDelete" = "刪除" -"customGeoDownload" = "立即更新" -"customGeoModalAdd" = "新增自訂 geo" -"customGeoModalEdit" = "編輯自訂 geo" -"customGeoModalSave" = "儲存" -"customGeoDeleteConfirm" = "刪除此自訂 geo 來源?" -"customGeoRoutingHint" = "在路由規則中將值欄寫為 ext:檔案.dat:標籤(替換標籤)。" -"customGeoInvalidId" = "無效的資源 ID" -"customGeoAliasesError" = "載入自訂 geo 別名失敗" -"customGeoValidationAlias" = "別名只能包含小寫字母、數字、- 和 _" -"customGeoValidationUrl" = "URL 必須以 http:// 或 https:// 開頭" -"customGeoAliasPlaceholder" = "a-z 0-9 _ -" -"customGeoAliasLabelSuffix" = "(自訂)" -"customGeoToastList" = "自訂 geo 清單" -"customGeoToastAdd" = "新增自訂 geo" -"customGeoToastUpdate" = "更新自訂 geo" -"customGeoToastDelete" = "自訂 geofile「{{ .fileName }}」已刪除" -"customGeoToastDownload" = "geofile「{{ .fileName }}」已更新" -"customGeoErrInvalidType" = "類型必須是 geosite 或 geoip" -"customGeoErrAliasRequired" = "請填寫別名" -"customGeoErrAliasPattern" = "別名包含不允許的字元" -"customGeoErrAliasReserved" = "此別名已保留" -"customGeoErrUrlRequired" = "請填寫 URL" -"customGeoErrInvalidUrl" = "URL 無效" -"customGeoErrUrlScheme" = "URL 必須使用 http 或 https" -"customGeoErrUrlHost" = "URL 主機無效" -"customGeoErrDuplicateAlias" = "此類型已使用該別名" -"customGeoErrNotFound" = "找不到自訂 geo 來源" -"customGeoErrDownload" = "下載失敗" -"customGeoErrUpdateAllIncomplete" = "有一個或多個自訂 geo 來源更新失敗" -"customGeoEmpty" = "尚無自訂 geo 來源 — 點擊「新增」以建立" - -[pages.inbounds] -"allTimeTraffic" = "累計總流量" -"allTimeTrafficUsage" = "所有时间总使用量" -"title" = "入站列表" -"totalDownUp" = "總上傳 / 下載" -"totalUsage" = "總用量" -"inboundCount" = "入站數量" -"operate" = "選單" -"enable" = "啟用" -"remark" = "備註" -"protocol" = "協議" -"port" = "埠" -"portMap" = "埠映射" -"traffic" = "流量" -"details" = "詳細資訊" -"transportConfig" = "傳輸配置" -"expireDate" = "到期時間" -"createdAt" = "建立時間" -"updatedAt" = "更新時間" -"resetTraffic" = "重置流量" -"addInbound" = "新增入站" -"generalActions" = "通用操作" -"autoRefresh" = "自動刷新" -"autoRefreshInterval" = "間隔" -"modifyInbound" = "修改入站" -"deleteInbound" = "刪除入站" -"deleteInboundContent" = "確定要刪除入站嗎?" -"deleteClient" = "刪除客戶端" -"deleteClientContent" = "確定要刪除客戶端嗎?" -"resetTrafficContent" = "確定要重置流量嗎?" -"copyLink" = "複製連結" -"address" = "地址" -"network" = "網路" -"destinationPort" = "目標埠" -"targetAddress" = "目標地址" -"monitorDesc" = "留空表示監聽所有 IP" -"meansNoLimit" = "= 無限制(單位:GB)" -"totalFlow" = "總流量" -"leaveBlankToNeverExpire" = "留空表示永不過期" -"noRecommendKeepDefault" = "建議保留預設值" -"certificatePath" = "檔案路徑" -"certificateContent" = "檔案內容" -"publicKey" = "公鑰" -"privatekey" = "私鑰" -"clickOnQRcode" = "點選二維碼複製" -"client" = "客戶" -"export" = "匯出連結" -"clone" = "複製" -"cloneInbound" = "複製" -"cloneInboundContent" = "此入站規則除埠(Port)、監聽 IP(Listening IP)和客戶端(Clients)以外的所有配置都將應用於克隆" -"cloneInboundOk" = "建立克隆" -"resetAllTraffic" = "重置所有入站流量" -"resetAllTrafficTitle" = "重置所有入站流量" -"resetAllTrafficContent" = "確定要重置所有入站流量嗎?" -"resetInboundClientTraffics" = "重置客戶端流量" -"resetInboundClientTrafficTitle" = "重置所有客戶端流量" -"resetInboundClientTrafficContent" = "確定要重置此入站客戶端的所有流量嗎?" -"resetAllClientTraffics" = "重置所有客戶端流量" -"resetAllClientTrafficTitle" = "重置所有客戶端流量" -"resetAllClientTrafficContent" = "確定要重置所有客戶端的所有流量嗎?" -"delDepletedClients" = "刪除流量耗盡的客戶端" -"delDepletedClientsTitle" = "刪除流量耗盡的客戶端" -"delDepletedClientsContent" = "確定要刪除所有流量耗盡的客戶端嗎?" -"email" = "電子郵件" -"emailDesc" = "電子郵件必須完全唯一" -"IPLimit" = "IP 限制" -"IPLimitDesc" = "如果數量超過設定值,則禁用入站流量。(0 = 禁用)" -"IPLimitlog" = "IP 日誌" -"IPLimitlogDesc" = "IP 歷史日誌(要啟用被禁用的入站流量,請清除日誌)" -"IPLimitlogclear" = "清除日誌" -"setDefaultCert" = "從面板設定證書" -"telegramDesc" = "請提供Telegram聊天ID。(在機器人中使用'/id'命令)或(@userinfobot" -"subscriptionDesc" = "要找到你的訂閱 URL,請導航到“詳細資訊”。此外,你可以為多個客戶端使用相同的名稱。" -"info" = "資訊" -"same" = "相同" -"inboundData" = "入站資料" -"exportInbound" = "匯出入站規則" -"import" = "匯入" -"importInbound" = "匯入入站規則" -"periodicTrafficResetTitle" = "流量重置" -"periodicTrafficResetDesc" = "按指定間隔自動重置流量計數器" -"lastReset" = "上次重置" - -[pages.client] -"add" = "新增客戶端" -"edit" = "編輯客戶端" -"submitAdd" = "新增客戶端" -"submitEdit" = "儲存修改" -"clientCount" = "客戶端數量" -"bulk" = "批量建立" -"copyFromInbound" = "從入站複製用戶端" -"copyToInbound" = "複製用戶端到" -"copySelected" = "複製所選" -"copySource" = "來源" -"copyEmailPreview" = "最終郵箱預覽" -"copySelectSourceFirst" = "請先選擇來源入站。" -"copyResult" = "複製結果" -"copyResultSuccess" = "複製成功" -"copyResultNone" = "沒有可複製的內容:未選擇用戶端或來源為空" -"copyResultErrors" = "複製錯誤" -"copyFlowLabel" = "新用戶端的 Flow (VLESS)" -"copyFlowHint" = "套用於所有複製的用戶端。留空則略過。" -"selectAll" = "全選" -"clearAll" = "全不選" -"method" = "方法" -"first" = "置頂" -"last" = "置底" -"prefix" = "字首" -"postfix" = "字尾" -"delayedStart" = "首次使用後開始" -"expireDays" = "期間" -"days" = "天" -"renew" = "自動續訂" -"renewDesc" = "到期後自動續訂。(0 = 禁用)(單位: 天)" - -[pages.inbounds.periodicTrafficReset] -"never" = "從不" -"daily" = "每日" -"weekly" = "每週" -"monthly" = "每月" -"hourly" = "每小時" - -[pages.inbounds.toasts] -"obtain" = "獲取" -"updateSuccess" = "更新成功" -"logCleanSuccess" = "日誌已清除" -"inboundsUpdateSuccess" = "入站連接已成功更新" -"inboundUpdateSuccess" = "入站連接已成功更新" -"inboundCreateSuccess" = "入站連接已成功建立" -"inboundDeleteSuccess" = "入站連接已成功刪除" -"inboundClientAddSuccess" = "已新增入站客戶端" -"inboundClientDeleteSuccess" = "入站客戶端已刪除" -"inboundClientUpdateSuccess" = "入站客戶端已更新" -"delDepletedClientsSuccess" = "所有耗盡客戶端已刪除" -"resetAllClientTrafficSuccess" = "客戶端所有流量已重置" -"resetAllTrafficSuccess" = "所有流量已重置" -"resetInboundClientTrafficSuccess" = "流量已重置" -"trafficGetError" = "取得流量資料時發生錯誤" -"getNewX25519CertError" = "取得X25519憑證時發生錯誤。" -"getNewmldsa65Error" = "取得mldsa65憑證時發生錯誤。" -"getNewVlessEncError" = "取得VlessEnc憑證時發生錯誤。" - -[pages.inbounds.stream.general] -"request" = "請求" -"response" = "響應" -"name" = "名稱" -"value" = "值" - -[pages.inbounds.stream.tcp] -"version" = "版本" -"method" = "方法" -"path" = "路徑" -"status" = "狀態" -"statusDescription" = "狀態說明" -"requestHeader" = "請求頭" -"responseHeader" = "響應頭" - -[pages.settings] -"title" = "面板設定" -"save" = "儲存" -"infoDesc" = "此處的所有更改都需要儲存並重啟面板才能生效" -"restartPanel" = "重啟面板" -"restartPanelDesc" = "確定要重啟面板嗎?若重啟後無法訪問面板,請前往伺服器檢視面板日誌資訊" -"restartPanelSuccess" = "面板已成功重新啟動" -"actions" = "操作" -"resetDefaultConfig" = "重置為預設配置" -"panelSettings" = "常規" -"securitySettings" = "安全設定" -"TGBotSettings" = "Telegram 機器人配置" -"panelListeningIP" = "面板監聽 IP" -"panelListeningIPDesc" = "預設留空監聽所有 IP" -"panelListeningDomain" = "面板監聽域名" -"panelListeningDomainDesc" = "預設情況下留空以監視所有域名和 IP 地址" -"panelPort" = "面板監聽埠" -"panelPortDesc" = "重啟面板生效" -"publicKeyPath" = "面板證書公鑰檔案路徑" -"publicKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑" -"privateKeyPath" = "面板證書金鑰檔案路徑" -"privateKeyPathDesc" = "填寫一個 '/' 開頭的絕對路徑" -"panelUrlPath" = "面板 url 根路徑" -"panelUrlPathDesc" = "必須以 '/' 開頭,以 '/' 結尾" -"pageSize" = "分頁大小" -"pageSizeDesc" = "定義入站表的頁面大小。設定 0 表示禁用" -"remarkModel" = "備註模型和分隔符" -"datepicker" = "日期選擇器" -"datepickerPlaceholder" = "選擇日期" -"datepickerDescription" = "選擇器日曆類型指定到期日期" -"sampleRemark" = "備註示例" -"oldUsername" = "原使用者名稱" -"currentPassword" = "原密碼" -"newUsername" = "新使用者名稱" -"newPassword" = "新密碼" -"telegramBotEnable" = "啟用 Telegram 機器人" -"telegramBotEnableDesc" = "啟用 Telegram 機器人功能" -"telegramToken" = "Telegram 機器人令牌(token)" -"telegramTokenDesc" = "從 '@BotFather' 獲取的 Telegram 機器人令牌" -"telegramProxy" = "SOCKS5 Proxy" -"telegramProxyDesc" = "啟用 SOCKS5 代理連線到 Telegram(根據指南調整設定)" -"telegramAPIServer" = "Telegram API Server" -"telegramAPIServerDesc" = "要使用的 Telegram API 伺服器。留空以使用預設伺服器。" -"telegramChatId" = "管理員聊天 ID" -"telegramChatIdDesc" = "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 @userinfobot 獲取,或在機器人中使用 '/id' 命令獲取)" -"telegramNotifyTime" = "通知時間" -"telegramNotifyTimeDesc" = "設定週期性的 Telegram 機器人通知時間(使用 crontab 時間格式)" -"tgNotifyBackup" = "資料庫備份" -"tgNotifyBackupDesc" = "傳送帶有報告的資料庫備份檔案" -"tgNotifyLogin" = "登入通知" -"tgNotifyLoginDesc" = "當有人試圖登入你的面板時顯示使用者名稱、IP 地址和時間" -"sessionMaxAge" = "會話時長" -"sessionMaxAgeDesc" = "保持登入狀態的時長(單位:分鐘)" -"expireTimeDiff" = "到期通知閾值" -"expireTimeDiffDesc" = "達到此閾值時,將收到有關到期時間的通知(單位:天)" -"trafficDiff" = "流量耗盡閾值" -"trafficDiffDesc" = "達到此閾值時,將收到有關流量耗盡的通知(單位:GB)" -"tgNotifyCpu" = "CPU 負載通知閾值" -"tgNotifyCpuDesc" = "CPU 負載超過此閾值時,將收到通知(單位:%)" -"timeZone" = "時區" -"timeZoneDesc" = "定時任務將按照該時區的時間執行" -"subSettings" = "訂閱設定" -"subEnable" = "啟用訂閱服務" -"subEnableDesc" = "啟用訂閱服務功能" -"subJsonEnable" = "獨立啟用/停用 JSON 訂閱端點。" -"subTitle" = "訂閱標題" -"subTitleDesc" = "在VPN客戶端中顯示的標題" -"subSupportUrl" = "支援連結" -"subSupportUrlDesc" = "VPN 用戶端中顯示的技術支援連結" -"subProfileUrl" = "個人資料連結" -"subProfileUrlDesc" = "VPN 用戶端中顯示的網站連結" -"subAnnounce" = "公告" -"subAnnounceDesc" = "VPN 用戶端中顯示的公告文字" -"subEnableRouting" = "啟用路由" -"subEnableRoutingDesc" = "在 VPN 用戶端中啟用路由的全域設定。(僅限 Happ)" -"subRoutingRules" = "路由規則" -"subRoutingRulesDesc" = "VPN 用戶端的全域路由規則。(僅限 Happ)" -"subListen" = "監聽 IP" -"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)" -"subPort" = "監聽埠" -"subPortDesc" = "訂閱服務監聽的埠號(必須是未使用的埠)" -"subCertPath" = "公鑰路徑" -"subCertPathDesc" = "訂閱服務使用的公鑰檔案路徑(以 '/' 開頭)" -"subKeyPath" = "私鑰路徑" -"subKeyPathDesc" = "訂閱服務使用的私鑰檔案路徑(以 '/' 開頭)" -"subPath" = "URI 路徑" -"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)" -"subDomain" = "監聽域名" -"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)" -"subUpdates" = "更新間隔" -"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)" -"subEncrypt" = "編碼" -"subEncryptDesc" = "訂閱服務返回的內容將採用 Base64 編碼" -"subShowInfo" = "顯示使用資訊" -"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊" -"subURI" = "反向代理 URI" -"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑" -"externalTrafficInformEnable" = "外部交通通知" -"externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API" -"externalTrafficInformURI" = "外部流量通知 URI" -"externalTrafficInformURIDesc" = "流量更新將會傳送到此 URI" -"restartXrayOnClientDisable" = "用戶自動停用後重新啟動 Xray" -"restartXrayOnClientDisableDesc" = "當用戶因到期或流量上限而被自動停用時,重新啟動 Xray。" -"fragment" = "分片" -"fragmentDesc" = "啟用 TLS hello 資料包分片" -"fragmentSett" = "設定" -"noisesDesc" = "啟用 Noises." -"noisesSett" = "Noises 設定" -"mux" = "多路複用器" -"muxDesc" = "在已建立的資料流內傳輸多個獨立的資料流" -"muxSett" = "複用器設定" -"direct" = "直接連線" -"directDesc" = "直接與特定國家的域或IP範圍建立連線" -"notifications" = "通知" -"certs" = "證書" -"externalTraffic" = "外部流量" -"dateAndTime" = "日期和時間" -"proxyAndServer" = "代理和伺服器" -"intervals" = "間隔" -"information" = "資訊" -"language" = "語言" -"telegramBotLanguage" = "Telegram 機器人語言" - -[pages.xray] -"title" = "Xray 配置" -"save" = "儲存" -"restart" = "重新啟動 Xray" -"restartSuccess" = "Xray 已成功重新啟動" -"stopSuccess" = "Xray 已成功停止" -"restartError" = "重新啟動Xray時發生錯誤。" -"stopError" = "停止Xray時發生錯誤。" -"basicTemplate" = "基礎配置" -"advancedTemplate" = "高階配置" -"generalConfigs" = "常規配置" -"generalConfigsDesc" = "這些選項將決定常規配置" -"logConfigs" = "日誌" -"logConfigsDesc" = "日誌可能會影響伺服器的效能,建議僅在需要時啟用" -"blockConfigsDesc" = "這些選項將阻止使用者連線到特定協議和網站" -"basicRouting" = "基本路由" -"blockConnectionsConfigsDesc" = "這些選項將根據特定的請求國家阻止流量。" -"directConnectionsConfigsDesc" = "直接連線確保特定的流量不會通過其他伺服器路由。" -"blockips" = "阻止IP" -"blockdomains" = "阻止域名" -"directips" = "直接IP" -"directdomains" = "直接域名" -"ipv4Routing" = "IPv4 路由" -"ipv4RoutingDesc" = "此選項將僅通過 IPv4 路由到目標域" -"warpRouting" = "WARP 路由" -"warpRoutingDesc" = "注意:在使用這些選項之前,請按照面板 GitHub 上的步驟在你的伺服器上以 socks5 代理模式安裝 WARP。WARP 將通過 Cloudflare 伺服器將流量路由到網站。" -"nordRouting" = "NordVPN 路由" -"nordRoutingDesc" = "這些選項將根據特定目的地通過 NordVPN 路由流量。" -"Template" = "高階 Xray 配置模板" -"TemplateDesc" = "最終的 Xray 配置檔案將基於此模板生成" -"FreedomStrategy" = "Freedom 協議策略" -"FreedomStrategyDesc" = "設定 Freedom 協議中網路的輸出策略" -"RoutingStrategy" = "配置路由域策略" -"RoutingStrategyDesc" = "設定 DNS 解析的整體路由策略" -"outboundTestUrl" = "出站測試 URL" -"outboundTestUrlDesc" = "測試出站連線時使用的 URL" -"Torrent" = "遮蔽 BitTorrent 協議" -"Inbounds" = "入站規則" -"InboundsDesc" = "接受來自特定客戶端的流量" -"Outbounds" = "出站規則" -"Balancers" = "負載均衡" -"OutboundsDesc" = "設定出站流量傳出方式" -"Routings" = "路由規則" -"RoutingsDesc" = "每條規則的優先順序都很重要" -"completeTemplate" = "全部" -"logLevel" = "日誌級別" -"logLevelDesc" = "錯誤日誌的日誌級別,用於指示需要記錄的資訊" -"accessLog" = "訪問日誌" -"accessLogDesc" = "訪問日誌的檔案路徑。特殊值 'none' 禁用訪問日誌" -"errorLog" = "錯誤日誌" -"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌" -"dnsLog" = "DNS 日誌" -"dnsLogDesc" = "是否啟用 DNS 查詢日誌" -"maskAddress" = "隱藏地址" -"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。" -"statistics" = "統計" -"statsInboundUplink" = "入站上傳統計" -"statsInboundUplinkDesc" = "啟用所有入站代理的上行流量統計收集。" -"statsInboundDownlink" = "入站下載統計" -"statsInboundDownlinkDesc" = "啟用所有入站代理的下行流量統計收集。" -"statsOutboundUplink" = "出站上傳統計" -"statsOutboundUplinkDesc" = "啟用所有出站代理的上行流量統計收集。" -"statsOutboundDownlink" = "出站下載統計" -"statsOutboundDownlinkDesc" = "啟用所有出站代理的下行流量統計收集。" - -[pages.xray.rules] -"first" = "置頂" -"last" = "置底" -"up" = "向上" -"down" = "向下" -"source" = "來源" -"dest" = "目的地址" -"inbound" = "入站" -"outbound" = "出站" -"balancer" = "負載均衡" -"info" = "資訊" -"add" = "新增規則" -"edit" = "編輯規則" -"useComma" = "逗號分隔的項目" - -[pages.xray.outbound] -"addOutbound" = "新增出站" -"addReverse" = "新增反向" -"editOutbound" = "編輯出站" -"editReverse" = "編輯反向" -"reverseTag" = "反向標籤" -"reverseTagDesc" = "VLESS 簡易反向代理出站標籤。留空則停用。設定後,此客戶端的連線可作為反向代理隧道。" -"reverseTagPlaceholder" = "出站標籤(留空則停用)" -"tag" = "標籤" -"tagDesc" = "唯一標籤" -"address" = "地址" -"reverse" = "反向" -"domain" = "域名" -"type" = "類型" -"bridge" = "Bridge" -"portal" = "Portal" -"link" = "連結" -"intercon" = "互連" -"settings" = "設定" -"accountInfo" = "帳戶資訊" -"outboundStatus" = "出站狀態" -"sendThrough" = "傳送通過" -"test" = "測試" -"testResult" = "測試結果" -"testing" = "正在測試連接..." -"testSuccess" = "測試成功" -"testFailed" = "測試失敗" -"testError" = "測試出站失敗" -"nordvpn" = "NordVPN" -"accessToken" = "訪問令牌" -"country" = "國家" -"server" = "伺服器" -"city" = "城市" -"allCities" = "所有城市" -"privateKey" = "私密金鑰" -"load" = "負載" - -[pages.xray.balancer] -"addBalancer" = "新增負載均衡" -"editBalancer" = "編輯負載均衡" -"balancerStrategy" = "策略" -"balancerSelectors" = "選擇器" -"tag" = "標籤" -"tagDesc" = "唯一標籤" -"balancerDesc" = "無法同時使用 balancerTag 和 outboundTag。如果同時使用,則只有 outboundTag 會生效。" - -[pages.xray.wireguard] -"secretKey" = "金鑰" -"publicKey" = "公鑰" -"allowedIPs" = "允許的 IP" -"endpoint" = "端點" -"psk" = "共享金鑰" -"domainStrategy" = "域策略" - -[pages.xray.tun] -"nameDesc" = "TUN 介面的名稱。預設值為 'xray0'" -"mtuDesc" = "最大傳輸單元。資料包的最大大小。預設值為 1500" -"userLevel" = "用戶級別" -"userLevelDesc" = "通過此入站的所有連接都將使用此用戶級別。預設值為 0" - -[pages.xray.dns] -"enable" = "啟用 DNS" -"enableDesc" = "啟用內建 DNS 伺服器" -"tag" = "DNS 入站標籤" -"tagDesc" = "此標籤將在路由規則中可用作入站標籤" -"clientIp" = "客戶端IP" -"clientIpDesc" = "用於在DNS查詢期間通知伺服器指定的IP位置" -"disableCache" = "禁用快取" -"disableCacheDesc" = "禁用DNS快取" -"disableFallback" = "禁用回退" -"disableFallbackDesc" = "禁用回退DNS查詢" -"disableFallbackIfMatch" = "匹配時禁用回退" -"disableFallbackIfMatchDesc" = "當DNS伺服器的匹配域名列表命中時,禁用回退DNS查詢" -"enableParallelQuery" = "啟用並行查詢" -"enableParallelQueryDesc" = "啟用並行DNS查詢到多個伺服器以實現更快的解析" -"strategy" = "查詢策略" -"strategyDesc" = "解析域名的總體策略" -"add" = "新增伺服器" -"edit" = "編輯伺服器" -"domains" = "域" -"expectIPs" = "預期 IP" -"unexpectIPs" = "意外IP" -"useSystemHosts" = "使用系統Hosts" -"useSystemHostsDesc" = "使用已安裝系統的hosts檔案" -"usePreset" = "使用範本" -"dnsPresetTitle" = "DNS範本" -"dnsPresetFamily" = "家庭" - -[pages.xray.fakedns] -"add" = "新增假 DNS" -"edit" = "編輯假 DNS" -"ipPool" = "IP 池子網" -"poolSize" = "池大小" - -[pages.settings.security] -"admin" = "管理員憑證" -"twoFactor" = "雙重驗證" -"twoFactorEnable" = "啟用2FA" -"twoFactorEnableDesc" = "增加額外的驗證層以提高安全性。" -"twoFactorModalSetTitle" = "啟用雙重認證" -"twoFactorModalDeleteTitle" = "停用雙重認證" -"twoFactorModalSteps" = "要設定雙重認證,請執行以下步驟:" -"twoFactorModalFirstStep" = "1. 在認證應用程式中掃描此QR碼,或複製QR碼附近的令牌並貼到應用程式中" -"twoFactorModalSecondStep" = "2. 輸入應用程式中的驗證碼" -"twoFactorModalRemoveStep" = "輸入應用程式中的驗證碼以移除雙重認證。" -"twoFactorModalChangeCredentialsTitle" = "更改憑證" -"twoFactorModalChangeCredentialsStep" = "輸入應用程式中的代碼以更改管理員憑證。" -"twoFactorModalSetSuccess" = "雙重身份驗證已成功建立" -"twoFactorModalDeleteSuccess" = "雙重身份驗證已成功刪除" -"twoFactorModalError" = "驗證碼錯誤" - -[pages.settings.toasts] -"modifySettings" = "參數已更改。" -"getSettings" = "取得參數時發生錯誤" -"modifyUserError" = "變更管理員憑證時發生錯誤。" -"modifyUser" = "您已成功變更管理員憑證。" -"originalUserPassIncorrect" = "原使用者名稱或原密碼錯誤" -"userPassMustBeNotEmpty" = "新使用者名稱和新密碼不能為空" -"getOutboundTrafficError" = "取得出站流量錯誤" -"resetOutboundTrafficError" = "重設出站流量錯誤" - -[tgbot] -"keyboardClosed" = "❌ 自定義鍵盤已關閉!" -"noResult" = "❗ 沒有結果!" -"noQuery" = "❌ 未找到查詢!請再次使用該命令!" -"wentWrong" = "❌ 出了點問題!" -"noIpRecord" = "❗ 沒有IP記錄!" -"noInbounds" = "❗ 未找到入站!" -"unlimited" = "♾ 無限(重置)" -"add" = "添加" -"month" = "月" -"months" = "月" -"day" = "天" -"days" = "天" -"hours" = "小時" -"minutes" = "分鐘" -"unknown" = "未知" -"inbounds" = "入站" -"clients" = "客戶端" -"offline" = "🔴 離線" -"online" = "🟢 在線" - -[tgbot.commands] -"unknown" = "❗ 未知命令" -"pleaseChoose" = "👇 請選擇:\r\n" -"help" = "🤖 歡迎使用本機器人!它旨在為您提供來自伺服器的特定資料,並允許您進行必要的修改。\r\n\r\n" -"start" = "👋 你好,{{ .Firstname }}。\r\n" -"welcome" = "🤖 歡迎來到 {{ .Hostname }} 管理機器人。\r\n" -"status" = "✅ 機器人正常執行!" -"usage" = "❗ 請輸入要搜尋的文字!" -"getID" = "🆔 您的 ID 為:{{ .ID }}" -"helpAdminCommands" = "要重新啟動 Xray Core:\r\n/restart\r\n\r\n要搜尋客戶電子郵件:\r\n/usage [電子郵件]\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n/inbound [備註]\r\n\r\nTelegram聊天ID:\r\n/id" -"helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n/usage [電子郵件]\r\n\r\nTelegram聊天ID:\r\n/id" -"restartUsage" = "\r\n\r\n/restart" -"restartSuccess" = "✅ 操作成功!" -"restartFailed" = "❗ 操作錯誤。\r\n\r\n錯誤: {{ .Error }}." -"xrayNotRunning" = "❗ Xray Core 未運行。" -"startDesc" = "顯示主選單" -"helpDesc" = "機器人幫助" -"statusDesc" = "檢查機器人狀態" -"idDesc" = "顯示您的 Telegram ID" - -[tgbot.messages] -"cpuThreshold" = "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%" -"selectUserFailed" = "❌ 使用者選擇錯誤!" -"userSaved" = "✅ 電報使用者已儲存。" -"loginSuccess" = "✅ 成功登入到面板。\r\n" -"loginFailed" = "❗️ 面板登入失敗。\r\n" -"2faFailed" = "2FA 失敗" -"report" = "🕰 定時報告:{{ .RunTime }}\r\n" -"datetime" = "⏰ 日期時間:{{ .DateTime }}\r\n" -"hostname" = "💻 主機名:{{ .Hostname }}\r\n" -"version" = "🚀 X-UI 版本:{{ .Version }}\r\n" -"xrayVersion" = "📡 Xray 版本: {{ .XrayVersion }}\r\n" -"ipv6" = "🌐 IPv6:{{ .IPv6 }}\r\n" -"ipv4" = "🌐 IPv4:{{ .IPv4 }}\r\n" -"ip" = "🌐 IP:{{ .IP }}\r\n" -"ips" = "🔢 IP 地址:\r\n{{ .IPs }}\r\n" -"serverUpTime" = "⏳ 伺服器執行時間:{{ .UpTime }} {{ .Unit }}\r\n" -"serverLoad" = "📈 伺服器負載:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n" -"serverMemory" = "📋 伺服器記憶體:{{ .Current }}/{{ .Total }}\r\n" -"tcpCount" = "🔹 TCP 連線數:{{ .Count }}\r\n" -"udpCount" = "🔸 UDP 連線數:{{ .Count }}\r\n" -"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" -"xrayStatus" = "ℹ️ Xray 狀態:{{ .State }}\r\n" -"username" = "👤 使用者名稱:{{ .Username }}\r\n" -"reason" = "❗️ 原因:{{ .Reason }}\r\n" -"time" = "⏰ 時間:{{ .Time }}\r\n" -"inbound" = "📍 入站:{{ .Remark }}\r\n" -"port" = "🔌 埠:{{ .Port }}\r\n" -"expire" = "📅 過期日期:{{ .Time }}\r\n" -"expireIn" = "📅 剩餘時間:{{ .Time }}\r\n" -"active" = "💡 啟用:{{ .Enable }}\r\n" -"enabled" = "🚨 已啟用:{{ .Enable }}\r\n" -"online" = "🌐 連線狀態:{{ .Status }}\r\n" -"lastOnline" = "🔙 上次上線: {{ .Time }}\r\n" -"email" = "📧 郵箱:{{ .Email }}\r\n" -"upload" = "🔼 上傳↑:{{ .Upload }}\r\n" -"download" = "🔽 下載↓:{{ .Download }}\r\n" -"total" = "📊 總計:{{ .UpDown }} / {{ .Total }}\r\n" -"TGUser" = "👤 電報使用者:{{ .TelegramID }}\r\n" -"exhaustedMsg" = "🚨 耗盡的 {{ .Type }}:\r\n" -"exhaustedCount" = "🚨 耗盡的 {{ .Type }} 數量:\r\n" -"onlinesCount" = "🌐 線上客戶:{{ .Count }}\r\n" -"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" -"depleteSoon" = "🔜 即將耗盡:{{ .Deplete }}\r\n\r\n" -"backupTime" = "🗄 備份時間:{{ .Time }}\r\n" -"refreshedOn" = "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n" -"yes" = "✅ 是的" -"no" = "❌ 沒有" -"received_id" = "🔑📥 ID 已更新。" -"received_password" = "🔑📥 密碼已更新。" -"received_email" = "📧📥 電子郵件已更新。" -"received_comment" = "💬📥 評論已更新。" -"id_prompt" = "🔑 預設 ID: {{ .ClientId }}\n\n請輸入您的 ID。" -"pass_prompt" = "🔑 預設密碼: {{ .ClientPassword }}\n\n請輸入您的密碼。" -"email_prompt" = "📧 預設電子郵件: {{ .ClientEmail }}\n\n請輸入您的電子郵件。" -"comment_prompt" = "💬 預設評論: {{ .ClientComment }}\n\n請輸入您的評論。" -"inbound_client_data_id" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!" -"inbound_client_data_pass" = "🔄 入站: {{ .InboundRemark }}\n\n🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!" -"cancel" = "❌ 程序已取消!\n\n您可以隨時使用 /start 重新開始。 🔄" -"error_add_client" = "⚠️ 錯誤:\n\n {{ .error }}" -"using_default_value" = "好的,我會使用預設值。 😊" -"incorrect_input" = "您的輸入無效。\n短語應連續輸入,不能有空格。\n正確示例: aaaaaa\n錯誤示例: aaa aaa 🚫" -"AreYouSure" = "你確定嗎?🤔" -"SuccessResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ✅ 成功" -"FailedResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]" -"FinishProcess" = "🔚 所有客戶的流量重置已完成。" - -[tgbot.buttons] -"closeKeyboard" = "❌ 關閉鍵盤" -"cancel" = "❌ 取消" -"cancelReset" = "❌ 取消重置" -"cancelIpLimit" = "❌ 取消 IP 限制" -"confirmResetTraffic" = "✅ 確認重置流量?" -"confirmClearIps" = "✅ 確認清除 IP?" -"confirmRemoveTGUser" = "✅ 確認移除 Telegram 使用者?" -"confirmToggle" = "✅ 確認啟用/禁用使用者?" -"dbBackup" = "獲取資料庫備份" -"serverUsage" = "伺服器使用情況" -"getInbounds" = "獲取入站資訊" -"depleteSoon" = "即將耗盡" -"clientUsage" = "獲取使用情況" -"onlines" = "線上客戶端" -"commands" = "命令" -"refresh" = "🔄 重新整理" -"clearIPs" = "❌ 清除 IP" -"removeTGUser" = "❌ 移除 Telegram 使用者" -"selectTGUser" = "👤 選擇 Telegram 使用者" -"selectOneTGUser" = "👤 選擇一個 Telegram 使用者:" -"resetTraffic" = "📈 重置流量" -"resetExpire" = "📅 更改到期日期" -"ipLog" = "🔢 IP 日誌" -"ipLimit" = "🔢 IP 限制" -"setTGUser" = "👤 設定 Telegram 使用者" -"toggle" = "🔘 啟用/禁用" -"custom" = "🔢 風俗" -"confirmNumber" = "✅ 確認: {{ .Num }}" -"confirmNumberAdd" = "✅ 確認新增:{{ .Num }}" -"limitTraffic" = "🚧 流量限制" -"getBanLogs" = "禁止日誌" -"allClients" = "所有客戶" -"addClient" = "新增客戶" -"submitDisable" = "以停用方式送出 ☑️" -"submitEnable" = "以啟用方式送出 ✅" -"use_default" = "🏷️ 使用預設值" -"change_id" = "⚙️🔑 ID" -"change_password" = "⚙️🔑 密碼" -"change_email" = "⚙️📧 電子郵件" -"change_comment" = "⚙️💬 評論" -"ResetAllTraffics" = "重設所有流量" -"SortedTrafficUsageReport" = "排序過的流量使用報告" - -[tgbot.answers] -"successfulOperation" = "✅ 成功!" -"errorOperation" = "❗ 操作錯誤。" -"getInboundsFailed" = "❌ 獲取入站資訊失敗。" -"getClientsFailed" = "❌ 獲取客戶失敗。" -"canceled" = "❌ {{ .Email }}:操作已取消。" -"clientRefreshSuccess" = "✅ {{ .Email }}:客戶端重新整理成功。" -"IpRefreshSuccess" = "✅ {{ .Email }}:IP 重新整理成功。" -"TGIdRefreshSuccess" = "✅ {{ .Email }}:客戶端的 Telegram 使用者重新整理成功。" -"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" -"setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制儲存成功。" -"expireResetSuccess" = "✅ {{ .Email }}:過期天數已重置成功。" -"resetIpSuccess" = "✅ {{ .Email }}:成功儲存 IP 限制數量為 {{ .Count }}。" -"clearIpSuccess" = "✅ {{ .Email }}:IP 已成功清除。" -"getIpLog" = "✅ {{ .Email }}:獲取 IP 日誌。" -"getUserInfo" = "✅ {{ .Email }}:獲取 Telegram 使用者資訊。" -"removedTGUserSuccess" = "✅ {{ .Email }}:Telegram 使用者已成功移除。" -"enableSuccess" = "✅ {{ .Email }}:已成功啟用。" -"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" -"askToAddUserId" = "未找到您的配置!\r\n請向管理員詢問,在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID:{{ .TgUserID }}" -"chooseClient" = "為入站 {{ .Inbound }} 選擇一個客戶" -"chooseInbound" = "選擇一個入站" diff --git a/web/translation/uk-UA.json b/web/translation/uk-UA.json new file mode 100644 index 00000000..eeabe71b --- /dev/null +++ b/web/translation/uk-UA.json @@ -0,0 +1,941 @@ +{ + "username": "Ім'я користувача", + "password": "Пароль", + "login": "Увійти", + "confirm": "Підтвердити", + "cancel": "Скасувати", + "close": "Закрити", + "save": "Зберегти", + "logout": "Вийти", + "create": "Створити", + "update": "Оновити", + "copy": "Копіювати", + "copied": "Скопійовано", + "download": "Завантажити", + "remark": "Примітка", + "enable": "Увімкнути", + "protocol": "Протокол", + "search": "Пошук", + "filter": "Фільтр", + "loading": "Завантаження...", + "second": "Секунда", + "minute": "Хвилина", + "hour": "Година", + "day": "День", + "check": "Перевірка", + "indefinite": "Безстроково", + "unlimited": "Безлімітний", + "none": "Немає", + "qrCode": "QR-Код", + "info": "Більше інформації", + "edit": "Редагувати", + "delete": "Видалити", + "reset": "Скидання", + "noData": "Немає даних.", + "copySuccess": "Скопійовано успішно", + "sure": "Звичайно", + "encryption": "Шифрування", + "useIPv4ForHost": "Використовувати IPv4 для хоста", + "transmission": "Протокол передачи", + "host": "Хост", + "path": "Шлях", + "camouflage": "Маскування", + "status": "Статус", + "enabled": "Увімкнено", + "disabled": "Вимкнено", + "depleted": "Вичерпано", + "depletingSoon": "Вичерпується", + "offline": "Офлайн", + "online": "Онлайн", + "domainName": "Доменне ім`я", + "monitor": "Слухати IP", + "certificate": "Цифровий сертифікат", + "fail": "Помилка", + "comment": "Коментар", + "success": "Успішно", + "lastOnline": "Був(ла) онлайн", + "getVersion": "Отримати версію", + "install": "Встановити", + "clients": "Клієнти", + "usage": "Використання", + "twoFactorCode": "Код", + "remained": "Залишилося", + "security": "Беспека", + "secAlertTitle": "Попередження системи безпеки", + "secAlertSsl": "Це з'єднання не є безпечним. Будь ласка, уникайте введення конфіденційної інформації, поки TLS не буде активовано для захисту даних.", + "secAlertConf": "Деякі налаштування вразливі до атак. Рекомендується посилити протоколи безпеки, щоб запобігти можливим порушенням.", + "secAlertSSL": "Панель не має безпечного з'єднання. Будь ласка, встановіть сертифікат TLS для захисту даних.", + "secAlertPanelPort": "Стандартний порт панелі вразливий. Будь ласка, сконфігуруйте випадковий або конкретний порт.", + "secAlertPanelURI": "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях.", + "secAlertSubURI": "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях.", + "secAlertSubJsonURI": "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях.", + "emptyDnsDesc": "Немає доданих DNS-серверів.", + "emptyFakeDnsDesc": "Немає доданих Fake DNS-серверів.", + "emptyBalancersDesc": "Немає доданих балансувальників.", + "emptyReverseDesc": "Немає доданих зворотних проксі.", + "somethingWentWrong": "Щось пішло не так", + "subscription": { + "title": "Інформація про підписку", + "subId": "ID підписки", + "status": "Статус", + "downloaded": "Завантажено", + "uploaded": "Відвантажено", + "expiry": "Термін дії", + "totalQuota": "Загальна квота", + "individualLinks": "Окремі посилання", + "active": "Активна", + "inactive": "Неактивна", + "unlimited": "Безліміт", + "noExpiry": "Без строку" + }, + "menu": { + "theme": "Тема", + "dark": "Темна", + "ultraDark": "Ультра темна", + "dashboard": "Огляд", + "inbounds": "Вхідні", + "settings": "Параметри панелі", + "xray": "Конфігурації Xray", + "logout": "Вийти", + "link": "Керувати" + }, + "pages": { + "login": { + "hello": "Привіт", + "title": "Привітання!", + "loginAgain": "Ваш сеанс закінчився, увійдіть знову", + "toasts": { + "invalidFormData": "Формат вхідних даних недійсний.", + "emptyUsername": "Потрібне ім'я користувача", + "emptyPassword": "Потрібен пароль", + "wrongUsernameOrPassword": "Невірне ім’я користувача, пароль або код двофакторної аутентифікації.", + "successLogin": "Ви успішно увійшли до свого облікового запису." + } + }, + "index": { + "title": "Огляд", + "cpu": "ЦП", + "logicalProcessors": "Логічні процесори", + "frequency": "Частота", + "swap": "Своп", + "storage": "Сховище", + "memory": "ОЗП", + "threads": "Потоки", + "xrayStatus": "Xray", + "stopXray": "Зупинити", + "restartXray": "Перезапустити", + "xraySwitch": "Версія", + "xraySwitchClick": "Виберіть версію, на яку ви хочете перейти.", + "xraySwitchClickDesk": "Вибирайте уважно, оскільки старіші версії можуть бути несумісними з поточними конфігураціями.", + "xrayUpdates": "Оновлення Xray", + "updatePanel": "Оновити панель", + "panelUpdateDesc": "Це оновить 3X-UI до останнього релізу та перезапустить сервіс панелі.", + "currentPanelVersion": "Поточна версія панелі", + "latestPanelVersion": "Остання версія панелі", + "panelUpToDate": "Панель оновлено", + "upToDate": "Оновлено", + "xrayStatusUnknown": "Невідомо", + "xrayStatusRunning": "Запущено", + "xrayStatusStop": "Зупинено", + "xrayStatusError": "Помилка", + "xrayErrorPopoverTitle": "Під час роботи Xray сталася помилка", + "operationHours": "Час роботи", + "systemHistoryTitle": "Історія системи", + "trendLast2Min": "Останні 2 хвилини", + "systemLoad": "Завантаження системи", + "systemLoadDesc": "Середнє завантаження системи за останні 1, 5 і 15 хвилин", + "connectionCount": "Статистика з'єднання", + "ipAddresses": "IP-адреси", + "toggleIpVisibility": "Перемкнути видимість IP", + "overallSpeed": "Загальна швидкість", + "upload": "Відправка", + "download": "Завантаження", + "totalData": "Загальний обсяг даних", + "sent": "Відправлено", + "received": "Отримано", + "documentation": "Документація", + "xraySwitchVersionDialog": "Ви дійсно хочете змінити версію Xray?", + "xraySwitchVersionDialogDesc": "Це змінить версію Xray на #version#.", + "xraySwitchVersionPopover": "Xray успішно оновлено", + "panelUpdateDialog": "Ви дійсно хочете оновити панель?", + "panelUpdateDialogDesc": "Це оновить 3X-UI до #version# та перезапустить сервіс панелі.", + "panelUpdateCheckPopover": "Перевірка оновлення панелі не вдалася", + "panelUpdateStartedPopover": "Розпочато оновлення панелі", + "geofileUpdateDialog": "Ви дійсно хочете оновити геофайл?", + "geofileUpdateDialogDesc": "Це оновить файл #filename#.", + "geofilesUpdateDialogDesc": "Це оновить усі геофайли.", + "geofilesUpdateAll": "Оновити все", + "geofileUpdatePopover": "Геофайл успішно оновлено", + "dontRefresh": "Інсталяція триває, будь ласка, не оновлюйте цю сторінку", + "logs": "Журнали", + "config": "Конфігурація", + "backup": "Резервна копія", + "backupTitle": "Резервне копіювання та відновлення", + "exportDatabase": "Резервна копія", + "exportDatabaseDesc": "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій.", + "importDatabase": "Відновити", + "importDatabaseDesc": "Натисніть, щоб вибрати та завантажити файл .db з вашого пристрою для відновлення бази даних з резервної копії.", + "importDatabaseSuccess": "Базу даних успішно імпортовано", + "importDatabaseError": "Виникла помилка під час імпорту бази даних", + "readDatabaseError": "Виникла помилка під час читання бази даних", + "getDatabaseError": "Виникла помилка під час отримання бази даних", + "getConfigError": "Виникла помилка під час отримання файлу конфігурації", + "customGeoTitle": "Користувацькі GeoSite / GeoIP", + "customGeoAdd": "Додати", + "customGeoType": "Тип", + "customGeoAlias": "Псевдонім", + "customGeoUrl": "URL", + "customGeoEnabled": "Увімкнено", + "customGeoLastUpdated": "Оновлено", + "customGeoExtColumn": "Маршрутизація (ext:…)", + "customGeoToastUpdateAll": "Усі користувацькі джерела оновлено", + "customGeoActions": "Дії", + "customGeoEdit": "Змінити", + "customGeoDelete": "Видалити", + "customGeoDownload": "Оновити зараз", + "customGeoModalAdd": "Додати користувацький geo", + "customGeoModalEdit": "Змінити користувацький geo", + "customGeoModalSave": "Зберегти", + "customGeoDeleteConfirm": "Видалити це джерело geo?", + "customGeoRoutingHint": "У правилах маршрутизації використовуйте значення як ext:файл.dat:тег (замініть тег).", + "customGeoInvalidId": "Некоректний ідентифікатор ресурсу", + "customGeoAliasesError": "Не вдалося завантажити псевдоніми geo", + "customGeoValidationAlias": "Псевдонім: лише a-z, цифри, - і _", + "customGeoValidationUrl": "URL має починатися з http:// або https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (власний)", + "customGeoToastList": "Список користувацьких geo", + "customGeoToastAdd": "Додати користувацький geo", + "customGeoToastUpdate": "Оновити користувацький geo", + "customGeoToastDelete": "Користувацький geofile «{{ .fileName }}» видалено", + "customGeoToastDownload": "Geofile «{{ .fileName }}» оновлено", + "customGeoErrInvalidType": "Тип має бути geosite або geoip", + "customGeoErrAliasRequired": "Потрібен псевдонім", + "customGeoErrAliasPattern": "Псевдонім містить недопустимі символи", + "customGeoErrAliasReserved": "Цей псевдонім зарезервовано", + "customGeoErrUrlRequired": "Потрібен URL", + "customGeoErrInvalidUrl": "Некоректний URL", + "customGeoErrUrlScheme": "URL має використовувати http або https", + "customGeoErrUrlHost": "Некоректний хост URL", + "customGeoErrDuplicateAlias": "Цей псевдонім уже використовується для цього типу", + "customGeoErrNotFound": "Джерело geo не знайдено", + "customGeoErrDownload": "Помилка завантаження", + "customGeoErrUpdateAllIncomplete": "Не вдалося оновити один або кілька користувацьких джерел", + "customGeoEmpty": "Користувацьких джерел geo поки немає — натисніть «Додати», щоб створити" + }, + "inbounds": { + "allTimeTraffic": "Загальний трафік", + "allTimeTrafficUsage": "Загальне використання за весь час", + "title": "Вхідні", + "totalDownUp": "Всього надісланих/отриманих", + "totalUsage": "Всього використанно", + "inboundCount": "Загальна кількість вхідних", + "operate": "Меню", + "enable": "Увімкнено", + "remark": "Примітка", + "node": "Вузол", + "deployTo": "Розгорнути на", + "localPanel": "Локальна панель", + "protocol": "Протокол", + "port": "Порт", + "portMap": "Порт-перехід", + "traffic": "Трафік", + "details": "Деталі", + "transportConfig": "Транспорт", + "expireDate": "Тривалість", + "createdAt": "Створено", + "updatedAt": "Оновлено", + "resetTraffic": "Скинути трафік", + "addInbound": "Додати вхідний", + "generalActions": "Загальні дії", + "modifyInbound": "Змінити вхідний", + "deleteInbound": "Видалити вхідні", + "deleteInboundContent": "Ви впевнені, що хочете видалити вхідні?", + "deleteClient": "Видалити клієнта", + "deleteClientContent": "Ви впевнені, що хочете видалити клієнт?", + "resetTrafficContent": "Ви впевнені, що хочете скинути трафік?", + "copyLink": "Копіювати URL", + "address": "Адреса", + "network": "Мережа", + "destinationPort": "Порт призначення", + "targetAddress": "Цільова адреса", + "monitorDesc": "Залиште порожнім, щоб слухати всі IP-адреси", + "meansNoLimit": "= Необмежено. (одиниця: ГБ)", + "totalFlow": "Загальна витрата", + "leaveBlankToNeverExpire": "Залиште порожнім, щоб ніколи не закінчувався", + "noRecommendKeepDefault": "Рекомендується зберегти значення за замовчуванням", + "certificatePath": "Шлях до файлу", + "certificateContent": "Вміст файлу", + "publicKey": "Публічний ключ", + "privatekey": "Закритий ключ", + "clickOnQRcode": "Натисніть QR-код, щоб скопіювати", + "client": "Клієнт", + "export": "Експортувати всі URL-адреси", + "clone": "Клон", + "cloneInbound": "Клонувати", + "cloneInboundContent": "Усі налаштування цього вхідного потоку, крім порту, IP-адреси прослуховування та клієнтів, будуть застосовані до клону.", + "cloneInboundOk": "Клонувати", + "resetAllTraffic": "Скинути весь вхідний трафік", + "resetAllTrafficTitle": "Скинути весь вхідний трафік", + "resetAllTrafficContent": "Ви впевнені, що бажаєте скинути трафік усіх вхідних?", + "resetInboundClientTraffics": "Скинути трафік клієнтів", + "resetInboundClientTrafficTitle": "Скинути трафік клієнтів", + "resetInboundClientTrafficContent": "Ви впевнені, що бажаєте скинути трафік клієнтів цього вхідного потоку?", + "resetAllClientTraffics": "Скинути весь трафік клієнтів", + "resetAllClientTrafficTitle": "Скинути весь трафік клієнтів", + "resetAllClientTrafficContent": "Ви впевнені, що бажаєте скинути трафік усіх клієнтів?", + "delDepletedClients": "Видалити вичерпані клієнти", + "delDepletedClientsTitle": "Видалити вичерпані клієнти", + "delDepletedClientsContent": "Ви впевнені, що хочете видалити всі вичерпані клієнти?", + "email": "Електронна пошта", + "emailDesc": "Будь ласка, надайте унікальну адресу електронної пошти.", + "IPLimit": "Обмеження IP", + "IPLimitDesc": "Вимикає вхідний, якщо кількість перевищує встановлене значення. (0 = вимкнено)", + "IPLimitlog": "Журнал IP", + "IPLimitlogDesc": "Журнал історії IP-адрес. (щоб увімкнути вхідну після вимкнення, очистіть журнал)", + "IPLimitlogclear": "Очистити журнал", + "setDefaultCert": "Установити сертифікат з панелі", + "telegramDesc": "Будь ласка, вкажіть ID чату Telegram. (використовуйте команду '/id' у боті) або ({'@'}userinfobot)", + "subscriptionDesc": "Щоб знайти URL-адресу вашої підписки, перейдіть до «Деталі». Крім того, ви можете використовувати одне ім'я для кількох клієнтів.", + "info": "Інформація", + "same": "Те саме", + "inboundData": "Вхідні дані", + "exportInbound": "Експортувати вхідні", + "import": "Імпорт", + "importInbound": "Імпортувати вхідний", + "periodicTrafficResetTitle": "Скидання трафіку", + "periodicTrafficResetDesc": "Автоматично скидати лічильник трафіку через певні проміжки часу", + "lastReset": "Останнє скидання", + "periodicTrafficReset": { + "never": "Ніколи", + "daily": "Щодня", + "weekly": "Щотижня", + "monthly": "Щомісяця", + "hourly": "Щогодини" + }, + "toasts": { + "obtain": "Отримати", + "updateSuccess": "Оновлення пройшло успішно", + "logCleanSuccess": "Журнал очищено", + "inboundsUpdateSuccess": "Вхідні підключення успішно оновлено", + "inboundUpdateSuccess": "Вхідне підключення успішно оновлено", + "inboundCreateSuccess": "Вхідне підключення успішно створено", + "inboundDeleteSuccess": "Вхідне підключення успішно видалено", + "inboundClientAddSuccess": "Клієнт(и) вхідного підключення додано", + "inboundClientDeleteSuccess": "Клієнта вхідного підключення видалено", + "inboundClientUpdateSuccess": "Клієнта вхідного підключення оновлено", + "delDepletedClientsSuccess": "Усі вичерпані клієнти видалені", + "resetAllClientTrafficSuccess": "Весь трафік клієнта скинуто", + "resetAllTrafficSuccess": "Весь трафік скинуто", + "resetInboundClientTrafficSuccess": "Трафік скинуто", + "trafficGetError": "Помилка отримання даних про трафік", + "getNewX25519CertError": "Помилка при отриманні сертифіката X25519.", + "getNewmldsa65Error": "Помилка при отриманні сертифіката mldsa65.", + "getNewVlessEncError": "Помилка при отриманні сертифіката VlessEnc." + }, + "stream": { + "general": { + "request": "Запит", + "response": "Відповідь", + "name": "Ім'я", + "value": "Значення" + }, + "tcp": { + "version": "Версія", + "method": "Метод", + "path": "Шлях", + "status": "Статус", + "statusDescription": "Опис стану", + "requestHeader": "Заголовок запиту", + "responseHeader": "Заголовок відповіді" + } + } + }, + "client": { + "add": "Додати клієнта", + "edit": "Редагувати клієнта", + "submitAdd": "Додати клієнта", + "submitEdit": "Зберегти зміни", + "clientCount": "Кількість клієнтів", + "bulk": "Додати групу", + "copyFromInbound": "Скопіювати клієнтів з інбаунда", + "copyToInbound": "Скопіювати клієнтів у", + "copySelected": "Скопіювати вибраних", + "copySource": "Джерело", + "copyEmailPreview": "Попередній перегляд підсумкових email", + "copySelectSourceFirst": "Спочатку виберіть джерело.", + "copyResult": "Результат копіювання", + "copyResultSuccess": "Успішно скопійовано", + "copyResultNone": "Нічого копіювати: жодного клієнта не вибрано або список джерела порожній", + "copyResultErrors": "Помилки під час копіювання", + "copyFlowLabel": "Flow для нових клієнтів (VLESS)", + "copyFlowHint": "Застосується до всіх скопійованих клієнтів. Залиште порожнім, щоб не задавати.", + "selectAll": "Вибрати всіх", + "clearAll": "Зняти все", + "method": "Метод", + "first": "Перший", + "last": "Останній", + "prefix": "Префікс", + "postfix": "Постфікс", + "delayedStart": "Початок використання", + "expireDays": "Тривалість", + "days": "Дні(в)", + "renew": "Автоматичне оновлення", + "renewDesc": "Автоматичне поновлення після закінчення терміну дії. (0 = вимкнено)(одиниця: день)" + }, + "nodes": { + "title": "Вузли", + "addNode": "Додати вузол", + "editNode": "Редагувати вузол", + "totalNodes": "Усього вузлів", + "onlineNodes": "Онлайн", + "offlineNodes": "Офлайн", + "avgLatency": "Середня затримка", + "name": "Назва", + "namePlaceholder": "напр. de-frankfurt-1", + "addressPlaceholder": "panel.example.com або 1.2.3.4", + "remark": "Примітка", + "scheme": "Схема", + "address": "Адреса", + "port": "Порт", + "basePath": "Базовий шлях", + "apiToken": "Токен API", + "apiTokenPlaceholder": "Токен зі сторінки Налаштувань віддаленої панелі", + "apiTokenHint": "Віддалена панель показує свій токен API в Налаштуваннях → Токен API.", + "regenerate": "Перегенерувати токен", + "regenerateConfirm": "Перегенерація скасовує поточний токен. Будь-яка центральна панель, що його використовує, втратить доступ до оновлення. Продовжити?", + "enable": "Увімкнено", + "status": "Статус", + "cpu": "CPU", + "mem": "Пам'ять", + "uptime": "Час роботи", + "latency": "Затримка", + "lastHeartbeat": "Останній пінг", + "xrayVersion": "Версія Xray", + "actions": "Дії", + "probe": "Перевірити зараз", + "testConnection": "Перевірити з'єднання", + "connectionOk": "З'єднання в порядку ({ms} мс)", + "connectionFailed": "Помилка з'єднання", + "never": "ніколи", + "justNow": "щойно", + "deleteConfirmTitle": "Видалити вузол \"{name}\"?", + "deleteConfirmContent": "Це зупинить моніторинг вузла. Сама віддалена панель не зазнає змін.", + "statusValues": { + "online": "Онлайн", + "offline": "Офлайн", + "unknown": "Невідомо" + }, + "toasts": { + "list": "Не вдалося завантажити вузли", + "obtain": "Не вдалося завантажити вузол", + "add": "Додати вузол", + "update": "Оновити вузол", + "delete": "Видалити вузол", + "deleted": "Вузол видалено", + "test": "Перевірити з'єднання", + "fillRequired": "Назва, адреса, порт та токен API є обов'язковими", + "probeFailed": "Помилка перевірки" + } + }, + "settings": { + "title": "Параметри панелі", + "save": "Зберегти", + "infoDesc": "Кожна внесена тут зміна повинна бути збережена. Перезапустіть панель, щоб застосувати зміни.", + "restartPanel": "Перезапустити панель", + "restartPanelDesc": "Ви впевнені, що бажаєте перезапустити панель? Якщо ви не можете отримати доступ до панелі після перезапуску, будь ласка, перегляньте інформацію журналу панелі на сервері.", + "restartPanelSuccess": "Панель успішно перезапущено", + "actions": "Дії", + "resetDefaultConfig": "Відновити значення за замовчуванням", + "panelSettings": "Загальні", + "securitySettings": "Автентифікація", + "TGBotSettings": "Telegram Бот", + "panelListeningIP": "Слухати IP", + "panelListeningIPDesc": "IP-адреса для веб-панелі. (залиште порожнім, щоб слухати всі IP-адреси)", + "panelListeningDomain": "Домен прослуховування", + "panelListeningDomainDesc": "Доменне ім'я для веб-панелі. (залиште порожнім, щоб слухати всі домени та IP-адреси)", + "panelPort": "Порт прослуховування", + "panelPortDesc": "Номер порту для веб-панелі. (має бути невикористаний порт)", + "publicKeyPath": "Шлях відкритого ключа", + "publicKeyPathDesc": "Шлях до файлу відкритого ключа для веб-панелі. (починається з ‘/‘)", + "privateKeyPath": "Шлях приватного ключа", + "privateKeyPathDesc": "Шлях до файлу приватного ключа для веб-панелі. (починається з ‘/‘)", + "panelUrlPath": "Шлях URL", + "panelUrlPathDesc": "Шлях URL для веб-панелі. (починається з ‘/‘ і закінчується ‘/‘)", + "pageSize": "Розмір сторінки", + "pageSizeDesc": "Визначити розмір сторінки для вхідної таблиці. (0 = вимкнено)", + "remarkModel": "Модель зауваження та роздільний символ", + "datepicker": "Тип календаря", + "datepickerPlaceholder": "Виберіть дату", + "datepickerDescription": "Заплановані завдання виконуватимуться на основі цього календаря.", + "sampleRemark": "Зразок зауваження", + "oldUsername": "Поточне ім'я користувача", + "currentPassword": "Поточний пароль", + "newUsername": "Нове ім'я користувача", + "newPassword": "Новий пароль", + "telegramBotEnable": "Увімкнути Telegram Bot", + "telegramBotEnableDesc": "Вмикає бота Telegram.", + "telegramToken": "Telegram Токен", + "telegramTokenDesc": "Токен бота Telegram, отриманий від '{'@'}BotFather'.", + "telegramProxy": "SOCKS Проксі", + "telegramProxyDesc": "Вмикає проксі-сервер SOCKS5 для підключення до Telegram. (відкоригуйте параметри відповідно до посібника)", + "telegramAPIServer": "Сервер Telegram API", + "telegramAPIServerDesc": "Сервер Telegram API для використання. Залиште поле порожнім, щоб використовувати сервер за умовчанням.", + "telegramChatId": "Ідентифікатор чату адміністратора", + "telegramChatIdDesc": "Ідентифікатори чату адміністратора Telegram. (розділені комами) (отримайте тут {'@'}userinfobot) або (використовуйте команду '/id' у боті)", + "telegramNotifyTime": "Час сповіщення", + "telegramNotifyTimeDesc": "Час повідомлення бота Telegram, встановлений для періодичних звітів. (використовуйте формат часу crontab)", + "tgNotifyBackup": "Резервне копіювання бази даних", + "tgNotifyBackupDesc": "Надіслати файл резервної копії бази даних зі звітом.", + "tgNotifyLogin": "Сповіщення про вхід", + "tgNotifyLoginDesc": "Отримувати сповіщення про ім'я користувача, IP-адресу та час щоразу, коли хтось намагається увійти у вашу веб-панель.", + "sessionMaxAge": "Тривалість сеансу", + "sessionMaxAgeDesc": "Тривалість, протягом якої ви можете залишатися в системі. (одиниця: хвилина)", + "expireTimeDiff": "Повідомлення про дату закінчення", + "expireTimeDiffDesc": "Отримувати сповіщення про термін дії при досягненні цього порогу. (одиниця: день)", + "trafficDiff": "Повідомлення про обмеження трафіку", + "trafficDiffDesc": "Отримувати сповіщення про обмеження трафіку при досягненні цього порогу. (одиниця: ГБ)", + "tgNotifyCpu": "Сповіщення про завантаження ЦП", + "tgNotifyCpuDesc": "Отримувати сповіщення, якщо навантаження ЦП перевищує це порогове значення. (одиниця: %)", + "timeZone": "Часовий пояс", + "timeZoneDesc": "Заплановані завдання виконуватимуться на основі цього часового поясу.", + "subSettings": "Підписка", + "subEnable": "Увімкнути службу підписки", + "subEnableDesc": "Вмикає службу підписки.", + "subJsonEnable": "Увімкнути/вимкнути JSON-кінець підписки незалежно.", + "subTitle": "Назва Підписки", + "subTitleDesc": "Назва, яка відображається у VPN-клієнті", + "subSupportUrl": "URL підтримки", + "subSupportUrlDesc": "Посилання на технічну підтримку, що відображається у VPN-клієнті", + "subProfileUrl": "URL профілю", + "subProfileUrlDesc": "Посилання на ваш вебсайт, що відображається у VPN-клієнті", + "subAnnounce": "Оголошення", + "subAnnounceDesc": "Текст оголошення, що відображається у VPN-клієнті", + "subEnableRouting": "Увімкнути маршрутизацію", + "subEnableRoutingDesc": "Глобальне налаштування для увімкнення маршрутизації у VPN-клієнті. (Тільки для Happ)", + "subRoutingRules": "Правила маршрутизації", + "subRoutingRulesDesc": "Глобальні правила маршрутизації для VPN-клієнта. (Тільки для Happ)", + "subListen": "Слухати IP", + "subListenDesc": "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)", + "subPort": "Слухати порт", + "subPortDesc": "Номер порту для служби підписки. (має бути невикористаний порт)", + "subCertPath": "Шлях відкритого ключа", + "subCertPathDesc": "Шлях до файлу відкритого ключа для служби підписки. (починається з ‘/‘)", + "subKeyPath": "Шлях приватного ключа", + "subKeyPathDesc": "Шлях до файлу приватного ключа для служби підписки. (починається з ‘/‘)", + "subPath": "Шлях URI", + "subPathDesc": "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)", + "subDomain": "Домен прослуховування", + "subDomainDesc": "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)", + "subUpdates": "Інтервали оновлення", + "subUpdatesDesc": "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)", + "subEncrypt": "Закодувати", + "subEncryptDesc": "Повернений вміст послуги підписки матиме кодування Base64.", + "subShowInfo": "Показати інформацію про використання", + "subShowInfoDesc": "Залишок трафіку та дата відображатимуться в клієнтських програмах.", + "subURI": "URI зворотного проксі", + "subURIDesc": "URI до URL-адреси підписки для використання за проксі.", + "externalTrafficInformEnable": "Інформація про зовнішній трафік", + "externalTrafficInformEnableDesc": "Інформувати зовнішній API про кожне оновлення трафіку.", + "externalTrafficInformURI": "Інформаційний URI зовнішнього трафіку", + "externalTrafficInformURIDesc": "Оновлення трафіку надсилаються на цей URI.", + "restartXrayOnClientDisable": "Перезапускати Xray після авто-вимкнення", + "restartXrayOnClientDisableDesc": "Коли клієнт автоматично вимикається через закінчення терміну дії або ліміт трафіку, перезапускати Xray.", + "fragment": "Фрагментація", + "fragmentDesc": "Увімкнути фрагментацію для пакету привітання TLS", + "fragmentSett": "Параметри фрагментації", + "noisesDesc": "Увімкнути Noises.", + "noisesSett": "Налаштування Noises", + "mux": "Mux", + "muxDesc": "Передавати кілька незалежних потоків даних у межах встановленого потоку даних.", + "muxSett": "Налаштування Mux", + "direct": "Пряме підключення", + "directDesc": "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни.", + "notifications": "Сповіщення", + "certs": "Сертифікати", + "externalTraffic": "Зовнішній трафік", + "dateAndTime": "Дата та час", + "proxyAndServer": "Проксі та сервер", + "intervals": "Інтервали", + "information": "Інформація", + "language": "Мова", + "telegramBotLanguage": "Мова Telegram-бота", + "security": { + "admin": "Облікові дані адміністратора", + "twoFactor": "Двофакторна аутентифікація", + "twoFactorEnable": "Увімкнути 2FA", + "twoFactorEnableDesc": "Додає додатковий рівень аутентифікації для підвищення безпеки.", + "twoFactorModalSetTitle": "Увімкнути двофакторну аутентифікацію", + "twoFactorModalDeleteTitle": "Вимкнути двофакторну аутентифікацію", + "twoFactorModalSteps": "Щоб налаштувати двофакторну аутентифікацію, виконайте кілька кроків:", + "twoFactorModalFirstStep": "1. Відскануйте цей QR-код у програмі для аутентифікації або скопіюйте токен біля QR-коду та вставте його в програму", + "twoFactorModalSecondStep": "2. Введіть код з програми", + "twoFactorModalRemoveStep": "Введіть код з програми, щоб вимкнути двофакторну аутентифікацію.", + "twoFactorModalChangeCredentialsTitle": "Змінити облікові дані", + "twoFactorModalChangeCredentialsStep": "Введіть код з додатку, щоб змінити облікові дані адміністратора.", + "twoFactorModalSetSuccess": "Двофакторна аутентифікація була успішно встановлена", + "twoFactorModalDeleteSuccess": "Двофакторна аутентифікація була успішно видалена", + "twoFactorModalError": "Невірний код" + }, + "toasts": { + "modifySettings": "Параметри було змінено.", + "getSettings": "Виникла помилка під час отримання параметрів.", + "modifyUserError": "Виникла помилка під час зміни облікових даних адміністратора.", + "modifyUser": "Ви успішно змінили облікові дані адміністратора.", + "originalUserPassIncorrect": "Поточне ім'я користувача або пароль недійсні", + "userPassMustBeNotEmpty": "Нове ім'я користувача та пароль порожні", + "getOutboundTrafficError": "Помилка отримання вихідного трафіку", + "resetOutboundTrafficError": "Помилка скидання вихідного трафіку" + } + }, + "xray": { + "title": "Xray конфігурації", + "save": "Зберегти", + "restart": "Перезапустити Xray", + "restartSuccess": "Xray успішно перезапущено", + "stopSuccess": "Xray успішно зупинено", + "restartError": "Виникла помилка під час перезапуску Xray.", + "stopError": "Виникла помилка під час зупинки Xray.", + "basicTemplate": "Базовий шаблон", + "advancedTemplate": "Додатково", + "generalConfigs": "Загальні конфігурації", + "generalConfigsDesc": "Ці параметри визначатимуть загальні налаштування.", + "logConfigs": "Журнал", + "logConfigsDesc": "Журнали можуть вплинути на ефективність вашого сервера. Рекомендується вмикати його з розумом лише у випадку ваших потреб", + "blockConfigsDesc": "Ці параметри блокуватимуть трафік на основі конкретних запитуваних протоколів і веб-сайтів.", + "basicRouting": "Основна Маршрутизація", + "blockConnectionsConfigsDesc": "Ці параметри блокуватимуть трафік на основі запитаних країн.", + "directConnectionsConfigsDesc": "Пряме з'єднання гарантує, що певний трафік не буде маршрутизовано через інший сервер.", + "blockips": "Блокувати IP", + "blockdomains": "Блокувати домени", + "directips": "Прямі IP", + "directdomains": "Прямі домени", + "ipv4Routing": "Маршрутизація IPv4", + "ipv4RoutingDesc": "Ці параметри спрямовуватимуть трафік на основі певного призначення через IPv4.", + "warpRouting": "WARP Маршрутизація", + "warpRoutingDesc": "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через WARP.", + "nordRouting": "Маршрутизація NordVPN", + "nordRoutingDesc": "Ці параметри маршрутизуватимуть трафік на основі певного пункту призначення через NordVPN.", + "Template": "Шаблон розширеної конфігурації Xray", + "TemplateDesc": "Остаточний конфігураційний файл Xray буде створено на основі цього шаблону.", + "FreedomStrategy": "Стратегія протоколу свободи", + "FreedomStrategyDesc": "Установити стратегію виведення для мережі в протоколі свободи.", + "RoutingStrategy": "Загальна стратегія маршрутизації", + "RoutingStrategyDesc": "Установити загальну стратегію маршрутизації трафіку для вирішення всіх запитів.", + "outboundTestUrl": "URL тесту outbound", + "outboundTestUrlDesc": "URL для перевірки з'єднання outbound", + "Torrent": "Блокувати протокол BitTorrent", + "Inbounds": "Вхідні", + "InboundsDesc": "Прийняття певних клієнтів.", + "Outbounds": "Вихід", + "Balancers": "Балансери", + "OutboundsDesc": "Встановити шлях вихідного трафіку.", + "Routings": "Правила маршрутизації", + "RoutingsDesc": "Пріоритет кожного правила важливий!", + "completeTemplate": "Усі", + "logLevel": "Рівень журналу", + "logLevelDesc": "Рівень журналу для журналів помилок із зазначенням інформації, яку потрібно записати.", + "accessLog": "Журнал доступу", + "accessLogDesc": "Шлях до файлу журналу доступу. Спеціальне значення 'none' вимикає журнали доступу", + "errorLog": "Журнал помилок", + "errorLogDesc": "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок", + "dnsLog": "Журнал DNS", + "dnsLogDesc": "Чи включити журнали запитів DNS", + "maskAddress": "Маскувати Адресу", + "maskAddressDesc": "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі.", + "statistics": "Статистика", + "statsInboundUplink": "Статистика вхідного аплінку", + "statsInboundUplinkDesc": "Увімкнення збору статистики для вхідного трафіку всіх вхідних проксі.", + "statsInboundDownlink": "Статистика вхідного даунлінку", + "statsInboundDownlinkDesc": "Увімкнення збору статистики для вихідного трафіку всіх вхідних проксі.", + "statsOutboundUplink": "Статистика вихідного аплінку", + "statsOutboundUplinkDesc": "Увімкнення збору статистики для вхідного трафіку всіх вихідних проксі.", + "statsOutboundDownlink": "Статистика вихідного даунлінку", + "statsOutboundDownlinkDesc": "Увімкнення збору статистики для вихідного трафіку всіх вихідних проксі.", + "rules": { + "first": "Перший", + "last": "Останній", + "up": "Вгору", + "down": "Вниз", + "source": "Джерело", + "dest": "Пункт призначення", + "inbound": "Вхідний", + "outbound": "Вихідний", + "balancer": "Балансувальник", + "info": "Інформація", + "add": "Додати правило", + "edit": "Редагувати правило", + "useComma": "Елементи, розділені комами" + }, + "outbound": { + "addOutbound": "Додати вихідний", + "addReverse": "Додати реверс", + "editOutbound": "Редагувати вихідні", + "editReverse": "Редагувати реверс", + "reverseTag": "Тег реверс-проксі", + "reverseTagDesc": "Тег вихідного з'єднання для простого реверс-проксі VLESS. Залиште порожнім для вимкнення.", + "reverseTagPlaceholder": "тег вихідного (порожнє = вимкнено)", + "tag": "Тег", + "tagDesc": "Унікальний тег", + "address": "Адреса", + "reverse": "Зворотний", + "domain": "Домен", + "type": "Тип", + "bridge": "Міст", + "portal": "Портал", + "link": "Посилання", + "intercon": "Взаємозв'язок", + "settings": "Налаштування", + "accountInfo": "Інформація про обліковий запис", + "outboundStatus": "Статус виходу", + "sendThrough": "Надіслати через", + "test": "Тест", + "testResult": "Результат тесту", + "testing": "Тестування з'єднання...", + "testSuccess": "Тест успішний", + "testFailed": "Тест не пройдено", + "testError": "Не вдалося протестувати вихідне з'єднання", + "nordvpn": "NordVPN", + "accessToken": "Токен доступу", + "country": "Країна", + "server": "Сервер", + "city": "Місто", + "allCities": "Усі міста", + "privateKey": "Приватний ключ", + "load": "Навантаження" + }, + "balancer": { + "addBalancer": "Додати балансир", + "editBalancer": "Редагувати балансир", + "balancerStrategy": "Стратегія", + "balancerSelectors": "Селектори", + "tag": "Тег", + "tagDesc": "Унікальний тег", + "balancerDesc": "Неможливо використовувати balancerTag і outboundTag одночасно. Якщо використовувати одночасно, працюватиме лише outboundTag." + }, + "wireguard": { + "secretKey": "Приватний ключ", + "publicKey": "Публічний ключ", + "allowedIPs": "Дозволені IP-адреси", + "endpoint": "Кінцева точка", + "psk": "Спільний ключ", + "domainStrategy": "Стратегія домену" + }, + "tun": { + "nameDesc": "Назва інтерфейсу TUN. Значення за замовчуванням - 'xray0'", + "mtuDesc": "Максимальна одиниця передачі. Максимальний розмір пакетів даних. Значення за замовчуванням - 1500", + "userLevel": "Рівень користувача", + "userLevelDesc": "Всі з'єднання, встановлені через цей вхід, використовуватимуть цей рівень користувача. Значення за замовчуванням - 0" + }, + "dns": { + "enable": "Увімкнути DNS", + "enableDesc": "Увімкнути вбудований DNS-сервер", + "tag": "Мітка вхідного DNS", + "tagDesc": "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації.", + "clientIp": "IP клієнта", + "clientIpDesc": "Використовується для повідомлення серверу про вказане місцезнаходження IP під час DNS-запитів", + "disableCache": "Вимкнути кеш", + "disableCacheDesc": "Вимкнути кешування DNS", + "disableFallback": "Вимкнути резервний DNS", + "disableFallbackDesc": "Вимкнути резервні DNS-запити", + "disableFallbackIfMatch": "Вимкнути резервний DNS при збігу", + "disableFallbackIfMatchDesc": "Вимкнути резервні DNS-запити при збігу списку доменів DNS-сервера", + "enableParallelQuery": "Увімкнути паралельні запити", + "enableParallelQueryDesc": "Увімкнути паралельні DNS-запити до кількох серверів для швидшого вирішення", + "strategy": "Стратегія запиту", + "strategyDesc": "Загальна стратегія вирішення доменних імен", + "add": "Додати сервер", + "edit": "Редагувати сервер", + "domains": "Домени", + "expectIPs": "Очікувані IP", + "unexpectIPs": "Неочікувані IP", + "useSystemHosts": "Використовувати системні Hosts", + "useSystemHostsDesc": "Використовувати файл hosts з встановленої системи", + "usePreset": "Використати шаблон", + "dnsPresetTitle": "Шаблони DNS", + "dnsPresetFamily": "Сімейний" + }, + "fakedns": { + "add": "Додати підроблений DNS", + "edit": "Редагувати підроблений DNS", + "ipPool": "Підмережа IP-пулу", + "poolSize": "Розмір пулу" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Клавіатуру закрито!", + "noResult": "❗ Немає результату!", + "noQuery": "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!", + "wentWrong": "❌ Щось пішло не так!", + "noIpRecord": "❗ Немає запису IP!", + "noInbounds": "❗ Вхідні не знайдені!", + "unlimited": "♾ Необмежено (Скинути)", + "add": "Додати", + "month": "Місяць", + "months": "Місяці", + "day": "День", + "days": "Дні", + "hours": "Години", + "minutes": "Хвилини", + "unknown": "Невідомо", + "inbounds": "Вхідні", + "clients": "Клієнти", + "offline": "🔴 Офлайн", + "online": "🟢 Онлайн", + "commands": { + "unknown": "❗ Невідома команда.", + "pleaseChoose": "👇 Будь ласка, виберіть:\r\n", + "help": "🤖 Ласкаво просимо до цього бота! Він розроблений, щоб надавати певні дані з веб-панелі та дозволяє вносити зміни за потреби.\r\n\r\n", + "start": "👋 Привіт {{ .Firstname }}.\r\n", + "welcome": "🤖 Ласкаво просимо до {{ .Hostname }} бота керування.\r\n", + "status": "✅ Бот в порядку!", + "usage": "❗ Введіть текст для пошуку!", + "getID": "🆔 Ваш ідентифікатор: {{ .ID }}", + "helpAdminCommands": "Для перезапуску Xray Core:\r\n/restart\r\n\r\nДля пошуку електронної пошти клієнта:\r\n/usage [Електронна пошта]\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n/inbound [Примітка]\r\n\r\nID чату Telegram:\r\n/id", + "helpClientCommands": "Для пошуку статистики використовуйте наступну команду:\r\n/usage [Електронна пошта]\r\n\r\nID чату Telegram:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Операція успішна!", + "restartFailed": "❗ Помилка в операції.\r\n\r\nПомилка: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core не запущений.", + "startDesc": "Показати головне меню", + "helpDesc": "Довідка по боту", + "statusDesc": "Перевірити статус бота", + "idDesc": "Показати ваш Telegram ID" + }, + "messages": { + "cpuThreshold": "🔴 Навантаження ЦП {{ .Percent }}% перевищує порогове значення {{ .Threshold }}%", + "selectUserFailed": "❌ Помилка під час вибору користувача!", + "userSaved": "✅ Користувача Telegram збережено.", + "loginSuccess": "✅ Успішно ввійшли в панель\r\n", + "loginFailed": "❗️ Помилка входу в панель.\r\n", + "2faFailed": "Помилка 2FA", + "report": "🕰 Заплановані звіти: {{ .RunTime }}\r\n", + "datetime": "⏰ Дата й час: {{ .DateTime }}\r\n", + "hostname": "💻 Хост: {{ .Hostname }}\r\n", + "version": "🚀 3X-UI Версія: {{ .Version }}\r\n", + "xrayVersion": "📡 Xray Версія: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 IP-адреси:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Час роботи: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Завантаження системи: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 RAM: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP: {{ .Count }}\r\n", + "udpCount": "🔸 UDP: {{ .Count }}\r\n", + "traffic": "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Статус: {{ .State }}\r\n", + "username": "👤 Ім'я користувача: {{ .Username }}\r\n", + "reason": "❗️ Причина: {{ .Reason }}\r\n", + "time": "⏰ Час: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Порт: {{ .Port }}\r\n", + "expire": "📅 Дата закінчення: {{ .Time }}\r\n", + "expireIn": "📅 Термін дії: {{ .Time }}\r\n", + "active": "💡 Активний: {{ .Enable }}\r\n", + "enabled": "🚨 Увімкнено: {{ .Enable }}\r\n", + "online": "🌐 Стан підключення: {{ .Status }}\r\n", + "lastOnline": "🔙 Був(ла) онлайн: {{ .Time }}\r\n", + "email": "📧 Електронна пошта: {{ .Email }}\r\n", + "upload": "🔼 Upload: ↑{{ .Upload }}\r\n", + "download": "🔽 Download: ↓{{ .Download }}\r\n", + "total": "📊 Всього: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Користувач Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Вичерпано {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Вичерпано кількість {{ .Type }} count:\r\n", + "onlinesCount": "🌐 Онлайн-клієнти: {{ .Count }}\r\n", + "disabled": "🛑 Вимкнено: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Скоро вичерпається: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Час резервного копіювання: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n", + "yes": "✅ Так", + "no": "❌ Ні", + "received_id": "🔑📥 ID оновлено.", + "received_password": "🔑📥 Пароль оновлено.", + "received_email": "📧📥 Електронна пошта оновлена.", + "received_comment": "💬📥 Коментар оновлено.", + "id_prompt": "🔑 Стандартний ID: {{ .ClientId }}\n\nВведіть ваш ID.", + "pass_prompt": "🔑 Стандартний пароль: {{ .ClientPassword }}\n\nВведіть ваш пароль.", + "email_prompt": "📧 Стандартний email: {{ .ClientEmail }}\n\nВведіть ваш email.", + "comment_prompt": "💬 Стандартний коментар: {{ .ClientComment }}\n\nВведіть ваш коментар.", + "inbound_client_data_id": "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!", + "inbound_client_data_pass": "🔄 Вхід: {{ .InboundRemark }}\n\n🔑 Пароль: {{ .ClientPass }}\n📧 Електронна пошта: {{ .ClientEmail }}\n📊 Трафік: {{ .ClientTraffic }}\n📅 Дата завершення: {{ .ClientExp }}\n🌐 Обмеження IP: {{ .IpLimit }}\n💬 Коментар: {{ .ClientComment }}\n\nТепер ви можете додати клієнта до вхідного з'єднання!", + "cancel": "❌ Процес скасовано! \n\nВи можете знову розпочати, використовуючи /start у будь-який час. 🔄", + "error_add_client": "⚠️ Помилка:\n\n {{ .error }}", + "using_default_value": "Гаразд, залишу значення за замовчуванням. 😊", + "incorrect_input": "Ваш ввід невірний.\nФрази повинні бути без пробілів.\nПравильний приклад: aaaaaa\nНеправильний приклад: aaa aaa 🚫", + "AreYouSure": "Ви впевнені? 🤔", + "SuccessResetTraffic": "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ✅ Успішно", + "FailedResetTraffic": "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ❌ Невдача \n\n🛠️ Помилка: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Процес скидання трафіку завершено для всіх клієнтів." + }, + "buttons": { + "closeKeyboard": "❌ Закрити клавіатуру", + "cancel": "❌ Скасувати", + "cancelReset": "❌ Скасувати скидання", + "cancelIpLimit": "❌ Скасувати обмеження IP", + "confirmResetTraffic": "✅ Підтвердити скидання трафіку?", + "confirmClearIps": "✅ Підтвердити очищення IP-адрес?", + "confirmRemoveTGUser": "✅ Підтвердити видалення користувача Telegram?", + "confirmToggle": "✅ Підтвердити ввімкнути/вимкнути користувача?", + "dbBackup": "Отримати резервну копію БД", + "serverUsage": "Використання сервера", + "getInbounds": "Отримати вхідні", + "depleteSoon": "Скоро вичерпати", + "clientUsage": "Отримати використання", + "onlines": "Онлайн-клієнти", + "commands": "Команди", + "refresh": "🔄 Оновити", + "clearIPs": "❌ Очистити IP-адреси", + "removeTGUser": "❌ Видалити користувача Telegram", + "selectTGUser": "👤 Виберіть користувача Telegram", + "selectOneTGUser": "👤 Виберіть користувача Telegram:", + "resetTraffic": "📈 Скинути трафік", + "resetExpire": "📅 Змінити термін дії", + "ipLog": "🔢 IP журнал", + "ipLimit": "🔢 IP Ліміт", + "setTGUser": "👤 Встановити користувача Telegram", + "toggle": "🔘 Увімкнути / Вимкнути", + "custom": "🔢 Custom", + "confirmNumber": "✅ Підтвердити: {{ .Num }}", + "confirmNumberAdd": "✅ Підтвердити додавання: {{ .Num }}", + "limitTraffic": "🚧 Ліміт трафіку", + "getBanLogs": "Отримати журнали заборон", + "allClients": "Всі Клієнти", + "addClient": "Додати клієнта", + "submitDisable": "Надіслати як вимкнено ☑️", + "submitEnable": "Надіслати як увімкнено ✅", + "use_default": "🏷️ Використати типове", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Пароль", + "change_email": "⚙️📧 Електронна пошта", + "change_comment": "⚙️💬 Коментар", + "ResetAllTraffics": "Скинути весь трафік", + "SortedTrafficUsageReport": "Відсортований звіт про використання трафіку" + }, + "answers": { + "successfulOperation": "✅ Операція успішна!", + "errorOperation": "❗ Помилка в роботі.", + "getInboundsFailed": "❌ Не вдалося отримати вхідні повідомлення.", + "getClientsFailed": "❌ Не вдалося отримати клієнтів.", + "canceled": "❌ {{ .Email }}: Операцію скасовано.", + "clientRefreshSuccess": "✅ {{ .Email }}: Клієнт успішно оновлено.", + "IpRefreshSuccess": "✅ {{ .Email }}: IP-адреси успішно оновлено.", + "TGIdRefreshSuccess": "✅ {{ .Email }}: Користувач Telegram клієнта успішно оновлено.", + "resetTrafficSuccess": "✅ {{ .Email }}: Трафік скинуто успішно.", + "setTrafficLimitSuccess": "✅ {{ .Email }}: Ліміт трафіку успішно збережено.", + "expireResetSuccess": "✅ {{ .Email }}: Успішно скинуто дні закінчення терміну дії.", + "resetIpSuccess": "✅ {{ .Email }}: IP обмеження {{ .Count }} успішно збережено.", + "clearIpSuccess": "✅ {{ .Email }}: IP успішно очищено.", + "getIpLog": "✅ {{ .Email }}: Отримати IP-журнал.", + "getUserInfo": "✅ {{ .Email }}: Отримати інформацію про користувача Telegram.", + "removedTGUserSuccess": "✅ {{ .Email }}: Користувача Telegram видалено успішно.", + "enableSuccess": "✅ {{ .Email }}: Увімкнути успішно.", + "disableSuccess": "✅ {{ .Email }}: Успішно вимкнено.", + "askToAddUserId": "Вашу конфігурацію не знайдено!\r\nБудь ласка, попросіть свого адміністратора використовувати ваш ідентифікатор Telegram у вашій конфігурації.\r\n\r\nВаш ідентифікатор користувача: {{ .TgUserID }}", + "chooseClient": "Виберіть клієнта для Вхідного {{ .Inbound }}", + "chooseInbound": "Виберіть Вхідний" + } + } +} diff --git a/web/translation/vi-VN.json b/web/translation/vi-VN.json new file mode 100644 index 00000000..543fb257 --- /dev/null +++ b/web/translation/vi-VN.json @@ -0,0 +1,941 @@ +{ + "username": "Tên người dùng", + "password": "Mật khẩu", + "login": "Đăng nhập", + "confirm": "Xác nhận", + "cancel": "Hủy bỏ", + "close": "Đóng", + "save": "Lưu", + "logout": "Đăng xuất", + "create": "Tạo", + "update": "Cập nhật", + "copy": "Sao chép", + "copied": "Đã sao chép", + "download": "Tải xuống", + "remark": "Ghi chú", + "enable": "Kích hoạt", + "protocol": "Giao thức", + "search": "Tìm kiếm", + "filter": "Bộ lọc", + "loading": "Đang tải", + "second": "Giây", + "minute": "Phút", + "hour": "Giờ", + "day": "Ngày", + "check": "Kiểm tra", + "indefinite": "Không xác định", + "unlimited": "Không giới hạn", + "none": "None", + "qrCode": "Mã QR", + "info": "Thông tin thêm", + "edit": "Chỉnh sửa", + "delete": "Xóa", + "reset": "Đặt lại", + "noData": "Không có dữ liệu.", + "copySuccess": "Đã sao chép thành công", + "sure": "Chắc chắn", + "encryption": "Mã hóa", + "useIPv4ForHost": "Sử dụng IPv4 cho máy chủ", + "transmission": "Truyền tải", + "host": "Máy chủ", + "path": "Đường dẫn", + "camouflage": "Ngụy trang", + "status": "Trạng thái", + "enabled": "Đã kích hoạt", + "disabled": "Đã tắt", + "depleted": "Depleted", + "depletingSoon": "Depleting...", + "offline": "Ngoại tuyến", + "online": "Trực tuyến", + "domainName": "Tên miền", + "monitor": "Listening IP", + "certificate": "Chứng chỉ số", + "fail": "Thất bại", + "comment": "Bình luận", + "success": "Thành công", + "lastOnline": "Lần online gần nhất", + "getVersion": "Lấy phiên bản", + "install": "Cài đặt", + "clients": "Các khách hàng", + "usage": "Sử dụng", + "twoFactorCode": "Mã", + "remained": "Còn lại", + "security": "Bảo vệ", + "secAlertTitle": "Cảnh báo an ninh-Tiếng Việt by Ohoang7", + "secAlertSsl": "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn", + "secAlertConf": "Một số cài đặt có thể dễ bị tấn công. Đề xuất tăng cường các giao thức bảo mật để ngăn chặn các vi phạm tiềm ẩn.", + "secAlertSSL": "Bảng điều khiển thiếu kết nối an toàn. Vui lòng cài đặt chứng chỉ TLS để bảo vệ dữ liệu.", + "secAlertPanelPort": "Cổng mặc định của bảng điều khiển có thể dễ bị tấn công. Vui lòng cấu hình một cổng ngẫu nhiên hoặc cụ thể.", + "secAlertPanelURI": "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp.", + "secAlertSubURI": "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp.", + "secAlertSubJsonURI": "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp.", + "emptyDnsDesc": "Không có máy chủ DNS nào được thêm.", + "emptyFakeDnsDesc": "Không có máy chủ Fake DNS nào được thêm.", + "emptyBalancersDesc": "Không có bộ cân bằng tải nào được thêm.", + "emptyReverseDesc": "Không có proxy ngược nào được thêm.", + "somethingWentWrong": "Đã xảy ra lỗi", + "subscription": { + "title": "Thông tin đăng ký", + "subId": "ID đăng ký", + "status": "Trạng thái", + "downloaded": "Đã tải xuống", + "uploaded": "Đã tải lên", + "expiry": "Hết hạn", + "totalQuota": "Tổng hạn mức", + "individualLinks": "Liên kết riêng lẻ", + "active": "Hoạt động", + "inactive": "Không hoạt động", + "unlimited": "Không giới hạn", + "noExpiry": "Không hết hạn" + }, + "menu": { + "theme": "Chủ đề", + "dark": "Tối", + "ultraDark": "Siêu tối", + "dashboard": "Trạng thái hệ thống", + "inbounds": "Đầu vào khách hàng", + "settings": "Cài đặt bảng điều khiển", + "logout": "Đăng xuất", + "xray": "Cài đặt Xray", + "link": "Quản lý" + }, + "pages": { + "login": { + "hello": "Xin chào", + "title": "Chào mừng", + "loginAgain": "Thời hạn đăng nhập đã hết. Vui lòng đăng nhập lại.", + "toasts": { + "invalidFormData": "Dạng dữ liệu nhập không hợp lệ.", + "emptyUsername": "Vui lòng nhập tên người dùng.", + "emptyPassword": "Vui lòng nhập mật khẩu.", + "wrongUsernameOrPassword": "Tên người dùng, mật khẩu hoặc mã xác thực hai yếu tố không hợp lệ.", + "successLogin": "Bạn đã đăng nhập vào tài khoản thành công." + } + }, + "index": { + "title": "Trạng thái hệ thống", + "cpu": "CPU", + "logicalProcessors": "Bộ xử lý logic", + "frequency": "Tần số", + "swap": "Swap", + "storage": "Lưu trữ", + "memory": "RAM", + "threads": "Luồng", + "xrayStatus": "Xray", + "stopXray": "Dừng lại", + "restartXray": "Khởi động lại", + "xraySwitch": "Phiên bản", + "xraySwitchClick": "Chọn phiên bản mà bạn muốn chuyển đổi sang.", + "xraySwitchClickDesk": "Hãy lựa chọn thận trọng, vì các phiên bản cũ có thể không tương thích với các cấu hình hiện tại.", + "xrayUpdates": "Cập nhật Xray", + "updatePanel": "Cập nhật Panel", + "panelUpdateDesc": "Điều này sẽ cập nhật 3X-UI lên bản phát hành mới nhất và khởi động lại dịch vụ panel.", + "currentPanelVersion": "Phiên bản panel hiện tại", + "latestPanelVersion": "Phiên bản panel mới nhất", + "panelUpToDate": "Panel đã được cập nhật", + "upToDate": "Đã cập nhật", + "xrayStatusUnknown": "Không xác định", + "xrayStatusRunning": "Đang chạy", + "xrayStatusStop": "Dừng", + "xrayStatusError": "Lỗi", + "xrayErrorPopoverTitle": "Đã xảy ra lỗi khi chạy Xray", + "operationHours": "Thời gian hoạt động", + "systemHistoryTitle": "Lịch sử hệ thống", + "trendLast2Min": "2 phút gần nhất", + "systemLoad": "Tải hệ thống", + "systemLoadDesc": "trung bình tải hệ thống trong 1, 5 và 15 phút qua", + "connectionCount": "Số lượng kết nối", + "ipAddresses": "Địa chỉ IP", + "toggleIpVisibility": "Chuyển đổi hiển thị IP", + "overallSpeed": "Tốc độ tổng thể", + "upload": "Tải lên", + "download": "Tải xuống", + "totalData": "Tổng dữ liệu", + "sent": "Đã gửi", + "received": "Đã nhận", + "documentation": "Tài liệu", + "xraySwitchVersionDialog": "Bạn có chắc chắn muốn thay đổi phiên bản Xray không?", + "xraySwitchVersionDialogDesc": "Hành động này sẽ thay đổi phiên bản Xray thành #version#.", + "xraySwitchVersionPopover": "Xray đã được cập nhật thành công", + "panelUpdateDialog": "Bạn có chắc muốn cập nhật panel không?", + "panelUpdateDialogDesc": "Điều này sẽ cập nhật 3X-UI lên #version# và khởi động lại dịch vụ panel.", + "panelUpdateCheckPopover": "Kiểm tra cập nhật panel thất bại", + "panelUpdateStartedPopover": "Bắt đầu cập nhật panel", + "geofileUpdateDialog": "Bạn có chắc chắn muốn cập nhật geofile không?", + "geofileUpdateDialogDesc": "Hành động này sẽ cập nhật tệp #filename#.", + "geofilesUpdateDialogDesc": "Thao tác này sẽ cập nhật tất cả các tập tin.", + "geofilesUpdateAll": "Cập nhật tất cả", + "geofileUpdatePopover": "Geofile đã được cập nhật thành công", + "dontRefresh": "Đang tiến hành cài đặt, vui lòng không làm mới trang này.", + "logs": "Nhật ký", + "config": "Cấu hình", + "backup": "Sao lưu", + "backupTitle": "Sao lưu & Khôi phục", + "exportDatabase": "Sao lưu", + "exportDatabaseDesc": "Nhấp để tải xuống tệp .db chứa bản sao lưu cơ sở dữ liệu hiện tại của bạn vào thiết bị.", + "importDatabase": "Khôi phục", + "importDatabaseDesc": "Nhấp để chọn và tải lên tệp .db từ thiết bị của bạn để khôi phục cơ sở dữ liệu từ bản sao lưu.", + "importDatabaseSuccess": "Đã nhập cơ sở dữ liệu thành công", + "importDatabaseError": "Lỗi xảy ra khi nhập cơ sở dữ liệu", + "readDatabaseError": "Lỗi xảy ra khi đọc cơ sở dữ liệu", + "getDatabaseError": "Lỗi xảy ra khi truy xuất cơ sở dữ liệu", + "getConfigError": "Lỗi xảy ra khi truy xuất tệp cấu hình", + "customGeoTitle": "GeoSite / GeoIP tùy chỉnh", + "customGeoAdd": "Thêm", + "customGeoType": "Loại", + "customGeoAlias": "Bí danh", + "customGeoUrl": "URL", + "customGeoEnabled": "Bật", + "customGeoLastUpdated": "Cập nhật lần cuối", + "customGeoExtColumn": "Định tuyến (ext:…)", + "customGeoToastUpdateAll": "Đã cập nhật tất cả nguồn tùy chỉnh", + "customGeoActions": "Thao tác", + "customGeoEdit": "Sửa", + "customGeoDelete": "Xóa", + "customGeoDownload": "Cập nhật ngay", + "customGeoModalAdd": "Thêm geo tùy chỉnh", + "customGeoModalEdit": "Sửa geo tùy chỉnh", + "customGeoModalSave": "Lưu", + "customGeoDeleteConfirm": "Xóa nguồn geo tùy chỉnh này?", + "customGeoRoutingHint": "Trong quy tắc định tuyến dùng cột giá trị dạng ext:file.dat:tag (thay tag).", + "customGeoInvalidId": "ID tài nguyên không hợp lệ", + "customGeoAliasesError": "Không tải được bí danh geo tùy chỉnh", + "customGeoValidationAlias": "Bí danh chỉ gồm chữ thường, số, - và _", + "customGeoValidationUrl": "URL phải bắt đầu bằng http:// hoặc https://", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": " (tùy chỉnh)", + "customGeoToastList": "Danh sách geo tùy chỉnh", + "customGeoToastAdd": "Thêm geo tùy chỉnh", + "customGeoToastUpdate": "Cập nhật geo tùy chỉnh", + "customGeoToastDelete": "Đã xóa geofile tùy chỉnh “{{ .fileName }}”", + "customGeoToastDownload": "Đã cập nhật geofile “{{ .fileName }}”", + "customGeoErrInvalidType": "Loại phải là geosite hoặc geoip", + "customGeoErrAliasRequired": "Cần bí danh", + "customGeoErrAliasPattern": "Bí danh có ký tự không hợp lệ", + "customGeoErrAliasReserved": "Bí danh này được dành riêng", + "customGeoErrUrlRequired": "Cần URL", + "customGeoErrInvalidUrl": "URL không hợp lệ", + "customGeoErrUrlScheme": "URL phải dùng http hoặc https", + "customGeoErrUrlHost": "Máy chủ URL không hợp lệ", + "customGeoErrDuplicateAlias": "Bí danh này đã dùng cho loại này", + "customGeoErrNotFound": "Không tìm thấy nguồn geo tùy chỉnh", + "customGeoErrDownload": "Tải xuống thất bại", + "customGeoErrUpdateAllIncomplete": "Một hoặc nhiều nguồn geo tùy chỉnh không cập nhật được", + "customGeoEmpty": "Chưa có nguồn geo tùy chỉnh nào — nhấp Thêm để tạo" + }, + "inbounds": { + "allTimeTraffic": "Tổng Lưu Lượng", + "allTimeTrafficUsage": "Tổng mức sử dụng mọi lúc", + "title": "Điểm vào (Inbounds)", + "totalDownUp": "Tổng tải lên/tải xuống", + "totalUsage": "Tổng sử dụng", + "inboundCount": "Số lượng điểm vào", + "operate": "Thao tác", + "enable": "Kích hoạt", + "remark": "Chú thích", + "node": "Nút", + "deployTo": "Triển khai tới", + "localPanel": "Panel cục bộ", + "protocol": "Giao thức", + "port": "Cổng", + "portMap": "Cổng tạo", + "traffic": "Lưu lượng", + "details": "Chi tiết", + "transportConfig": "Giao vận", + "expireDate": "Ngày hết hạn", + "createdAt": "Tạo lúc", + "updatedAt": "Cập nhật", + "resetTraffic": "Đặt lại lưu lượng", + "addInbound": "Thêm điểm vào", + "generalActions": "Hành động chung", + "modifyInbound": "Chỉnh sửa điểm vào (Inbound)", + "deleteInbound": "Xóa điểm vào (Inbound)", + "deleteInboundContent": "Xác nhận xóa điểm vào? (Inbound)", + "deleteClient": "Xóa người dùng", + "deleteClientContent": "Bạn có chắc chắn muốn xóa người dùng không?", + "resetTrafficContent": "Xác nhận đặt lại lưu lượng?", + "copyLink": "Sao chép liên kết", + "address": "Địa chỉ", + "network": "Mạng", + "destinationPort": "Cổng đích", + "targetAddress": "Địa chỉ mục tiêu", + "monitorDesc": "Mặc định để trống", + "meansNoLimit": "= Không giới hạn (đơn vị: GB)", + "totalFlow": "Tổng lưu lượng", + "leaveBlankToNeverExpire": "Để trống để không bao giờ hết hạn", + "noRecommendKeepDefault": "Không yêu cầu đặc biệt để giữ nguyên cài đặt mặc định", + "certificatePath": "Đường dẫn tập", + "certificateContent": "Nội dung tập", + "publicKey": "Khóa công khai", + "privatekey": "Khóa cá nhân", + "clickOnQRcode": "Nhấn vào Mã QR để sao chép", + "client": "Người dùng", + "export": "Xuất liên kết", + "clone": "Sao chép", + "cloneInbound": "Sao chép điểm vào (Inbound)", + "cloneInboundContent": "Tất cả cài đặt của điểm vào này, trừ Cổng, IP nghe và máy khách, sẽ được áp dụng cho bản sao.", + "cloneInboundOk": "Sao chép", + "resetAllTraffic": "Đặt lại lưu lượng cho tất cả điểm vào", + "resetAllTrafficTitle": "Đặt lại lưu lượng cho tất cả điểm vào", + "resetAllTrafficContent": "Bạn có chắc chắn muốn đặt lại lưu lượng cho tất cả điểm vào không?", + "resetInboundClientTraffics": "Đặt lại lưu lượng toàn bộ người dùng của điểm vào", + "resetInboundClientTrafficTitle": "Đặt lại lưu lượng cho toàn bộ người dùng của điểm vào", + "resetInboundClientTrafficContent": "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho các người dùng của điểm vào này không?", + "resetAllClientTraffics": "Đặt lại lưu lượng cho toàn bộ người dùng", + "resetAllClientTrafficTitle": "Đặt lại lưu lượng cho toàn bộ người dùng", + "resetAllClientTrafficContent": "Bạn có chắc chắn muốn đặt lại tất cả lưu lượng cho toàn bộ người dùng không?", + "delDepletedClients": "Xóa các người dùng đã cạn kiệt", + "delDepletedClientsTitle": "Xóa các người dùng đã cạn kiệt", + "delDepletedClientsContent": "Bạn có chắc chắn muốn xóa toàn bộ người dùng đã cạn kiệt không?", + "email": "Email", + "emailDesc": "Vui lòng cung cấp một địa chỉ email duy nhất.", + "IPLimit": "Giới hạn IP", + "IPLimitDesc": "Vô hiệu hóa điểm vào nếu số lượng vượt quá giá trị đã nhập (nhập 0 để vô hiệu hóa giới hạn IP).", + "IPLimitlog": "Lịch sử IP", + "IPLimitlogDesc": "Lịch sử đăng nhập IP (trước khi kích hoạt điểm vào sau khi bị vô hiệu hóa bởi giới hạn IP, bạn nên xóa lịch sử).", + "IPLimitlogclear": "Xóa Lịch sử", + "setDefaultCert": "Đặt chứng chỉ từ bảng điều khiển", + "telegramDesc": "Vui lòng cung cấp ID Trò chuyện Telegram. (sử dụng lệnh '/id' trong bot) hoặc ({'@'}userinfobot)", + "subscriptionDesc": "Bạn có thể tìm liên kết gói đăng ký của mình trong Chi tiết, cũng như bạn có thể sử dụng cùng tên cho nhiều cấu hình khác nhau", + "info": "Thông tin", + "same": "Giống nhau", + "inboundData": "Dữ liệu gửi đến", + "exportInbound": "Xuất nhập khẩu", + "import": "Nhập", + "importInbound": "Nhập inbound", + "periodicTrafficResetTitle": "Đặt lại lưu lượng", + "periodicTrafficResetDesc": "Tự động đặt lại bộ đếm lưu lượng theo khoảng thời gian xác định", + "lastReset": "Đặt lại lần cuối", + "periodicTrafficReset": { + "never": "Không bao giờ", + "daily": "Hàng ngày", + "weekly": "Hàng tuần", + "monthly": "Hàng tháng", + "hourly": "Hàng giờ" + }, + "toasts": { + "obtain": "Nhận", + "updateSuccess": "Cập nhật thành công", + "logCleanSuccess": "Đã xóa nhật ký", + "inboundsUpdateSuccess": "Đã cập nhật thành công các kết nối inbound", + "inboundUpdateSuccess": "Đã cập nhật thành công kết nối inbound", + "inboundCreateSuccess": "Đã tạo thành công kết nối inbound", + "inboundDeleteSuccess": "Đã xóa thành công kết nối inbound", + "inboundClientAddSuccess": "Đã thêm client inbound", + "inboundClientDeleteSuccess": "Đã xóa client inbound", + "inboundClientUpdateSuccess": "Đã cập nhật client inbound", + "delDepletedClientsSuccess": "Đã xóa tất cả client hết hạn", + "resetAllClientTrafficSuccess": "Đã đặt lại toàn bộ lưu lượng client", + "resetAllTrafficSuccess": "Đã đặt lại toàn bộ lưu lượng", + "resetInboundClientTrafficSuccess": "Đã đặt lại lưu lượng", + "trafficGetError": "Lỗi khi lấy thông tin lưu lượng", + "getNewX25519CertError": "Lỗi khi lấy chứng chỉ X25519.", + "getNewmldsa65Error": "Lỗi khi lấy chứng chỉ mldsa65.", + "getNewVlessEncError": "Lỗi khi lấy chứng chỉ VlessEnc." + }, + "stream": { + "general": { + "request": "Lời yêu cầu", + "response": "Phản ứng", + "name": "Tên", + "value": "Giá trị" + }, + "tcp": { + "version": "Phiên bản", + "method": "Phương pháp", + "path": "Đường dẫn", + "status": "Trạng thái", + "statusDescription": "Tình trạng Mô tả", + "requestHeader": "Header yêu cầu", + "responseHeader": "Header phản hồi" + } + } + }, + "client": { + "add": "Thêm người dùng", + "edit": "Chỉnh sửa người dùng", + "submitAdd": "Thêm", + "submitEdit": "Lưu thay đổi", + "clientCount": "Số lượng người dùng", + "bulk": "Thêm hàng loạt", + "copyFromInbound": "Sao chép người dùng từ Inbound", + "copyToInbound": "Sao chép người dùng đến", + "copySelected": "Sao chép đã chọn", + "copySource": "Nguồn", + "copyEmailPreview": "Xem trước email kết quả", + "copySelectSourceFirst": "Vui lòng chọn Inbound nguồn trước.", + "copyResult": "Kết quả sao chép", + "copyResultSuccess": "Đã sao chép thành công", + "copyResultNone": "Không có gì để sao chép: chưa chọn người dùng hoặc nguồn trống", + "copyResultErrors": "Lỗi sao chép", + "copyFlowLabel": "Flow cho người dùng mới (VLESS)", + "copyFlowHint": "Áp dụng cho tất cả người dùng được sao chép. Để trống để bỏ qua.", + "selectAll": "Chọn tất cả", + "clearAll": "Bỏ chọn tất cả", + "method": "Phương pháp", + "first": "Đầu tiên", + "last": "Cuối cùng", + "prefix": "Tiền tố", + "postfix": "Hậu tố", + "delayedStart": "Bắt đầu ở Lần Đầu", + "expireDays": "Khoảng thời gian", + "days": "ngày", + "renew": "Tự động gia hạn", + "renewDesc": "Tự động gia hạn sau khi hết hạn. (0 = tắt)(đơn vị: ngày)" + }, + "nodes": { + "title": "Nút", + "addNode": "Thêm nút", + "editNode": "Chỉnh sửa nút", + "totalNodes": "Tổng số nút", + "onlineNodes": "Trực tuyến", + "offlineNodes": "Ngoại tuyến", + "avgLatency": "Độ trễ trung bình", + "name": "Tên", + "namePlaceholder": "vd: de-frankfurt-1", + "addressPlaceholder": "panel.example.com hoặc 1.2.3.4", + "remark": "Chú thích", + "scheme": "Giao thức", + "address": "Địa chỉ", + "port": "Cổng", + "basePath": "Đường dẫn cơ sở", + "apiToken": "Token API", + "apiTokenPlaceholder": "Token từ trang Cài đặt của panel từ xa", + "apiTokenHint": "Panel từ xa hiển thị token API tại Cài đặt → Token API.", + "regenerate": "Tạo lại token", + "regenerateConfirm": "Tạo lại sẽ vô hiệu hóa token hiện tại. Mọi panel trung tâm dùng nó sẽ mất quyền truy cập cho đến khi được cập nhật. Tiếp tục?", + "enable": "Kích hoạt", + "status": "Trạng thái", + "cpu": "CPU", + "mem": "Bộ nhớ", + "uptime": "Thời gian hoạt động", + "latency": "Độ trễ", + "lastHeartbeat": "Heartbeat gần nhất", + "xrayVersion": "Phiên bản Xray", + "actions": "Hành động", + "probe": "Kiểm tra ngay", + "testConnection": "Kiểm tra kết nối", + "connectionOk": "Kết nối OK ({ms} ms)", + "connectionFailed": "Kết nối thất bại", + "never": "chưa bao giờ", + "justNow": "vừa xong", + "deleteConfirmTitle": "Xóa nút \"{name}\"?", + "deleteConfirmContent": "Việc này dừng giám sát nút. Panel từ xa không bị ảnh hưởng.", + "statusValues": { + "online": "Trực tuyến", + "offline": "Ngoại tuyến", + "unknown": "Không xác định" + }, + "toasts": { + "list": "Không tải được danh sách nút", + "obtain": "Không tải được nút", + "add": "Thêm nút", + "update": "Cập nhật nút", + "delete": "Xóa nút", + "deleted": "Đã xóa nút", + "test": "Kiểm tra kết nối", + "fillRequired": "Tên, địa chỉ, cổng và token API là bắt buộc", + "probeFailed": "Kiểm tra thất bại" + } + }, + "settings": { + "title": "Cài đặt", + "save": "Lưu", + "infoDesc": "Mọi thay đổi được thực hiện ở đây cần phải được lưu. Vui lòng khởi động lại bảng điều khiển để áp dụng các thay đổi.", + "restartPanel": "Khởi động lại bảng điều khiển", + "restartPanelDesc": "Bạn có chắc chắn muốn khởi động lại bảng điều khiển? Nhấn OK để khởi động lại sau 3 giây. Nếu bạn không thể truy cập bảng điều khiển sau khi khởi động lại, vui lòng xem thông tin nhật ký của bảng điều khiển trên máy chủ.", + "restartPanelSuccess": "Đã khởi động lại bảng điều khiển thành công", + "actions": "Hành động", + "resetDefaultConfig": "Đặt lại cấu hình mặc định", + "panelSettings": "Bảng điều khiển", + "securitySettings": "Bảo mật", + "TGBotSettings": "Bot Telegram", + "panelListeningIP": "IP Nghe của bảng điều khiển", + "panelListeningIPDesc": "Mặc định để trống để nghe tất cả các IP.", + "panelListeningDomain": "Tên miền của nghe bảng điều khiển", + "panelListeningDomainDesc": "Mặc định để trống để nghe tất cả các tên miền và IP", + "panelPort": "Cổng bảng điều khiển", + "panelPortDesc": "Cổng được sử dụng để kết nối với bảng điều khiển này", + "publicKeyPath": "Đường dẫn file chứng chỉ bảng điều khiển", + "publicKeyPathDesc": "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')", + "privateKeyPath": "Đường dẫn file khóa của chứng chỉ bảng điều khiển", + "privateKeyPathDesc": "Điền vào đường dẫn đầy đủ (bắt đầu từ '/')", + "panelUrlPath": "Đường dẫn gốc URL bảng điều khiển", + "panelUrlPathDesc": "Phải bắt đầu và kết thúc bằng '/'", + "pageSize": "Kích thước phân trang", + "pageSizeDesc": "Xác định kích thước trang cho bảng gửi đến. Đặt 0 để tắt", + "remarkModel": "Ghi chú mô hình và ký tự phân tách", + "datepicker": "Kiểu lịch", + "datepickerPlaceholder": "Chọn ngày", + "datepickerDescription": "Tác vụ chạy theo lịch trình sẽ chạy theo kiểu lịch này.", + "sampleRemark": "Nhận xét mẫu", + "oldUsername": "Tên người dùng hiện tại", + "currentPassword": "Mật khẩu hiện tại", + "newUsername": "Tên người dùng mới", + "newPassword": "Mật khẩu mới", + "telegramBotEnable": "Bật Bot Telegram", + "telegramBotEnableDesc": "Kết nối với các tính năng của bảng điều khiển này thông qua bot Telegram", + "telegramToken": "Token Telegram", + "telegramTokenDesc": "Bạn phải nhận token từ quản lý bot Telegram {'@'}botfather", + "telegramProxy": "Socks5 Proxy", + "telegramProxyDesc": "Nếu bạn cần socks5 proxy để kết nối với Telegram. Điều chỉnh cài đặt của nó theo hướng dẫn.", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "Máy chủ API Telegram để sử dụng. Để trống để sử dụng máy chủ mặc định.", + "telegramChatId": "Chat ID Telegram của quản trị viên", + "telegramChatIdDesc": "Nhiều Chat ID phân tách bằng dấu phẩy. Sử dụng {'@'}userinfobot hoặc sử dụng lệnh '/id' trong bot để lấy Chat ID của bạn.", + "telegramNotifyTime": "Thời gian thông báo của bot Telegram", + "telegramNotifyTimeDesc": "Sử dụng định dạng thời gian Crontab.", + "tgNotifyBackup": "Sao lưu Cơ sở dữ liệu", + "tgNotifyBackupDesc": "Bao gồm tệp sao lưu cơ sở dữ liệu với thông báo báo cáo.", + "tgNotifyLogin": "Thông báo Đăng nhập", + "tgNotifyLoginDesc": "Hiển thị tên người dùng, địa chỉ IP và thời gian khi ai đó cố gắng đăng nhập vào bảng điều khiển của bạn.", + "sessionMaxAge": "Thời gian tối đa của phiên", + "sessionMaxAgeDesc": "Thời gian của phiên đăng nhập (đơn vị: phút)", + "expireTimeDiff": "Ngưỡng hết hạn cho thông báo", + "expireTimeDiffDesc": "Nhận thông báo về việc hết hạn tài khoản trước ngưỡng này (đơn vị: ngày)", + "trafficDiff": "Ngưỡng lưu lượng cho thông báo", + "trafficDiffDesc": "Nhận thông báo về việc cạn kiệt lưu lượng trước khi đạt đến ngưỡng này (đơn vị: GB)", + "tgNotifyCpu": "Ngưỡng cảnh báo tỷ lệ CPU", + "tgNotifyCpuDesc": "Nhận thông báo nếu tỷ lệ sử dụng CPU vượt quá ngưỡng này (đơn vị: %)", + "timeZone": "Múi giờ", + "timeZoneDesc": "Các tác vụ được lên lịch chạy theo thời gian trong múi giờ này.", + "subSettings": "Gói đăng ký", + "subEnable": "Bật dịch vụ", + "subEnableDesc": "Tính năng gói đăng ký với cấu hình riêng", + "subJsonEnable": "Bật/Tắt điểm cuối đăng ký JSON độc lập.", + "subTitle": "Tiêu đề Đăng ký", + "subTitleDesc": "Tiêu đề hiển thị trong ứng dụng VPN", + "subSupportUrl": "URL Hỗ trợ", + "subSupportUrlDesc": "Liên kết hỗ trợ kỹ thuật hiển thị trong ứng dụng VPN", + "subProfileUrl": "URL Hồ sơ", + "subProfileUrlDesc": "Liên kết đến trang web của bạn hiển thị trong ứng dụng VPN", + "subAnnounce": "Thông báo", + "subAnnounceDesc": "Văn bản thông báo hiển thị trong ứng dụng VPN", + "subEnableRouting": "Bật định tuyến", + "subEnableRoutingDesc": "Cài đặt toàn cục để bật định tuyến trong ứng dụng khách VPN. (Chỉ dành cho Happ)", + "subRoutingRules": "Quy tắc định tuyến", + "subRoutingRulesDesc": "Quy tắc định tuyến toàn cầu cho client VPN. (Chỉ dành cho Happ)", + "subListen": "Listening IP", + "subListenDesc": "Mặc định để trống để nghe tất cả các IP", + "subPort": "Cổng gói đăng ký", + "subPortDesc": "Số cổng dịch vụ đăng ký phải chưa được sử dụng trên máy chủ", + "subCertPath": "Đường dẫn file chứng chỉ gói đăng ký", + "subCertPathDesc": "Điền vào đường dẫn đầy đủ (bắt đầu với '/')", + "subKeyPath": "Đường dẫn file khóa của chứng chỉ gói đăng ký", + "subKeyPathDesc": "Điền vào đường dẫn đầy đủ (bắt đầu với '/')", + "subPath": "Đường dẫn gốc URL gói đăng ký", + "subPathDesc": "Phải bắt đầu và kết thúc bằng '/'", + "subDomain": "Tên miền con", + "subDomainDesc": "Mặc định để trống để nghe tất cả các tên miền và IP", + "subUpdates": "Khoảng thời gian cập nhật gói đăng ký", + "subUpdatesDesc": "Số giờ giữa các cập nhật trong ứng dụng khách", + "subEncrypt": "Mã hóa cấu hình", + "subEncryptDesc": "Mã hóa các cấu hình được trả về trong gói đăng ký", + "subShowInfo": "Hiển thị thông tin sử dụng", + "subShowInfoDesc": "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình", + "subURI": "URI proxy trung gian", + "subURIDesc": "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian", + "externalTrafficInformEnable": "Thông báo giao thông bên ngoài", + "externalTrafficInformEnableDesc": "Thông báo cho API bên ngoài về mọi cập nhật lưu lượng truy cập.", + "externalTrafficInformURI": "URI thông báo lưu lượng truy cập bên ngoài", + "externalTrafficInformURIDesc": "Cập nhật lưu lượng truy cập được gửi tới URI này.", + "restartXrayOnClientDisable": "Khởi Động Lại Xray Sau Khi Tự Động Vô Hiệu Hóa", + "restartXrayOnClientDisableDesc": "Khi người dùng bị vô hiệu hóa tự động do hết hạn hoặc chạm giới hạn lưu lượng, hãy khởi động lại Xray.", + "fragment": "Sự phân mảnh", + "fragmentDesc": "Kích hoạt phân mảnh cho gói TLS hello", + "fragmentSett": "Cài đặt phân mảnh", + "noisesDesc": "Bật Noises.", + "noisesSett": "Cài đặt Noises", + "mux": "Mux", + "muxDesc": "Truyền nhiều luồng dữ liệu độc lập trong luồng dữ liệu đã thiết lập.", + "muxSett": "Mux Cài đặt", + "direct": "Kết nối trực tiếp", + "directDesc": "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể.", + "notifications": "Thông báo", + "certs": "Chứng chỉ", + "externalTraffic": "Lưu lượng bên ngoài", + "dateAndTime": "Ngày và giờ", + "proxyAndServer": "Proxy và máy chủ", + "intervals": "Khoảng thời gian", + "information": "Thông tin", + "language": "Ngôn ngữ", + "telegramBotLanguage": "Ngôn ngữ của Bot Telegram", + "security": { + "admin": "Thông tin đăng nhập quản trị viên", + "twoFactor": "Xác thực hai yếu tố", + "twoFactorEnable": "Bật 2FA", + "twoFactorEnableDesc": "Thêm một lớp bảo mật bổ sung để tăng cường an toàn.", + "twoFactorModalSetTitle": "Bật xác thực hai yếu tố", + "twoFactorModalDeleteTitle": "Tắt xác thực hai yếu tố", + "twoFactorModalSteps": "Để thiết lập xác thực hai yếu tố, hãy thực hiện các bước sau:", + "twoFactorModalFirstStep": "1. Quét mã QR này trong ứng dụng xác thực hoặc sao chép mã token gần mã QR và dán vào ứng dụng", + "twoFactorModalSecondStep": "2. Nhập mã từ ứng dụng", + "twoFactorModalRemoveStep": "Nhập mã từ ứng dụng để xóa xác thực hai yếu tố.", + "twoFactorModalChangeCredentialsTitle": "Thay đổi thông tin xác thực", + "twoFactorModalChangeCredentialsStep": "Nhập mã từ ứng dụng để thay đổi thông tin xác thực quản trị viên.", + "twoFactorModalSetSuccess": "Xác thực hai yếu tố đã được thiết lập thành công", + "twoFactorModalDeleteSuccess": "Xác thực hai yếu tố đã được xóa thành công", + "twoFactorModalError": "Mã sai" + }, + "toasts": { + "modifySettings": "Các tham số đã được thay đổi.", + "getSettings": "Lỗi xảy ra khi truy xuất tham số.", + "modifyUserError": "Đã xảy ra lỗi khi thay đổi thông tin đăng nhập quản trị viên.", + "modifyUser": "Bạn đã thay đổi thông tin đăng nhập quản trị viên thành công.", + "originalUserPassIncorrect": "Tên người dùng hoặc mật khẩu gốc không đúng", + "userPassMustBeNotEmpty": "Tên người dùng mới và mật khẩu mới không thể để trống", + "getOutboundTrafficError": "Lỗi khi lấy lưu lượng truy cập đi", + "resetOutboundTrafficError": "Lỗi khi đặt lại lưu lượng truy cập đi" + } + }, + "xray": { + "title": "Cài đặt Xray", + "save": "Lưu cài đặt", + "restart": "Khởi động lại Xray", + "restartSuccess": "Đã khởi động lại Xray thành công", + "stopSuccess": "Xray đã được dừng thành công", + "restartError": "Đã xảy ra lỗi khi khởi động lại Xray.", + "stopError": "Đã xảy ra lỗi khi dừng Xray.", + "basicTemplate": "Mẫu Cơ bản", + "advancedTemplate": "Mẫu Nâng cao", + "generalConfigs": "Cấu hình Chung", + "generalConfigsDesc": "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát.", + "logConfigs": "Nhật ký", + "logConfigsDesc": "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần", + "blockConfigsDesc": "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể.", + "basicRouting": "Định tuyến Cơ bản", + "blockConnectionsConfigsDesc": "Các tùy chọn này sẽ chặn lưu lượng truy cập dựa trên quốc gia được yêu cầu cụ thể.", + "directConnectionsConfigsDesc": "Kết nối trực tiếp đảm bảo rằng lưu lượng truy cập cụ thể không được định tuyến qua máy chủ khác.", + "blockips": "Chặn IP", + "blockdomains": "Chặn Tên Miền", + "directips": "IP Trực Tiếp", + "directdomains": "Tên Miền Trực Tiếp", + "ipv4Routing": "Định tuyến IPv4", + "ipv4RoutingDesc": "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4.", + "warpRouting": "Định tuyến WARP", + "warpRoutingDesc": "Cảnh báo: Trước khi sử dụng những tùy chọn này, hãy cài đặt WARP ở chế độ proxy socks5 trên máy chủ của bạn bằng cách làm theo các bước trên GitHub của bảng điều khiển. WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare.", + "nordRouting": "Định tuyến NordVPN", + "nordRoutingDesc": "Các tùy chọn này sẽ định tuyến lưu lượng dựa trên đích cụ thể qua NordVPN.", + "Template": "Mẫu Cấu hình Xray", + "TemplateDesc": "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này.", + "FreedomStrategy": "Cấu hình Chiến lược cho Giao thức Freedom", + "FreedomStrategyDesc": "Đặt chiến lược đầu ra của mạng trong Giao thức Freedom.", + "RoutingStrategy": "Cấu hình Chiến lược Định tuyến Tên miền", + "RoutingStrategyDesc": "Đặt chiến lược định tuyến tổng thể cho việc giải quyết DNS.", + "outboundTestUrl": "URL kiểm tra outbound", + "outboundTestUrlDesc": "URL dùng khi kiểm tra kết nối outbound", + "Torrent": "Cấu hình sử dụng BitTorrent", + "Inbounds": "Đầu vào", + "InboundsDesc": "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể.", + "Outbounds": "Đầu ra", + "Balancers": "Cân bằng", + "OutboundsDesc": "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này.", + "Routings": "Quy tắc định tuyến", + "RoutingsDesc": "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!", + "completeTemplate": "All", + "logLevel": "Mức đăng nhập", + "logLevelDesc": "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại.", + "accessLog": "Nhật ký truy cập", + "accessLogDesc": "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'", + "errorLog": "Nhật ký lỗi", + "errorLogDesc": "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'", + "dnsLog": "Nhật ký DNS", + "dnsLogDesc": "Có bật nhật ký truy vấn DNS không", + "maskAddress": "Ẩn Địa Chỉ", + "maskAddressDesc": "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký.", + "statistics": "Thống kê", + "statsInboundUplink": "Thống kê tải lên đầu vào", + "statsInboundUplinkDesc": "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu vào.", + "statsInboundDownlink": "Thống kê tải xuống đầu vào", + "statsInboundDownlinkDesc": "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu vào.", + "statsOutboundUplink": "Thống kê tải lên đầu ra", + "statsOutboundUplinkDesc": "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu ra.", + "statsOutboundDownlink": "Thống kê tải xuống đầu ra", + "statsOutboundDownlinkDesc": "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu ra.", + "rules": { + "first": "Đầu tiên", + "last": "Cuối cùng", + "up": "Lên", + "down": "Xuống", + "source": "Nguồn", + "dest": "Đích", + "inbound": "Vào", + "outbound": "Ra", + "balancer": "Cân bằng", + "info": "Thông tin", + "add": "Thêm quy tắc", + "edit": "Chỉnh sửa quy tắc", + "useComma": "Các mục được phân tách bằng dấu phẩy" + }, + "outbound": { + "addOutbound": "Thêm thư đi", + "addReverse": "Thêm đảo ngược", + "editOutbound": "Chỉnh sửa gửi đi", + "editReverse": "Chỉnh sửa ngược lại", + "reverseTag": "Thẻ Ngược", + "reverseTagDesc": "Thẻ outbound của proxy ngược đơn giản VLESS. Để trống để vô hiệu hóa.", + "reverseTagPlaceholder": "thẻ outbound (để trống để vô hiệu hóa)", + "tag": "Thẻ", + "tagDesc": "thẻ duy nhất", + "address": "Địa chỉ", + "reverse": "Đảo ngược", + "domain": "Miền", + "type": "Loại", + "bridge": "Cầu", + "portal": "Cổng thông tin", + "link": "Liên kết", + "intercon": "Kết nối", + "settings": "cài đặt", + "accountInfo": "Thông tin tài khoản", + "outboundStatus": "Trạng thái đầu ra", + "sendThrough": "Gửi qua", + "test": "Kiểm tra", + "testResult": "Kết quả kiểm tra", + "testing": "Đang kiểm tra kết nối...", + "testSuccess": "Kiểm tra thành công", + "testFailed": "Kiểm tra thất bại", + "testError": "Không thể kiểm tra đầu ra", + "nordvpn": "NordVPN", + "accessToken": "Mã truy cập", + "country": "Quốc gia", + "server": "Máy chủ", + "city": "Thành phố", + "allCities": "Tất cả thành phố", + "privateKey": "Khóa riêng", + "load": "Tải" + }, + "balancer": { + "addBalancer": "Thêm cân bằng", + "editBalancer": "Chỉnh sửa cân bằng", + "balancerStrategy": "Chiến lược", + "balancerSelectors": "Bộ chọn", + "tag": "Thẻ", + "tagDesc": "thẻ duy nhất", + "balancerDesc": "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động." + }, + "wireguard": { + "secretKey": "Khoá bí mật", + "publicKey": "Khóa công khai", + "allowedIPs": "IP được phép", + "endpoint": "Điểm cuối", + "psk": "Khóa chia sẻ", + "domainStrategy": "Chiến lược tên miền" + }, + "tun": { + "nameDesc": "Tên của giao diện TUN. Giá trị mặc định là 'xray0'", + "mtuDesc": "Đơn vị Truyền Tối đa. Kích thước tối đa của các gói dữ liệu. Giá trị mặc định là 1500", + "userLevel": "Mức Người Dùng", + "userLevelDesc": "Tất cả các kết nối được thực hiện thông qua inbound này sẽ sử dụng mức người dùng này. Giá trị mặc định là 0" + }, + "dns": { + "enable": "Kích hoạt DNS", + "enableDesc": "Kích hoạt máy chủ DNS tích hợp", + "tag": "Thẻ gửi đến DNS", + "tagDesc": "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến.", + "clientIp": "IP khách hàng", + "clientIpDesc": "Được sử dụng để thông báo cho máy chủ về vị trí IP được chỉ định trong các truy vấn DNS", + "disableCache": "Tắt bộ nhớ đệm", + "disableCacheDesc": "Tắt bộ nhớ đệm DNS", + "disableFallback": "Tắt Fallback", + "disableFallbackDesc": "Tắt các truy vấn DNS Fallback", + "disableFallbackIfMatch": "Tắt Fallback Nếu Khớp", + "disableFallbackIfMatchDesc": "Tắt các truy vấn DNS Fallback khi danh sách tên miền khớp của máy chủ DNS được kích hoạt", + "enableParallelQuery": "Bật Truy vấn Song song", + "enableParallelQueryDesc": "Bật truy vấn DNS song song đến nhiều máy chủ để phân giải nhanh hơn", + "strategy": "Chiến lược truy vấn", + "strategyDesc": "Chiến lược tổng thể để phân giải tên miền", + "add": "Thêm máy chủ", + "edit": "Chỉnh sửa máy chủ", + "domains": "Tên miền", + "expectIPs": "Các IP Dự Kiến", + "unexpectIPs": "IP không mong muốn", + "useSystemHosts": "Sử dụng Hosts hệ thống", + "useSystemHostsDesc": "Sử dụng file hosts từ hệ thống đã cài đặt", + "usePreset": "Dùng mẫu", + "dnsPresetTitle": "Mẫu DNS", + "dnsPresetFamily": "Gia đình" + }, + "fakedns": { + "add": "Thêm DNS giả", + "edit": "Chỉnh sửa DNS giả", + "ipPool": "Mạng con nhóm IP", + "poolSize": "Kích thước bể bơi" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ Bàn phím đã đóng!", + "noResult": "❗ Không có kết quả!", + "noQuery": "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!", + "wentWrong": "❌ Đã xảy ra lỗi!", + "noIpRecord": "❗ Không có bản ghi IP!", + "noInbounds": "❗ Không tìm thấy inbound!", + "unlimited": "♾ Không giới hạn (Đặt lại)", + "add": "Thêm", + "month": "Tháng", + "months": "Tháng", + "day": "Ngày", + "days": "Ngày", + "hours": "Giờ", + "minutes": "Phút", + "unknown": "Không xác định", + "inbounds": "Inbound", + "clients": "Client", + "offline": "🔴 Ngoại tuyến", + "online": "🟢 Trực tuyến", + "commands": { + "unknown": "❗ Lệnh không rõ", + "pleaseChoose": "👇 Vui lòng chọn:\r\n", + "help": "🤖 Chào mừng bạn đến với bot này! Bot được thiết kế để cung cấp cho bạn dữ liệu cụ thể từ máy chủ và cho phép bạn thực hiện các thay đổi cần thiết.\r\n\r\n", + "start": "👋 Xin chào {{ .Firstname }}.\r\n", + "welcome": "🤖 Chào mừng đến với bot quản lý của {{ .Hostname }}.\r\n", + "status": "✅ Bot hoạt động bình thường!", + "usage": "❗ Vui lòng cung cấp văn bản để tìm kiếm!", + "getID": "🆔 ID của bạn: {{ .ID }}", + "helpAdminCommands": "Để khởi động lại Xray Core:\r\n/restart\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n/usage [Email]\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n/inbound [Ghi chú]\r\n\r\nID Trò chuyện Telegram:\r\n/id", + "helpClientCommands": "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n/usage [Email]\r\n\r\nID Trò chuyện Telegram:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ Hoạt động thành công!", + "restartFailed": "❗ Lỗi trong quá trình hoạt động.\r\n\r\nLỗi: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core không chạy.", + "startDesc": "Hiển thị menu chính", + "helpDesc": "Trợ giúp bot", + "statusDesc": "Kiểm tra trạng thái bot", + "idDesc": "Hiển thị ID Telegram của bạn" + }, + "messages": { + "cpuThreshold": "🔴 Sử dụng CPU {{ .Percent }}% vượt quá ngưỡng {{ .Threshold }}%", + "selectUserFailed": "❌ Lỗi khi chọn người dùng!", + "userSaved": "✅ Người dùng Telegram đã được lưu.", + "loginSuccess": "✅ Đăng nhập thành công vào bảng điều khiển.\r\n", + "loginFailed": "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n", + "2faFailed": "Lỗi 2FA", + "report": "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n", + "datetime": "⏰ Ngày-Giờ: {{ .DateTime }}\r\n", + "hostname": "💻 Tên máy chủ: {{ .Hostname }}\r\n", + "version": "🚀 Phiên bản X-UI: {{ .Version }}\r\n", + "xrayVersion": "📡 Phiên bản Xray: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6: {{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4: {{ .IPv4 }}\r\n", + "ip": "🌐 IP: {{ .IP }}\r\n", + "ips": "🔢 Các IP:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ Thời gian hoạt động của máy chủ: {{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 Tải máy chủ: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 Bộ nhớ máy chủ: {{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 Số lượng kết nối TCP: {{ .Count }}\r\n", + "udpCount": "🔸 Số lượng kết nối UDP: {{ .Count }}\r\n", + "traffic": "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Trạng thái Xray: {{ .State }}\r\n", + "username": "👤 Tên người dùng: {{ .Username }}\r\n", + "reason": "❗️ Lý do: {{ .Reason }}\r\n", + "time": "⏰ Thời gian: {{ .Time }}\r\n", + "inbound": "📍 Inbound: {{ .Remark }}\r\n", + "port": "🔌 Cổng: {{ .Port }}\r\n", + "expire": "📅 Ngày hết hạn: {{ .Time }}\r\n", + "expireIn": "📅 Hết hạn sau: {{ .Time }}\r\n", + "active": "💡 Đang hoạt động: {{ .Enable }}\r\n", + "enabled": "🚨 Đã bật: {{ .Enable }}\r\n", + "online": "🌐 Trạng thái kết nối: {{ .Status }}\r\n", + "lastOnline": "🔙 Lần online gần nhất: {{ .Time }}\r\n", + "email": "📧 Email: {{ .Email }}\r\n", + "upload": "🔼 Tải lên: ↑{{ .Upload }}\r\n", + "download": "🔽 Tải xuống: ↓{{ .Download }}\r\n", + "total": "📊 Tổng cộng: ↑↓{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 Người dùng Telegram: {{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 Sự cạn kiệt {{ .Type }}:\r\n", + "exhaustedCount": "🚨 Số lần cạn kiệt {{ .Type }}:\r\n", + "onlinesCount": "🌐 Khách hàng trực tuyến: {{ .Count }}\r\n", + "disabled": "🛑 Vô hiệu hóa: {{ .Disabled }}\r\n", + "depleteSoon": "🔜 Sắp cạn kiệt: {{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 Thời gian sao lưu: {{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n", + "yes": "✅ Có", + "no": "❌ Không", + "received_id": "🔑📥 ID đã được cập nhật.", + "received_password": "🔑📥 Mật khẩu đã được cập nhật.", + "received_email": "📧📥 Email đã được cập nhật.", + "received_comment": "💬📥 Bình luận đã được cập nhật.", + "id_prompt": "🔑 ID mặc định: {{ .ClientId }}\n\nVui lòng nhập ID của bạn.", + "pass_prompt": "🔑 Mật khẩu mặc định: {{ .ClientPassword }}\n\nVui lòng nhập mật khẩu của bạn.", + "email_prompt": "📧 Email mặc định: {{ .ClientEmail }}\n\nVui lòng nhập email của bạn.", + "comment_prompt": "💬 Bình luận mặc định: {{ .ClientComment }}\n\nVui lòng nhập bình luận của bạn.", + "inbound_client_data_id": "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!", + "inbound_client_data_pass": "🔄 Kết nối vào: {{ .InboundRemark }}\n\n🔑 Mật khẩu: {{ .ClientPass }}\n📧 Email: {{ .ClientEmail }}\n📊 Dung lượng: {{ .ClientTraffic }}\n📅 Ngày hết hạn: {{ .ClientExp }}\n🌐 Giới hạn IP: {{ .IpLimit }}\n💬 Ghi chú: {{ .ClientComment }}\n\nBây giờ bạn có thể thêm khách hàng vào inbound!", + "cancel": "❌ Quá trình đã bị hủy! \n\nBạn có thể bắt đầu lại bất cứ lúc nào bằng cách nhập /start. 🔄", + "error_add_client": "⚠️ Lỗi:\n\n {{ .error }}", + "using_default_value": "Được rồi, tôi sẽ sử dụng giá trị mặc định. 😊", + "incorrect_input": "Dữ liệu bạn nhập không hợp lệ.\nCác chuỗi phải liền mạch và không có dấu cách.\nVí dụ đúng: aaaaaa\nVí dụ sai: aaa aaa 🚫", + "AreYouSure": "Bạn có chắc không? 🤔", + "SuccessResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ✅ Thành công", + "FailedResetTraffic": "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 Quá trình đặt lại lưu lượng đã hoàn tất cho tất cả khách hàng." + }, + "buttons": { + "closeKeyboard": "❌ Đóng Bàn Phím", + "cancel": "❌ Hủy", + "cancelReset": "❌ Hủy Đặt Lại", + "cancelIpLimit": "❌ Hủy Giới Hạn IP", + "confirmResetTraffic": "✅ Xác Nhận Đặt Lại Lưu Lượng?", + "confirmClearIps": "✅ Xác Nhận Xóa Các IP?", + "confirmRemoveTGUser": "✅ Xác Nhận Xóa Người Dùng Telegram?", + "confirmToggle": "✅ Xác nhận Bật/Tắt người dùng?", + "dbBackup": "Tải bản sao lưu cơ sở dữ liệu", + "serverUsage": "Sử Dụng Máy Chủ", + "getInbounds": "Lấy cổng vào", + "depleteSoon": "Depleted Soon", + "clientUsage": "Lấy Sử Dụng", + "onlines": "Khách hàng trực tuyến", + "commands": "Lệnh", + "refresh": "🔄 Cập Nhật", + "clearIPs": "❌ Xóa IP", + "removeTGUser": "❌ Xóa Người Dùng Telegram", + "selectTGUser": "👤 Chọn Người Dùng Telegram", + "selectOneTGUser": "👤 Chọn một người dùng telegram:", + "resetTraffic": "📈 Đặt Lại Lưu Lượng", + "resetExpire": "📅 Thay đổi ngày hết hạn", + "ipLog": "🔢 Nhật ký địa chỉ IP", + "ipLimit": "🔢 Giới Hạn địa chỉ IP", + "setTGUser": "👤 Đặt Người Dùng Telegram", + "toggle": "🔘 Bật / Tắt", + "custom": "🔢 Tùy chỉnh", + "confirmNumber": "✅ Xác nhận: {{ .Num }}", + "confirmNumberAdd": "✅ Xác nhận thêm: {{ .Num }}", + "limitTraffic": "🚧 Giới hạn lưu lượng", + "getBanLogs": "Cấm nhật ký", + "allClients": "Tất cả Khách hàng", + "addClient": "Thêm Khách Hàng", + "submitDisable": "Gửi Dưới Dạng Vô Hiệu ☑️", + "submitEnable": "Gửi Dưới Dạng Kích Hoạt ✅", + "use_default": "🏷️ Sử Dụng Mặc Định", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 Mật Khẩu", + "change_email": "⚙️📧 Email", + "change_comment": "⚙️💬 Bình Luận", + "ResetAllTraffics": "Đặt lại tất cả lưu lượng", + "SortedTrafficUsageReport": "Báo cáo sử dụng lưu lượng đã sắp xếp" + }, + "answers": { + "successfulOperation": "✅ Thành công!", + "errorOperation": "❗ Lỗi Trong Quá Trình Thực Hiện.", + "getInboundsFailed": "❌ Không Thể Lấy Được Inbounds", + "getClientsFailed": "❌ Không thể lấy khách hàng.", + "canceled": "❌ {{ .Email }} : Thao Tác Đã Bị Hủy.", + "clientRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho Khách Hàng.", + "IpRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho IPs.", + "TGIdRefreshSuccess": "✅ {{ .Email }} : Cập Nhật Thành Công Cho Người Dùng Telegram.", + "resetTrafficSuccess": "✅ {{ .Email }} : Đặt Lại Lưu Lượng Thành Công.", + "setTrafficLimitSuccess": "✅ {{ .Email }} : Đã lưu thành công giới hạn lưu lượng.", + "expireResetSuccess": "✅ {{ .Email }} : Đặt Lại Ngày Hết Hạn Thành Công.", + "resetIpSuccess": "✅ {{ .Email }} : Giới Hạn IP {{ .Count }} Đã Được Lưu Thành Công.", + "clearIpSuccess": "✅ {{ .Email }} : IP Đã Được Xóa Thành Công.", + "getIpLog": "✅ {{ .Email }} : Lấy nhật ký IP Thành Công.", + "getUserInfo": "✅ {{ .Email }} : Lấy Thông Tin Người Dùng Telegram Thành Công.", + "removedTGUserSuccess": "✅ {{ .Email }} : Người Dùng Telegram Đã Được Xóa Thành Công.", + "enableSuccess": "✅ {{ .Email }} : Đã Bật Thành Công.", + "disableSuccess": "✅ {{ .Email }} : Đã Tắt Thành Công.", + "askToAddUserId": "Cấu hình của bạn không được tìm thấy!\r\nVui lòng yêu cầu Quản trị viên sử dụng ID người dùng telegram của bạn trong cấu hình của bạn.\r\n\r\nID người dùng của bạn: {{ .TgUserID }}", + "chooseClient": "Chọn một Khách hàng cho Inbound {{ .Inbound }}", + "chooseInbound": "Chọn một Inbound" + } + } +} diff --git a/web/translation/zh-CN.json b/web/translation/zh-CN.json new file mode 100644 index 00000000..18920dd0 --- /dev/null +++ b/web/translation/zh-CN.json @@ -0,0 +1,941 @@ +{ + "username": "用户名", + "password": "密码", + "login": "登录", + "confirm": "确定", + "cancel": "取消", + "close": "关闭", + "save": "保存", + "logout": "登出", + "create": "创建", + "update": "更新", + "copy": "复制", + "copied": "已复制", + "download": "下载", + "remark": "备注", + "enable": "启用", + "protocol": "协议", + "search": "搜索", + "filter": "筛选", + "loading": "加载中...", + "second": "秒", + "minute": "分钟", + "hour": "小时", + "day": "天", + "check": "查看", + "indefinite": "无限期", + "unlimited": "无限制", + "none": "无", + "qrCode": "二维码", + "info": "更多信息", + "edit": "编辑", + "delete": "删除", + "reset": "重置", + "noData": "无数据。", + "copySuccess": "复制成功", + "sure": "确定", + "encryption": "加密", + "useIPv4ForHost": "使用 IPv4 连接主机", + "transmission": "传输", + "host": "主机", + "path": "路径", + "camouflage": "伪装", + "status": "状态", + "enabled": "开启", + "disabled": "关闭", + "depleted": "耗尽", + "depletingSoon": "即将耗尽", + "offline": "离线", + "online": "在线", + "domainName": "域名", + "monitor": "监听", + "certificate": "数字证书", + "fail": "失败", + "comment": "评论", + "success": "成功", + "lastOnline": "上次在线", + "getVersion": "获取版本", + "install": "安装", + "clients": "客户端", + "usage": "使用情况", + "twoFactorCode": "代码", + "remained": "剩余", + "security": "安全", + "secAlertTitle": "安全警报", + "secAlertSsl": "此连接不安全。在激活 TLS 进行数据保护之前,请勿输入敏感信息。", + "secAlertConf": "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。", + "secAlertSSL": "面板缺少安全连接。请安装 TLS 证书以保护数据安全。", + "secAlertPanelPort": "面板默认端口存在安全风险。请配置随机端口或特定端口。", + "secAlertPanelURI": "面板默认 URI 路径不安全。请配置复杂的 URI 路径。", + "secAlertSubURI": "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。", + "secAlertSubJsonURI": "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。", + "emptyDnsDesc": "未添加DNS服务器。", + "emptyFakeDnsDesc": "未添加Fake DNS服务器。", + "emptyBalancersDesc": "未添加负载均衡器。", + "emptyReverseDesc": "未添加反向代理。", + "somethingWentWrong": "出了点问题", + "subscription": { + "title": "订阅信息", + "subId": "订阅 ID", + "status": "状态", + "downloaded": "已下载", + "uploaded": "已上传", + "expiry": "到期", + "totalQuota": "总配额", + "individualLinks": "单独链接", + "active": "启用", + "inactive": "停用", + "unlimited": "无限制", + "noExpiry": "无到期" + }, + "menu": { + "theme": "主题", + "dark": "暗色", + "ultraDark": "超暗色", + "dashboard": "系统状态", + "inbounds": "入站列表", + "settings": "面板设置", + "xray": "Xray 设置", + "logout": "退出登录", + "link": "管理" + }, + "pages": { + "login": { + "hello": "你好", + "title": "欢迎", + "loginAgain": "登录时效已过,请重新登录", + "toasts": { + "invalidFormData": "数据格式错误", + "emptyUsername": "请输入用户名", + "emptyPassword": "请输入密码", + "wrongUsernameOrPassword": "用户名、密码或双重验证码无效。", + "successLogin": "您已成功登录您的账户。" + } + }, + "index": { + "title": "系统状态", + "cpu": "CPU", + "logicalProcessors": "逻辑处理器", + "frequency": "频率", + "swap": "交换分区", + "storage": "存储", + "memory": "内存", + "threads": "线程", + "xrayStatus": "Xray", + "stopXray": "停止", + "restartXray": "重启", + "xraySwitch": "版本", + "xraySwitchClick": "选择你要切换到的版本", + "xraySwitchClickDesk": "请谨慎选择,因为较旧版本可能与当前配置不兼容", + "xrayUpdates": "Xray 更新", + "updatePanel": "更新面板", + "panelUpdateDesc": "这将把 3X-UI 更新到最新版本并重启面板服务。", + "currentPanelVersion": "当前面板版本", + "latestPanelVersion": "最新面板版本", + "panelUpToDate": "面板已是最新", + "upToDate": "已是最新", + "xrayStatusUnknown": "未知", + "xrayStatusRunning": "运行中", + "xrayStatusStop": "停止", + "xrayStatusError": "错误", + "xrayErrorPopoverTitle": "运行Xray时发生错误", + "operationHours": "系统正常运行时间", + "systemHistoryTitle": "系统历史", + "trendLast2Min": "最近 2 分钟", + "systemLoad": "系统负载", + "systemLoadDesc": "过去 1、5 和 15 分钟的系统平均负载", + "connectionCount": "连接数", + "ipAddresses": "IP地址", + "toggleIpVisibility": "切换IP可见性", + "overallSpeed": "整体速度", + "upload": "上传", + "download": "下载", + "totalData": "总数据", + "sent": "已发送", + "received": "已接收", + "documentation": "文档", + "xraySwitchVersionDialog": "您确定要更改Xray版本吗?", + "xraySwitchVersionDialogDesc": "这将把Xray版本更改为#version#。", + "xraySwitchVersionPopover": "Xray 更新成功", + "panelUpdateDialog": "您确定要更新面板吗?", + "panelUpdateDialogDesc": "这将把 3X-UI 更新到 #version# 并重启面板服务。", + "panelUpdateCheckPopover": "面板更新检查失败", + "panelUpdateStartedPopover": "已开始更新面板", + "geofileUpdateDialog": "您确定要更新地理文件吗?", + "geofileUpdateDialogDesc": "这将更新 #filename# 文件。", + "geofilesUpdateDialogDesc": "这将更新所有文件。", + "geofilesUpdateAll": "全部更新", + "geofileUpdatePopover": "地理文件更新成功", + "dontRefresh": "安装中,请勿刷新此页面", + "logs": "日志", + "config": "配置", + "backup": "备份", + "backupTitle": "备份和恢复", + "exportDatabase": "备份", + "exportDatabaseDesc": "点击下载包含当前数据库备份的 .db 文件到您的设备。", + "importDatabase": "恢复", + "importDatabaseDesc": "点击选择并上传设备中的 .db 文件以从备份恢复数据库。", + "importDatabaseSuccess": "数据库导入成功", + "importDatabaseError": "导入数据库时出错", + "readDatabaseError": "读取数据库时出错", + "getDatabaseError": "检索数据库时出错", + "getConfigError": "检索配置文件时出错", + "customGeoTitle": "自定义 GeoSite / GeoIP", + "customGeoAdd": "添加", + "customGeoType": "类型", + "customGeoAlias": "别名", + "customGeoUrl": "URL", + "customGeoEnabled": "启用", + "customGeoLastUpdated": "上次更新", + "customGeoExtColumn": "路由 (ext:…)", + "customGeoToastUpdateAll": "所有自定义来源已更新", + "customGeoActions": "操作", + "customGeoEdit": "编辑", + "customGeoDelete": "删除", + "customGeoDownload": "立即更新", + "customGeoModalAdd": "添加自定义 geo", + "customGeoModalEdit": "编辑自定义 geo", + "customGeoModalSave": "保存", + "customGeoDeleteConfirm": "删除此自定义 geo 源?", + "customGeoRoutingHint": "在路由规则中将值列写为 ext:文件.dat:标签(替换标签)。", + "customGeoInvalidId": "无效的资源 ID", + "customGeoAliasesError": "加载自定义 geo 别名失败", + "customGeoValidationAlias": "别名只能包含小写字母、数字、- 和 _", + "customGeoValidationUrl": "URL 必须以 http:// 或 https:// 开头", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": "(自定义)", + "customGeoToastList": "自定义 geo 列表", + "customGeoToastAdd": "添加自定义 geo", + "customGeoToastUpdate": "更新自定义 geo", + "customGeoToastDelete": "自定义 geofile「{{ .fileName }}」已删除", + "customGeoToastDownload": "geofile「{{ .fileName }}」已更新", + "customGeoErrInvalidType": "类型必须是 geosite 或 geoip", + "customGeoErrAliasRequired": "请填写别名", + "customGeoErrAliasPattern": "别名包含不允许的字符", + "customGeoErrAliasReserved": "该别名已保留", + "customGeoErrUrlRequired": "请填写 URL", + "customGeoErrInvalidUrl": "URL 无效", + "customGeoErrUrlScheme": "URL 必须使用 http 或 https", + "customGeoErrUrlHost": "URL 主机无效", + "customGeoErrDuplicateAlias": "此类型下已使用该别名", + "customGeoErrNotFound": "未找到自定义 geo 源", + "customGeoErrDownload": "下载失败", + "customGeoErrUpdateAllIncomplete": "有一个或多个自定义 geo 源更新失败", + "customGeoEmpty": "暂无自定义 geo 源 — 点击「添加」以创建" + }, + "inbounds": { + "allTimeTraffic": "累计总流量", + "allTimeTrafficUsage": "所有时间总使用量", + "title": "入站列表", + "totalDownUp": "总上传 / 下载", + "totalUsage": "总用量", + "inboundCount": "入站数量", + "operate": "菜单", + "enable": "启用", + "remark": "备注", + "node": "节点", + "deployTo": "部署到", + "localPanel": "本地面板", + "protocol": "协议", + "port": "端口", + "portMap": "端口映射", + "traffic": "流量", + "details": "详细信息", + "transportConfig": "传输配置", + "expireDate": "到期时间", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "resetTraffic": "重置流量", + "addInbound": "添加入站", + "generalActions": "通用操作", + "modifyInbound": "修改入站", + "deleteInbound": "删除入站", + "deleteInboundContent": "确定要删除入站吗?", + "deleteClient": "删除客户端", + "deleteClientContent": "确定要删除客户端吗?", + "resetTrafficContent": "确定要重置流量吗?", + "copyLink": "复制链接", + "address": "地址", + "network": "网络", + "destinationPort": "目标端口", + "targetAddress": "目标地址", + "monitorDesc": "留空表示监听所有 IP", + "meansNoLimit": "= 无限制(单位:GB)", + "totalFlow": "总流量", + "leaveBlankToNeverExpire": "留空表示永不过期", + "noRecommendKeepDefault": "建议保留默认值", + "certificatePath": "文件路径", + "certificateContent": "文件内容", + "publicKey": "公钥", + "privatekey": "私钥", + "clickOnQRcode": "点击二维码复制", + "client": "客户", + "export": "导出链接", + "clone": "克隆", + "cloneInbound": "克隆", + "cloneInboundContent": "此入站规则除端口(Port)、监听 IP(Listening IP)和客户端(Clients)以外的所有配置都将应用于克隆", + "cloneInboundOk": "创建克隆", + "resetAllTraffic": "重置所有入站流量", + "resetAllTrafficTitle": "重置所有入站流量", + "resetAllTrafficContent": "确定要重置所有入站流量吗?", + "resetInboundClientTraffics": "重置客户端流量", + "resetInboundClientTrafficTitle": "重置所有客户端流量", + "resetInboundClientTrafficContent": "确定要重置此入站客户端的所有流量吗?", + "resetAllClientTraffics": "重置所有客户端流量", + "resetAllClientTrafficTitle": "重置所有客户端流量", + "resetAllClientTrafficContent": "确定要重置所有客户端的所有流量吗?", + "delDepletedClients": "删除流量耗尽的客户端", + "delDepletedClientsTitle": "删除流量耗尽的客户端", + "delDepletedClientsContent": "确定要删除所有流量耗尽的客户端吗?", + "email": "电子邮件", + "emailDesc": "电子邮件必须完全唯一", + "IPLimit": "IP 限制", + "IPLimitDesc": "如果数量超过设置值,则禁用入站流量。(0 = 禁用)", + "IPLimitlog": "IP 日志", + "IPLimitlogDesc": "IP 历史日志(要启用被禁用的入站流量,请清除日志)", + "IPLimitlogclear": "清除日志", + "setDefaultCert": "从面板设置证书", + "telegramDesc": "请提供Telegram聊天ID。(在机器人中使用'/id'命令)或({'@'}userinfobot", + "subscriptionDesc": "要找到你的订阅 URL,请导航到“详细信息”。此外,你可以为多个客户端使用相同的名称。", + "info": "信息", + "same": "相同", + "inboundData": "入站数据", + "exportInbound": "导出入站规则", + "import": "导入", + "importInbound": "导入入站规则", + "periodicTrafficResetTitle": "流量重置", + "periodicTrafficResetDesc": "按指定间隔自动重置流量计数器", + "lastReset": "上次重置", + "periodicTrafficReset": { + "never": "从不", + "daily": "每日", + "weekly": "每周", + "monthly": "每月", + "hourly": "每小时" + }, + "toasts": { + "obtain": "获取", + "updateSuccess": "更新成功", + "logCleanSuccess": "日志已清除", + "inboundsUpdateSuccess": "入站连接已成功更新", + "inboundUpdateSuccess": "入站连接已成功更新", + "inboundCreateSuccess": "入站连接已成功创建", + "inboundDeleteSuccess": "入站连接已成功删除", + "inboundClientAddSuccess": "已添加入站客户端", + "inboundClientDeleteSuccess": "入站客户端已删除", + "inboundClientUpdateSuccess": "入站客户端已更新", + "delDepletedClientsSuccess": "所有耗尽客户端已删除", + "resetAllClientTrafficSuccess": "客户端所有流量已重置", + "resetAllTrafficSuccess": "所有流量已重置", + "resetInboundClientTrafficSuccess": "流量已重置", + "trafficGetError": "获取流量数据时出错", + "getNewX25519CertError": "获取X25519证书时出错。", + "getNewmldsa65Error": "获取mldsa65证书时出错。", + "getNewVlessEncError": "获取VlessEnc证书时出错。" + }, + "stream": { + "general": { + "request": "请求", + "response": "响应", + "name": "名称", + "value": "值" + }, + "tcp": { + "version": "版本", + "method": "方法", + "path": "路径", + "status": "状态", + "statusDescription": "状态说明", + "requestHeader": "请求头", + "responseHeader": "响应头" + } + } + }, + "client": { + "add": "添加客户端", + "edit": "编辑客户端", + "submitAdd": "添加客户端", + "submitEdit": "保存修改", + "clientCount": "客户端数量", + "bulk": "批量创建", + "copyFromInbound": "从入站复制客户端", + "copyToInbound": "复制客户端到", + "copySelected": "复制所选", + "copySource": "来源", + "copyEmailPreview": "最终邮箱预览", + "copySelectSourceFirst": "请先选择来源入站。", + "copyResult": "复制结果", + "copyResultSuccess": "复制成功", + "copyResultNone": "没有可复制的内容:未选择客户端或来源为空", + "copyResultErrors": "复制错误", + "copyFlowLabel": "新客户端的 Flow (VLESS)", + "copyFlowHint": "应用于所有复制的客户端。留空则跳过。", + "selectAll": "全选", + "clearAll": "全不选", + "method": "方法", + "first": "置顶", + "last": "置底", + "prefix": "前缀", + "postfix": "后缀", + "delayedStart": "首次使用后开始", + "expireDays": "期间", + "days": "天", + "renew": "自动续订", + "renewDesc": "到期后自动续订。(0 = 禁用)(单位: 天)" + }, + "nodes": { + "title": "节点", + "addNode": "添加节点", + "editNode": "编辑节点", + "totalNodes": "节点总数", + "onlineNodes": "在线", + "offlineNodes": "离线", + "avgLatency": "平均延迟", + "name": "名称", + "namePlaceholder": "例如:de-frankfurt-1", + "addressPlaceholder": "panel.example.com 或 1.2.3.4", + "remark": "备注", + "scheme": "协议", + "address": "地址", + "port": "端口", + "basePath": "基础路径", + "apiToken": "API 令牌", + "apiTokenPlaceholder": "远程面板设置页中的令牌", + "apiTokenHint": "远程面板在 设置 → API 令牌 中显示其 API 令牌。", + "regenerate": "重新生成令牌", + "regenerateConfirm": "重新生成会使当前令牌失效。任何使用该令牌的中央面板都会失去访问权限,直至更新。是否继续?", + "enable": "已启用", + "status": "状态", + "cpu": "CPU", + "mem": "内存", + "uptime": "运行时间", + "latency": "延迟", + "lastHeartbeat": "上次心跳", + "xrayVersion": "Xray 版本", + "actions": "操作", + "probe": "立即探测", + "testConnection": "测试连接", + "connectionOk": "连接正常 ({ms} ms)", + "connectionFailed": "连接失败", + "never": "从未", + "justNow": "刚刚", + "deleteConfirmTitle": "删除节点 \"{name}\"?", + "deleteConfirmContent": "这将停止监控该节点。远程面板本身不受影响。", + "statusValues": { + "online": "在线", + "offline": "离线", + "unknown": "未知" + }, + "toasts": { + "list": "加载节点失败", + "obtain": "加载节点失败", + "add": "添加节点", + "update": "更新节点", + "delete": "删除节点", + "deleted": "节点已删除", + "test": "测试连接", + "fillRequired": "名称、地址、端口和 API 令牌为必填项", + "probeFailed": "探测失败" + } + }, + "settings": { + "title": "面板设置", + "save": "保存", + "infoDesc": "此处的所有更改都需要保存并重启面板才能生效", + "restartPanel": "重启面板", + "restartPanelDesc": "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息", + "restartPanelSuccess": "面板已成功重启", + "actions": "操作", + "resetDefaultConfig": "重置为默认配置", + "panelSettings": "常规", + "securitySettings": "安全设定", + "TGBotSettings": "Telegram 机器人配置", + "panelListeningIP": "面板监听 IP", + "panelListeningIPDesc": "默认留空监听所有 IP", + "panelListeningDomain": "面板监听域名", + "panelListeningDomainDesc": "默认情况下留空以监视所有域名和 IP 地址", + "panelPort": "面板监听端口", + "panelPortDesc": "重启面板生效", + "publicKeyPath": "面板证书公钥文件路径", + "publicKeyPathDesc": "填写一个 '/' 开头的绝对路径", + "privateKeyPath": "面板证书密钥文件路径", + "privateKeyPathDesc": "填写一个 '/' 开头的绝对路径", + "panelUrlPath": "面板 url 根路径", + "panelUrlPathDesc": "必须以 '/' 开头,以 '/' 结尾", + "pageSize": "分页大小", + "pageSizeDesc": "定义入站表的页面大小。设置 0 表示禁用", + "remarkModel": "备注模型和分隔符", + "datepicker": "日期选择器", + "datepickerPlaceholder": "选择日期", + "datepickerDescription": "选择器日历类型指定到期日期", + "sampleRemark": "备注示例", + "oldUsername": "原用户名", + "currentPassword": "原密码", + "newUsername": "新用户名", + "newPassword": "新密码", + "telegramBotEnable": "启用 Telegram 机器人", + "telegramBotEnableDesc": "启用 Telegram 机器人功能", + "telegramToken": "Telegram 机器人令牌(token)", + "telegramTokenDesc": "从 '{'@'}BotFather' 获取的 Telegram 机器人令牌", + "telegramProxy": "SOCKS5 Proxy", + "telegramProxyDesc": "启用 SOCKS5 代理连接到 Telegram(根据指南调整设置)", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "要使用的 Telegram API 服务器。留空以使用默认服务器。", + "telegramChatId": "管理员聊天 ID", + "telegramChatIdDesc": "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 {'@'}userinfobot 获取,或在机器人中使用 '/id' 命令获取)", + "telegramNotifyTime": "通知时间", + "telegramNotifyTimeDesc": "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)", + "tgNotifyBackup": "数据库备份", + "tgNotifyBackupDesc": "发送带有报告的数据库备份文件", + "tgNotifyLogin": "登录通知", + "tgNotifyLoginDesc": "当有人试图登录你的面板时显示用户名、IP 地址和时间", + "sessionMaxAge": "会话时长", + "sessionMaxAgeDesc": "保持登录状态的时长(单位:分钟)", + "expireTimeDiff": "到期通知阈值", + "expireTimeDiffDesc": "达到此阈值时,将收到有关到期时间的通知(单位:天)", + "trafficDiff": "流量耗尽阈值", + "trafficDiffDesc": "达到此阈值时,将收到有关流量耗尽的通知(单位:GB)", + "tgNotifyCpu": "CPU 负载通知阈值", + "tgNotifyCpuDesc": "CPU 负载超过此阈值时,将收到通知(单位:%)", + "timeZone": "时区", + "timeZoneDesc": "定时任务将按照该时区的时间运行", + "subSettings": "订阅设置", + "subEnable": "启用订阅服务", + "subEnableDesc": "启用订阅服务功能", + "subJsonEnable": "单独启用/禁用 JSON 订阅端点。", + "subTitle": "订阅标题", + "subTitleDesc": "在VPN客户端中显示的标题", + "subSupportUrl": "支持链接", + "subSupportUrlDesc": "VPN 客户端中显示的技术支持链接", + "subProfileUrl": "个人资料链接", + "subProfileUrlDesc": "VPN 客户端中显示的网站链接", + "subAnnounce": "公告", + "subAnnounceDesc": "VPN 客户端中显示的公告文本", + "subEnableRouting": "启用路由", + "subEnableRoutingDesc": "在 VPN 客户端中启用路由的全局设置。(僅限 Happ)", + "subRoutingRules": "路由規則", + "subRoutingRulesDesc": "VPN 用戶端的全域路由規則。(僅限 Happ)", + "subListen": "监听 IP", + "subListenDesc": "订阅服务监听的 IP 地址(留空表示监听所有 IP)", + "subPort": "监听端口", + "subPortDesc": "订阅服务监听的端口号(必须是未使用的端口)", + "subCertPath": "公钥路径", + "subCertPathDesc": "订阅服务使用的公钥文件路径(以 '/' 开头)", + "subKeyPath": "私钥路径", + "subKeyPathDesc": "订阅服务使用的私钥文件路径(以 '/' 开头)", + "subPath": "URI 路径", + "subPathDesc": "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)", + "subDomain": "监听域名", + "subDomainDesc": "订阅服务监听的域名(留空表示监听所有域名和 IP)", + "subUpdates": "更新间隔", + "subUpdatesDesc": "客户端应用中订阅 URL 的更新间隔(单位:小时)", + "subEncrypt": "编码", + "subEncryptDesc": "订阅服务返回的内容将采用 Base64 编码", + "subShowInfo": "显示使用信息", + "subShowInfoDesc": "客户端应用中将显示剩余流量和日期信息", + "subURI": "反向代理 URI", + "subURIDesc": "用于代理后面的订阅 URL 的 URI 路径", + "externalTrafficInformEnable": "外部交通通知", + "externalTrafficInformEnableDesc": "每次流量更新时通知外部 API", + "externalTrafficInformURI": "外部流量通知 URI", + "externalTrafficInformURIDesc": "流量更新将发送到此 URI", + "restartXrayOnClientDisable": "客户端自动禁用后重启 Xray", + "restartXrayOnClientDisableDesc": "当客户端因到期或流量超限被自动禁用时,重启 Xray。", + "fragment": "分片", + "fragmentDesc": "启用 TLS hello 数据包分片", + "fragmentSett": "设置", + "noisesDesc": "启用 Noises.", + "noisesSett": "Noises 设置", + "mux": "多路复用器", + "muxDesc": "在已建立的数据流内传输多个独立的数据流", + "muxSett": "复用器设置", + "direct": "直接连接", + "directDesc": "直接与特定国家的域或IP范围建立连接", + "notifications": "通知", + "certs": "证书", + "externalTraffic": "外部流量", + "dateAndTime": "日期和时间", + "proxyAndServer": "代理和服务器", + "intervals": "间隔", + "information": "信息", + "language": "语言", + "telegramBotLanguage": "Telegram 机器人语言", + "security": { + "admin": "管理员凭据", + "twoFactor": "双重验证", + "twoFactorEnable": "启用2FA", + "twoFactorEnableDesc": "增加额外的验证层以提高安全性。", + "twoFactorModalSetTitle": "启用双重认证", + "twoFactorModalDeleteTitle": "停用双重认证", + "twoFactorModalSteps": "要设定双重认证,请执行以下步骤:", + "twoFactorModalFirstStep": "1. 在认证应用程序中扫描此QR码,或复制QR码附近的令牌并粘贴到应用程序中", + "twoFactorModalSecondStep": "2. 输入应用程序中的验证码", + "twoFactorModalRemoveStep": "输入应用程序中的验证码以移除双重认证。", + "twoFactorModalChangeCredentialsTitle": "更改凭据", + "twoFactorModalChangeCredentialsStep": "输入应用程序中的代码以更改管理员凭据。", + "twoFactorModalSetSuccess": "双因素认证已成功建立", + "twoFactorModalDeleteSuccess": "双因素认证已成功删除", + "twoFactorModalError": "验证码错误" + }, + "toasts": { + "modifySettings": "参数已更改。", + "getSettings": "获取参数时发生错误", + "modifyUserError": "更改管理员凭据时发生错误。", + "modifyUser": "您已成功更改管理员凭据。", + "originalUserPassIncorrect": "原用户名或原密码错误", + "userPassMustBeNotEmpty": "新用户名和新密码不能为空", + "getOutboundTrafficError": "获取出站流量错误", + "resetOutboundTrafficError": "重置出站流量错误" + } + }, + "xray": { + "title": "Xray 配置", + "save": "保存", + "restart": "重新启动 Xray", + "restartSuccess": "Xray 已成功重新启动", + "stopSuccess": "Xray 已成功停止", + "restartError": "重启Xray时发生错误。", + "stopError": "停止Xray时发生错误。", + "basicTemplate": "基础配置", + "advancedTemplate": "高级配置", + "generalConfigs": "常规配置", + "generalConfigsDesc": "这些选项将决定常规配置", + "logConfigs": "日志", + "logConfigsDesc": "日志可能会影响服务器的性能,建议仅在需要时启用", + "blockConfigsDesc": "这些选项将阻止用户连接到特定协议和网站", + "basicRouting": "基本路由", + "blockConnectionsConfigsDesc": "这些选项将根据特定的请求国家阻止流量。", + "directConnectionsConfigsDesc": "直接连接确保特定的流量不会通过其他服务器路由。", + "blockips": "阻止IP", + "blockdomains": "阻止域名", + "directips": "直接IP", + "directdomains": "直接域名", + "ipv4Routing": "IPv4 路由", + "ipv4RoutingDesc": "此选项将仅通过 IPv4 路由到目标域", + "warpRouting": "WARP 路由", + "warpRoutingDesc": "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在你的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。", + "nordRouting": "NordVPN 路由", + "nordRoutingDesc": "这些选项将根据特定目的地通过 NordVPN 路由流量。", + "Template": "高级 Xray 配置模板", + "TemplateDesc": "最终的 Xray 配置文件将基于此模板生成", + "FreedomStrategy": "Freedom 协议策略", + "FreedomStrategyDesc": "设置 Freedom 协议中网络的输出策略", + "RoutingStrategy": "配置路由域策略", + "RoutingStrategyDesc": "设置 DNS 解析的整体路由策略", + "outboundTestUrl": "出站测试 URL", + "outboundTestUrlDesc": "测试出站连接时使用的 URL", + "Torrent": "屏蔽 BitTorrent 协议", + "Inbounds": "入站规则", + "InboundsDesc": "接受来自特定客户端的流量", + "Outbounds": "出站规则", + "Balancers": "负载均衡", + "OutboundsDesc": "设置出站流量传出方式", + "Routings": "路由规则", + "RoutingsDesc": "每条规则的优先级都很重要", + "completeTemplate": "全部", + "logLevel": "日志级别", + "logLevelDesc": "错误日志的日志级别,用于指示需要记录的信息", + "accessLog": "访问日志", + "accessLogDesc": "访问日志的文件路径。特殊值 'none' 禁用访问日志", + "errorLog": "错误日志", + "errorLogDesc": "错误日志的文件路径。特殊值 'none' 禁用错误日志", + "dnsLog": "DNS 日志", + "dnsLogDesc": "是否启用 DNS 查询日志", + "maskAddress": "隐藏地址", + "maskAddressDesc": "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。", + "statistics": "统计", + "statsInboundUplink": "入站上传统计", + "statsInboundUplinkDesc": "启用所有入站代理的上行流量统计收集。", + "statsInboundDownlink": "入站下载统计", + "statsInboundDownlinkDesc": "启用所有入站代理的下行流量统计收集。", + "statsOutboundUplink": "出站上传统计", + "statsOutboundUplinkDesc": "启用所有出站代理的上行流量统计收集。", + "statsOutboundDownlink": "出站下载统计", + "statsOutboundDownlinkDesc": "启用所有出站代理的下行流量统计收集。", + "rules": { + "first": "置顶", + "last": "置底", + "up": "向上", + "down": "向下", + "source": "来源", + "dest": "目的地址", + "inbound": "入站", + "outbound": "出站", + "balancer": "负载均衡", + "info": "信息", + "add": "添加规则", + "edit": "编辑规则", + "useComma": "逗号分隔的项目" + }, + "outbound": { + "addOutbound": "添加出站", + "addReverse": "添加反向", + "editOutbound": "编辑出站", + "editReverse": "编辑反向", + "reverseTag": "反向标签", + "reverseTagDesc": "VLESS 简易反向代理出站标签。留空则禁用。设置后,此客户端的连接可用作反向代理隧道。", + "reverseTagPlaceholder": "出站标签(留空则禁用)", + "tag": "标签", + "tagDesc": "唯一标签", + "address": "地址", + "reverse": "反向", + "domain": "域名", + "type": "类型", + "bridge": "Bridge", + "portal": "Portal", + "link": "链接", + "intercon": "互连", + "settings": "设置", + "accountInfo": "帐户信息", + "outboundStatus": "出站状态", + "sendThrough": "发送通过", + "test": "测试", + "testResult": "测试结果", + "testing": "正在测试连接...", + "testSuccess": "测试成功", + "testFailed": "测试失败", + "testError": "测试出站失败", + "nordvpn": "NordVPN", + "accessToken": "访问令牌", + "country": "国家", + "server": "服务器", + "city": "城市", + "allCities": "所有城市", + "privateKey": "私钥", + "load": "负载" + }, + "balancer": { + "addBalancer": "添加负载均衡", + "editBalancer": "编辑负载均衡", + "balancerStrategy": "策略", + "balancerSelectors": "选择器", + "tag": "标签", + "tagDesc": "唯一标签", + "balancerDesc": "无法同时使用 balancerTag 和 outboundTag。如果同时使用,则只有 outboundTag 会生效。" + }, + "wireguard": { + "secretKey": "密钥", + "publicKey": "公钥", + "allowedIPs": "允许的 IP", + "endpoint": "端点", + "psk": "共享密钥", + "domainStrategy": "域策略" + }, + "tun": { + "nameDesc": "TUN 接口的名称。默认值为 'xray0'", + "mtuDesc": "最大传输单元。数据包的最大大小。默认值为 1500", + "userLevel": "用户级别", + "userLevelDesc": "通过此入站的所有连接都将使用此用户级别。默认值为 0" + }, + "dns": { + "enable": "启用 DNS", + "enableDesc": "启用内置 DNS 服务器", + "tag": "DNS 入站标签", + "tagDesc": "此标签将在路由规则中可用作入站标签", + "clientIp": "客户端IP", + "clientIpDesc": "用于在DNS查询期间通知服务器指定的IP位置", + "disableCache": "禁用缓存", + "disableCacheDesc": "禁用DNS缓存", + "disableFallback": "禁用回退", + "disableFallbackDesc": "禁用回退DNS查询", + "disableFallbackIfMatch": "匹配时禁用回退", + "disableFallbackIfMatchDesc": "当DNS服务器的匹配域名列表命中时,禁用回退DNS查询", + "enableParallelQuery": "启用并行查询", + "enableParallelQueryDesc": "启用并行DNS查询到多个服务器以实现更快的解析", + "strategy": "查询策略", + "strategyDesc": "解析域名的总体策略", + "add": "添加服务器", + "edit": "编辑服务器", + "domains": "域", + "expectIPs": "预期 IP", + "unexpectIPs": "意外IP", + "useSystemHosts": "使用系统Hosts", + "useSystemHostsDesc": "使用已安装系统的hosts文件", + "usePreset": "使用模板", + "dnsPresetTitle": "DNS模板", + "dnsPresetFamily": "家庭" + }, + "fakedns": { + "add": "添加假 DNS", + "edit": "编辑假 DNS", + "ipPool": "IP 池子网", + "poolSize": "池大小" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ 自定义键盘已关闭!", + "noResult": "❗ 没有结果!", + "noQuery": "❌ 未找到查询!请再次使用该命令!", + "wentWrong": "❌ 出了点问题!", + "noIpRecord": "❗ 没有IP记录!", + "noInbounds": "❗ 未找到入站!", + "unlimited": "♾ 无限(重置)", + "add": "添加", + "month": "月", + "months": "月", + "day": "天", + "days": "天", + "hours": "小时", + "minutes": "分钟", + "unknown": "未知", + "inbounds": "入站", + "clients": "客户端", + "offline": "🔴 离线", + "online": "🟢 在线", + "commands": { + "unknown": "❗ 未知命令", + "pleaseChoose": "👇 请选择:\r\n", + "help": "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n", + "start": "👋 你好,{{ .Firstname }}。\r\n", + "welcome": "🤖 欢迎来到 {{ .Hostname }} 管理机器人。\r\n", + "status": "✅ 机器人正常运行!", + "usage": "❗ 请输入要搜索的文本!", + "getID": "🆔 您的 ID 为:{{ .ID }}", + "helpAdminCommands": "要重新启动 Xray Core:\r\n/restart\r\n\r\n要搜索客户电子邮件:\r\n/usage [电子邮件]\r\n\r\n要搜索入站(带有客户统计数据):\r\n/inbound [备注]\r\n\r\nTelegram聊天ID:\r\n/id", + "helpClientCommands": "要搜索统计数据,请使用以下命令:\r\n/usage [电子邮件]\r\n\r\nTelegram聊天ID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ 操作成功!", + "restartFailed": "❗ 操作错误。\r\n\r\n错误: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core 未运行。", + "startDesc": "显示主菜单", + "helpDesc": "机器人帮助", + "statusDesc": "检查机器人状态", + "idDesc": "显示您的 Telegram ID" + }, + "messages": { + "cpuThreshold": "🔴 CPU 使用率为 {{ .Percent }}%,超过阈值 {{ .Threshold }}%", + "selectUserFailed": "❌ 用户选择错误!", + "userSaved": "✅ 电报用户已保存。", + "loginSuccess": "✅ 成功登录到面板。\r\n", + "loginFailed": "❗️ 面板登录失败。\r\n", + "2faFailed": "2FA 失败", + "report": "🕰 定时报告:{{ .RunTime }}\r\n", + "datetime": "⏰ 日期时间:{{ .DateTime }}\r\n", + "hostname": "💻 主机名:{{ .Hostname }}\r\n", + "version": "🚀 X-UI 版本:{{ .Version }}\r\n", + "xrayVersion": "📡 Xray 版本: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6:{{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4:{{ .IPv4 }}\r\n", + "ip": "🌐 IP:{{ .IP }}\r\n", + "ips": "🔢 IP 地址:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ 服务器运行时间:{{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 服务器负载:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 服务器内存:{{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP 连接数:{{ .Count }}\r\n", + "udpCount": "🔸 UDP 连接数:{{ .Count }}\r\n", + "traffic": "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Xray 状态:{{ .State }}\r\n", + "username": "👤 用户名:{{ .Username }}\r\n", + "reason": "❗️ 原因:{{ .Reason }}\r\n", + "time": "⏰ 时间:{{ .Time }}\r\n", + "inbound": "📍 入站:{{ .Remark }}\r\n", + "port": "🔌 端口:{{ .Port }}\r\n", + "expire": "📅 过期日期:{{ .Time }}\r\n", + "expireIn": "📅 剩余时间:{{ .Time }}\r\n", + "active": "💡 激活:{{ .Enable }}\r\n", + "enabled": "🚨 已启用:{{ .Enable }}\r\n", + "online": "🌐 连接状态:{{ .Status }}\r\n", + "lastOnline": "🔙 上次在线: {{ .Time }}\r\n", + "email": "📧 邮箱:{{ .Email }}\r\n", + "upload": "🔼 上传↑:{{ .Upload }}\r\n", + "download": "🔽 下载↓:{{ .Download }}\r\n", + "total": "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 电报用户:{{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 耗尽的 {{ .Type }}:\r\n", + "exhaustedCount": "🚨 耗尽的 {{ .Type }} 数量:\r\n", + "onlinesCount": "🌐 在线客户:{{ .Count }}\r\n", + "disabled": "🛑 禁用:{{ .Disabled }}\r\n", + "depleteSoon": "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 备份时间:{{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n", + "yes": "✅ 是的", + "no": "❌ 没有", + "received_id": "🔑📥 ID 已更新。", + "received_password": "🔑📥 密码已更新。", + "received_email": "📧📥 邮箱已更新。", + "received_comment": "💬📥 评论已更新。", + "id_prompt": "🔑 默认 ID: {{ .ClientId }}\n\n请输入您的 ID。", + "pass_prompt": "🔑 默认密码: {{ .ClientPassword }}\n\n请输入您的密码。", + "email_prompt": "📧 默认邮箱: {{ .ClientEmail }}\n\n请输入您的邮箱。", + "comment_prompt": "💬 默认评论: {{ .ClientComment }}\n\n请输入您的评论。", + "inbound_client_data_id": "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!", + "inbound_client_data_pass": "🔄 入站: {{ .InboundRemark }}\n\n🔑 密码: {{ .ClientPass }}\n📧 邮箱: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日期: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 备注: {{ .ClientComment }}\n\n你现在可以将客户添加到入站了!", + "cancel": "❌ 进程已取消!\n\n您可以随时使用 /start 重新开始。 🔄", + "error_add_client": "⚠️ 错误:\n\n {{ .error }}", + "using_default_value": "好的,我会使用默认值。 😊", + "incorrect_input": "您的输入无效。\n短语应连续输入,不能有空格。\n正确示例: aaaaaa\n错误示例: aaa aaa 🚫", + "AreYouSure": "你确定吗?🤔", + "SuccessResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ✅ 成功", + "FailedResetTraffic": "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 所有客户的流量重置已完成。" + }, + "buttons": { + "closeKeyboard": "❌ 关闭键盘", + "cancel": "❌ 取消", + "cancelReset": "❌ 取消重置", + "cancelIpLimit": "❌ 取消 IP 限制", + "confirmResetTraffic": "✅ 确认重置流量?", + "confirmClearIps": "✅ 确认清除 IP?", + "confirmRemoveTGUser": "✅ 确认移除 Telegram 用户?", + "confirmToggle": "✅ 确认启用/禁用用户?", + "dbBackup": "获取数据库备份", + "serverUsage": "服务器使用情况", + "getInbounds": "获取入站信息", + "depleteSoon": "即将耗尽", + "clientUsage": "获取使用情况", + "onlines": "在线客户端", + "commands": "命令", + "refresh": "🔄 刷新", + "clearIPs": "❌ 清除 IP", + "removeTGUser": "❌ 移除 Telegram 用户", + "selectTGUser": "👤 选择 Telegram 用户", + "selectOneTGUser": "👤 选择一个 Telegram 用户:", + "resetTraffic": "📈 重置流量", + "resetExpire": "📅 更改到期日期", + "ipLog": "🔢 IP 日志", + "ipLimit": "🔢 IP 限制", + "setTGUser": "👤 设置 Telegram 用户", + "toggle": "🔘 启用/禁用", + "custom": "🔢 风俗", + "confirmNumber": "✅ 确认: {{ .Num }}", + "confirmNumberAdd": "✅ 确认添加:{{ .Num }}", + "limitTraffic": "🚧 流量限制", + "getBanLogs": "禁止日志", + "allClients": "所有客户", + "addClient": "添加客户", + "submitDisable": "提交为禁用 ☑️", + "submitEnable": "提交为启用 ✅", + "use_default": "🏷️ 使用默认", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 密码", + "change_email": "⚙️📧 邮箱", + "change_comment": "⚙️💬 评论", + "ResetAllTraffics": "重置所有流量", + "SortedTrafficUsageReport": "排序的流量使用报告" + }, + "answers": { + "successfulOperation": "✅ 成功!", + "errorOperation": "❗ 操作错误。", + "getInboundsFailed": "❌ 获取入站信息失败。", + "getClientsFailed": "❌ 获取客户失败。", + "canceled": "❌ {{ .Email }}:操作已取消。", + "clientRefreshSuccess": "✅ {{ .Email }}:客户端刷新成功。", + "IpRefreshSuccess": "✅ {{ .Email }}:IP 刷新成功。", + "TGIdRefreshSuccess": "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。", + "resetTrafficSuccess": "✅ {{ .Email }}:流量已重置成功。", + "setTrafficLimitSuccess": "✅ {{ .Email }}: 流量限制保存成功。", + "expireResetSuccess": "✅ {{ .Email }}:过期天数已重置成功。", + "resetIpSuccess": "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。", + "clearIpSuccess": "✅ {{ .Email }}:IP 已成功清除。", + "getIpLog": "✅ {{ .Email }}:获取 IP 日志。", + "getUserInfo": "✅ {{ .Email }}:获取 Telegram 用户信息。", + "removedTGUserSuccess": "✅ {{ .Email }}:Telegram 用户已成功移除。", + "enableSuccess": "✅ {{ .Email }}:已成功启用。", + "disableSuccess": "✅ {{ .Email }}:已成功禁用。", + "askToAddUserId": "未找到您的配置!\r\n请向管理员询问,在您的配置中使用您的 Telegram 用户 ChatID。\r\n\r\n您的用户 ChatID:{{ .TgUserID }}", + "chooseClient": "为入站 {{ .Inbound }} 选择一个客户", + "chooseInbound": "选择一个入站" + } + } +} diff --git a/web/translation/zh-TW.json b/web/translation/zh-TW.json new file mode 100644 index 00000000..d5d27591 --- /dev/null +++ b/web/translation/zh-TW.json @@ -0,0 +1,941 @@ +{ + "username": "使用者名稱", + "password": "密碼", + "login": "登入", + "confirm": "確定", + "cancel": "取消", + "close": "關閉", + "save": "儲存", + "logout": "登出", + "create": "建立", + "update": "更新", + "copy": "複製", + "copied": "已複製", + "download": "下載", + "remark": "備註", + "enable": "啟用", + "protocol": "協議", + "search": "搜尋", + "filter": "篩選", + "loading": "載入中...", + "second": "秒", + "minute": "分鐘", + "hour": "小時", + "day": "天", + "check": "檢視", + "indefinite": "無限期", + "unlimited": "無限制", + "none": "無", + "qrCode": "二維碼", + "info": "更多資訊", + "edit": "編輯", + "delete": "刪除", + "reset": "重置", + "noData": "無數據。", + "copySuccess": "複製成功", + "sure": "確定", + "encryption": "加密", + "useIPv4ForHost": "使用 IPv4 連接主機", + "transmission": "傳輸", + "host": "主機", + "path": "路徑", + "camouflage": "偽裝", + "status": "狀態", + "enabled": "開啟", + "disabled": "關閉", + "depleted": "耗盡", + "depletingSoon": "即將耗盡", + "offline": "離線", + "online": "線上", + "domainName": "域名", + "monitor": "監聽", + "certificate": "憑證", + "fail": "失敗", + "comment": "評論", + "success": "成功", + "lastOnline": "上次上線", + "getVersion": "獲取版本", + "install": "安裝", + "clients": "客戶端", + "usage": "使用情況", + "twoFactorCode": "代碼", + "remained": "剩餘", + "security": "安全", + "secAlertTitle": "安全警報", + "secAlertSsl": "此連線不安全。在啟用 TLS 進行資料保護之前,請勿輸入敏感資訊。", + "secAlertConf": "某些設定易受攻擊。建議加強安全協議以防止潛在漏洞。", + "secAlertSSL": "面板缺少安全連線。請安裝 TLS 證書以保護資料安全。", + "secAlertPanelPort": "面板預設埠存在安全風險。請配置隨機埠或特定埠。", + "secAlertPanelURI": "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。", + "secAlertSubURI": "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。", + "secAlertSubJsonURI": "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。", + "emptyDnsDesc": "未添加DNS伺服器。", + "emptyFakeDnsDesc": "未添加Fake DNS伺服器。", + "emptyBalancersDesc": "未添加負載平衡器。", + "emptyReverseDesc": "未添加反向代理。", + "somethingWentWrong": "發生錯誤", + "subscription": { + "title": "訂閱資訊", + "subId": "訂閱 ID", + "status": "狀態", + "downloaded": "已下載", + "uploaded": "已上傳", + "expiry": "到期", + "totalQuota": "總配額", + "individualLinks": "個別連結", + "active": "啟用", + "inactive": "停用", + "unlimited": "無限制", + "noExpiry": "無到期" + }, + "menu": { + "theme": "主題", + "dark": "深色", + "ultraDark": "超深色", + "dashboard": "系統狀態", + "inbounds": "入站列表", + "settings": "面板設定", + "xray": "Xray 設定", + "logout": "退出登入", + "link": "管理" + }, + "pages": { + "login": { + "hello": "你好", + "title": "歡迎", + "loginAgain": "登入時效已過,請重新登入", + "toasts": { + "invalidFormData": "資料格式錯誤", + "emptyUsername": "請輸入使用者名稱", + "emptyPassword": "請輸入密碼", + "wrongUsernameOrPassword": "用戶名、密碼或雙重驗證碼無效。", + "successLogin": "您已成功登入您的帳戶。" + } + }, + "index": { + "title": "系統狀態", + "cpu": "CPU", + "logicalProcessors": "邏輯處理器", + "frequency": "頻率", + "swap": "交換空間", + "storage": "儲存", + "memory": "記憶體", + "threads": "執行緒", + "xrayStatus": "Xray", + "stopXray": "停止", + "restartXray": "重啟", + "xraySwitch": "版本", + "xraySwitchClick": "選擇你要切換到的版本", + "xraySwitchClickDesk": "請謹慎選擇,因為較舊版本可能與當前配置不相容", + "xrayUpdates": "Xray 更新", + "updatePanel": "更新面板", + "panelUpdateDesc": "這將把 3X-UI 更新到最新版本並重新啟動面板服務。", + "currentPanelVersion": "目前面板版本", + "latestPanelVersion": "最新面板版本", + "panelUpToDate": "面板已是最新", + "upToDate": "已是最新", + "xrayStatusUnknown": "未知", + "xrayStatusRunning": "運行中", + "xrayStatusStop": "停止", + "xrayStatusError": "錯誤", + "xrayErrorPopoverTitle": "執行Xray時發生錯誤", + "operationHours": "系統正常執行時間", + "systemHistoryTitle": "系統歷史", + "trendLast2Min": "最近 2 分鐘", + "systemLoad": "系統負載", + "systemLoadDesc": "過去 1、5 和 15 分鐘的系統平均負載", + "connectionCount": "連線數", + "ipAddresses": "IP地址", + "toggleIpVisibility": "切換IP可見性", + "overallSpeed": "整體速度", + "upload": "上傳", + "download": "下載", + "totalData": "總數據", + "sent": "已發送", + "received": "已接收", + "documentation": "文件", + "xraySwitchVersionDialog": "您確定要變更Xray版本嗎?", + "xraySwitchVersionDialogDesc": "這將會把Xray版本變更為#version#。", + "xraySwitchVersionPopover": "Xray 更新成功", + "panelUpdateDialog": "您確定要更新面板嗎?", + "panelUpdateDialogDesc": "這將把 3X-UI 更新到 #version# 並重新啟動面板服務。", + "panelUpdateCheckPopover": "面板更新檢查失敗", + "panelUpdateStartedPopover": "面板更新已開始", + "geofileUpdateDialog": "您確定要更新地理檔案嗎?", + "geofileUpdateDialogDesc": "這將更新 #filename# 檔案。", + "geofilesUpdateDialogDesc": "這將更新所有文件。", + "geofilesUpdateAll": "全部更新", + "geofileUpdatePopover": "地理檔案更新成功", + "dontRefresh": "安裝中,請勿重新整理此頁面", + "logs": "日誌", + "config": "配置", + "backup": "備份和恢復", + "backupTitle": "備份和恢復", + "exportDatabase": "備份", + "exportDatabaseDesc": "點擊下載包含當前資料庫備份的 .db 文件到您的設備。", + "importDatabase": "恢復", + "importDatabaseDesc": "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。", + "importDatabaseSuccess": "資料庫匯入成功", + "importDatabaseError": "匯入資料庫時發生錯誤", + "readDatabaseError": "讀取資料庫時發生錯誤", + "getDatabaseError": "檢索資料庫時發生錯誤", + "getConfigError": "檢索設定檔時發生錯誤", + "customGeoTitle": "自訂 GeoSite / GeoIP", + "customGeoAdd": "新增", + "customGeoType": "類型", + "customGeoAlias": "別名", + "customGeoUrl": "URL", + "customGeoEnabled": "啟用", + "customGeoLastUpdated": "上次更新", + "customGeoExtColumn": "路由 (ext:…)", + "customGeoToastUpdateAll": "所有自訂來源已更新", + "customGeoActions": "操作", + "customGeoEdit": "編輯", + "customGeoDelete": "刪除", + "customGeoDownload": "立即更新", + "customGeoModalAdd": "新增自訂 geo", + "customGeoModalEdit": "編輯自訂 geo", + "customGeoModalSave": "儲存", + "customGeoDeleteConfirm": "刪除此自訂 geo 來源?", + "customGeoRoutingHint": "在路由規則中將值欄寫為 ext:檔案.dat:標籤(替換標籤)。", + "customGeoInvalidId": "無效的資源 ID", + "customGeoAliasesError": "載入自訂 geo 別名失敗", + "customGeoValidationAlias": "別名只能包含小寫字母、數字、- 和 _", + "customGeoValidationUrl": "URL 必須以 http:// 或 https:// 開頭", + "customGeoAliasPlaceholder": "a-z 0-9 _ -", + "customGeoAliasLabelSuffix": "(自訂)", + "customGeoToastList": "自訂 geo 清單", + "customGeoToastAdd": "新增自訂 geo", + "customGeoToastUpdate": "更新自訂 geo", + "customGeoToastDelete": "自訂 geofile「{{ .fileName }}」已刪除", + "customGeoToastDownload": "geofile「{{ .fileName }}」已更新", + "customGeoErrInvalidType": "類型必須是 geosite 或 geoip", + "customGeoErrAliasRequired": "請填寫別名", + "customGeoErrAliasPattern": "別名包含不允許的字元", + "customGeoErrAliasReserved": "此別名已保留", + "customGeoErrUrlRequired": "請填寫 URL", + "customGeoErrInvalidUrl": "URL 無效", + "customGeoErrUrlScheme": "URL 必須使用 http 或 https", + "customGeoErrUrlHost": "URL 主機無效", + "customGeoErrDuplicateAlias": "此類型已使用該別名", + "customGeoErrNotFound": "找不到自訂 geo 來源", + "customGeoErrDownload": "下載失敗", + "customGeoErrUpdateAllIncomplete": "有一個或多個自訂 geo 來源更新失敗", + "customGeoEmpty": "尚無自訂 geo 來源 — 點擊「新增」以建立" + }, + "inbounds": { + "allTimeTraffic": "累計總流量", + "allTimeTrafficUsage": "所有时间总使用量", + "title": "入站列表", + "totalDownUp": "總上傳 / 下載", + "totalUsage": "總用量", + "inboundCount": "入站數量", + "operate": "選單", + "enable": "啟用", + "remark": "備註", + "node": "節點", + "deployTo": "部署到", + "localPanel": "本機面板", + "protocol": "協議", + "port": "埠", + "portMap": "埠映射", + "traffic": "流量", + "details": "詳細資訊", + "transportConfig": "傳輸配置", + "expireDate": "到期時間", + "createdAt": "建立時間", + "updatedAt": "更新時間", + "resetTraffic": "重置流量", + "addInbound": "新增入站", + "generalActions": "通用操作", + "modifyInbound": "修改入站", + "deleteInbound": "刪除入站", + "deleteInboundContent": "確定要刪除入站嗎?", + "deleteClient": "刪除客戶端", + "deleteClientContent": "確定要刪除客戶端嗎?", + "resetTrafficContent": "確定要重置流量嗎?", + "copyLink": "複製連結", + "address": "地址", + "network": "網路", + "destinationPort": "目標埠", + "targetAddress": "目標地址", + "monitorDesc": "留空表示監聽所有 IP", + "meansNoLimit": "= 無限制(單位:GB)", + "totalFlow": "總流量", + "leaveBlankToNeverExpire": "留空表示永不過期", + "noRecommendKeepDefault": "建議保留預設值", + "certificatePath": "檔案路徑", + "certificateContent": "檔案內容", + "publicKey": "公鑰", + "privatekey": "私鑰", + "clickOnQRcode": "點選二維碼複製", + "client": "客戶", + "export": "匯出連結", + "clone": "複製", + "cloneInbound": "複製", + "cloneInboundContent": "此入站規則除埠(Port)、監聽 IP(Listening IP)和客戶端(Clients)以外的所有配置都將應用於克隆", + "cloneInboundOk": "建立克隆", + "resetAllTraffic": "重置所有入站流量", + "resetAllTrafficTitle": "重置所有入站流量", + "resetAllTrafficContent": "確定要重置所有入站流量嗎?", + "resetInboundClientTraffics": "重置客戶端流量", + "resetInboundClientTrafficTitle": "重置所有客戶端流量", + "resetInboundClientTrafficContent": "確定要重置此入站客戶端的所有流量嗎?", + "resetAllClientTraffics": "重置所有客戶端流量", + "resetAllClientTrafficTitle": "重置所有客戶端流量", + "resetAllClientTrafficContent": "確定要重置所有客戶端的所有流量嗎?", + "delDepletedClients": "刪除流量耗盡的客戶端", + "delDepletedClientsTitle": "刪除流量耗盡的客戶端", + "delDepletedClientsContent": "確定要刪除所有流量耗盡的客戶端嗎?", + "email": "電子郵件", + "emailDesc": "電子郵件必須完全唯一", + "IPLimit": "IP 限制", + "IPLimitDesc": "如果數量超過設定值,則禁用入站流量。(0 = 禁用)", + "IPLimitlog": "IP 日誌", + "IPLimitlogDesc": "IP 歷史日誌(要啟用被禁用的入站流量,請清除日誌)", + "IPLimitlogclear": "清除日誌", + "setDefaultCert": "從面板設定證書", + "telegramDesc": "請提供Telegram聊天ID。(在機器人中使用'/id'命令)或({'@'}userinfobot", + "subscriptionDesc": "要找到你的訂閱 URL,請導航到“詳細資訊”。此外,你可以為多個客戶端使用相同的名稱。", + "info": "資訊", + "same": "相同", + "inboundData": "入站資料", + "exportInbound": "匯出入站規則", + "import": "匯入", + "importInbound": "匯入入站規則", + "periodicTrafficResetTitle": "流量重置", + "periodicTrafficResetDesc": "按指定間隔自動重置流量計數器", + "lastReset": "上次重置", + "periodicTrafficReset": { + "never": "從不", + "daily": "每日", + "weekly": "每週", + "monthly": "每月", + "hourly": "每小時" + }, + "toasts": { + "obtain": "獲取", + "updateSuccess": "更新成功", + "logCleanSuccess": "日誌已清除", + "inboundsUpdateSuccess": "入站連接已成功更新", + "inboundUpdateSuccess": "入站連接已成功更新", + "inboundCreateSuccess": "入站連接已成功建立", + "inboundDeleteSuccess": "入站連接已成功刪除", + "inboundClientAddSuccess": "已新增入站客戶端", + "inboundClientDeleteSuccess": "入站客戶端已刪除", + "inboundClientUpdateSuccess": "入站客戶端已更新", + "delDepletedClientsSuccess": "所有耗盡客戶端已刪除", + "resetAllClientTrafficSuccess": "客戶端所有流量已重置", + "resetAllTrafficSuccess": "所有流量已重置", + "resetInboundClientTrafficSuccess": "流量已重置", + "trafficGetError": "取得流量資料時發生錯誤", + "getNewX25519CertError": "取得X25519憑證時發生錯誤。", + "getNewmldsa65Error": "取得mldsa65憑證時發生錯誤。", + "getNewVlessEncError": "取得VlessEnc憑證時發生錯誤。" + }, + "stream": { + "general": { + "request": "請求", + "response": "響應", + "name": "名稱", + "value": "值" + }, + "tcp": { + "version": "版本", + "method": "方法", + "path": "路徑", + "status": "狀態", + "statusDescription": "狀態說明", + "requestHeader": "請求頭", + "responseHeader": "響應頭" + } + } + }, + "client": { + "add": "新增客戶端", + "edit": "編輯客戶端", + "submitAdd": "新增客戶端", + "submitEdit": "儲存修改", + "clientCount": "客戶端數量", + "bulk": "批量建立", + "copyFromInbound": "從入站複製用戶端", + "copyToInbound": "複製用戶端到", + "copySelected": "複製所選", + "copySource": "來源", + "copyEmailPreview": "最終郵箱預覽", + "copySelectSourceFirst": "請先選擇來源入站。", + "copyResult": "複製結果", + "copyResultSuccess": "複製成功", + "copyResultNone": "沒有可複製的內容:未選擇用戶端或來源為空", + "copyResultErrors": "複製錯誤", + "copyFlowLabel": "新用戶端的 Flow (VLESS)", + "copyFlowHint": "套用於所有複製的用戶端。留空則略過。", + "selectAll": "全選", + "clearAll": "全不選", + "method": "方法", + "first": "置頂", + "last": "置底", + "prefix": "字首", + "postfix": "字尾", + "delayedStart": "首次使用後開始", + "expireDays": "期間", + "days": "天", + "renew": "自動續訂", + "renewDesc": "到期後自動續訂。(0 = 禁用)(單位: 天)" + }, + "nodes": { + "title": "節點", + "addNode": "新增節點", + "editNode": "編輯節點", + "totalNodes": "節點總數", + "onlineNodes": "線上", + "offlineNodes": "離線", + "avgLatency": "平均延遲", + "name": "名稱", + "namePlaceholder": "例如:de-frankfurt-1", + "addressPlaceholder": "panel.example.com 或 1.2.3.4", + "remark": "備註", + "scheme": "協議", + "address": "位址", + "port": "埠", + "basePath": "基礎路徑", + "apiToken": "API 權杖", + "apiTokenPlaceholder": "遠端面板設定頁中的權杖", + "apiTokenHint": "遠端面板在 設定 → API 權杖 中顯示其 API 權杖。", + "regenerate": "重新產生權杖", + "regenerateConfirm": "重新產生會使目前的權杖失效。任何使用該權杖的中央面板將失去存取權,直到更新為止。是否繼續?", + "enable": "已啟用", + "status": "狀態", + "cpu": "CPU", + "mem": "記憶體", + "uptime": "執行時間", + "latency": "延遲", + "lastHeartbeat": "上次心跳", + "xrayVersion": "Xray 版本", + "actions": "操作", + "probe": "立即探測", + "testConnection": "測試連線", + "connectionOk": "連線正常 ({ms} ms)", + "connectionFailed": "連線失敗", + "never": "從未", + "justNow": "剛剛", + "deleteConfirmTitle": "刪除節點「{name}」?", + "deleteConfirmContent": "這將停止監控該節點。遠端面板本身不受影響。", + "statusValues": { + "online": "線上", + "offline": "離線", + "unknown": "未知" + }, + "toasts": { + "list": "載入節點失敗", + "obtain": "載入節點失敗", + "add": "新增節點", + "update": "更新節點", + "delete": "刪除節點", + "deleted": "節點已刪除", + "test": "測試連線", + "fillRequired": "名稱、位址、埠與 API 權杖為必填", + "probeFailed": "探測失敗" + } + }, + "settings": { + "title": "面板設定", + "save": "儲存", + "infoDesc": "此處的所有更改都需要儲存並重啟面板才能生效", + "restartPanel": "重啟面板", + "restartPanelDesc": "確定要重啟面板嗎?若重啟後無法訪問面板,請前往伺服器檢視面板日誌資訊", + "restartPanelSuccess": "面板已成功重新啟動", + "actions": "操作", + "resetDefaultConfig": "重置為預設配置", + "panelSettings": "常規", + "securitySettings": "安全設定", + "TGBotSettings": "Telegram 機器人配置", + "panelListeningIP": "面板監聽 IP", + "panelListeningIPDesc": "預設留空監聽所有 IP", + "panelListeningDomain": "面板監聽域名", + "panelListeningDomainDesc": "預設情況下留空以監視所有域名和 IP 地址", + "panelPort": "面板監聽埠", + "panelPortDesc": "重啟面板生效", + "publicKeyPath": "面板證書公鑰檔案路徑", + "publicKeyPathDesc": "填寫一個 '/' 開頭的絕對路徑", + "privateKeyPath": "面板證書金鑰檔案路徑", + "privateKeyPathDesc": "填寫一個 '/' 開頭的絕對路徑", + "panelUrlPath": "面板 url 根路徑", + "panelUrlPathDesc": "必須以 '/' 開頭,以 '/' 結尾", + "pageSize": "分頁大小", + "pageSizeDesc": "定義入站表的頁面大小。設定 0 表示禁用", + "remarkModel": "備註模型和分隔符", + "datepicker": "日期選擇器", + "datepickerPlaceholder": "選擇日期", + "datepickerDescription": "選擇器日曆類型指定到期日期", + "sampleRemark": "備註示例", + "oldUsername": "原使用者名稱", + "currentPassword": "原密碼", + "newUsername": "新使用者名稱", + "newPassword": "新密碼", + "telegramBotEnable": "啟用 Telegram 機器人", + "telegramBotEnableDesc": "啟用 Telegram 機器人功能", + "telegramToken": "Telegram 機器人令牌(token)", + "telegramTokenDesc": "從 '{'@'}BotFather' 獲取的 Telegram 機器人令牌", + "telegramProxy": "SOCKS5 Proxy", + "telegramProxyDesc": "啟用 SOCKS5 代理連線到 Telegram(根據指南調整設定)", + "telegramAPIServer": "Telegram API Server", + "telegramAPIServerDesc": "要使用的 Telegram API 伺服器。留空以使用預設伺服器。", + "telegramChatId": "管理員聊天 ID", + "telegramChatIdDesc": "Telegram 管理員聊天 ID (多個以逗號分隔)(可通過 {'@'}userinfobot 獲取,或在機器人中使用 '/id' 命令獲取)", + "telegramNotifyTime": "通知時間", + "telegramNotifyTimeDesc": "設定週期性的 Telegram 機器人通知時間(使用 crontab 時間格式)", + "tgNotifyBackup": "資料庫備份", + "tgNotifyBackupDesc": "傳送帶有報告的資料庫備份檔案", + "tgNotifyLogin": "登入通知", + "tgNotifyLoginDesc": "當有人試圖登入你的面板時顯示使用者名稱、IP 地址和時間", + "sessionMaxAge": "會話時長", + "sessionMaxAgeDesc": "保持登入狀態的時長(單位:分鐘)", + "expireTimeDiff": "到期通知閾值", + "expireTimeDiffDesc": "達到此閾值時,將收到有關到期時間的通知(單位:天)", + "trafficDiff": "流量耗盡閾值", + "trafficDiffDesc": "達到此閾值時,將收到有關流量耗盡的通知(單位:GB)", + "tgNotifyCpu": "CPU 負載通知閾值", + "tgNotifyCpuDesc": "CPU 負載超過此閾值時,將收到通知(單位:%)", + "timeZone": "時區", + "timeZoneDesc": "定時任務將按照該時區的時間執行", + "subSettings": "訂閱設定", + "subEnable": "啟用訂閱服務", + "subEnableDesc": "啟用訂閱服務功能", + "subJsonEnable": "獨立啟用/停用 JSON 訂閱端點。", + "subTitle": "訂閱標題", + "subTitleDesc": "在VPN客戶端中顯示的標題", + "subSupportUrl": "支援連結", + "subSupportUrlDesc": "VPN 用戶端中顯示的技術支援連結", + "subProfileUrl": "個人資料連結", + "subProfileUrlDesc": "VPN 用戶端中顯示的網站連結", + "subAnnounce": "公告", + "subAnnounceDesc": "VPN 用戶端中顯示的公告文字", + "subEnableRouting": "啟用路由", + "subEnableRoutingDesc": "在 VPN 用戶端中啟用路由的全域設定。(僅限 Happ)", + "subRoutingRules": "路由規則", + "subRoutingRulesDesc": "VPN 用戶端的全域路由規則。(僅限 Happ)", + "subListen": "監聽 IP", + "subListenDesc": "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)", + "subPort": "監聽埠", + "subPortDesc": "訂閱服務監聽的埠號(必須是未使用的埠)", + "subCertPath": "公鑰路徑", + "subCertPathDesc": "訂閱服務使用的公鑰檔案路徑(以 '/' 開頭)", + "subKeyPath": "私鑰路徑", + "subKeyPathDesc": "訂閱服務使用的私鑰檔案路徑(以 '/' 開頭)", + "subPath": "URI 路徑", + "subPathDesc": "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)", + "subDomain": "監聽域名", + "subDomainDesc": "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)", + "subUpdates": "更新間隔", + "subUpdatesDesc": "客戶端應用中訂閱 URL 的更新間隔(單位:小時)", + "subEncrypt": "編碼", + "subEncryptDesc": "訂閱服務返回的內容將採用 Base64 編碼", + "subShowInfo": "顯示使用資訊", + "subShowInfoDesc": "客戶端應用中將顯示剩餘流量和日期資訊", + "subURI": "反向代理 URI", + "subURIDesc": "用於代理後面的訂閱 URL 的 URI 路徑", + "externalTrafficInformEnable": "外部交通通知", + "externalTrafficInformEnableDesc": "每次流量更新時通知外部 API", + "externalTrafficInformURI": "外部流量通知 URI", + "externalTrafficInformURIDesc": "流量更新將會傳送到此 URI", + "restartXrayOnClientDisable": "用戶自動停用後重新啟動 Xray", + "restartXrayOnClientDisableDesc": "當用戶因到期或流量上限而被自動停用時,重新啟動 Xray。", + "fragment": "分片", + "fragmentDesc": "啟用 TLS hello 資料包分片", + "fragmentSett": "設定", + "noisesDesc": "啟用 Noises.", + "noisesSett": "Noises 設定", + "mux": "多路複用器", + "muxDesc": "在已建立的資料流內傳輸多個獨立的資料流", + "muxSett": "複用器設定", + "direct": "直接連線", + "directDesc": "直接與特定國家的域或IP範圍建立連線", + "notifications": "通知", + "certs": "證書", + "externalTraffic": "外部流量", + "dateAndTime": "日期和時間", + "proxyAndServer": "代理和伺服器", + "intervals": "間隔", + "information": "資訊", + "language": "語言", + "telegramBotLanguage": "Telegram 機器人語言", + "security": { + "admin": "管理員憑證", + "twoFactor": "雙重驗證", + "twoFactorEnable": "啟用2FA", + "twoFactorEnableDesc": "增加額外的驗證層以提高安全性。", + "twoFactorModalSetTitle": "啟用雙重認證", + "twoFactorModalDeleteTitle": "停用雙重認證", + "twoFactorModalSteps": "要設定雙重認證,請執行以下步驟:", + "twoFactorModalFirstStep": "1. 在認證應用程式中掃描此QR碼,或複製QR碼附近的令牌並貼到應用程式中", + "twoFactorModalSecondStep": "2. 輸入應用程式中的驗證碼", + "twoFactorModalRemoveStep": "輸入應用程式中的驗證碼以移除雙重認證。", + "twoFactorModalChangeCredentialsTitle": "更改憑證", + "twoFactorModalChangeCredentialsStep": "輸入應用程式中的代碼以更改管理員憑證。", + "twoFactorModalSetSuccess": "雙重身份驗證已成功建立", + "twoFactorModalDeleteSuccess": "雙重身份驗證已成功刪除", + "twoFactorModalError": "驗證碼錯誤" + }, + "toasts": { + "modifySettings": "參數已更改。", + "getSettings": "取得參數時發生錯誤", + "modifyUserError": "變更管理員憑證時發生錯誤。", + "modifyUser": "您已成功變更管理員憑證。", + "originalUserPassIncorrect": "原使用者名稱或原密碼錯誤", + "userPassMustBeNotEmpty": "新使用者名稱和新密碼不能為空", + "getOutboundTrafficError": "取得出站流量錯誤", + "resetOutboundTrafficError": "重設出站流量錯誤" + } + }, + "xray": { + "title": "Xray 配置", + "save": "儲存", + "restart": "重新啟動 Xray", + "restartSuccess": "Xray 已成功重新啟動", + "stopSuccess": "Xray 已成功停止", + "restartError": "重新啟動Xray時發生錯誤。", + "stopError": "停止Xray時發生錯誤。", + "basicTemplate": "基礎配置", + "advancedTemplate": "高階配置", + "generalConfigs": "常規配置", + "generalConfigsDesc": "這些選項將決定常規配置", + "logConfigs": "日誌", + "logConfigsDesc": "日誌可能會影響伺服器的效能,建議僅在需要時啟用", + "blockConfigsDesc": "這些選項將阻止使用者連線到特定協議和網站", + "basicRouting": "基本路由", + "blockConnectionsConfigsDesc": "這些選項將根據特定的請求國家阻止流量。", + "directConnectionsConfigsDesc": "直接連線確保特定的流量不會通過其他伺服器路由。", + "blockips": "阻止IP", + "blockdomains": "阻止域名", + "directips": "直接IP", + "directdomains": "直接域名", + "ipv4Routing": "IPv4 路由", + "ipv4RoutingDesc": "此選項將僅通過 IPv4 路由到目標域", + "warpRouting": "WARP 路由", + "warpRoutingDesc": "注意:在使用這些選項之前,請按照面板 GitHub 上的步驟在你的伺服器上以 socks5 代理模式安裝 WARP。WARP 將通過 Cloudflare 伺服器將流量路由到網站。", + "nordRouting": "NordVPN 路由", + "nordRoutingDesc": "這些選項將根據特定目的地通過 NordVPN 路由流量。", + "Template": "高階 Xray 配置模板", + "TemplateDesc": "最終的 Xray 配置檔案將基於此模板生成", + "FreedomStrategy": "Freedom 協議策略", + "FreedomStrategyDesc": "設定 Freedom 協議中網路的輸出策略", + "RoutingStrategy": "配置路由域策略", + "RoutingStrategyDesc": "設定 DNS 解析的整體路由策略", + "outboundTestUrl": "出站測試 URL", + "outboundTestUrlDesc": "測試出站連線時使用的 URL", + "Torrent": "遮蔽 BitTorrent 協議", + "Inbounds": "入站規則", + "InboundsDesc": "接受來自特定客戶端的流量", + "Outbounds": "出站規則", + "Balancers": "負載均衡", + "OutboundsDesc": "設定出站流量傳出方式", + "Routings": "路由規則", + "RoutingsDesc": "每條規則的優先順序都很重要", + "completeTemplate": "全部", + "logLevel": "日誌級別", + "logLevelDesc": "錯誤日誌的日誌級別,用於指示需要記錄的資訊", + "accessLog": "訪問日誌", + "accessLogDesc": "訪問日誌的檔案路徑。特殊值 'none' 禁用訪問日誌", + "errorLog": "錯誤日誌", + "errorLogDesc": "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌", + "dnsLog": "DNS 日誌", + "dnsLogDesc": "是否啟用 DNS 查詢日誌", + "maskAddress": "隱藏地址", + "maskAddressDesc": "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。", + "statistics": "統計", + "statsInboundUplink": "入站上傳統計", + "statsInboundUplinkDesc": "啟用所有入站代理的上行流量統計收集。", + "statsInboundDownlink": "入站下載統計", + "statsInboundDownlinkDesc": "啟用所有入站代理的下行流量統計收集。", + "statsOutboundUplink": "出站上傳統計", + "statsOutboundUplinkDesc": "啟用所有出站代理的上行流量統計收集。", + "statsOutboundDownlink": "出站下載統計", + "statsOutboundDownlinkDesc": "啟用所有出站代理的下行流量統計收集。", + "rules": { + "first": "置頂", + "last": "置底", + "up": "向上", + "down": "向下", + "source": "來源", + "dest": "目的地址", + "inbound": "入站", + "outbound": "出站", + "balancer": "負載均衡", + "info": "資訊", + "add": "新增規則", + "edit": "編輯規則", + "useComma": "逗號分隔的項目" + }, + "outbound": { + "addOutbound": "新增出站", + "addReverse": "新增反向", + "editOutbound": "編輯出站", + "editReverse": "編輯反向", + "reverseTag": "反向標籤", + "reverseTagDesc": "VLESS 簡易反向代理出站標籤。留空則停用。設定後,此客戶端的連線可作為反向代理隧道。", + "reverseTagPlaceholder": "出站標籤(留空則停用)", + "tag": "標籤", + "tagDesc": "唯一標籤", + "address": "地址", + "reverse": "反向", + "domain": "域名", + "type": "類型", + "bridge": "Bridge", + "portal": "Portal", + "link": "連結", + "intercon": "互連", + "settings": "設定", + "accountInfo": "帳戶資訊", + "outboundStatus": "出站狀態", + "sendThrough": "傳送通過", + "test": "測試", + "testResult": "測試結果", + "testing": "正在測試連接...", + "testSuccess": "測試成功", + "testFailed": "測試失敗", + "testError": "測試出站失敗", + "nordvpn": "NordVPN", + "accessToken": "訪問令牌", + "country": "國家", + "server": "伺服器", + "city": "城市", + "allCities": "所有城市", + "privateKey": "私密金鑰", + "load": "負載" + }, + "balancer": { + "addBalancer": "新增負載均衡", + "editBalancer": "編輯負載均衡", + "balancerStrategy": "策略", + "balancerSelectors": "選擇器", + "tag": "標籤", + "tagDesc": "唯一標籤", + "balancerDesc": "無法同時使用 balancerTag 和 outboundTag。如果同時使用,則只有 outboundTag 會生效。" + }, + "wireguard": { + "secretKey": "金鑰", + "publicKey": "公鑰", + "allowedIPs": "允許的 IP", + "endpoint": "端點", + "psk": "共享金鑰", + "domainStrategy": "域策略" + }, + "tun": { + "nameDesc": "TUN 介面的名稱。預設值為 'xray0'", + "mtuDesc": "最大傳輸單元。資料包的最大大小。預設值為 1500", + "userLevel": "用戶級別", + "userLevelDesc": "通過此入站的所有連接都將使用此用戶級別。預設值為 0" + }, + "dns": { + "enable": "啟用 DNS", + "enableDesc": "啟用內建 DNS 伺服器", + "tag": "DNS 入站標籤", + "tagDesc": "此標籤將在路由規則中可用作入站標籤", + "clientIp": "客戶端IP", + "clientIpDesc": "用於在DNS查詢期間通知伺服器指定的IP位置", + "disableCache": "禁用快取", + "disableCacheDesc": "禁用DNS快取", + "disableFallback": "禁用回退", + "disableFallbackDesc": "禁用回退DNS查詢", + "disableFallbackIfMatch": "匹配時禁用回退", + "disableFallbackIfMatchDesc": "當DNS伺服器的匹配域名列表命中時,禁用回退DNS查詢", + "enableParallelQuery": "啟用並行查詢", + "enableParallelQueryDesc": "啟用並行DNS查詢到多個伺服器以實現更快的解析", + "strategy": "查詢策略", + "strategyDesc": "解析域名的總體策略", + "add": "新增伺服器", + "edit": "編輯伺服器", + "domains": "域", + "expectIPs": "預期 IP", + "unexpectIPs": "意外IP", + "useSystemHosts": "使用系統Hosts", + "useSystemHostsDesc": "使用已安裝系統的hosts檔案", + "usePreset": "使用範本", + "dnsPresetTitle": "DNS範本", + "dnsPresetFamily": "家庭" + }, + "fakedns": { + "add": "新增假 DNS", + "edit": "編輯假 DNS", + "ipPool": "IP 池子網", + "poolSize": "池大小" + } + } + }, + "tgbot": { + "keyboardClosed": "❌ 自定義鍵盤已關閉!", + "noResult": "❗ 沒有結果!", + "noQuery": "❌ 未找到查詢!請再次使用該命令!", + "wentWrong": "❌ 出了點問題!", + "noIpRecord": "❗ 沒有IP記錄!", + "noInbounds": "❗ 未找到入站!", + "unlimited": "♾ 無限(重置)", + "add": "添加", + "month": "月", + "months": "月", + "day": "天", + "days": "天", + "hours": "小時", + "minutes": "分鐘", + "unknown": "未知", + "inbounds": "入站", + "clients": "客戶端", + "offline": "🔴 離線", + "online": "🟢 在線", + "commands": { + "unknown": "❗ 未知命令", + "pleaseChoose": "👇 請選擇:\r\n", + "help": "🤖 歡迎使用本機器人!它旨在為您提供來自伺服器的特定資料,並允許您進行必要的修改。\r\n\r\n", + "start": "👋 你好,{{ .Firstname }}。\r\n", + "welcome": "🤖 歡迎來到 {{ .Hostname }} 管理機器人。\r\n", + "status": "✅ 機器人正常執行!", + "usage": "❗ 請輸入要搜尋的文字!", + "getID": "🆔 您的 ID 為:{{ .ID }}", + "helpAdminCommands": "要重新啟動 Xray Core:\r\n/restart\r\n\r\n要搜尋客戶電子郵件:\r\n/usage [電子郵件]\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n/inbound [備註]\r\n\r\nTelegram聊天ID:\r\n/id", + "helpClientCommands": "要搜尋統計資料,請使用以下命令:\r\n/usage [電子郵件]\r\n\r\nTelegram聊天ID:\r\n/id", + "restartUsage": "\r\n\r\n/restart", + "restartSuccess": "✅ 操作成功!", + "restartFailed": "❗ 操作錯誤。\r\n\r\n錯誤: {{ .Error }}.", + "xrayNotRunning": "❗ Xray Core 未運行。", + "startDesc": "顯示主選單", + "helpDesc": "機器人幫助", + "statusDesc": "檢查機器人狀態", + "idDesc": "顯示您的 Telegram ID" + }, + "messages": { + "cpuThreshold": "🔴 CPU 使用率為 {{ .Percent }}%,超過閾值 {{ .Threshold }}%", + "selectUserFailed": "❌ 使用者選擇錯誤!", + "userSaved": "✅ 電報使用者已儲存。", + "loginSuccess": "✅ 成功登入到面板。\r\n", + "loginFailed": "❗️ 面板登入失敗。\r\n", + "2faFailed": "2FA 失敗", + "report": "🕰 定時報告:{{ .RunTime }}\r\n", + "datetime": "⏰ 日期時間:{{ .DateTime }}\r\n", + "hostname": "💻 主機名:{{ .Hostname }}\r\n", + "version": "🚀 X-UI 版本:{{ .Version }}\r\n", + "xrayVersion": "📡 Xray 版本: {{ .XrayVersion }}\r\n", + "ipv6": "🌐 IPv6:{{ .IPv6 }}\r\n", + "ipv4": "🌐 IPv4:{{ .IPv4 }}\r\n", + "ip": "🌐 IP:{{ .IP }}\r\n", + "ips": "🔢 IP 地址:\r\n{{ .IPs }}\r\n", + "serverUpTime": "⏳ 伺服器執行時間:{{ .UpTime }} {{ .Unit }}\r\n", + "serverLoad": "📈 伺服器負載:{{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n", + "serverMemory": "📋 伺服器記憶體:{{ .Current }}/{{ .Total }}\r\n", + "tcpCount": "🔹 TCP 連線數:{{ .Count }}\r\n", + "udpCount": "🔸 UDP 連線數:{{ .Count }}\r\n", + "traffic": "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n", + "xrayStatus": "ℹ️ Xray 狀態:{{ .State }}\r\n", + "username": "👤 使用者名稱:{{ .Username }}\r\n", + "reason": "❗️ 原因:{{ .Reason }}\r\n", + "time": "⏰ 時間:{{ .Time }}\r\n", + "inbound": "📍 入站:{{ .Remark }}\r\n", + "port": "🔌 埠:{{ .Port }}\r\n", + "expire": "📅 過期日期:{{ .Time }}\r\n", + "expireIn": "📅 剩餘時間:{{ .Time }}\r\n", + "active": "💡 啟用:{{ .Enable }}\r\n", + "enabled": "🚨 已啟用:{{ .Enable }}\r\n", + "online": "🌐 連線狀態:{{ .Status }}\r\n", + "lastOnline": "🔙 上次上線: {{ .Time }}\r\n", + "email": "📧 郵箱:{{ .Email }}\r\n", + "upload": "🔼 上傳↑:{{ .Upload }}\r\n", + "download": "🔽 下載↓:{{ .Download }}\r\n", + "total": "📊 總計:{{ .UpDown }} / {{ .Total }}\r\n", + "TGUser": "👤 電報使用者:{{ .TelegramID }}\r\n", + "exhaustedMsg": "🚨 耗盡的 {{ .Type }}:\r\n", + "exhaustedCount": "🚨 耗盡的 {{ .Type }} 數量:\r\n", + "onlinesCount": "🌐 線上客戶:{{ .Count }}\r\n", + "disabled": "🛑 禁用:{{ .Disabled }}\r\n", + "depleteSoon": "🔜 即將耗盡:{{ .Deplete }}\r\n\r\n", + "backupTime": "🗄 備份時間:{{ .Time }}\r\n", + "refreshedOn": "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n", + "yes": "✅ 是的", + "no": "❌ 沒有", + "received_id": "🔑📥 ID 已更新。", + "received_password": "🔑📥 密碼已更新。", + "received_email": "📧📥 電子郵件已更新。", + "received_comment": "💬📥 評論已更新。", + "id_prompt": "🔑 預設 ID: {{ .ClientId }}\n\n請輸入您的 ID。", + "pass_prompt": "🔑 預設密碼: {{ .ClientPassword }}\n\n請輸入您的密碼。", + "email_prompt": "📧 預設電子郵件: {{ .ClientEmail }}\n\n請輸入您的電子郵件。", + "comment_prompt": "💬 預設評論: {{ .ClientComment }}\n\n請輸入您的評論。", + "inbound_client_data_id": "🔄 入站: {{ .InboundRemark }}\n\n🔑 ID: {{ .ClientId }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!", + "inbound_client_data_pass": "🔄 入站: {{ .InboundRemark }}\n\n🔑 密碼: {{ .ClientPass }}\n📧 電子郵件: {{ .ClientEmail }}\n📊 流量: {{ .ClientTraffic }}\n📅 到期日: {{ .ClientExp }}\n🌐 IP 限制: {{ .IpLimit }}\n💬 備註: {{ .ClientComment }}\n\n你現在可以將客戶加入入站了!", + "cancel": "❌ 程序已取消!\n\n您可以隨時使用 /start 重新開始。 🔄", + "error_add_client": "⚠️ 錯誤:\n\n {{ .error }}", + "using_default_value": "好的,我會使用預設值。 😊", + "incorrect_input": "您的輸入無效。\n短語應連續輸入,不能有空格。\n正確示例: aaaaaa\n錯誤示例: aaa aaa 🚫", + "AreYouSure": "你確定嗎?🤔", + "SuccessResetTraffic": "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ✅ 成功", + "FailedResetTraffic": "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]", + "FinishProcess": "🔚 所有客戶的流量重置已完成。" + }, + "buttons": { + "closeKeyboard": "❌ 關閉鍵盤", + "cancel": "❌ 取消", + "cancelReset": "❌ 取消重置", + "cancelIpLimit": "❌ 取消 IP 限制", + "confirmResetTraffic": "✅ 確認重置流量?", + "confirmClearIps": "✅ 確認清除 IP?", + "confirmRemoveTGUser": "✅ 確認移除 Telegram 使用者?", + "confirmToggle": "✅ 確認啟用/禁用使用者?", + "dbBackup": "獲取資料庫備份", + "serverUsage": "伺服器使用情況", + "getInbounds": "獲取入站資訊", + "depleteSoon": "即將耗盡", + "clientUsage": "獲取使用情況", + "onlines": "線上客戶端", + "commands": "命令", + "refresh": "🔄 重新整理", + "clearIPs": "❌ 清除 IP", + "removeTGUser": "❌ 移除 Telegram 使用者", + "selectTGUser": "👤 選擇 Telegram 使用者", + "selectOneTGUser": "👤 選擇一個 Telegram 使用者:", + "resetTraffic": "📈 重置流量", + "resetExpire": "📅 更改到期日期", + "ipLog": "🔢 IP 日誌", + "ipLimit": "🔢 IP 限制", + "setTGUser": "👤 設定 Telegram 使用者", + "toggle": "🔘 啟用/禁用", + "custom": "🔢 風俗", + "confirmNumber": "✅ 確認: {{ .Num }}", + "confirmNumberAdd": "✅ 確認新增:{{ .Num }}", + "limitTraffic": "🚧 流量限制", + "getBanLogs": "禁止日誌", + "allClients": "所有客戶", + "addClient": "新增客戶", + "submitDisable": "以停用方式送出 ☑️", + "submitEnable": "以啟用方式送出 ✅", + "use_default": "🏷️ 使用預設值", + "change_id": "⚙️🔑 ID", + "change_password": "⚙️🔑 密碼", + "change_email": "⚙️📧 電子郵件", + "change_comment": "⚙️💬 評論", + "ResetAllTraffics": "重設所有流量", + "SortedTrafficUsageReport": "排序過的流量使用報告" + }, + "answers": { + "successfulOperation": "✅ 成功!", + "errorOperation": "❗ 操作錯誤。", + "getInboundsFailed": "❌ 獲取入站資訊失敗。", + "getClientsFailed": "❌ 獲取客戶失敗。", + "canceled": "❌ {{ .Email }}:操作已取消。", + "clientRefreshSuccess": "✅ {{ .Email }}:客戶端重新整理成功。", + "IpRefreshSuccess": "✅ {{ .Email }}:IP 重新整理成功。", + "TGIdRefreshSuccess": "✅ {{ .Email }}:客戶端的 Telegram 使用者重新整理成功。", + "resetTrafficSuccess": "✅ {{ .Email }}:流量已重置成功。", + "setTrafficLimitSuccess": "✅ {{ .Email }}: 流量限制儲存成功。", + "expireResetSuccess": "✅ {{ .Email }}:過期天數已重置成功。", + "resetIpSuccess": "✅ {{ .Email }}:成功儲存 IP 限制數量為 {{ .Count }}。", + "clearIpSuccess": "✅ {{ .Email }}:IP 已成功清除。", + "getIpLog": "✅ {{ .Email }}:獲取 IP 日誌。", + "getUserInfo": "✅ {{ .Email }}:獲取 Telegram 使用者資訊。", + "removedTGUserSuccess": "✅ {{ .Email }}:Telegram 使用者已成功移除。", + "enableSuccess": "✅ {{ .Email }}:已成功啟用。", + "disableSuccess": "✅ {{ .Email }}:已成功禁用。", + "askToAddUserId": "未找到您的配置!\r\n請向管理員詢問,在您的配置中使用您的 Telegram 使用者 ChatID。\r\n\r\n您的使用者 ChatID:{{ .TgUserID }}", + "chooseClient": "為入站 {{ .Inbound }} 選擇一個客戶", + "chooseInbound": "選擇一個入站" + } + } +} diff --git a/web/web.go b/web/web.go index 7d634e70..8c7577f1 100644 --- a/web/web.go +++ b/web/web.go @@ -6,7 +6,6 @@ import ( "context" "crypto/tls" "embed" - "html/template" "io" "io/fs" "net" @@ -24,6 +23,7 @@ import ( "github.com/mhsanaei/3x-ui/v2/web/locale" "github.com/mhsanaei/3x-ui/v2/web/middleware" "github.com/mhsanaei/3x-ui/v2/web/network" + "github.com/mhsanaei/3x-ui/v2/web/runtime" "github.com/mhsanaei/3x-ui/v2/web/service" "github.com/mhsanaei/3x-ui/v2/web/websocket" @@ -34,23 +34,34 @@ import ( "github.com/robfig/cron/v3" ) -//go:embed assets -var assetsFS embed.FS - -//go:embed html/* -var htmlFS embed.FS - //go:embed translation/* var i18nFS embed.FS +// distFS embeds the Vite-built frontend (web/dist/). Every user-facing +// HTML route is served straight out of this FS — the legacy Go +// templates and `web/assets/` tree are gone post-Phase 8. +// +// `all:` is required so files whose names start with `_` are NOT +// silently excluded by go:embed's default rules. Vite/rolldown emits +// `_plugin-vue_export-helper-.js` for the @vitejs/plugin-vue +// runtime; without `all:` the chunk would be missing from the binary +// at runtime → 404 → blank-page boot failure. +// +//go:embed all:dist +var distFS embed.FS + var startTime = time.Now() -type wrapAssetsFS struct { +// wrapDistFS adapts the embedded `dist/` directory so it can be mounted +// as the panel's `/assets/` static route. Vite emits its bundled JS/CSS +// under `dist/assets/`; serving the FS rooted at `dist/assets` makes +// `/assets/.js` URLs resolve directly. +type wrapDistFS struct { embed.FS } -func (f *wrapAssetsFS) Open(name string) (fs.File, error) { - file, err := f.FS.Open("assets/" + name) +func (f *wrapDistFS) Open(name string) (fs.File, error) { + file, err := f.FS.Open("dist/assets/" + name) if err != nil { return nil, err } @@ -81,14 +92,11 @@ func (f *wrapAssetsFileInfo) ModTime() time.Time { return startTime } -// EmbeddedHTML returns the embedded HTML templates filesystem for reuse by other servers. -func EmbeddedHTML() embed.FS { - return htmlFS -} - -// EmbeddedAssets returns the embedded assets filesystem for reuse by other servers. -func EmbeddedAssets() embed.FS { - return assetsFS +// EmbeddedDist returns the embedded Vite-built frontend filesystem. +// Controllers serve their HTML out of this FS via the dist-page handler +// installed in NewEngine(). +func EmbeddedDist() embed.FS { + return distFS } // Server represents the main web server for the 3x-ui panel with controllers, services, and scheduled jobs. @@ -123,53 +131,6 @@ func NewServer() *Server { } } -// getHtmlFiles walks the local `web/html` directory and returns a list of -// template file paths. Used only in debug/development mode. -func (s *Server) getHtmlFiles() ([]string, error) { - files := make([]string, 0) - dir, _ := os.Getwd() - err := fs.WalkDir(os.DirFS(dir), "web/html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - files = append(files, path) - return nil - }) - if err != nil { - return nil, err - } - return files, nil -} - -// getHtmlTemplate parses embedded HTML templates from the bundled `htmlFS` -// using the provided template function map and returns the resulting -// template set for production usage. -func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { - t := template.New("").Funcs(funcMap) - err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - - if d.IsDir() { - newT, err := t.ParseFS(htmlFS, path+"/*.html") - if err != nil { - // ignore - return nil - } - t = newT - } - return nil - }) - if err != nil { - return nil, err - } - return t, nil -} - func (s *Server) isDirectHTTPSConfigured() bool { certFile, certErr := s.settingService.GetCertFile() keyFile, keyErr := s.settingService.GetKeyFile() @@ -239,46 +200,34 @@ func (s *Server) initRouter() (*gin.Engine, error) { } }) - // init i18n + // init i18n — still used by backend strings (errors, log messages, + // SubPage menu entries) even though the Go template engine is gone. err = locale.InitLocalizer(i18nFS, &s.settingService) if err != nil { return nil, err } - // Apply locale middleware for i18n - i18nWebFunc := func(key string, params ...string) string { - return locale.I18n(locale.Web, key, params...) - } - // Register template functions before loading templates - funcMap := template.FuncMap{ - "i18n": i18nWebFunc, - } - engine.SetFuncMap(funcMap) engine.Use(locale.LocalizerMiddleware()) - // set static files and template + // `/assets/` serves the Vite-built bundle. In dev we pull from disk + // so the Vite watcher's incremental rebuilds show up without + // restarting the binary; in prod we serve the embedded dist FS + // rooted at `dist/assets/`. if config.IsDebug() { - // for development - files, err := s.getHtmlFiles() - if err != nil { - return nil, err - } - // Use the registered func map with the loaded templates - engine.LoadHTMLFiles(files...) - engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/assets"))) + engine.StaticFS(basePath+"assets", http.FS(os.DirFS("web/dist/assets"))) } else { - // for production - template, err := s.getHtmlTemplate(funcMap) - if err != nil { - return nil, err - } - engine.SetHTMLTemplate(template) - engine.StaticFS(basePath+"assets", http.FS(&wrapAssetsFS{FS: assetsFS})) + engine.StaticFS(basePath+"assets", http.FS(&wrapDistFS{FS: distFS})) } // Apply the redirect middleware (`/xui` to `/panel`) engine.Use(middleware.RedirectMiddleware(basePath)) + // Hand the embedded `dist/` filesystem to the controller package + // before any HTML-serving controller is constructed. Phase 8 + // cutover: every HTML route reads from web/dist/ instead of + // rendering a legacy template. + controller.SetDistFS(distFS) + g := engine.Group(basePath) s.index = controller.NewIndexController(g) @@ -338,6 +287,13 @@ func (s *Server) startTask() { // check client ips from log file every 10 sec s.cron.AddJob("@every 10s", job.NewCheckClientIpJob()) + // Probe every enabled remote node every 10 sec + s.cron.AddJob("@every 10s", job.NewNodeHeartbeatJob()) + + // Pull traffic + online-clients from every online node every 10 sec + // and merge absolute counters into the central DB. + s.cron.AddJob("@every 10s", job.NewNodeTrafficSyncJob()) + // check client ips from log file every day s.cron.AddJob("@daily", job.NewClearLogsJob()) @@ -410,6 +366,15 @@ func (s *Server) Start() (err error) { s.cron = cron.New(cron.WithLocation(loc), cron.WithSeconds()) s.cron.Start() + // Wire the inbound-runtime manager once so InboundService can route + // add/update/delete to either the local xray or a remote node panel. + // The closures bridge into XrayService (which owns the running xray + // process state) without forcing the runtime package to import service. + runtime.SetManager(runtime.NewManager(runtime.LocalDeps{ + APIPort: func() int { return s.xrayService.GetXrayAPIPort() }, + SetNeedRestart: func() { s.xrayService.SetToNeedRestart() }, + })) + s.customGeoService = service.NewCustomGeoService() engine, err := s.initRouter() diff --git a/web/websocket/hub.go b/web/websocket/hub.go index 29ba384e..1aed8f2e 100644 --- a/web/websocket/hub.go +++ b/web/websocket/hub.go @@ -18,6 +18,7 @@ const ( MessageTypeTraffic MessageType = "traffic" MessageTypeInbounds MessageType = "inbounds" MessageTypeOutbounds MessageType = "outbounds" + MessageTypeNodes MessageType = "nodes" MessageTypeNotification MessageType = "notification" MessageTypeXrayState MessageType = "xray_state" // MessageTypeClientStats carries absolute traffic counters for the clients diff --git a/web/websocket/notifier.go b/web/websocket/notifier.go index 8115ae0f..069aaf73 100644 --- a/web/websocket/notifier.go +++ b/web/websocket/notifier.go @@ -62,6 +62,15 @@ func BroadcastInbounds(inbounds any) { } } +// BroadcastNodes broadcasts the fresh node list to all connected clients. +// Pushed by NodeHeartbeatJob at the end of each 10s tick so the Nodes page +// reflects status / latency / cpu / mem updates without polling. +func BroadcastNodes(nodes any) { + if hub := GetHub(); hub != nil { + hub.Broadcast(MessageTypeNodes, nodes) + } +} + // BroadcastOutbounds broadcasts outbounds list update to all connected clients. func BroadcastOutbounds(outbounds any) { if hub := GetHub(); hub != nil { diff --git a/xray/process.go b/xray/process.go index 009ec7a5..a60fea03 100644 --- a/xray/process.go +++ b/xray/process.go @@ -10,6 +10,7 @@ import ( "os/exec" "runtime" "strings" + "sync" "syscall" "time" @@ -126,6 +127,13 @@ type process struct { apiPort int onlineClients []string + // nodeOnlineClients holds the online-emails list reported by each + // remote node, keyed by node id. NodeTrafficSyncJob populates entries + // per cron tick and clears them when a node's probe fails. The mutex + // guards both this map and onlineClients above so GetOnlineClients + // can build the union without a torn read. + nodeOnlineClients map[int][]string + onlineMu sync.RWMutex config *Config configPath string // if set, use this path instead of GetConfigPath() and remove on Stop @@ -190,14 +198,69 @@ func (p *Process) GetConfig() *Config { return p.config } -// GetOnlineClients returns the list of online clients for the Xray process. +// GetOnlineClients returns the union of locally-online clients and +// node-online clients from every registered remote panel. Dedupes by +// email so a client connected to both a local and a node-managed inbound +// surfaces once. Cheap allocation — typical online sets are small and +// the union is recomputed on demand. func (p *Process) GetOnlineClients() []string { - return p.onlineClients + p.onlineMu.RLock() + defer p.onlineMu.RUnlock() + + if len(p.nodeOnlineClients) == 0 { + // Hot path for single-panel deployments: avoid the map+dedupe + // work entirely and return the local slice as-is. + return p.onlineClients + } + + seen := make(map[string]struct{}, len(p.onlineClients)) + out := make([]string, 0, len(p.onlineClients)) + for _, email := range p.onlineClients { + if _, dup := seen[email]; dup { + continue + } + seen[email] = struct{}{} + out = append(out, email) + } + for _, list := range p.nodeOnlineClients { + for _, email := range list { + if _, dup := seen[email]; dup { + continue + } + seen[email] = struct{}{} + out = append(out, email) + } + } + return out } -// SetOnlineClients sets the list of online clients for the Xray process. +// SetOnlineClients sets the locally-online list. Called by the local +// XrayTrafficJob after each xray gRPC stats poll. func (p *Process) SetOnlineClients(users []string) { + p.onlineMu.Lock() p.onlineClients = users + p.onlineMu.Unlock() +} + +// SetNodeOnlineClients records the online-emails set for one remote +// node. Replaces any previous entry for that node — NodeTrafficSyncJob +// always sends the full list per tick. +func (p *Process) SetNodeOnlineClients(nodeID int, emails []string) { + p.onlineMu.Lock() + defer p.onlineMu.Unlock() + if p.nodeOnlineClients == nil { + p.nodeOnlineClients = map[int][]string{} + } + p.nodeOnlineClients[nodeID] = emails +} + +// ClearNodeOnlineClients drops a node's contribution to the online set. +// Called when a probe fails so a downed node doesn't keep its clients +// listed as "online" until the next successful probe. +func (p *Process) ClearNodeOnlineClients(nodeID int) { + p.onlineMu.Lock() + defer p.onlineMu.Unlock() + delete(p.nodeOnlineClients, nodeID) } // GetUptime returns the uptime of the Xray process in seconds.