Compare commits
65 Commits
v1.0.0-beta.29
...
v1.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f3cac3c1c | |||
| 32253e3717 | |||
| a76471a806 | |||
| d53a00611e | |||
| 2fb98be591 | |||
| 634be371b1 | |||
| ffa5c86d23 | |||
| f84f98c80f | |||
| e8ea597664 | |||
| 04af2f9433 | |||
| 96eba171d5 | |||
| 2675034b75 | |||
| 8f772c2364 | |||
| 166e48f442 | |||
| eec0b12154 | |||
| 3e290d7cb5 | |||
| 3304a55fd4 | |||
| 7cdc6bdd8f | |||
| 1fb305e7a1 | |||
| 7f81de31ab | |||
| bcfb10a6c7 | |||
| 9235fef3c6 | |||
| ee0c3371f7 | |||
| 7d18ff6825 | |||
| c86d28f798 | |||
| d323af8bb6 | |||
| 356ae5b777 | |||
| 50bdd2cd21 | |||
| 4a4d364f17 | |||
| 5ea64893e8 | |||
| 674a01c813 | |||
| 3c5542a3ea | |||
| 6e0c9b6698 | |||
| 3bb714d15c | |||
| 7023875548 | |||
| a7aa5fee3e | |||
| 64cd842926 | |||
| 8b43e69bfe | |||
| ef153747bd | |||
| 328838d754 | |||
| 423b24077f | |||
| fa2fb2864f | |||
| 8d49daca21 | |||
| cf5c39cfe5 | |||
| 383638f702 | |||
| 5939763b57 | |||
| fa56eb8293 | |||
| 6a34bfb78d | |||
| b8f4f1e694 | |||
| aa1d42651d | |||
| 0c8f0911c7 | |||
| db0d9e003e | |||
| f219c52306 | |||
| 3f7b6d16ed | |||
| 6dbebd152d | |||
| bb3bd7b50c | |||
| fbad3b0e51 | |||
| e551232564 | |||
| bf58f25072 | |||
| 85f55def2e | |||
| f72df3a5e8 | |||
| 48a1b97051 | |||
| 786ba0e41c | |||
| ce3197298d | |||
| 092477b2d3 |
+560
-2
@@ -1,13 +1,571 @@
|
|||||||
<a name="readme-top"></a>
|
<a name="readme-top"></a>
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.1.5](https://github.com/perfect-panel/ppanel-web/compare/v1.1.4...v1.1.5) (2025-07-26)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* **subscribe**: Filter out items that are not marked as visible in subscription list ([32253e3](https://github.com/perfect-panel/ppanel-web/commit/32253e3))
|
||||||
|
|
||||||
|
## [1.1.4](https://github.com/perfect-panel/ppanel-web/compare/v1.1.3...v1.1.4) (2025-07-25)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* **locales**: Simplify "show" label in subscription localization files ([d53a006](https://github.com/perfect-panel/ppanel-web/commit/d53a006))
|
||||||
|
* **order**: Preserve last successful order on error during order creation ([2fb98be](https://github.com/perfect-panel/ppanel-web/commit/2fb98be))
|
||||||
|
* **subscribe**: Filter out hidden items in subscription list display ([634be37](https://github.com/perfect-panel/ppanel-web/commit/634be37))
|
||||||
|
|
||||||
|
## [1.1.3](https://github.com/perfect-panel/ppanel-web/compare/v1.1.2...v1.1.3) (2025-07-24)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* **auth**: Implement user redirection to dashboard upon authentication ([f84f98c](https://github.com/perfect-panel/ppanel-web/commit/f84f98c))
|
||||||
|
|
||||||
|
## [1.1.2](https://github.com/perfect-panel/ppanel-web/compare/v1.1.1...v1.1.2) (2025-07-24)
|
||||||
|
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
* **billing**: Add display for gift amount in subscription billing ([04af2f9](https://github.com/perfect-panel/ppanel-web/commit/04af2f9))
|
||||||
|
* **order**: Update subscription cell to display name and quantity ([96eba17](https://github.com/perfect-panel/ppanel-web/commit/96eba17))
|
||||||
|
* **tool**: Added API for obtaining version, updated version information display logic ([2675034](https://github.com/perfect-panel/ppanel-web/commit/2675034))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.1.1](https://github.com/perfect-panel/ppanel-web/compare/v1.1.0...v1.1.1) (2025-07-20)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **node-table**: Update translations for headers and no data display ([eec0b12](https://github.com/perfect-panel/ppanel-web/commit/eec0b12))
|
||||||
|
- **rules**: Change rule type from 'auto' to 'default' and update ([3e290d7](https://github.com/perfect-panel/ppanel-web/commit/3e290d7))
|
||||||
|
- **rules**: Update rule settings ([3304a55](https://github.com/perfect-panel/ppanel-web/commit/3304a55))
|
||||||
|
- **subscribe-form**: Optimize discount calculation logic and debounce updates ([166e48f](https://github.com/perfect-panel/ppanel-web/commit/166e48f))
|
||||||
|
- **tutorial**: Comment out unused getVersion function and simplify getVersionPath ([7cdc6bd](https://github.com/perfect-panel/ppanel-web/commit/7cdc6bd))
|
||||||
|
- **tutorial**: Return latest version in case of fetch error ([1fb305e](https://github.com/perfect-panel/ppanel-web/commit/1fb305e))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
# [1.1.0](https://github.com/perfect-panel/ppanel-web/compare/v1.0.2...v1.1.0) (2025-07-06)
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **view**: Add AnyTLS protocol support and enhance node configuration options ([bcfb10a](https://github.com/perfect-panel/ppanel-web/commit/bcfb10a))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.0.2](https://github.com/perfect-panel/ppanel-web/compare/v1.0.1...v1.0.2) (2025-06-29)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **subscription**: User subscription information ([7d18ff6](https://github.com/perfect-panel/ppanel-web/commit/7d18ff6))
|
||||||
|
|
||||||
|
## [1.0.1](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0...v1.0.1) (2025-04-28)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **payment**: Disable webhook_secret field in PaymentForm component ([d323af8](https://github.com/perfect-panel/ppanel-web/commit/d323af8))
|
||||||
|
- **recharge**: Set balance prop to false in PaymentMethods component ([356ae5b](https://github.com/perfect-panel/ppanel-web/commit/356ae5b))
|
||||||
|
|
||||||
|
# 1.0.0 (2025-04-24)
|
||||||
|
|
||||||
|
### ♻ Code Refactoring
|
||||||
|
|
||||||
|
- **api**: Sort and Announcement ([38d5616](https://github.com/perfect-panel/ppanel-web/commit/38d5616))
|
||||||
|
- **auth**: Refactor user authorization handling and improve error logging ([68bc18f](https://github.com/perfect-panel/ppanel-web/commit/68bc18f))
|
||||||
|
- **config**: GenerateMetadata ([a0bb101](https://github.com/perfect-panel/ppanel-web/commit/a0bb101))
|
||||||
|
- **config**: Simplify environment variable handling and improve build script ([cf54d0f](https://github.com/perfect-panel/ppanel-web/commit/cf54d0f))
|
||||||
|
- **config**: Viewport ([24b8601](https://github.com/perfect-panel/ppanel-web/commit/24b8601))
|
||||||
|
- **core**: Restructure project for better module separation ([9d0cb8b](https://github.com/perfect-panel/ppanel-web/commit/9d0cb8b))
|
||||||
|
- **deps**: Update ([19837a1](https://github.com/perfect-panel/ppanel-web/commit/19837a1))
|
||||||
|
- **empty**: Content ([aa4c667](https://github.com/perfect-panel/ppanel-web/commit/aa4c667))
|
||||||
|
- **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
|
||||||
|
- **sbscribe**: Rename and reorganize components for better structure and clarity ([5e5e4ed](https://github.com/perfect-panel/ppanel-web/commit/5e5e4ed))
|
||||||
|
- **ui**: Dependencies ([727d779](https://github.com/perfect-panel/ppanel-web/commit/727d779))
|
||||||
|
- **ui**: Layout ([9262d7d](https://github.com/perfect-panel/ppanel-web/commit/9262d7d))
|
||||||
|
- **ui**: Optimize document display ([2ca2992](https://github.com/perfect-panel/ppanel-web/commit/2ca2992))
|
||||||
|
- Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
|
||||||
|
- Reduce code complexity and improve readability ([e11f18c](https://github.com/perfect-panel/ppanel-web/commit/e11f18c))
|
||||||
|
|
||||||
|
### ⚡ Performance Improvements
|
||||||
|
|
||||||
|
- **subscribe**: Form discount price ([059a892](https://github.com/perfect-panel/ppanel-web/commit/059a892))
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **accounts**: Update third-party account binding and unbinding ([1841552](https://github.com/perfect-panel/ppanel-web/commit/1841552))
|
||||||
|
- **ad**: Advertise ([b1105cd](https://github.com/perfect-panel/ppanel-web/commit/b1105cd))
|
||||||
|
- **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
|
||||||
|
- **affiliate**: Add Affiliate component with commission display and invite link functionality ([4aea4e8](https://github.com/perfect-panel/ppanel-web/commit/4aea4e8))
|
||||||
|
- **affiliate**: Affiliate Detail ([a782c17](https://github.com/perfect-panel/ppanel-web/commit/a782c17))
|
||||||
|
- **affiliate**: Commission Rate ([5eec430](https://github.com/perfect-panel/ppanel-web/commit/5eec430))
|
||||||
|
- **affiliate**: Update affiliate component to display total commission and improve data fetching ([cc834ca](https://github.com/perfect-panel/ppanel-web/commit/cc834ca))
|
||||||
|
- **announcement**: Popup and pinned ([f3680a7](https://github.com/perfect-panel/ppanel-web/commit/f3680a7))
|
||||||
|
- **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
|
||||||
|
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
|
||||||
|
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
|
||||||
|
- **api**: Add new subscription properties and locale support for deduction ratios and reset cycles ([fec80f5](https://github.com/perfect-panel/ppanel-web/commit/fec80f5))
|
||||||
|
- **api**: Add Time Period Configuration ([837157c](https://github.com/perfect-panel/ppanel-web/commit/837157c))
|
||||||
|
- **api**: Telegram ([17ce96a](https://github.com/perfect-panel/ppanel-web/commit/17ce96a))
|
||||||
|
- **auth-control**: Adding phone number labels to mobile verification configurations in multiple languages ([046740f](https://github.com/perfect-panel/ppanel-web/commit/046740f))
|
||||||
|
- **auth-control**: Update general ([3883646](https://github.com/perfect-panel/ppanel-web/commit/3883646))
|
||||||
|
- **auth**: Add email and SMS code sending functionality with localization updates ([57eaa55](https://github.com/perfect-panel/ppanel-web/commit/57eaa55))
|
||||||
|
- **auth**: Add Oauth configuration for Telegram, Facebook, Google, Github, and Apple ([18ee600](https://github.com/perfect-panel/ppanel-web/commit/18ee600))
|
||||||
|
- **auth**: Add privacy policy link to the footer ([8e16ef1](https://github.com/perfect-panel/ppanel-web/commit/8e16ef1))
|
||||||
|
- **auth**: Add SMS and email configuration options to global store and update localization ([4acf7b1](https://github.com/perfect-panel/ppanel-web/commit/4acf7b1))
|
||||||
|
- **auth**: Add type parameter to SendCode and update related API typings ([4198871](https://github.com/perfect-panel/ppanel-web/commit/4198871))
|
||||||
|
- **auth**: Enhance user registration with invite handling and logo display ([207bc24](https://github.com/perfect-panel/ppanel-web/commit/207bc24))
|
||||||
|
- **auth**: Redirect user after OAuth login and add logos icon collection ([aa6dda8](https://github.com/perfect-panel/ppanel-web/commit/aa6dda8))
|
||||||
|
- **auth**: Refactor mobile authentication config to support whitelist functionality ([c761ec7](https://github.com/perfect-panel/ppanel-web/commit/c761ec7))
|
||||||
|
- **billing**: Update Billing ([078fc9d](https://github.com/perfect-panel/ppanel-web/commit/078fc9d))
|
||||||
|
- **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
|
||||||
|
- **config**: Add application selection and encryption settings to configuration form ([88b3504](https://github.com/perfect-panel/ppanel-web/commit/88b3504))
|
||||||
|
- **config**: FormatBytes ([9251a09](https://github.com/perfect-panel/ppanel-web/commit/9251a09))
|
||||||
|
- **config**: Protocol type ([a3b45b4](https://github.com/perfect-panel/ppanel-web/commit/a3b45b4))
|
||||||
|
- **config**: Update encryption fields in configuration form and refactor OAuth callback parameters ([652e032](https://github.com/perfect-panel/ppanel-web/commit/652e032))
|
||||||
|
- **config**: Webhook Domain ([01e06c6](https://github.com/perfect-panel/ppanel-web/commit/01e06c6))
|
||||||
|
- **dashboard**: Optimization ([5b3f4b4](https://github.com/perfect-panel/ppanel-web/commit/5b3f4b4))
|
||||||
|
- **dashboard**: Statistics ([2926abc](https://github.com/perfect-panel/ppanel-web/commit/2926abc))
|
||||||
|
- **device**: Modify IMEI to device identifier support ([e3f9ef6](https://github.com/perfect-panel/ppanel-web/commit/e3f9ef6))
|
||||||
|
- **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
|
||||||
|
- **favicon**: Update SVG favicon design for admin and user interfaces ([1d91738](https://github.com/perfect-panel/ppanel-web/commit/1d91738))
|
||||||
|
- **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
|
||||||
|
- **form**: Make version field optional and set default value; update site domain placeholder for clarity ([42ba9e8](https://github.com/perfect-panel/ppanel-web/commit/42ba9e8))
|
||||||
|
- **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
|
||||||
|
- **global**: Add SMS configuration options to global store ([39a9ce6](https://github.com/perfect-panel/ppanel-web/commit/39a9ce6))
|
||||||
|
- **header**: Update locales ([bfb6c27](https://github.com/perfect-panel/ppanel-web/commit/bfb6c27))
|
||||||
|
- **imei**: Add IMEI related internationalization support and menu items ([13c3337](https://github.com/perfect-panel/ppanel-web/commit/13c3337))
|
||||||
|
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
|
||||||
|
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
|
||||||
|
- **loading**: Add loading components and integrate them in Providers ([d5847fa](https://github.com/perfect-panel/ppanel-web/commit/d5847fa))
|
||||||
|
- **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
|
||||||
|
- **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
|
||||||
|
- **locale**: Add Persian ([93a0a88](https://github.com/perfect-panel/ppanel-web/commit/93a0a88))
|
||||||
|
- **locales**: Add area code and telephone fields to user forms in multiple languages ([9b8258c](https://github.com/perfect-panel/ppanel-web/commit/9b8258c))
|
||||||
|
- **locales**: Add description information of communication keys and encryption methods to enhance client configuration capabilities ([d1f5a9b](https://github.com/perfect-panel/ppanel-web/commit/d1f5a9b))
|
||||||
|
- **locales**: Add kick offline confirmation and success messages in multiple languages ([5db5343](https://github.com/perfect-panel/ppanel-web/commit/5db5343))
|
||||||
|
- **locales**: Add multiple languages ([b243ab9](https://github.com/perfect-panel/ppanel-web/commit/b243ab9))
|
||||||
|
- **locales**: Replace 'nodeGroupId' with 'groupId' in multiple language files for consistency ([a4e9d5d](https://github.com/perfect-panel/ppanel-web/commit/a4e9d5d))
|
||||||
|
- **locales**: Update 'deductBalance' to 'giftAmount' across multiple languages and fix newline in announcement.json ([70497af](https://github.com/perfect-panel/ppanel-web/commit/70497af))
|
||||||
|
- **locales**: Update 'sms' to 'mobile' in authentication methods across multiple languages ([fea2171](https://github.com/perfect-panel/ppanel-web/commit/fea2171))
|
||||||
|
- **log**: Add message log retrieval functionality and update related typings ([1c0ecae](https://github.com/perfect-panel/ppanel-web/commit/1c0ecae))
|
||||||
|
- **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
|
||||||
|
- **node-subscription**: Add copy functionality for columns ([3a81e37](https://github.com/perfect-panel/ppanel-web/commit/3a81e37))
|
||||||
|
- **node**: Add NodeStatus ([c712624](https://github.com/perfect-panel/ppanel-web/commit/c712624))
|
||||||
|
- **node**: Add protocol ([301b635](https://github.com/perfect-panel/ppanel-web/commit/301b635))
|
||||||
|
- **node**: Add serverKey ([25ce37e](https://github.com/perfect-panel/ppanel-web/commit/25ce37e))
|
||||||
|
- **node**: Add status ([c06372b](https://github.com/perfect-panel/ppanel-web/commit/c06372b))
|
||||||
|
- **node**: Add tags ([f408fdf](https://github.com/perfect-panel/ppanel-web/commit/f408fdf))
|
||||||
|
- **node**: Move the node configuration to the server module ([7f0f5ce](https://github.com/perfect-panel/ppanel-web/commit/7f0f5ce))
|
||||||
|
- **oauth**: Add certification component for handling OAuth login callbacks and improve user authentication flow ([5ed04c0](https://github.com/perfect-panel/ppanel-web/commit/5ed04c0))
|
||||||
|
- **oauth**: Implement OAuth token retrieval and refactor login callback handling ([40a6f7c](https://github.com/perfect-panel/ppanel-web/commit/40a6f7c))
|
||||||
|
- **oauth**: Refactor platform parameter handling and improve logout redirection logic ([8346c85](https://github.com/perfect-panel/ppanel-web/commit/8346c85))
|
||||||
|
- **oauth**: Update OAuth login handling to use callback parameter and improve URL parameter retrieval ([9227411](https://github.com/perfect-panel/ppanel-web/commit/9227411))
|
||||||
|
- **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
|
||||||
|
- **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
|
||||||
|
- **platform**: Update platform naming and add keywords and custom HTML fields ([6384237](https://github.com/perfect-panel/ppanel-web/commit/6384237))
|
||||||
|
- **privacy-policy**: Add privacy policy related text and links ([baa68f0](https://github.com/perfect-panel/ppanel-web/commit/baa68f0))
|
||||||
|
- **profile**: Update localization strings and enhance third-party account binding ([2d1effb](https://github.com/perfect-panel/ppanel-web/commit/2d1effb))
|
||||||
|
- **relay**: Add relay mode configuration and update related schemas ([3cc9477](https://github.com/perfect-panel/ppanel-web/commit/3cc9477))
|
||||||
|
- **release**: Extend supported platforms for Docker images, closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([e3a31eb](https://github.com/perfect-panel/ppanel-web/commit/e3a31eb))
|
||||||
|
- **schema**: Add security field to hysteria2 and tuic schemas ([cd59d44](https://github.com/perfect-panel/ppanel-web/commit/cd59d44))
|
||||||
|
- **site**: Added localization support for custom HTML and keyword fields ([f9d7736](https://github.com/perfect-panel/ppanel-web/commit/f9d7736))
|
||||||
|
- **sms**: Update locales ([938363b](https://github.com/perfect-panel/ppanel-web/commit/938363b))
|
||||||
|
- **stats**: Replace dynamic stat fetching with environment constants for user, server, and location counts ([46ae166](https://github.com/perfect-panel/ppanel-web/commit/46ae166))
|
||||||
|
- **subscribe**: Add 'sold' column to SubscribeTable and update inventory terminology ([19619fd](https://github.com/perfect-panel/ppanel-web/commit/19619fd))
|
||||||
|
- **subscribe**: Add reset_time to API typings and update unsubscribe logic ([eeea165](https://github.com/perfect-panel/ppanel-web/commit/eeea165))
|
||||||
|
- **subscribe**: Add subscribe_discount type ([f99c604](https://github.com/perfect-panel/ppanel-web/commit/f99c604))
|
||||||
|
- **subscribe**: Add subscription credits ([5bc7905](https://github.com/perfect-panel/ppanel-web/commit/5bc7905))
|
||||||
|
- **subscribe**: Add unit time ([39d07ec](https://github.com/perfect-panel/ppanel-web/commit/39d07ec))
|
||||||
|
- **subscribe**: Add unsubscribe functionality with confirmation messages and localized strings ([b2a2f42](https://github.com/perfect-panel/ppanel-web/commit/b2a2f42))
|
||||||
|
- **subscribe**: Improve error handling in subscription forms and update component props ([d28a10b](https://github.com/perfect-panel/ppanel-web/commit/d28a10b))
|
||||||
|
- **subscribe**: Improve layout and styling in subscription components ([5766376](https://github.com/perfect-panel/ppanel-web/commit/5766376))
|
||||||
|
- **subscribe**: Move subscription configuration and application to subscription module ([f90d4d2](https://github.com/perfect-panel/ppanel-web/commit/f90d4d2))
|
||||||
|
- **subscribe**: Update SubscribeTable component to use API.SubscribeItem type and ensure proper type casting ([f26f1c2](https://github.com/perfect-panel/ppanel-web/commit/f26f1c2))
|
||||||
|
- **subscribe**: Update suffix from 'MB' to 'Mbps' and enhance speed limit display logic ([3547bb1](https://github.com/perfect-panel/ppanel-web/commit/3547bb1))
|
||||||
|
- **subscription**: Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
|
||||||
|
- **subscription**: Add delete user subscription functionality ([1fc3a10](https://github.com/perfect-panel/ppanel-web/commit/1fc3a10))
|
||||||
|
- **subscription**: Add localized messages for existing subscriptions and deletion restrictions ([e8a72d5](https://github.com/perfect-panel/ppanel-web/commit/e8a72d5))
|
||||||
|
- **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
|
||||||
|
- **table**: Add sorting support for Node and subscription columns ([27924b0](https://github.com/perfect-panel/ppanel-web/commit/27924b0))
|
||||||
|
- **table**: Supports drag and drop sorting ([2f56ef5](https://github.com/perfect-panel/ppanel-web/commit/2f56ef5))
|
||||||
|
- **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))
|
||||||
|
- **tos**: Display data ([6024454](https://github.com/perfect-panel/ppanel-web/commit/6024454))
|
||||||
|
- **tutorial**: Add common tutorial list ([872252c](https://github.com/perfect-panel/ppanel-web/commit/872252c))
|
||||||
|
- **tutorial**: Fetch the latest tutorial version from GitHub API for dynamic URL generation ([28f8c78](https://github.com/perfect-panel/ppanel-web/commit/28f8c78))
|
||||||
|
- **ui**: System Tool ([1836980](https://github.com/perfect-panel/ppanel-web/commit/1836980))
|
||||||
|
- **ui**: Update homepage data ([8425b13](https://github.com/perfect-panel/ppanel-web/commit/8425b13))
|
||||||
|
- **ui**: Update input components and enhance card minimum width for better layout ([8a02310](https://github.com/perfect-panel/ppanel-web/commit/8a02310))
|
||||||
|
- **user**: Add 'gift_amount' field and update related references in user services and components ([b13c77e](https://github.com/perfect-panel/ppanel-web/commit/b13c77e))
|
||||||
|
- **user**: Add telephone input with area code selection and update localization ([585b99c](https://github.com/perfect-panel/ppanel-web/commit/585b99c))
|
||||||
|
- **user**: Add user Detail ([3a3d223](https://github.com/perfect-panel/ppanel-web/commit/3a3d223))
|
||||||
|
- **user**: Add User Detail ([fdaf11b](https://github.com/perfect-panel/ppanel-web/commit/fdaf11b))
|
||||||
|
- **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
|
||||||
|
- Update Auth Control ([c59742a](https://github.com/perfect-panel/ppanel-web/commit/c59742a))
|
||||||
|
|
||||||
|
### 🎫 Chores
|
||||||
|
|
||||||
|
- **config**: Entry locale ([5737331](https://github.com/perfect-panel/ppanel-web/commit/5737331))
|
||||||
|
- **deps**: Update package dependencies across multiple projects for improved stability and performance ([b01a5bc](https://github.com/perfect-panel/ppanel-web/commit/b01a5bc))
|
||||||
|
- **init**: Project initialization ([829edfa](https://github.com/perfect-panel/ppanel-web/commit/829edfa))
|
||||||
|
- **merge**: Add advertising module and device settings ([0130e02](https://github.com/perfect-panel/ppanel-web/commit/0130e02))
|
||||||
|
- **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
|
||||||
|
- **release**: V1.0.0-beta.1 [skip ci] ([7284d1c](https://github.com/perfect-panel/ppanel-web/commit/7284d1c))
|
||||||
|
- **release**: V1.0.0-beta.10 [skip ci] ([5cf573a](https://github.com/perfect-panel/ppanel-web/commit/5cf573a))
|
||||||
|
- **release**: V1.0.0-beta.11 [skip ci] ([1f29506](https://github.com/perfect-panel/ppanel-web/commit/1f29506))
|
||||||
|
- **release**: V1.0.0-beta.12 [skip ci] ([4418c47](https://github.com/perfect-panel/ppanel-web/commit/4418c47))
|
||||||
|
- **release**: V1.0.0-beta.13 [skip ci] ([23c974a](https://github.com/perfect-panel/ppanel-web/commit/23c974a))
|
||||||
|
- **release**: V1.0.0-beta.14 [skip ci] ([0fb0d8b](https://github.com/perfect-panel/ppanel-web/commit/0fb0d8b))
|
||||||
|
- **release**: V1.0.0-beta.15 [skip ci] ([b2e8fad](https://github.com/perfect-panel/ppanel-web/commit/b2e8fad))
|
||||||
|
- **release**: V1.0.0-beta.16 [skip ci] ([c3eff0a](https://github.com/perfect-panel/ppanel-web/commit/c3eff0a))
|
||||||
|
- **release**: V1.0.0-beta.17 [skip ci] ([5b64389](https://github.com/perfect-panel/ppanel-web/commit/5b64389))
|
||||||
|
- **release**: V1.0.0-beta.18 [skip ci] ([4a00233](https://github.com/perfect-panel/ppanel-web/commit/4a00233))
|
||||||
|
- **release**: V1.0.0-beta.19 [skip ci] ([0f15fb8](https://github.com/perfect-panel/ppanel-web/commit/0f15fb8))
|
||||||
|
- **release**: V1.0.0-beta.2 [skip ci] ([087c36c](https://github.com/perfect-panel/ppanel-web/commit/087c36c))
|
||||||
|
- **release**: V1.0.0-beta.20 [skip ci] ([bbd44f0](https://github.com/perfect-panel/ppanel-web/commit/bbd44f0))
|
||||||
|
- **release**: V1.0.0-beta.21 [skip ci] ([ca642c2](https://github.com/perfect-panel/ppanel-web/commit/ca642c2))
|
||||||
|
- **release**: V1.0.0-beta.22 [skip ci] ([c0fb34f](https://github.com/perfect-panel/ppanel-web/commit/c0fb34f))
|
||||||
|
- **release**: V1.0.0-beta.23 [skip ci] ([cf1d66d](https://github.com/perfect-panel/ppanel-web/commit/cf1d66d))
|
||||||
|
- **release**: V1.0.0-beta.24 [skip ci] ([01a3aa0](https://github.com/perfect-panel/ppanel-web/commit/01a3aa0))
|
||||||
|
- **release**: V1.0.0-beta.25 [skip ci] ([047a698](https://github.com/perfect-panel/ppanel-web/commit/047a698))
|
||||||
|
- **release**: V1.0.0-beta.26 [skip ci] ([79edea7](https://github.com/perfect-panel/ppanel-web/commit/79edea7))
|
||||||
|
- **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
|
||||||
|
- **release**: V1.0.0-beta.27 [skip ci] ([85fdc36](https://github.com/perfect-panel/ppanel-web/commit/85fdc36))
|
||||||
|
- **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
|
||||||
|
- **release**: V1.0.0-beta.28 [skip ci] ([d10ecc9](https://github.com/perfect-panel/ppanel-web/commit/d10ecc9))
|
||||||
|
- **release**: V1.0.0-beta.29 [skip ci] ([29bc3c7](https://github.com/perfect-panel/ppanel-web/commit/29bc3c7))
|
||||||
|
- **release**: V1.0.0-beta.3 [skip ci] ([cd49427](https://github.com/perfect-panel/ppanel-web/commit/cd49427))
|
||||||
|
- **release**: V1.0.0-beta.30 [skip ci] ([db0d9e0](https://github.com/perfect-panel/ppanel-web/commit/db0d9e0))
|
||||||
|
- **release**: V1.0.0-beta.31 [skip ci] ([aa1d426](https://github.com/perfect-panel/ppanel-web/commit/aa1d426))
|
||||||
|
- **release**: V1.0.0-beta.32 [skip ci] ([fa56eb8](https://github.com/perfect-panel/ppanel-web/commit/fa56eb8))
|
||||||
|
- **release**: V1.0.0-beta.33 [skip ci] ([383638f](https://github.com/perfect-panel/ppanel-web/commit/383638f))
|
||||||
|
- **release**: V1.0.0-beta.34 [skip ci] ([7023875](https://github.com/perfect-panel/ppanel-web/commit/7023875))
|
||||||
|
- **release**: V1.0.0-beta.4 [skip ci] ([10d322f](https://github.com/perfect-panel/ppanel-web/commit/10d322f))
|
||||||
|
- **release**: V1.0.0-beta.5 [skip ci] ([f275f01](https://github.com/perfect-panel/ppanel-web/commit/f275f01))
|
||||||
|
- **release**: V1.0.0-beta.6 [skip ci] ([dd23279](https://github.com/perfect-panel/ppanel-web/commit/dd23279))
|
||||||
|
- **release**: V1.0.0-beta.7 [skip ci] ([f60d40c](https://github.com/perfect-panel/ppanel-web/commit/f60d40c))
|
||||||
|
- **release**: V1.0.0-beta.8 [skip ci], closes [#9](https://github.com/perfect-panel/ppanel-web/issues/9) ([a593eac](https://github.com/perfect-panel/ppanel-web/commit/a593eac))
|
||||||
|
- **release**: V1.0.0-beta.9 [skip ci] ([855d1b0](https://github.com/perfect-panel/ppanel-web/commit/855d1b0))
|
||||||
|
- **ui**: Update package dependencies for improved stability and performance ([25da429](https://github.com/perfect-panel/ppanel-web/commit/25da429))
|
||||||
|
- Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))
|
||||||
|
- Update changelog, enhance prepare script, and add openapi command ([a93db4e](https://github.com/perfect-panel/ppanel-web/commit/a93db4e))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
|
||||||
|
- **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
|
||||||
|
- **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
|
||||||
|
- **api**: Fix type error in API request and add return URL parameter ([ee286dd](https://github.com/perfect-panel/ppanel-web/commit/ee286dd))
|
||||||
|
- **api**: PreCreateOrder ([ca747f5](https://github.com/perfect-panel/ppanel-web/commit/ca747f5))
|
||||||
|
- **api**: Purge ([98c1c30](https://github.com/perfect-panel/ppanel-web/commit/98c1c30))
|
||||||
|
- **api**: Remove redundant requestType parameter in appleLoginCallback ([0aa5d5b](https://github.com/perfect-panel/ppanel-web/commit/0aa5d5b))
|
||||||
|
- **api**: Rename app-related functions and types to application for consistency ([9d8b814](https://github.com/perfect-panel/ppanel-web/commit/9d8b814))
|
||||||
|
- **api**: Replace 'deduction' with 'gift_amount' and add 'commission' field in type definitions ([77edf1d](https://github.com/perfect-panel/ppanel-web/commit/77edf1d))
|
||||||
|
- **api**: Server and order ([255bd82](https://github.com/perfect-panel/ppanel-web/commit/255bd82))
|
||||||
|
- **api**: Statistics ([7962162](https://github.com/perfect-panel/ppanel-web/commit/7962162))
|
||||||
|
- **api**: Subscribe token ([1932ba7](https://github.com/perfect-panel/ppanel-web/commit/1932ba7))
|
||||||
|
- **api**: Update API type definitions to replace 'deduction' with 'gift_amount' and make 'commission' field optional ([c2af060](https://github.com/perfect-panel/ppanel-web/commit/c2af060))
|
||||||
|
- **api**: Update Model ([39aaa73](https://github.com/perfect-panel/ppanel-web/commit/39aaa73))
|
||||||
|
- **api**: Update subscription_protocol to subscribe_type for consistency across services ([b6da51b](https://github.com/perfect-panel/ppanel-web/commit/b6da51b))
|
||||||
|
- **auth-control**: Fix citation error for platform values ([c940f3c](https://github.com/perfect-panel/ppanel-web/commit/c940f3c))
|
||||||
|
- **auth-control**: Fix citation error for platform values ([28813d2](https://github.com/perfect-panel/ppanel-web/commit/28813d2))
|
||||||
|
- **auth-control**: Rename phone_variable to phone_number in mobile verification configuration ([e5455aa](https://github.com/perfect-panel/ppanel-web/commit/e5455aa))
|
||||||
|
- **auth**: Add error handling to form submission and reset Turnstile on failure ([715d011](https://github.com/perfect-panel/ppanel-web/commit/715d011))
|
||||||
|
- **auth**: Change Textarea value to defaultValue for client_secret in Apple auth page ([69fc670](https://github.com/perfect-panel/ppanel-web/commit/69fc670))
|
||||||
|
- **auth**: Refactor forms to use Turnstile ref for reset functionality ([320a7dc](https://github.com/perfect-panel/ppanel-web/commit/320a7dc))
|
||||||
|
- **auth**: Refactor reset password form to simplify code input and update placeholder text ([23833b4](https://github.com/perfect-panel/ppanel-web/commit/23833b4))
|
||||||
|
- **auth**: Refactor user authentication forms to remove global store dependency and improve type handling ([12026b0](https://github.com/perfect-panel/ppanel-web/commit/12026b0))
|
||||||
|
- **auth**: Remove unused telephone code login function and update typings for telephone login requests ([7239685](https://github.com/perfect-panel/ppanel-web/commit/7239685))
|
||||||
|
- **auth**: Require minimum length for invite string when forced invite is enabled ([a604f28](https://github.com/perfect-panel/ppanel-web/commit/a604f28))
|
||||||
|
- **auth**: Simplify email verification code input rendering ([6f7bc37](https://github.com/perfect-panel/ppanel-web/commit/6f7bc37))
|
||||||
|
- **auth**: Update authentication configuration and localization strings ([47f2c58](https://github.com/perfect-panel/ppanel-web/commit/47f2c58))
|
||||||
|
- **auth**: Update email verification logic to use domain suffix check ([62662bb](https://github.com/perfect-panel/ppanel-web/commit/62662bb))
|
||||||
|
- **auth**: Update user authentication flow to include email and phone code verification ([5d078fd](https://github.com/perfect-panel/ppanel-web/commit/5d078fd))
|
||||||
|
- **auth**: Update UserCheckForm to use setInitialValues and modify onSubmit type ([c984c0d](https://github.com/perfect-panel/ppanel-web/commit/c984c0d))
|
||||||
|
- **billing**: ExpiryDate ([e85e545](https://github.com/perfect-panel/ppanel-web/commit/e85e545))
|
||||||
|
- **billing**: I18n and styles ([81e0f21](https://github.com/perfect-panel/ppanel-web/commit/81e0f21))
|
||||||
|
- **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
|
||||||
|
- **config**: AlipayF2F ([6c07107](https://github.com/perfect-panel/ppanel-web/commit/6c07107))
|
||||||
|
- **config**: Bugs ([f57e40c](https://github.com/perfect-panel/ppanel-web/commit/f57e40c))
|
||||||
|
- **config**: Checkout Order ([a31e763](https://github.com/perfect-panel/ppanel-web/commit/a31e763))
|
||||||
|
- **config**: FormatBytes ([bbc2da0](https://github.com/perfect-panel/ppanel-web/commit/bbc2da0))
|
||||||
|
- **config**: NoStore ([2cc18cf](https://github.com/perfect-panel/ppanel-web/commit/2cc18cf))
|
||||||
|
- **config**: Runtime env ([a1e4999](https://github.com/perfect-panel/ppanel-web/commit/a1e4999))
|
||||||
|
- **config**: Status Percentag ([8f322fb](https://github.com/perfect-panel/ppanel-web/commit/8f322fb))
|
||||||
|
- **config**: SubLink ([1c61966](https://github.com/perfect-panel/ppanel-web/commit/1c61966))
|
||||||
|
- **config**: Subscribe Link ([11ea821](https://github.com/perfect-panel/ppanel-web/commit/11ea821))
|
||||||
|
- **content**: Parse subscription description and display features with icons ([3c5542a](https://github.com/perfect-panel/ppanel-web/commit/3c5542a))
|
||||||
|
- **controller**: Order status ([8c6a097](https://github.com/perfect-panel/ppanel-web/commit/8c6a097))
|
||||||
|
- **coupon**: Rename 'server' field to 'subscribe' in coupon form and update coupon update request type ([f8b6d82](https://github.com/perfect-panel/ppanel-web/commit/f8b6d82))
|
||||||
|
- **dashboard**: Improve URL encoding for subscription links and enhance success message handling ([4983c33](https://github.com/perfect-panel/ppanel-web/commit/4983c33))
|
||||||
|
- **dashboard**: Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
|
||||||
|
- **dashboard**: Correct progress value calculations and update groupId accessor ([36c7667](https://github.com/perfect-panel/ppanel-web/commit/36c7667))
|
||||||
|
- **dashboard**: Display subscription creation date in user dashboard ([d0e6df0](https://github.com/perfect-panel/ppanel-web/commit/d0e6df0))
|
||||||
|
- **dashboard**: Format Bytes ([d8b0bd9](https://github.com/perfect-panel/ppanel-web/commit/d8b0bd9))
|
||||||
|
- **dashboard**: Update icon imports for platform consistency and adjust icon size ([3e8912e](https://github.com/perfect-panel/ppanel-web/commit/3e8912e))
|
||||||
|
- **dashboard**: Update platform detection logic and improve layout responsiveness ([b0aa364](https://github.com/perfect-panel/ppanel-web/commit/b0aa364))
|
||||||
|
- **deps**: Remove outdated @iconify/react dependency and add iconify-json packages ([d6fbc38](https://github.com/perfect-panel/ppanel-web/commit/d6fbc38))
|
||||||
|
- **deps**: Typescript config ([34e24b8](https://github.com/perfect-panel/ppanel-web/commit/34e24b8))
|
||||||
|
- **deps**: Update clipboard ([5572710](https://github.com/perfect-panel/ppanel-web/commit/5572710))
|
||||||
|
- **editor**: Change value ([4fdfeb2](https://github.com/perfect-panel/ppanel-web/commit/4fdfeb2))
|
||||||
|
- **email**: Update platform configuration handling to use current ref for consistency ([c90175b](https://github.com/perfect-panel/ppanel-web/commit/c90175b))
|
||||||
|
- **footer**: Email address ([a451f44](https://github.com/perfect-panel/ppanel-web/commit/a451f44))
|
||||||
|
- **forms**: Add step attribute to number inputs for better value control ([b8f4f1e](https://github.com/perfect-panel/ppanel-web/commit/b8f4f1e))
|
||||||
|
- **icon**: Comment out unused icon collection imports ([f17bf8d](https://github.com/perfect-panel/ppanel-web/commit/f17bf8d))
|
||||||
|
- **layout**: Remove unnecessary cookie initialization in Logout function ([3065c3a](https://github.com/perfect-panel/ppanel-web/commit/3065c3a))
|
||||||
|
- **locale**: Default value ([937408f](https://github.com/perfect-panel/ppanel-web/commit/937408f))
|
||||||
|
- **locale**: Document ([6f0fa20](https://github.com/perfect-panel/ppanel-web/commit/6f0fa20))
|
||||||
|
- **locale**: Empty ([3832d20](https://github.com/perfect-panel/ppanel-web/commit/3832d20))
|
||||||
|
- **locale**: Input Placeholder Webhook Domain ([bca0935](https://github.com/perfect-panel/ppanel-web/commit/bca0935))
|
||||||
|
- **locale**: Language Select ([0befdb0](https://github.com/perfect-panel/ppanel-web/commit/0befdb0))
|
||||||
|
- **locales**: Add error message for incorrect user information ([52c1d1f](https://github.com/perfect-panel/ppanel-web/commit/52c1d1f))
|
||||||
|
- **locales**: Add error message for incorrect user information ([3d92902](https://github.com/perfect-panel/ppanel-web/commit/3d92902))
|
||||||
|
- **locales**: Add logout message to authentication localization files ([1d0d911](https://github.com/perfect-panel/ppanel-web/commit/1d0d911))
|
||||||
|
- **locales**: Fixed description in multilingual files, updated text related to email registration functionality ([c356bc2](https://github.com/perfect-panel/ppanel-web/commit/c356bc2))
|
||||||
|
- **locales**: Order recharge related fields ([35210fe](https://github.com/perfect-panel/ppanel-web/commit/35210fe))
|
||||||
|
- **locales**: Removed language file import to clean up unnecessary language support ([68f6ab2](https://github.com/perfect-panel/ppanel-web/commit/68f6ab2))
|
||||||
|
- **locales**: Removed multilingual files to clean up unnecessary language support ([5b151cd](https://github.com/perfect-panel/ppanel-web/commit/5b151cd))
|
||||||
|
- **locale**: Subscription Path Description ([4c67387](https://github.com/perfect-panel/ppanel-web/commit/4c67387))
|
||||||
|
- **locales**: Update custom HTML description for clarity across multiple languages ([557c5cd](https://github.com/perfect-panel/ppanel-web/commit/557c5cd))
|
||||||
|
- **locales**: Update custom HTML description in language file, ([87381da](https://github.com/perfect-panel/ppanel-web/commit/87381da))
|
||||||
|
- **locales**: Update expiration time description from minutes to seconds in multiple languages ([5bac933](https://github.com/perfect-panel/ppanel-web/commit/5bac933))
|
||||||
|
- **locales**: Update Hong Kong ([6d0d069](https://github.com/perfect-panel/ppanel-web/commit/6d0d069))
|
||||||
|
- **locales**: Update invite code text to indicate it's optional ([6a34bfb](https://github.com/perfect-panel/ppanel-web/commit/6a34bfb))
|
||||||
|
- **logs**: Update log display to render key-value pairs and remove badge ([5ea6489](https://github.com/perfect-panel/ppanel-web/commit/5ea6489))
|
||||||
|
- **metadata**: Global metadata ([15d5ecf](https://github.com/perfect-panel/ppanel-web/commit/15d5ecf))
|
||||||
|
- **nav**: Comment out unused social login options to simplify navigation configuration ([cefcb31](https://github.com/perfect-panel/ppanel-web/commit/cefcb31))
|
||||||
|
- **node-config**: Add null checks for time slots and ensure proper handling of undefined values ([1cdb7e7](https://github.com/perfect-panel/ppanel-web/commit/1cdb7e7))
|
||||||
|
- **node**: Add country and city fields to the form schema and localization files ([8775fb6](https://github.com/perfect-panel/ppanel-web/commit/8775fb6))
|
||||||
|
- **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
|
||||||
|
- **node**: Locale and form ([38be4d5](https://github.com/perfect-panel/ppanel-web/commit/38be4d5))
|
||||||
|
- **node**: Port config ([a20834a](https://github.com/perfect-panel/ppanel-web/commit/a20834a))
|
||||||
|
- **node**: Reality config ([fadd17f](https://github.com/perfect-panel/ppanel-web/commit/fadd17f))
|
||||||
|
- **node**: Service Name config ([d0be685](https://github.com/perfect-panel/ppanel-web/commit/d0be685))
|
||||||
|
- **node**: TLS config ([57fae12](https://github.com/perfect-panel/ppanel-web/commit/57fae12))
|
||||||
|
- **node**: Trojan protocol config ([7e1eb90](https://github.com/perfect-panel/ppanel-web/commit/7e1eb90))
|
||||||
|
- **notify**: Ensure user info is updated after notification settings submission ([9bc3a94](https://github.com/perfect-panel/ppanel-web/commit/9bc3a94))
|
||||||
|
- **notify**: Set default values for notification settings to false ([3652819](https://github.com/perfect-panel/ppanel-web/commit/3652819))
|
||||||
|
- **oauth**: Refactor OAuth configuration types and update related API methods ([6227ba9](https://github.com/perfect-panel/ppanel-web/commit/6227ba9))
|
||||||
|
- **oauth**: Remove redundant checks when updating configuration to simplify logic ([9140b8a](https://github.com/perfect-panel/ppanel-web/commit/9140b8a))
|
||||||
|
- **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
|
||||||
|
- **payment**: Config and types ([b0c87fb](https://github.com/perfect-panel/ppanel-web/commit/b0c87fb))
|
||||||
|
- **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
|
||||||
|
- **payment**: Qrcode ([a9a535b](https://github.com/perfect-panel/ppanel-web/commit/a9a535b))
|
||||||
|
- **payment**: Refactor payment form placeholder and update localization files ([4a4d364](https://github.com/perfect-panel/ppanel-web/commit/4a4d364))
|
||||||
|
- **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
|
||||||
|
- **payment**: Replace window.open with window.location.href for checkout links ([1d8c765](https://github.com/perfect-panel/ppanel-web/commit/1d8c765))
|
||||||
|
- **payment**: Update checkout type from 'link' to 'url' for consistency ([136a1ab](https://github.com/perfect-panel/ppanel-web/commit/136a1ab))
|
||||||
|
- **payment**: Update payment information ([70d6a38](https://github.com/perfect-panel/ppanel-web/commit/70d6a38))
|
||||||
|
- **payment**: Update payment method update logic to include row data ([6752420](https://github.com/perfect-panel/ppanel-web/commit/6752420))
|
||||||
|
- **phone**: Update SMS expiration time field to use 'sms_expire_time' with default value of 300 ([18b07c7](https://github.com/perfect-panel/ppanel-web/commit/18b07c7))
|
||||||
|
- **profile**: Restore filter to ensure only valid OAuth accounts are shown ([315c8f9](https://github.com/perfect-panel/ppanel-web/commit/315c8f9))
|
||||||
|
- **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
|
||||||
|
- **redirect**: Simplify redirect URL logic by removing unnecessary condition for sessionStorage ([c53ac61](https://github.com/perfect-panel/ppanel-web/commit/c53ac61))
|
||||||
|
- **redirect**: Update redirect URL logic to ensure proper handling of OAuth and auth paths ([7954762](https://github.com/perfect-panel/ppanel-web/commit/7954762))
|
||||||
|
- **register**: Adjust user email verification logic to handle domain suffix checks correctly ([686aa2d](https://github.com/perfect-panel/ppanel-web/commit/686aa2d))
|
||||||
|
- **request**: Add error code 40005 to trigger logout ([71bf002](https://github.com/perfect-panel/ppanel-web/commit/71bf002))
|
||||||
|
- **request**: Locale ([37d408f](https://github.com/perfect-panel/ppanel-web/commit/37d408f))
|
||||||
|
- **rule-form**: Remove redundant rule set display ([6e0c9b6](https://github.com/perfect-panel/ppanel-web/commit/6e0c9b6))
|
||||||
|
- **rules**: Remove unused MATCH rule ([674a01c](https://github.com/perfect-panel/ppanel-web/commit/674a01c))
|
||||||
|
- **site**: Add image upload functionality for site logo configuration ([4ea6e4a](https://github.com/perfect-panel/ppanel-web/commit/4ea6e4a))
|
||||||
|
- **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
|
||||||
|
- **sort**: Refactor sorting logic in NodeTable and SubscribeTable components for improved clarity and performance ([331bbea](https://github.com/perfect-panel/ppanel-web/commit/331bbea))
|
||||||
|
- **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
|
||||||
|
- **subscribe**: Discount ([35a9f69](https://github.com/perfect-panel/ppanel-web/commit/35a9f69))
|
||||||
|
- **subscribe**: Extract Domain ([40d61a9](https://github.com/perfect-panel/ppanel-web/commit/40d61a9))
|
||||||
|
- **subscribe**: Handle optional values in price and discount calculations ([5939763](https://github.com/perfect-panel/ppanel-web/commit/5939763))
|
||||||
|
- **subscribe**: Jumps and internationalization ([13fdec3](https://github.com/perfect-panel/ppanel-web/commit/13fdec3))
|
||||||
|
- **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
|
||||||
|
- **subscribe**: Server group id ([90e6764](https://github.com/perfect-panel/ppanel-web/commit/90e6764))
|
||||||
|
- **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
|
||||||
|
- **subscribe**: Update forms to include refetch functionality and improve toast messages ([fc55e95](https://github.com/perfect-panel/ppanel-web/commit/fc55e95))
|
||||||
|
- **subscribe**: Update payment return URL ([2b80496](https://github.com/perfect-panel/ppanel-web/commit/2b80496))
|
||||||
|
- **subscribe**: Update subscription domain placeholder to include examples; improve site name retrieval in global store ([c65a44c](https://github.com/perfect-panel/ppanel-web/commit/c65a44c))
|
||||||
|
- **subscribe**: Update value validation to check for number type in subscribe form ([6de29d5](https://github.com/perfect-panel/ppanel-web/commit/6de29d5))
|
||||||
|
- **subscription**: Add reset functionality for user subscription token ([39e89bf](https://github.com/perfect-panel/ppanel-web/commit/39e89bf))
|
||||||
|
- **table**: Update privacy policy tab translation key and remove unnecessary requestType from OAuth callback ([14b3af5](https://github.com/perfect-panel/ppanel-web/commit/14b3af5))
|
||||||
|
- **third-party-accounts**: Remove mobile display logic from third-party accounts component ([b4946f7](https://github.com/perfect-panel/ppanel-web/commit/b4946f7))
|
||||||
|
- **third-party-accounts**: Update redirect property name in binding response handling ([012e83a](https://github.com/perfect-panel/ppanel-web/commit/012e83a))
|
||||||
|
- **turnstile**: Turnstile_site_key ([0327b73](https://github.com/perfect-panel/ppanel-web/commit/0327b73))
|
||||||
|
- **type**: Fix ts type check error ([3cb0629](https://github.com/perfect-panel/ppanel-web/commit/3cb0629))
|
||||||
|
- **types**: Add 'gift_amount' field to API type definitions ([8f8a12a](https://github.com/perfect-panel/ppanel-web/commit/8f8a12a))
|
||||||
|
- **types**: Checking ([2992824](https://github.com/perfect-panel/ppanel-web/commit/2992824))
|
||||||
|
- **types**: Order type ([c7e50a9](https://github.com/perfect-panel/ppanel-web/commit/c7e50a9))
|
||||||
|
- **ui**: Bugs ([b023d0f](https://github.com/perfect-panel/ppanel-web/commit/b023d0f))
|
||||||
|
- **ui**: Components ([a7927d7](https://github.com/perfect-panel/ppanel-web/commit/a7927d7))
|
||||||
|
- **ui**: Fix json formatting ([e1ddd94](https://github.com/perfect-panel/ppanel-web/commit/e1ddd94))
|
||||||
|
- **ui**: Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab ([fc0da76](https://github.com/perfect-panel/ppanel-web/commit/fc0da76))
|
||||||
|
- **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
|
||||||
|
- **user-nav**: Update user avatar and label to display telephone if email is not available ([7b6bb7b](https://github.com/perfect-panel/ppanel-web/commit/7b6bb7b))
|
||||||
|
- **user**: Add the 'gift_amount' field to the user service's type definition ([6301409](https://github.com/perfect-panel/ppanel-web/commit/6301409))
|
||||||
|
- **user**: Refactor user form validation and reset password fields ([6733fc2](https://github.com/perfect-panel/ppanel-web/commit/6733fc2))
|
||||||
|
- **user**: Update locales ([4e7d249](https://github.com/perfect-panel/ppanel-web/commit/4e7d249))
|
||||||
|
- **user**: Update notification and verify code settings ([574b043](https://github.com/perfect-panel/ppanel-web/commit/574b043))
|
||||||
|
- **user**: Update user identifier field and localizations ([1b6befa](https://github.com/perfect-panel/ppanel-web/commit/1b6befa))
|
||||||
|
- **user**: Update user subscribe display ([3bb714d](https://github.com/perfect-panel/ppanel-web/commit/3bb714d))
|
||||||
|
- **utils**: Login redirect url ([cbe5f0d](https://github.com/perfect-panel/ppanel-web/commit/cbe5f0d))
|
||||||
|
- More bugs ([2d88a3a](https://github.com/perfect-panel/ppanel-web/commit/2d88a3a))
|
||||||
|
|
||||||
|
### 👷 Build System
|
||||||
|
|
||||||
|
- **config**: Update pm2 config ([d95b425](https://github.com/perfect-panel/ppanel-web/commit/d95b425))
|
||||||
|
|
||||||
|
### 💄 Styles
|
||||||
|
|
||||||
|
- **dashboard**: Adjust grid layout and update image dimensions in application display ([f3204b7](https://github.com/perfect-panel/ppanel-web/commit/f3204b7))
|
||||||
|
- **dashboard**: Enhance card components with full height and improved empty state handling ([7e1d551](https://github.com/perfect-panel/ppanel-web/commit/7e1d551))
|
||||||
|
- **document**: Update ([0a8109b](https://github.com/perfect-panel/ppanel-web/commit/0a8109b))
|
||||||
|
- **globals**: Refactor delete confirmation button and update badge styles in node and subscribe tables ([30ae781](https://github.com/perfect-panel/ppanel-web/commit/30ae781))
|
||||||
|
- **locales**: Remove unused subscription labels from multiple locale files ([fb0c510](https://github.com/perfect-panel/ppanel-web/commit/fb0c510))
|
||||||
|
- **locales**: Update server.json to reorganize relay mode options and improve labels ([701cdee](https://github.com/perfect-panel/ppanel-web/commit/701cdee))
|
||||||
|
- **node**: Form ([d5f5add](https://github.com/perfect-panel/ppanel-web/commit/d5f5add))
|
||||||
|
- **node**: Improve layout and spacing in NodeStatusCell component ([136287d](https://github.com/perfect-panel/ppanel-web/commit/136287d))
|
||||||
|
- **node**: Protocol Tab ([2bcb925](https://github.com/perfect-panel/ppanel-web/commit/2bcb925))
|
||||||
|
- **time-slot**: Add chart display ([c44ad47](https://github.com/perfect-panel/ppanel-web/commit/c44ad47))
|
||||||
|
- **ui**: Update mobile style ([eda18bc](https://github.com/perfect-panel/ppanel-web/commit/eda18bc))
|
||||||
|
- Update node secret UI and add telephone code field to authentication form ([770932e](https://github.com/perfect-panel/ppanel-web/commit/770932e))
|
||||||
|
|
||||||
|
### 📝 Documentation
|
||||||
|
|
||||||
|
- **readme**: License name ([74cb16b](https://github.com/perfect-panel/ppanel-web/commit/74cb16b))
|
||||||
|
|
||||||
|
### 🔧 Continuous Integration
|
||||||
|
|
||||||
|
- **github**: Release docker ([5af60aa](https://github.com/perfect-panel/ppanel-web/commit/5af60aa))
|
||||||
|
- **step**: Update step name ([9eca618](https://github.com/perfect-panel/ppanel-web/commit/9eca618))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
# [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02)
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
|
||||||
|
- **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
|
||||||
|
- **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
|
||||||
|
- **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
|
||||||
|
- **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
|
||||||
|
- **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
|
||||||
|
- **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
|
||||||
|
- **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
# [1.0.0-beta.33](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.32...v1.0.0-beta.33) (2025-03-18)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **subscribe**: Handle optional values in price and discount calculations ([5939763](https://github.com/perfect-panel/ppanel-web/commit/5939763))
|
||||||
|
|
||||||
|
# [1.0.0-beta.32](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.31...v1.0.0-beta.32) (2025-03-17)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **forms**: Add step attribute to number inputs for better value control ([b8f4f1e](https://github.com/perfect-panel/ppanel-web/commit/b8f4f1e))
|
||||||
|
- **locales**: Update invite code text to indicate it's optional ([6a34bfb](https://github.com/perfect-panel/ppanel-web/commit/6a34bfb))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
# [1.0.0-beta.31](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.30...v1.0.0-beta.31) (2025-03-15)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **site**: Se ref to store site configuration for updates ([0c8f091](https://github.com/perfect-panel/ppanel-web/commit/0c8f091))
|
||||||
|
|
||||||
|
# [1.0.0-beta.30](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.29...v1.0.0-beta.30) (2025-03-15)
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([48a1b97](https://github.com/perfect-panel/ppanel-web/commit/48a1b97))
|
||||||
|
- **email**: Add traffic exhaustion template ([bb3bd7b](https://github.com/perfect-panel/ppanel-web/commit/bb3bd7b))
|
||||||
|
- **formatting**: Update differenceInDays function to return whole days or two decimal places ([bf58f25](https://github.com/perfect-panel/ppanel-web/commit/bf58f25))
|
||||||
|
- **global**: Add custom data ([6dbebd1](https://github.com/perfect-panel/ppanel-web/commit/6dbebd1))
|
||||||
|
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([ce31972](https://github.com/perfect-panel/ppanel-web/commit/ce31972))
|
||||||
|
- **loading**: Replace loading animation with a simpler spinner and loading text ([f72df3a](https://github.com/perfect-panel/ppanel-web/commit/f72df3a))
|
||||||
|
- **node-form**: Update number input fields to enforce step, min, and max values ([3f7b6d1](https://github.com/perfect-panel/ppanel-web/commit/3f7b6d1))
|
||||||
|
- **payment**: Add isEdit prop to PaymentForm and disable fields when editing ([85f55de](https://github.com/perfect-panel/ppanel-web/commit/85f55de))
|
||||||
|
- **timeline**: Simplify timeline component layout and remove commented-out code ([fbad3b0](https://github.com/perfect-panel/ppanel-web/commit/fbad3b0))
|
||||||
|
|
||||||
|
### 🎫 Chores
|
||||||
|
|
||||||
|
- **release**: V1.0.0-beta.27 [skip ci] ([092477b](https://github.com/perfect-panel/ppanel-web/commit/092477b))
|
||||||
|
- **release**: V1.0.0-beta.28 [skip ci] ([786ba0e](https://github.com/perfect-panel/ppanel-web/commit/786ba0e))
|
||||||
|
- Merge branch 'beta' into develop ([f219c52](https://github.com/perfect-panel/ppanel-web/commit/f219c52))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **dashboard**: Update date display to use start_time if available ([e551232](https://github.com/perfect-panel/ppanel-web/commit/e551232))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
# [1.0.0-beta.29](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.28...v1.0.0-beta.29) (2025-03-14)
|
# [1.0.0-beta.29](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.28...v1.0.0-beta.29) (2025-03-14)
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
|
||||||
|
- **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
|
||||||
|
|
||||||
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
# [1.0.0-beta.28](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.27...v1.0.0-beta.28) (2025-03-13)
|
||||||
|
|
||||||
### ✨ Features
|
### ✨ Features
|
||||||
|
|
||||||
* **api**: Add CheckoutOrder request and response types, and update user purchase request parameters ([dddc21c](https://github.com/perfect-panel/ppanel-web/commit/dddc21c))
|
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
|
||||||
* **loading**: Replace loading animation with a simpler spinner and loading text ([b8316bb](https://github.com/perfect-panel/ppanel-web/commit/b8316bb))
|
|
||||||
|
# [1.0.0-beta.27](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.26...v1.0.0-beta.27) (2025-03-13)
|
||||||
|
|
||||||
|
### ♻ Code Refactoring
|
||||||
|
|
||||||
|
- **payment**: Reconstruct the payment page ([7109472](https://github.com/perfect-panel/ppanel-web/commit/7109472))
|
||||||
|
- Enhance user navigation dropdown ui and styling ([d2732e6](https://github.com/perfect-panel/ppanel-web/commit/d2732e6))
|
||||||
|
|
||||||
|
### ✨ Features
|
||||||
|
|
||||||
|
- **cdn**: Add CDN URL configuration and update related references ([0c90733](https://github.com/perfect-panel/ppanel-web/commit/0c90733))
|
||||||
|
- **payment**: Add bank card payment ([7fa3a57](https://github.com/perfect-panel/ppanel-web/commit/7fa3a57))
|
||||||
|
- **subscription**: Improve layout and organization of subscription detail tabs ([e4630f8](https://github.com/perfect-panel/ppanel-web/commit/e4630f8))
|
||||||
|
- **subscription**: Refactor subscription handling and update imports for better organization ([2215c7f](https://github.com/perfect-panel/ppanel-web/commit/2215c7f))
|
||||||
|
|
||||||
|
### 🎫 Chores
|
||||||
|
|
||||||
|
- **merge**: Bump version to 1.0.0-beta.26 and update changelog ([3222016](https://github.com/perfect-panel/ppanel-web/commit/3222016))
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- **affiliate**: Update user identifier ([35f92c9](https://github.com/perfect-panel/ppanel-web/commit/35f92c9))
|
||||||
|
- **changelog**: Update change log style ([cfa3fc0](https://github.com/perfect-panel/ppanel-web/commit/cfa3fc0))
|
||||||
|
- **payment**: Add notification URL field to payment management interface ([5c710e1](https://github.com/perfect-panel/ppanel-web/commit/5c710e1))
|
||||||
|
- **payment**: Fix payment related type definitions and update payment method references ([c3138a8](https://github.com/perfect-panel/ppanel-web/commit/c3138a8))
|
||||||
|
- **payment**: Refactor purchaseCheckout usage and remove redundant code ([a5e2079](https://github.com/perfect-panel/ppanel-web/commit/a5e2079))
|
||||||
|
- **payment**: Update checkout type from 'link' to 'url' for consistency ([136a1ab](https://github.com/perfect-panel/ppanel-web/commit/136a1ab))
|
||||||
|
- **payment**: Update payment information ([70d6a38](https://github.com/perfect-panel/ppanel-web/commit/70d6a38))
|
||||||
|
- **payment**: Update payment method update logic to include row data ([6752420](https://github.com/perfect-panel/ppanel-web/commit/6752420))
|
||||||
|
- **purchasing**: Update payment type to lowercase and add optional chaining for discounts ([c06ea49](https://github.com/perfect-panel/ppanel-web/commit/c06ea49))
|
||||||
|
- **ui**: Improve dashboard layout and enhance button functionality; open checkout URLs in a new tab ([fc0da76](https://github.com/perfect-panel/ppanel-web/commit/fc0da76))
|
||||||
|
- **ui**: Multiple display bugs ([f5d8fd3](https://github.com/perfect-panel/ppanel-web/commit/f5d8fd3))
|
||||||
|
|
||||||
<a name="readme-top"></a>
|
<a name="readme-top"></a>
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,22 @@ import LoginLottie from '@workspace/ui/lotties/login.json';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Image from 'next/legacy/image';
|
import Image from 'next/legacy/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import EmailAuthForm from './email/auth-form';
|
import EmailAuthForm from './email/auth-form';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('auth');
|
const t = useTranslations('auth');
|
||||||
const { common } = useGlobalStore();
|
const { common, user } = useGlobalStore();
|
||||||
const { site } = common;
|
const { site } = common;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
router.replace('/dashboard');
|
||||||
|
}
|
||||||
|
}, [router, user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className='bg-muted/50 flex h-full min-h-screen items-center'>
|
<main className='bg-muted/50 flex h-full min-h-screen items-center'>
|
||||||
<div className='flex size-full flex-auto flex-col justify-center lg:flex-row'>
|
<div className='flex size-full flex-auto flex-col justify-center lg:flex-row'>
|
||||||
|
|||||||
+25
-13
@@ -232,16 +232,6 @@ export default function SubscribeAppForm<
|
|||||||
name: 'is_default',
|
name: 'is_default',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
placeholder: t('defaultVersion'),
|
placeholder: t('defaultVersion'),
|
||||||
calculateValue: (value) => {
|
|
||||||
const newField = field.value?.map((item) => {
|
|
||||||
if (item.is_default) {
|
|
||||||
item.is_default = false;
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
form.setValue(field.name, newField);
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'url',
|
name: 'url',
|
||||||
@@ -252,10 +242,32 @@ export default function SubscribeAppForm<
|
|||||||
]}
|
]}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
form.setValue(
|
const filteredValue = value.filter((item) => item.url);
|
||||||
field.name,
|
|
||||||
value.filter((item) => item.url),
|
const newDefaultIndex = value.findIndex(
|
||||||
|
(item, idx) =>
|
||||||
|
item.is_default &&
|
||||||
|
(!field.value?.[idx] || !field.value[idx].is_default),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let finalValue = filteredValue;
|
||||||
|
|
||||||
|
if (newDefaultIndex >= 0 && filteredValue[newDefaultIndex]) {
|
||||||
|
finalValue = filteredValue.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
is_default: index === newDefaultIndex,
|
||||||
|
}));
|
||||||
|
} else if (
|
||||||
|
!filteredValue.some((item) => item.is_default) &&
|
||||||
|
filteredValue.length > 0
|
||||||
|
) {
|
||||||
|
finalValue = filteredValue.map((item, index) => ({
|
||||||
|
...item,
|
||||||
|
is_default: index === 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
form.setValue(field.name, finalValue as any);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -286,9 +286,13 @@ export default function Page() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value='template'>
|
<TabsContent value='template'>
|
||||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
|
||||||
{['verify_email_template', 'expiration_email_template', 'maintenance_email_template'].map(
|
{[
|
||||||
(templateKey) => (
|
'verify_email_template',
|
||||||
|
'expiration_email_template',
|
||||||
|
'maintenance_email_template',
|
||||||
|
'traffic_exceed_email_template',
|
||||||
|
].map((templateKey) => (
|
||||||
<Card key={templateKey}>
|
<Card key={templateKey}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
||||||
@@ -309,8 +313,7 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
),
|
))}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -136,14 +136,14 @@ export function Register() {
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow>
|
<TableRow className='flex flex-col'>
|
||||||
<TableCell>
|
<TableCell className='w-full'>
|
||||||
<Label>{t('trialSubscribePlan')}</Label>
|
<Label>{t('trialSubscribePlan')}</Label>
|
||||||
<p className='text-muted-foreground text-xs'>
|
<p className='text-muted-foreground text-xs'>
|
||||||
{t('trialSubscribePlanDescription')}
|
{t('trialSubscribePlanDescription')}
|
||||||
</p>
|
</p>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className='text-right'>
|
<TableCell className='max-w-96'>
|
||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('trialDuration')}
|
placeholder={t('trialDuration')}
|
||||||
type='number'
|
type='number'
|
||||||
|
|||||||
@@ -307,6 +307,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('form.countPlaceholder')}
|
placeholder={t('form.countPlaceholder')}
|
||||||
type='number'
|
type='number'
|
||||||
|
step={1}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
@@ -327,6 +328,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('form.userLimitPlaceholder')}
|
placeholder={t('form.userLimitPlaceholder')}
|
||||||
type='number'
|
type='number'
|
||||||
|
step={1}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
|
|||||||
@@ -57,8 +57,13 @@ export default function Page(props: any) {
|
|||||||
{
|
{
|
||||||
accessorKey: 'subscribe_id',
|
accessorKey: 'subscribe_id',
|
||||||
header: t('subscribe'),
|
header: t('subscribe'),
|
||||||
cell: ({ row }) =>
|
cell: ({ row }) => {
|
||||||
subscribeList?.find((item) => item.id === row.getValue('subscribe_id'))?.name,
|
const name = subscribeList?.find(
|
||||||
|
(item) => item.id === row.getValue('subscribe_id'),
|
||||||
|
)?.name;
|
||||||
|
const quantity = row.original.quantity;
|
||||||
|
return name ? `${name} × ${quantity}` : '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'amount',
|
accessorKey: 'amount',
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ interface PaymentFormProps<T> {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
initialValues?: T;
|
initialValues?: T;
|
||||||
onSubmit: (values: T) => Promise<boolean>;
|
onSubmit: (values: T) => Promise<boolean>;
|
||||||
|
isEdit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PaymentForm<T>({
|
export default function PaymentForm<T>({
|
||||||
@@ -53,6 +54,7 @@ export default function PaymentForm<T>({
|
|||||||
loading,
|
loading,
|
||||||
initialValues,
|
initialValues,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
isEdit,
|
||||||
}: PaymentFormProps<T>) {
|
}: PaymentFormProps<T>) {
|
||||||
const t = useTranslations('payment');
|
const t = useTranslations('payment');
|
||||||
const { common } = useGlobalStore();
|
const { common } = useGlobalStore();
|
||||||
@@ -199,7 +201,7 @@ export default function PaymentForm<T>({
|
|||||||
<FormLabel>{t('domain')}</FormLabel>
|
<FormLabel>{t('domain')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('domainPlaceholder', { example: 'http(s)://example.com' })}
|
placeholder='http(s)://example.com'
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onValueChange={(value) => form.setValue('domain', value as string)}
|
onValueChange={(value) => form.setValue('domain', value as string)}
|
||||||
/>
|
/>
|
||||||
@@ -317,6 +319,8 @@ export default function PaymentForm<T>({
|
|||||||
}}
|
}}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
|
// @ts-expect-error
|
||||||
|
disabled={isEdit && Boolean(initialValues?.platform)}
|
||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
@@ -367,6 +371,7 @@ export default function PaymentForm<T>({
|
|||||||
? configValues[fieldKey]
|
? configValues[fieldKey]
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
disabled={fieldKey === 'webhook_secret'}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const newConfig = { ...configValues };
|
const newConfig = { ...configValues };
|
||||||
newConfig[fieldKey] = value;
|
newConfig[fieldKey] = value;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export default function PaymentTable() {
|
|||||||
actions={{
|
actions={{
|
||||||
render: (row) => [
|
render: (row) => [
|
||||||
<PaymentForm<API.UpdatePaymentMethodRequest>
|
<PaymentForm<API.UpdatePaymentMethodRequest>
|
||||||
|
isEdit
|
||||||
key='edit'
|
key='edit'
|
||||||
trigger={<Button>{t('edit')}</Button>}
|
trigger={<Button>{t('edit')}</Button>}
|
||||||
title={t('editPayment')}
|
title={t('editPayment')}
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createRuleGroup } from '@/services/admin/server';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@workspace/ui/components/dialog';
|
||||||
|
import { Label } from '@workspace/ui/components/label';
|
||||||
|
import { Progress } from '@workspace/ui/components/progress';
|
||||||
|
import { Textarea } from '@workspace/ui/components/textarea';
|
||||||
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
import yaml from 'js-yaml';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface ImportYamlRulesProps {
|
||||||
|
onImportSuccess?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RuleGroup {
|
||||||
|
name: string;
|
||||||
|
rules: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImportYamlRules({ onImportSuccess }: ImportYamlRulesProps) {
|
||||||
|
const t = useTranslations('rules');
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [yamlContent, setYamlContent] = useState('');
|
||||||
|
const [importProgress, setImportProgress] = useState(0);
|
||||||
|
const [importTotal, setImportTotal] = useState(0);
|
||||||
|
const [analyzing, setAnalyzing] = useState(false);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const content = event.target?.result as string;
|
||||||
|
setYamlContent(content);
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
|
||||||
|
e.target.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const processRule = (rule: string): { policyGroup: string; cleanRule: string } | null => {
|
||||||
|
const parts = rule.split(',');
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let policyGroup = 'default';
|
||||||
|
let cleanRule = rule;
|
||||||
|
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const thirdPart = parts[2]?.trim();
|
||||||
|
if (thirdPart) {
|
||||||
|
policyGroup = thirdPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanRule = parts.slice(0, 2).join(',');
|
||||||
|
|
||||||
|
return { policyGroup, cleanRule };
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseRulesIntoGroups = (rules: string[]): Record<string, string[]> => {
|
||||||
|
const groups: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (!rule.trim()) continue;
|
||||||
|
|
||||||
|
const result = processRule(rule);
|
||||||
|
if (result === null) continue;
|
||||||
|
|
||||||
|
const { policyGroup, cleanRule } = result;
|
||||||
|
if (!groups[policyGroup]) {
|
||||||
|
groups[policyGroup] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不插入 MATCH 规则,只用于标识默认规则组
|
||||||
|
if (!rule.trim().startsWith('MATCH,')) {
|
||||||
|
groups[policyGroup].push(cleanRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkIfDefaultRule = (originalRules: string[], groupName: string): boolean => {
|
||||||
|
return originalRules.some((rule) => {
|
||||||
|
const trimmedRule = rule.trim();
|
||||||
|
if (!trimmedRule.startsWith('MATCH,')) return false;
|
||||||
|
|
||||||
|
// 检查 MATCH 规则是否属于当前组
|
||||||
|
const parts = trimmedRule.split(',');
|
||||||
|
if (parts.length >= 3) {
|
||||||
|
const ruleGroup = parts[2]?.trim();
|
||||||
|
return ruleGroup === groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupName === 'default';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImport = async () => {
|
||||||
|
if (!yamlContent) {
|
||||||
|
toast.error(t('pleaseUploadFile'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setAnalyzing(true);
|
||||||
|
try {
|
||||||
|
const parsedYaml = yaml.load(yamlContent) as any;
|
||||||
|
|
||||||
|
if (!parsedYaml || !parsedYaml.rules) {
|
||||||
|
throw new Error(t('invalidYamlFormat'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let allRules: string[] = [];
|
||||||
|
if (Array.isArray(parsedYaml.rules)) {
|
||||||
|
allRules = parsedYaml.rules.filter((rule: string) => rule.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allRules.length === 0) {
|
||||||
|
throw new Error(t('noValidRules'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleGroups = parseRulesIntoGroups(allRules);
|
||||||
|
const groups = Object.entries(ruleGroups).map(([name, rules]) => ({
|
||||||
|
name,
|
||||||
|
rules,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setImportTotal(groups.length);
|
||||||
|
setAnalyzing(false);
|
||||||
|
|
||||||
|
for (let i = 0; i < groups.length; i++) {
|
||||||
|
const group = groups[i];
|
||||||
|
if (!group?.name || !group?.rules.length) continue;
|
||||||
|
|
||||||
|
const isDefault = checkIfDefaultRule(allRules, group.name);
|
||||||
|
|
||||||
|
await createRuleGroup({
|
||||||
|
name: group.name,
|
||||||
|
rules: group?.rules.join('\n'),
|
||||||
|
enable: false,
|
||||||
|
tags: [],
|
||||||
|
icon: '',
|
||||||
|
type: 'default',
|
||||||
|
default: isDefault,
|
||||||
|
});
|
||||||
|
setImportProgress(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(t('importSuccess'));
|
||||||
|
setOpen(false);
|
||||||
|
setYamlContent('');
|
||||||
|
setImportProgress(0);
|
||||||
|
setImportTotal(0);
|
||||||
|
onImportSuccess?.();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Import error:', error);
|
||||||
|
toast.error(error instanceof Error ? error.message : t('importFailed'));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setAnalyzing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type='file'
|
||||||
|
accept='.yml,.yaml'
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
<Button variant='default' onClick={() => fileInputRef.current?.click()}>
|
||||||
|
{t('import')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent className='sm:max-w-[500px]'>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t('importYamlRules')}</DialogTitle>
|
||||||
|
<DialogDescription>{t('importYamlDescription')}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className='grid gap-4 py-4'>
|
||||||
|
{yamlContent && (
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='preview'>{t('preview')}</Label>
|
||||||
|
<Textarea
|
||||||
|
id='preview'
|
||||||
|
value={yamlContent}
|
||||||
|
readOnly
|
||||||
|
rows={10}
|
||||||
|
className='font-mono text-xs'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{importTotal > 0 && (
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<div className='flex justify-between text-sm'>
|
||||||
|
<span>{analyzing ? t('analyzing') : t('importing')}</span>
|
||||||
|
<span>
|
||||||
|
{importProgress} / {importTotal}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={(importProgress / importTotal) * 100} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant='outline' onClick={() => setOpen(false)} disabled={loading}>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleImport} disabled={loading || !yamlContent}>
|
||||||
|
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
|
||||||
|
{t('import')}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||||
|
import {
|
||||||
|
createRuleGroup,
|
||||||
|
deleteRuleGroup,
|
||||||
|
getRuleGroupList,
|
||||||
|
updateRuleGroup,
|
||||||
|
} from '@/services/admin/server';
|
||||||
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import { Switch } from '@workspace/ui/components/switch';
|
||||||
|
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||||
|
import { formatDate } from '@workspace/ui/utils';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import Image from 'next/legacy/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import ImportYamlRules from './import-yaml-rules';
|
||||||
|
import RuleForm from './rule-form';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const t = useTranslations('rules');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const ref = useRef<ProTableActions>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProTable<API.ServerRuleGroup, { query: string }>
|
||||||
|
action={ref}
|
||||||
|
header={{
|
||||||
|
toolbar: (
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<Button variant='default' asChild>
|
||||||
|
<Link href='/template/rules.yml' target='_blank' download>
|
||||||
|
{t('downloadTemplate')}
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<ImportYamlRules onImportSuccess={() => ref.current?.refresh()} />
|
||||||
|
<RuleForm<API.CreateRuleGroupRequest>
|
||||||
|
trigger={t('create')}
|
||||||
|
title={t('createRule')}
|
||||||
|
loading={loading}
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await createRuleGroup({
|
||||||
|
name: values.name,
|
||||||
|
rules: values.rules || '',
|
||||||
|
enable: false,
|
||||||
|
tags: values.tags || [],
|
||||||
|
icon: values.icon || '',
|
||||||
|
type: values.type || 'default',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
toast.success(t('createSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
setLoading(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
params={[
|
||||||
|
{
|
||||||
|
key: 'search',
|
||||||
|
placeholder: t('searchRule'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
request={async (pagination, filters) => {
|
||||||
|
const { data } = await getRuleGroupList({
|
||||||
|
...pagination,
|
||||||
|
...filters,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
list: data.data?.list || [],
|
||||||
|
total: data.data?.total || 0,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
accessorKey: 'enable',
|
||||||
|
header: t('enable'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={row.getValue('enable')}
|
||||||
|
onCheckedChange={async (checked) => {
|
||||||
|
await updateRuleGroup({
|
||||||
|
...row.original,
|
||||||
|
enable: checked,
|
||||||
|
} as API.UpdateRuleGroupRequest);
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'default',
|
||||||
|
header: t('defaultRule'),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={row.original.default}
|
||||||
|
onCheckedChange={async (checked) => {
|
||||||
|
await updateRuleGroup({
|
||||||
|
...row.original,
|
||||||
|
default: checked,
|
||||||
|
} as API.UpdateRuleGroupRequest);
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'type',
|
||||||
|
header: t('type'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const type = row.original.type || 'default';
|
||||||
|
if (type === 'default') {
|
||||||
|
return <Badge variant='default'>{t('default')}</Badge>;
|
||||||
|
}
|
||||||
|
if (type === 'reject') {
|
||||||
|
return <Badge variant='destructive'>{t('reject')}</Badge>;
|
||||||
|
}
|
||||||
|
if (type === 'direct') {
|
||||||
|
return <Badge variant='secondary'>{t('direct')}</Badge>;
|
||||||
|
}
|
||||||
|
return <Badge variant='default'>{t('default')}</Badge>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: t('name'),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{row.original.icon && (
|
||||||
|
<Image
|
||||||
|
src={row.original.icon}
|
||||||
|
alt={row.original.name}
|
||||||
|
className='h-6 w-6 rounded-md'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>{row.original.name}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'tags',
|
||||||
|
header: t('tags'),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const tags = row.original.tags.filter((item) => item) || [];
|
||||||
|
if (!tags.length) return '--';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tags.map((tag) => (
|
||||||
|
<Badge key={tag} variant='outline' className='mr-1'>
|
||||||
|
{tag}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'created_at',
|
||||||
|
header: t('createdAt'),
|
||||||
|
cell: ({ row }) => formatDate(row.original.created_at),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
actions={{
|
||||||
|
render: (row) => [
|
||||||
|
<RuleForm<API.UpdateRuleGroupRequest>
|
||||||
|
key='edit'
|
||||||
|
trigger={t('edit')}
|
||||||
|
title={t('editRule')}
|
||||||
|
loading={loading}
|
||||||
|
initialValues={row}
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await updateRuleGroup({
|
||||||
|
id: row.id,
|
||||||
|
name: values.name,
|
||||||
|
tags: values.tags,
|
||||||
|
rules: values.rules,
|
||||||
|
enable: row.enable,
|
||||||
|
icon: values.icon,
|
||||||
|
type: values.type,
|
||||||
|
default: row.default,
|
||||||
|
});
|
||||||
|
toast.success(t('updateSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
setLoading(false);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
<ConfirmButton
|
||||||
|
key='delete'
|
||||||
|
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||||
|
title={t('confirmDelete')}
|
||||||
|
description={t('deleteWarning')}
|
||||||
|
onConfirm={async () => {
|
||||||
|
await deleteRuleGroup({ id: row.id });
|
||||||
|
toast.success(t('deleteSuccess'));
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
cancelText={t('cancel')}
|
||||||
|
confirmText={t('confirm')}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
batchRender: (rows) => [
|
||||||
|
<ConfirmButton
|
||||||
|
key='delete'
|
||||||
|
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||||
|
title={t('confirmDelete')}
|
||||||
|
description={t('deleteWarning')}
|
||||||
|
onConfirm={async () => {
|
||||||
|
for (const row of rows) {
|
||||||
|
await deleteRuleGroup({ id: row.id });
|
||||||
|
}
|
||||||
|
toast.success(t('deleteSuccess'));
|
||||||
|
ref.current?.reset();
|
||||||
|
ref.current?.refresh();
|
||||||
|
}}
|
||||||
|
cancelText={t('cancel')}
|
||||||
|
confirmText={t('confirm')}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { getNodeTagList } from '@/services/admin/server';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@workspace/ui/components/form';
|
||||||
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@workspace/ui/components/select';
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetFooter,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from '@workspace/ui/components/sheet';
|
||||||
|
import { Textarea } from '@workspace/ui/components/textarea';
|
||||||
|
import { Combobox } from '@workspace/ui/custom-components/combobox';
|
||||||
|
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||||
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
import { UploadImage } from '@workspace/ui/custom-components/upload-image';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(1, { message: '请输入规则名称' }),
|
||||||
|
tags: z.array(z.string()).default([]),
|
||||||
|
rules: z.string().default(''),
|
||||||
|
icon: z.string().default(''),
|
||||||
|
type: z.string().default('default'),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface RuleFormProps<T> {
|
||||||
|
onSubmit: (data: T) => Promise<boolean> | boolean;
|
||||||
|
initialValues?: T;
|
||||||
|
loading?: boolean;
|
||||||
|
trigger: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RuleForm<T extends Record<string, any>>({
|
||||||
|
onSubmit,
|
||||||
|
initialValues,
|
||||||
|
loading,
|
||||||
|
trigger,
|
||||||
|
title,
|
||||||
|
}: RuleFormProps<T>) {
|
||||||
|
const t = useTranslations('rules');
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
...initialValues,
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialValues) {
|
||||||
|
form.reset(initialValues);
|
||||||
|
}
|
||||||
|
}, [form, initialValues]);
|
||||||
|
|
||||||
|
async function handleSubmit(data: { [x: string]: any }) {
|
||||||
|
const bool = await onSubmit(data as T);
|
||||||
|
if (bool) setOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: tags } = useQuery({
|
||||||
|
queryKey: ['getNodeTagList'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await getNodeTagList();
|
||||||
|
return data.data?.tags || [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
form.reset();
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</Button>
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent className='w-[500px] max-w-full md:max-w-screen-md'>
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>{title}</SheetTitle>
|
||||||
|
</SheetHeader>
|
||||||
|
<ScrollArea className='-mx-6 h-[calc(100vh-48px-36px-36px-env(safe-area-inset-top))]'>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-4 px-6 pt-4'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='icon'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('appIcon')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('enterIconUrl')}
|
||||||
|
value={field.value}
|
||||||
|
suffix={
|
||||||
|
<UploadImage
|
||||||
|
className='bg-muted h-9 rounded-none border-none px-2'
|
||||||
|
onChange={(value) => {
|
||||||
|
form.setValue(field.name, value as string);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='name'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('name')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<EnhancedInput
|
||||||
|
placeholder={t('enterRuleName')}
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='type'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('type')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t('selectType')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='default'>{t('default')}</SelectItem>
|
||||||
|
<SelectItem value='reject'>{t('reject')}</SelectItem>
|
||||||
|
<SelectItem value='direct'>{t('direct')}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='tags'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('tagsLabel')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Combobox<string, true>
|
||||||
|
multiple
|
||||||
|
placeholder={t('selectTags')}
|
||||||
|
value={field.value}
|
||||||
|
onChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
options={tags?.map((item: string) => ({
|
||||||
|
value: item,
|
||||||
|
label: item,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='rules'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('rulesLabel')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea
|
||||||
|
placeholder={t('enterRules')}
|
||||||
|
value={field.value}
|
||||||
|
rows={10}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setValue(field.name, e.target.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className='text-muted-foreground mt-1 text-xs'>
|
||||||
|
<pre>{t('rulesFormat')}</pre>
|
||||||
|
<div className='border-muted mt-2 space-y-1 border-l-2 pl-2'>
|
||||||
|
<p className='font-mono'>DOMAIN,example.com</p>
|
||||||
|
<p className='font-mono'>DOMAIN-SUFFIX,google.com,DIRECT</p>
|
||||||
|
<p className='font-mono'>DOMAIN-KEYWORD,amazon,REJECT</p>
|
||||||
|
<p className='font-mono'>IP-CIDR,192.168.0.0/16</p>
|
||||||
|
<p className='font-mono'>IP-CIDR6,2001:db8::/32,REJECT</p>
|
||||||
|
<p className='font-mono'>SRC-IP-CIDR,192.168.1.201/32</p>
|
||||||
|
<p className='font-mono'>GEOIP,CN,DIRECT</p>
|
||||||
|
<p className='font-mono'>GEOIP,US</p>
|
||||||
|
<p className='font-mono'>DST-PORT,80,DIRECT</p>
|
||||||
|
<p className='font-mono'>SRC-PORT,7777,REJECT</p>
|
||||||
|
<p className='font-mono'>PROCESS-NAME,telegram</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</ScrollArea>
|
||||||
|
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
|
||||||
|
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}
|
||||||
|
{t('confirm')}
|
||||||
|
</Button>
|
||||||
|
</SheetFooter>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const protocols = ['shadowsocks', 'vmess', 'vless', 'trojan', 'hysteria2', 'tuic'];
|
export const protocols = ['shadowsocks', 'vmess', 'vless', 'trojan', 'hysteria2', 'tuic', 'anytls'];
|
||||||
|
|
||||||
const nullableString = z.string().nullish();
|
const nullableString = z.string().nullish();
|
||||||
const portSchema = z.number().max(65535).nullish();
|
const portSchema = z.number().max(65535).nullish();
|
||||||
@@ -58,7 +58,15 @@ const hysteria2Schema = z.object({
|
|||||||
|
|
||||||
const tuicSchema = z.object({
|
const tuicSchema = z.object({
|
||||||
port: portSchema,
|
port: portSchema,
|
||||||
security: z.string(),
|
disable_sni: z.boolean().default(false),
|
||||||
|
reduce_rtt: z.boolean().default(false),
|
||||||
|
udp_relay_mode: z.string().default('native'),
|
||||||
|
congestion_controller: z.string().default('bbr'),
|
||||||
|
security_config: securityConfigSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const anytlsSchema = z.object({
|
||||||
|
port: portSchema,
|
||||||
security_config: securityConfigSchema,
|
security_config: securityConfigSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,6 +95,10 @@ const protocolConfigSchema = z.discriminatedUnion('protocol', [
|
|||||||
protocol: z.literal('tuic'),
|
protocol: z.literal('tuic'),
|
||||||
config: tuicSchema,
|
config: tuicSchema,
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
protocol: z.literal('anytls'),
|
||||||
|
config: anytlsSchema,
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const baseFormSchema = z.object({
|
const baseFormSchema = z.object({
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
{trigger}
|
{trigger}
|
||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent className='w-[520px] max-w-full md:max-w-screen-md'>
|
<SheetContent className='w-[580px] max-w-full md:max-w-screen-md'>
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle>{title}</SheetTitle>
|
<SheetTitle>{title}</SheetTitle>
|
||||||
</SheetHeader>
|
</SheetHeader>
|
||||||
@@ -290,7 +290,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
if (['trojan', 'hysteria2', 'tuic'].includes(value)) {
|
if (['trojan', 'hysteria2', 'tuic', 'anytls'].includes(value)) {
|
||||||
form.setValue('config.security', 'tls');
|
form.setValue('config.security', 'tls');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -362,9 +362,10 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
{...field}
|
{...field}
|
||||||
type='number'
|
type='number'
|
||||||
placeholder='1-65535'
|
step={1}
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
|
placeholder='1-65535'
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
}}
|
}}
|
||||||
@@ -401,11 +402,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic'].includes(protocol) && (
|
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic', 'anytls'].includes(protocol) && (
|
||||||
<div className='grid gap-4'>
|
<div className='grid gap-4'>
|
||||||
<div
|
<div
|
||||||
className={cn('flex gap-4 *:flex-1', {
|
className={cn('flex gap-4 *:flex-1', {
|
||||||
'grid grid-cols-2': ['hysteria2'].includes(protocol),
|
'grid grid-cols-2': ['hysteria2', 'tuic'].includes(protocol),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -418,6 +419,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
{...field}
|
{...field}
|
||||||
type='number'
|
type='number'
|
||||||
|
step={1}
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
placeholder='1-65535'
|
placeholder='1-65535'
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
@@ -520,6 +524,109 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{protocol === 'tuic' && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='config.udp_relay_mode'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('form.udpRelayMode')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t('form.pleaseSelect')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='native'>Native</SelectItem>
|
||||||
|
<SelectItem value='quic'>QUIC</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='config.congestion_controller'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('form.congestionController')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select
|
||||||
|
value={field.value}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={t('form.pleaseSelect')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='bbr'>BBR</SelectItem>
|
||||||
|
<SelectItem value='cubic'>Cubic</SelectItem>
|
||||||
|
<SelectItem value='reno'>Reno</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className='flex gap-2'>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='config.disable_sni'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('form.disableSni')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className='pt-2'>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
form.setValue(field.name, checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name='config.reduce_rtt'
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('form.reduceRtt')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className='pt-2'>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
form.setValue(field.name, checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{['vmess', 'vless', 'trojan'].includes(protocol) && (
|
{['vmess', 'vless', 'trojan'].includes(protocol) && (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -626,9 +733,12 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
{(['vmess', 'vless', 'trojan'].includes(protocol) ||
|
||||||
|
['anytls', 'tuic', 'hysteria2'].includes(protocol)) && (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className='flex flex-row items-center justify-between p-3'>
|
<CardHeader className='flex flex-row items-center justify-between p-3'>
|
||||||
<CardTitle>{t('form.securityConfig')}</CardTitle>
|
<CardTitle>{t('form.securityConfig')}</CardTitle>
|
||||||
|
{['vmess', 'vless', 'trojan'].includes(protocol) && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='config.security'
|
name='config.security'
|
||||||
@@ -659,8 +769,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
{security !== 'none' && (
|
{(['anytls', 'tuic', 'hysteria2'].includes(protocol) ||
|
||||||
|
(['vmess', 'vless', 'trojan'].includes(protocol) &&
|
||||||
|
security !== 'none')) && (
|
||||||
<CardContent className='grid grid-cols-2 gap-4 p-3'>
|
<CardContent className='grid grid-cols-2 gap-4 p-3'>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -680,6 +793,8 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Reality 特殊配置只在 vless + reality 时显示 */}
|
||||||
{protocol === 'vless' && security === 'reality' && (
|
{protocol === 'vless' && security === 'reality' && (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -715,7 +830,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
type='number'
|
type='number'
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
placeholder={t('form.security_config.serverPortPlaceholder')}
|
placeholder={t(
|
||||||
|
'form.security_config.serverPortPlaceholder',
|
||||||
|
)}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
}}
|
}}
|
||||||
@@ -734,7 +851,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
{...field}
|
{...field}
|
||||||
placeholder={t('form.security_config.privateKeyPlaceholder')}
|
placeholder={t(
|
||||||
|
'form.security_config.privateKeyPlaceholder',
|
||||||
|
)}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
}}
|
}}
|
||||||
@@ -784,6 +903,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{protocol === 'vless' && (
|
{protocol === 'vless' && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -814,11 +934,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='config.security_config.allow_insecure'
|
name='config.security_config.allow_insecure'
|
||||||
@@ -829,8 +949,8 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
<div className='pt-2'>
|
<div className='pt-2'>
|
||||||
<Switch
|
<Switch
|
||||||
checked={!!field.value}
|
checked={!!field.value}
|
||||||
onCheckedChange={(value) => {
|
onCheckedChange={(checked) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, checked);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -842,6 +962,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -899,6 +1020,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
|||||||
{
|
{
|
||||||
name: 'port',
|
name: 'port',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: 1,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 65535,
|
max: 65535,
|
||||||
placeholder: t('form.relayPort'),
|
placeholder: t('form.relayPort'),
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionContent,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionTrigger,
|
||||||
|
} from '@workspace/ui/components/accordion';
|
||||||
import { Badge } from '@workspace/ui/components/badge';
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
import { Progress } from '@workspace/ui/components/progress';
|
import { Progress } from '@workspace/ui/components/progress';
|
||||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||||
@@ -11,6 +17,8 @@ import {
|
|||||||
} from '@workspace/ui/components/tooltip';
|
} from '@workspace/ui/components/tooltip';
|
||||||
import { formatDate } from '@workspace/ui/utils';
|
import { formatDate } from '@workspace/ui/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { UserSubscribeDetail } from '../user/user-detail';
|
||||||
|
|
||||||
export function formatPercentage(value: number): string {
|
export function formatPercentage(value: number): string {
|
||||||
return `${value.toFixed(1)}%`;
|
return `${value.toFixed(1)}%`;
|
||||||
@@ -18,25 +26,19 @@ export function formatPercentage(value: number): string {
|
|||||||
|
|
||||||
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
||||||
const t = useTranslations('server.node');
|
const t = useTranslations('server.node');
|
||||||
|
const [openItem, setOpenItem] = useState<string | null>(null);
|
||||||
|
|
||||||
const {
|
const { online, cpu, mem, disk, updated_at } = status || {
|
||||||
last_at,
|
online: {},
|
||||||
online_users,
|
|
||||||
status: serverStatus,
|
|
||||||
} = status || {
|
|
||||||
online_users: [],
|
|
||||||
status: {
|
|
||||||
cpu: 0,
|
cpu: 0,
|
||||||
mem: 0,
|
mem: 0,
|
||||||
disk: 0,
|
disk: 0,
|
||||||
updated_at: 0,
|
updated_at: 0,
|
||||||
},
|
|
||||||
last_at: 0,
|
|
||||||
};
|
};
|
||||||
const isOnline = last_at > 0;
|
const isOnline = updated_at > 0;
|
||||||
const badgeVariant = isOnline ? 'default' : 'destructive';
|
const badgeVariant = isOnline ? 'default' : 'destructive';
|
||||||
const badgeText = isOnline ? t('normal') : t('abnormal');
|
const badgeText = isOnline ? t('normal') : t('abnormal');
|
||||||
const onlineCount = Array.isArray(online_users) ? online_users?.length : 0;
|
const onlineCount = (online && Object.keys(online).length) || 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
@@ -52,45 +54,56 @@ export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
|||||||
<div className='flex flex-col space-y-1'>
|
<div className='flex flex-col space-y-1'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<span>CPU</span>
|
<span>CPU</span>
|
||||||
<span>{formatPercentage(serverStatus?.cpu ?? 0)}</span>
|
<span>{formatPercentage(cpu ?? 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={serverStatus?.cpu ?? 0} className='h-2' max={100} />
|
<Progress value={cpu ?? 0} className='h-2' max={100} />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col space-y-1'>
|
<div className='flex flex-col space-y-1'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<span>{t('memory')}</span>
|
<span>{t('memory')}</span>
|
||||||
<span>{formatPercentage(serverStatus?.mem ?? 0)}</span>
|
<span>{formatPercentage(mem ?? 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={serverStatus?.mem ?? 0} className='h-2' max={100} />
|
<Progress value={mem ?? 0} className='h-2' max={100} />
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col space-y-1'>
|
<div className='flex flex-col space-y-1'>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<span>{t('disk')}</span>
|
<span>{t('disk')}</span>
|
||||||
<span>{formatPercentage(serverStatus?.disk ?? 0)}</span>
|
<span>{formatPercentage(disk ?? 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={serverStatus?.disk ?? 0} className='h-2' max={100} />
|
<Progress value={disk ?? 0} className='h-2' max={100} />
|
||||||
</div>
|
</div>
|
||||||
{isOnline && (
|
{isOnline && (
|
||||||
<div>
|
<div>
|
||||||
{t('lastUpdated')}: {formatDate(serverStatus?.updated_at ?? 0)}
|
{t('lastUpdated')}: {formatDate(updated_at ?? 0)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
{isOnline && onlineCount > 0 && (
|
{isOnline && onlineCount > 0 && (
|
||||||
<TooltipContent className='bg-muted text-foreground w-80'>
|
<TooltipContent className='bg-card text-foreground w-96'>
|
||||||
<div className='space-y-4'>
|
<ScrollArea className='h-[540px] rounded-md border px-4 py-2'>
|
||||||
<div className='space-y-2'>
|
<h4 className='py-1 text-sm font-semibold'>{t('onlineUsers')}</h4>
|
||||||
<h4 className='text-sm font-semibold'>{t('onlineUsers')}</h4>
|
<Accordion
|
||||||
<ScrollArea className='h-[400px] rounded-md border p-2'>
|
type='single'
|
||||||
{online_users.map((user, index) => (
|
collapsible
|
||||||
<div key={user.uid} className='py-1 text-xs'>
|
className='w-full'
|
||||||
{user.ip} (UID: {user.uid})
|
onValueChange={(value) => setOpenItem(value)}
|
||||||
</div>
|
>
|
||||||
|
{Object.entries(online).map(([uid, ips]) => (
|
||||||
|
<AccordionItem key={uid} value={uid}>
|
||||||
|
<AccordionTrigger>{`[UID: ${uid}] - ${ips[0]}`}</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<ul>
|
||||||
|
{ips.map((ip: string) => (
|
||||||
|
<li key={ip}>{ip}</li>
|
||||||
))}
|
))}
|
||||||
|
</ul>
|
||||||
|
<UserSubscribeDetail id={Number(uid)} enabled={openItem === uid} />
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export default function NodeTable() {
|
|||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
accessorKey: 'id',
|
accessorKey: 'id',
|
||||||
header: 'ID',
|
header: t('id'),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -87,6 +87,7 @@ export default function NodeTable() {
|
|||||||
'bg-yellow-500': row.original.protocol === 'trojan',
|
'bg-yellow-500': row.original.protocol === 'trojan',
|
||||||
'bg-purple-500': row.original.protocol === 'hysteria2',
|
'bg-purple-500': row.original.protocol === 'hysteria2',
|
||||||
'bg-cyan-500': row.original.protocol === 'tuic',
|
'bg-cyan-500': row.original.protocol === 'tuic',
|
||||||
|
'bg-gray-500': row.original.protocol === 'anytls',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{row.getValue('id')}
|
{row.getValue('id')}
|
||||||
@@ -159,7 +160,7 @@ export default function NodeTable() {
|
|||||||
header: t('nodeGroup'),
|
header: t('nodeGroup'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const name = groups?.find((group) => group.id === row.getValue('group_id'))?.name;
|
const name = groups?.find((group) => group.id === row.getValue('group_id'))?.name;
|
||||||
return name ? <Badge variant='outline'>{name}</Badge> : '--';
|
return name ? <Badge variant='outline'>{name}</Badge> : t('noData');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -176,7 +177,7 @@ export default function NodeTable() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
'--'
|
t('noData')
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -272,17 +273,17 @@ export default function NodeTable() {
|
|||||||
<ConfirmButton
|
<ConfirmButton
|
||||||
key='delete'
|
key='delete'
|
||||||
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||||
title={t('group.confirmDelete')}
|
title={t('confirmDelete')}
|
||||||
description={t('group.deleteWarning')}
|
description={t('deleteWarning')}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
await batchDeleteNode({
|
await batchDeleteNode({
|
||||||
ids: rows.map((item) => item.id),
|
ids: rows.map((item) => item.id),
|
||||||
});
|
});
|
||||||
toast.success(t('group.deleteSuccess'));
|
toast.success(t('deleteSuccess'));
|
||||||
ref.current?.refresh();
|
ref.current?.refresh();
|
||||||
}}
|
}}
|
||||||
cancelText={t('group.cancel')}
|
cancelText={t('cancel')}
|
||||||
confirmText={t('group.confirm')}
|
confirmText={t('confirm')}
|
||||||
/>,
|
/>,
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { getTranslations } from 'next-intl/server';
|
|||||||
|
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||||
|
|
||||||
import SubscribeApp from './app/table';
|
|
||||||
import GroupTable from './group/table';
|
import GroupTable from './group/table';
|
||||||
import SubscribeConfig from './subscribe-config';
|
import SubscribeConfig from './subscribe-config';
|
||||||
import SubscribeTable from './subscribe-table';
|
import SubscribeTable from './subscribe-table';
|
||||||
@@ -16,7 +15,6 @@ export default async function Page() {
|
|||||||
<TabsTrigger value='subscribe'>{t('tabs.subscribe')}</TabsTrigger>
|
<TabsTrigger value='subscribe'>{t('tabs.subscribe')}</TabsTrigger>
|
||||||
<TabsTrigger value='group'>{t('tabs.subscribeGroup')}</TabsTrigger>
|
<TabsTrigger value='group'>{t('tabs.subscribeGroup')}</TabsTrigger>
|
||||||
<TabsTrigger value='config'>{t('tabs.subscribeConfig')}</TabsTrigger>
|
<TabsTrigger value='config'>{t('tabs.subscribeConfig')}</TabsTrigger>
|
||||||
<TabsTrigger value='app'>{t('tabs.subscribeApp')}</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value='subscribe'>
|
<TabsContent value='subscribe'>
|
||||||
<SubscribeTable />
|
<SubscribeTable />
|
||||||
@@ -27,9 +25,6 @@ export default async function Page() {
|
|||||||
<TabsContent value='config'>
|
<TabsContent value='config'>
|
||||||
<SubscribeConfig />
|
<SubscribeConfig />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='app'>
|
|
||||||
<SubscribeApp />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import { evaluateWithPrecision, unitConversion } from '@workspace/ui/utils';
|
|||||||
import { CreditCard, Server, Settings } from 'lucide-react';
|
import { CreditCard, Server, Settings } from 'lucide-react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { assign, shake } from 'radash';
|
import { assign, shake } from 'radash';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -81,6 +81,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
}: Readonly<SubscribeFormProps<T>>) {
|
}: Readonly<SubscribeFormProps<T>>) {
|
||||||
const t = useTranslations('subscribe');
|
const t = useTranslations('subscribe');
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@@ -118,12 +119,111 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const debouncedCalculateDiscount = useCallback(
|
||||||
|
(values: any[], fieldName: string, lastChangedField?: string, changedIndex?: number) => {
|
||||||
|
if (updateTimeoutRef.current) {
|
||||||
|
clearTimeout(updateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeoutRef.current = setTimeout(() => {
|
||||||
|
const { unit_price } = form.getValues();
|
||||||
|
if (!unit_price || !values?.length) return;
|
||||||
|
|
||||||
|
let hasChanges = false;
|
||||||
|
const calculatedValues = values.map((item: any, index: number) => {
|
||||||
|
const result = { ...item };
|
||||||
|
|
||||||
|
if (changedIndex !== undefined && index !== changedIndex) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quantity = Number(item.quantity) || 0;
|
||||||
|
const discount = Number(item.discount) || 0;
|
||||||
|
const price = Number(item.price) || 0;
|
||||||
|
|
||||||
|
switch (lastChangedField) {
|
||||||
|
case 'quantity':
|
||||||
|
case 'discount':
|
||||||
|
if (quantity > 0 && discount > 0) {
|
||||||
|
const newPrice = evaluateWithPrecision(
|
||||||
|
`${unit_price} * ${quantity} * ${discount} / 100`,
|
||||||
|
);
|
||||||
|
if (Math.abs(newPrice - price) > 0.01) {
|
||||||
|
result.price = newPrice;
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'price':
|
||||||
|
if (quantity > 0 && price > 0) {
|
||||||
|
const newDiscount = evaluateWithPrecision(
|
||||||
|
`${price} / ${quantity} / ${unit_price} * 100`,
|
||||||
|
);
|
||||||
|
if (Math.abs(newDiscount - discount) > 0.01) {
|
||||||
|
result.discount = Math.min(100, Math.max(0, newDiscount));
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
} else if (discount > 0 && price > 0) {
|
||||||
|
const newQuantity = evaluateWithPrecision(
|
||||||
|
`${price} / ${unit_price} / ${discount} * 100`,
|
||||||
|
);
|
||||||
|
if (Math.abs(newQuantity - quantity) > 0.01 && newQuantity > 0) {
|
||||||
|
result.quantity = Math.max(1, Math.round(newQuantity));
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (quantity > 0 && discount > 0 && price === 0) {
|
||||||
|
result.price = evaluateWithPrecision(
|
||||||
|
`${unit_price} * ${quantity} * ${discount} / 100`,
|
||||||
|
);
|
||||||
|
hasChanges = true;
|
||||||
|
} else if (quantity > 0 && price > 0 && discount === 0) {
|
||||||
|
const newDiscount = evaluateWithPrecision(
|
||||||
|
`${price} / ${quantity} / ${unit_price} * 100`,
|
||||||
|
);
|
||||||
|
result.discount = Math.min(100, Math.max(0, newDiscount));
|
||||||
|
hasChanges = true;
|
||||||
|
} else if (discount > 0 && price > 0 && quantity === 0) {
|
||||||
|
const newQuantity = evaluateWithPrecision(
|
||||||
|
`${price} / ${unit_price} / ${discount} * 100`,
|
||||||
|
);
|
||||||
|
if (newQuantity > 0) {
|
||||||
|
result.quantity = Math.max(1, Math.round(newQuantity));
|
||||||
|
hasChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
form.setValue(fieldName, calculatedValues, { shouldDirty: true });
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form?.reset(
|
form?.reset(
|
||||||
assign(defaultValues, shake(initialValues, (value) => value === null) as Record<string, any>),
|
assign(defaultValues, shake(initialValues, (value) => value === null) as Record<string, any>),
|
||||||
);
|
);
|
||||||
}, [form, initialValues]);
|
}, [form, initialValues]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (updateTimeoutRef.current) {
|
||||||
|
clearTimeout(updateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
async function handleSubmit(data: { [x: string]: any }) {
|
async function handleSubmit(data: { [x: string]: any }) {
|
||||||
const bool = await onSubmit(data as T);
|
const bool = await onSubmit(data as T);
|
||||||
if (bool) setOpen(false);
|
if (bool) setOpen(false);
|
||||||
@@ -157,6 +257,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const unit_time = form.watch('unit_time');
|
const unit_time = form.watch('unit_time');
|
||||||
|
const unit_price = form.watch('unit_price');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={open} onOpenChange={setOpen}>
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
@@ -298,6 +399,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('form.noLimit')}
|
placeholder={t('form.noLimit')}
|
||||||
type='number'
|
type='number'
|
||||||
|
step={1}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
@@ -321,7 +423,8 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('form.noLimit')}
|
placeholder={t('form.noLimit')}
|
||||||
type='number'
|
type='number'
|
||||||
value={field.value === -1 ? 0 : field.value}
|
step={1}
|
||||||
|
value={field.value}
|
||||||
min={0}
|
min={0}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
@@ -343,6 +446,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<EnhancedInput
|
<EnhancedInput
|
||||||
placeholder={t('form.noLimit')}
|
placeholder={t('form.noLimit')}
|
||||||
type='number'
|
type='number'
|
||||||
|
step={1}
|
||||||
{...field}
|
{...field}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
@@ -540,53 +644,75 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('form.discount')}</FormLabel>
|
<FormLabel>{t('form.discount')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<ArrayInput<API.SubscribeDiscount>
|
<ArrayInput<API.SubscribeDiscount & { price?: number }>
|
||||||
fields={[
|
fields={[
|
||||||
{
|
{
|
||||||
name: 'quantity',
|
name: 'quantity',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: 1,
|
||||||
min: 1,
|
min: 1,
|
||||||
suffix: unit_time && t(`form.${unit_time}`),
|
suffix: unit_time && t(`form.${unit_time}`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'discount',
|
name: 'discount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: 1,
|
min: 0.01,
|
||||||
max: 100,
|
max: 100,
|
||||||
|
step: 0.01,
|
||||||
placeholder: t('form.discountPercent'),
|
placeholder: t('form.discountPercent'),
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
calculateValue: function (data) {
|
|
||||||
const { unit_price } = form.getValues();
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
price: evaluateWithPrecision(
|
|
||||||
`${unit_price} * ${data.quantity} * ${data.discount} / 100`,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'price',
|
name: 'price',
|
||||||
placeholder: t('form.discount_price'),
|
placeholder: t('form.discount_price'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: 0,
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
formatInput: (value) => unitConversion('centsToDollars', value),
|
formatInput: (value) => unitConversion('centsToDollars', value),
|
||||||
formatOutput: (value) => unitConversion('dollarsToCents', value),
|
formatOutput: (value) => unitConversion('dollarsToCents', value),
|
||||||
internal: true,
|
|
||||||
calculateValue: (data) => {
|
|
||||||
const { unit_price } = form.getValues();
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
discount: evaluateWithPrecision(
|
|
||||||
`${data.price} / ${data.quantity} / ${unit_price} * 100`,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(value) => {
|
onChange={(
|
||||||
form.setValue(field.name, value);
|
newValues: (API.SubscribeDiscount & { price?: number })[],
|
||||||
|
) => {
|
||||||
|
const oldValues = field.value || [];
|
||||||
|
let lastChangedField: string | undefined;
|
||||||
|
let changedIndex: number | undefined;
|
||||||
|
|
||||||
|
for (
|
||||||
|
let i = 0;
|
||||||
|
i < Math.max(newValues.length, oldValues.length);
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
const newItem = newValues[i] || {};
|
||||||
|
const oldItem = oldValues[i] || {};
|
||||||
|
|
||||||
|
if ((newItem as any).quantity !== (oldItem as any).quantity) {
|
||||||
|
lastChangedField = 'quantity';
|
||||||
|
changedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((newItem as any).discount !== (oldItem as any).discount) {
|
||||||
|
lastChangedField = 'discount';
|
||||||
|
changedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((newItem as any).price !== (oldItem as any).price) {
|
||||||
|
lastChangedField = 'price';
|
||||||
|
changedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
form.setValue(field.name, newValues, { shouldDirty: true });
|
||||||
|
if (newValues?.length > 0) {
|
||||||
|
debouncedCalculateDiscount(
|
||||||
|
newValues,
|
||||||
|
field.name,
|
||||||
|
lastChangedField,
|
||||||
|
changedIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -5,18 +5,21 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { Label } from '@workspace/ui/components/label';
|
import { Label } from '@workspace/ui/components/label';
|
||||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||||
import { Textarea } from '@workspace/ui/components/textarea';
|
import { Textarea } from '@workspace/ui/components/textarea';
|
||||||
|
import { JSONEditor } from '@workspace/ui/custom-components/editor';
|
||||||
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
|
||||||
import { UploadImage } from '@workspace/ui/custom-components/upload-image';
|
import { UploadImage } from '@workspace/ui/custom-components/upload-image';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useRef } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
export default function Site() {
|
export default function Site() {
|
||||||
const t = useTranslations('system.site');
|
const t = useTranslations('system.site');
|
||||||
|
const ref = useRef<API.SiteConfig | undefined>(undefined);
|
||||||
const { data, refetch } = useQuery({
|
const { data, refetch } = useQuery({
|
||||||
queryKey: ['getSiteConfig'],
|
queryKey: ['getSiteConfig'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await getSiteConfig();
|
const { data } = await getSiteConfig();
|
||||||
|
ref.current = data.data;
|
||||||
return data.data;
|
return data.data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -25,7 +28,7 @@ export default function Site() {
|
|||||||
if (data?.[key] === value) return;
|
if (data?.[key] === value) return;
|
||||||
try {
|
try {
|
||||||
await updateSiteConfig({
|
await updateSiteConfig({
|
||||||
...data,
|
...ref.current,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
} as API.SiteConfig);
|
} as API.SiteConfig);
|
||||||
toast.success(t('saveSuccess'));
|
toast.success(t('saveSuccess'));
|
||||||
@@ -130,6 +133,49 @@ export default function Site() {
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
<Label>{t('customData')}</Label>
|
||||||
|
<p className='text-muted-foreground text-xs'>{t('customDataDescription')}</p>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className='w-1/2 text-right'>
|
||||||
|
<JSONEditor
|
||||||
|
schema={{
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: true,
|
||||||
|
properties: {
|
||||||
|
website: { type: 'string', title: 'Website' },
|
||||||
|
contacts: {
|
||||||
|
type: 'object',
|
||||||
|
title: 'Contacts',
|
||||||
|
additionalProperties: true,
|
||||||
|
properties: {
|
||||||
|
email: { type: 'string', title: 'Email' },
|
||||||
|
telephone: { type: 'string', title: 'Telephone' },
|
||||||
|
address: { type: 'string', title: 'Address' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
community: {
|
||||||
|
type: 'object',
|
||||||
|
title: 'Community',
|
||||||
|
additionalProperties: true,
|
||||||
|
properties: {
|
||||||
|
telegram: { type: 'string', title: 'Telegram' },
|
||||||
|
twitter: { type: 'string', title: 'Twitter' },
|
||||||
|
discord: { type: 'string', title: 'Discord' },
|
||||||
|
instagram: { type: 'string', title: 'Instagram' },
|
||||||
|
linkedin: { type: 'string', title: 'Linkedin' },
|
||||||
|
facebook: { type: 'string', title: 'Facebook' },
|
||||||
|
github: { type: 'string', title: 'Github' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
value={data?.custom_data}
|
||||||
|
onBlur={(value) => updateConfig('custom_data', value)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { getSystemLog, restartSystem } from '@/services/admin/tool';
|
import { getSystemLog, getVersion, restartSystem } from '@/services/admin/tool';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from '@workspace/ui/components/accordion';
|
} from '@workspace/ui/components/accordion';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
AlertDialogCancel,
|
||||||
AlertDialogContent,
|
AlertDialogContent,
|
||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
@@ -30,8 +29,10 @@ import {
|
|||||||
} from '@workspace/ui/components/card';
|
} from '@workspace/ui/components/card';
|
||||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
|
import { formatDate } from '@workspace/ui/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import packageJson from '../../../../../package.json';
|
||||||
|
|
||||||
const getLogLevelColor = (level: string) => {
|
const getLogLevelColor = (level: string) => {
|
||||||
const colorMap: { [key: string]: string } = {
|
const colorMap: { [key: string]: string } = {
|
||||||
@@ -59,6 +60,91 @@ export default function Page() {
|
|||||||
const [openRestart, setOpenRestart] = useState(false);
|
const [openRestart, setOpenRestart] = useState(false);
|
||||||
const [isRestarting, setIsRestarting] = useState(false);
|
const [isRestarting, setIsRestarting] = useState(false);
|
||||||
|
|
||||||
|
const { data: latestReleases } = useQuery({
|
||||||
|
queryKey: ['getLatestReleases'],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const [webResponse, serverResponse] = await Promise.all([
|
||||||
|
fetch('https://api.github.com/repos/perfect-panel/ppanel-web/releases/latest'),
|
||||||
|
fetch('https://api.github.com/repos/perfect-panel/server/releases/latest'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const webData = webResponse.ok ? await webResponse.json() : null;
|
||||||
|
const serverData = serverResponse.ok ? await serverResponse.json() : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
web: webData
|
||||||
|
? {
|
||||||
|
version: webData.tag_name,
|
||||||
|
url: webData.html_url,
|
||||||
|
publishedAt: webData.published_at,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
server: serverData
|
||||||
|
? {
|
||||||
|
version: serverData.tag_name,
|
||||||
|
url: serverData.html_url,
|
||||||
|
publishedAt: serverData.published_at,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch latest releases:', error);
|
||||||
|
return { web: null, server: null };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
staleTime: 60 * 60 * 1000,
|
||||||
|
retry: 1,
|
||||||
|
retryDelay: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查是否有新版本
|
||||||
|
const hasNewVersion =
|
||||||
|
latestReleases?.web && packageJson.version !== latestReleases.web.version.replace(/^v/, '');
|
||||||
|
|
||||||
|
const { data: systemInfo } = useQuery({
|
||||||
|
queryKey: ['getVersion'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await getVersion();
|
||||||
|
|
||||||
|
const versionString = data.data?.version || '';
|
||||||
|
const releaseVersionRegex = /^[Vv]?\d+\.\d+\.\d+$/;
|
||||||
|
const timeMatch = versionString.match(/\(([^)]+)\)/);
|
||||||
|
const timeInBrackets = timeMatch ? timeMatch[1] : '';
|
||||||
|
|
||||||
|
const versionWithoutTime = versionString.replace(/\([^)]*\)/, '').trim();
|
||||||
|
const isDevelopment = !releaseVersionRegex.test(versionWithoutTime);
|
||||||
|
|
||||||
|
let baseVersion = versionWithoutTime;
|
||||||
|
let versionSuffix = '';
|
||||||
|
let lastUpdated = '';
|
||||||
|
|
||||||
|
if (isDevelopment && versionWithoutTime.includes('-')) {
|
||||||
|
const parts = versionWithoutTime.split('-');
|
||||||
|
baseVersion = parts[0] || versionWithoutTime;
|
||||||
|
versionSuffix = parts.slice(1).join('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdated = formatDate(new Date(timeInBrackets || Date.now())) || '';
|
||||||
|
|
||||||
|
const displayVersion =
|
||||||
|
baseVersion.startsWith('V') || baseVersion.startsWith('v')
|
||||||
|
? baseVersion
|
||||||
|
: `V${baseVersion}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isRelease: !isDevelopment,
|
||||||
|
version: displayVersion,
|
||||||
|
lastUpdated,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasServerNewVersion =
|
||||||
|
latestReleases?.server &&
|
||||||
|
systemInfo &&
|
||||||
|
systemInfo.version.replace(/^V/, '') !== latestReleases.server.version.replace(/^v/, '');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className='border-none'>
|
<Card className='border-none'>
|
||||||
<CardHeader className='flex flex-col items-start justify-between sm:flex-row sm:items-center'>
|
<CardHeader className='flex flex-col items-start justify-between sm:flex-row sm:items-center'>
|
||||||
@@ -67,7 +153,7 @@ export default function Page() {
|
|||||||
<CardDescription>{t('viewLogsAndManage')}</CardDescription>
|
<CardDescription>{t('viewLogsAndManage')}</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-4 flex flex-col space-y-2 sm:mt-0 sm:flex-row sm:space-x-2 sm:space-y-0'>
|
<div className='mt-4 flex flex-col space-y-2 sm:mt-0 sm:flex-row sm:space-x-2 sm:space-y-0'>
|
||||||
<AlertDialog>
|
{/* <AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button>{t('systemUpgrade')}</Button>
|
<Button>{t('systemUpgrade')}</Button>
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
@@ -81,7 +167,7 @@ export default function Page() {
|
|||||||
<AlertDialogAction>{t('confirmUpgrade')}</AlertDialogAction>
|
<AlertDialogAction>{t('confirmUpgrade')}</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog> */}
|
||||||
<AlertDialog open={openRestart} onOpenChange={setOpenRestart}>
|
<AlertDialog open={openRestart} onOpenChange={setOpenRestart}>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant='destructive'>{t('systemReboot')}</Button>
|
<Button variant='destructive'>{t('systemReboot')}</Button>
|
||||||
@@ -112,13 +198,88 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='space-y-6'>
|
<div className='space-y-4'>
|
||||||
<div className='flex items-center justify-between'>
|
{/* 版本信息紧凑显示 */}
|
||||||
<div className='text-lg font-semibold'>
|
<div className='flex flex-col space-y-2 sm:flex-row sm:space-x-3 sm:space-y-0'>
|
||||||
{t('currentVersion')} <span>V1.0.0</span>
|
{/* 用户端/管理端版本 */}
|
||||||
|
<div className='bg-muted/30 flex flex-1 items-center justify-between rounded-md p-2'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Icon icon='mdi:web' className='mr-2 h-4 w-4 text-green-600' />
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<span className='text-xs font-medium'>{t('webVersion')}</span>
|
||||||
|
<Badge variant='default' className='px-1.5 py-0.5 text-xs'>
|
||||||
|
V{packageJson.version}
|
||||||
|
</Badge>
|
||||||
|
{hasNewVersion && (
|
||||||
|
<Badge variant='destructive' className='animate-pulse px-1.5 py-0.5 text-xs'>
|
||||||
|
{t('newVersionAvailable')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{hasNewVersion && (
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
size='sm'
|
||||||
|
className='h-6 px-2 text-xs'
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
latestReleases?.web?.url ||
|
||||||
|
'https://github.com/perfect-panel/ppanel-web/releases',
|
||||||
|
'_blank',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:open-in-new' className='mr-1 h-3 w-3' />
|
||||||
|
{t('viewNewVersion')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 服务端版本 */}
|
||||||
|
<div className='bg-muted/30 flex flex-1 items-center justify-between rounded-md p-2'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<Icon icon='mdi:server' className='mr-2 h-4 w-4 text-blue-600' />
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<span className='text-xs font-medium'>{t('serverVersion')}</span>
|
||||||
|
<Badge
|
||||||
|
variant={!systemInfo?.isRelease ? 'destructive' : 'default'}
|
||||||
|
className='px-1.5 py-0.5 text-xs'
|
||||||
|
>
|
||||||
|
{systemInfo?.version || 'V1.0.0'}
|
||||||
|
{!systemInfo?.isRelease && (
|
||||||
|
<span className='ml-1'>{t('developmentVersion')}</span>
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
{hasServerNewVersion && (
|
||||||
|
<Badge variant='destructive' className='animate-pulse px-1.5 py-0.5 text-xs'>
|
||||||
|
{t('newVersionAvailable')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
{hasServerNewVersion && (
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
size='sm'
|
||||||
|
className='h-6 px-2 text-xs'
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
latestReleases?.server?.url ||
|
||||||
|
'https://github.com/perfect-panel/server/releases',
|
||||||
|
'_blank',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:open-in-new' className='mr-1 h-3 w-3' />
|
||||||
|
{t('viewNewVersion')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className='text-muted-foreground hidden text-right text-xs sm:block'>
|
||||||
|
<div className='font-mono'>{systemInfo?.lastUpdated || '--'}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-muted-foreground text-sm'>
|
|
||||||
{t('lastUpdated')} <span>2024-12-16 12:00:00</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Card className='overflow-hidden'>
|
<Card className='overflow-hidden'>
|
||||||
@@ -146,20 +307,20 @@ export default function Page() {
|
|||||||
<AccordionItem key={index} value={`item-${index}`} className='px-4'>
|
<AccordionItem key={index} value={`item-${index}`} className='px-4'>
|
||||||
<AccordionTrigger className='hover:no-underline'>
|
<AccordionTrigger className='hover:no-underline'>
|
||||||
<div className='flex w-full flex-col items-start space-y-2 sm:flex-row sm:items-center sm:space-x-4 sm:space-y-0'>
|
<div className='flex w-full flex-col items-start space-y-2 sm:flex-row sm:items-center sm:space-x-4 sm:space-y-0'>
|
||||||
<Badge
|
<span className='text-xs font-medium sm:text-sm'>{log.timestamp}</span>
|
||||||
variant='outline'
|
|
||||||
className={`${getLogLevelColor(log.level)} mb-2 sm:mb-0`}
|
|
||||||
>
|
|
||||||
{log.level}
|
|
||||||
</Badge>
|
|
||||||
<span className='text-xs font-medium sm:text-sm'>{log.time}</span>
|
|
||||||
<span className='text-muted-foreground flex-1 truncate text-xs sm:text-sm'>
|
|
||||||
{log.message}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className='px-2'>
|
<AccordionContent className='px-2'>
|
||||||
<div className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'>
|
{Object.entries(log).map(([key, value]) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'
|
||||||
|
>
|
||||||
|
<span className='font-medium'>{key}:</span>
|
||||||
|
<span className='break-all'>{value as string}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{/* <div className='grid grid-cols-1 gap-2 text-xs sm:grid-cols-2 sm:text-sm'>
|
||||||
<div className='font-medium'>{t('ip')}:</div>
|
<div className='font-medium'>{t('ip')}:</div>
|
||||||
<div>{log.ip}</div>
|
<div>{log.ip}</div>
|
||||||
<div className='font-medium'>{t('request')}:</div>
|
<div className='font-medium'>{t('request')}:</div>
|
||||||
@@ -174,7 +335,7 @@ export default function Page() {
|
|||||||
<div className='break-all'>{log.query || t('none')}</div>
|
<div className='break-all'>{log.query || t('none')}</div>
|
||||||
<div className='font-medium'>{t('userAgent')}:</div>
|
<div className='font-medium'>{t('userAgent')}:</div>
|
||||||
<div className='break-all'>{log['user-agent']}</div>
|
<div className='break-all'>{log['user-agent']}</div>
|
||||||
</div>
|
</div> */}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ const formSchema = z.object({
|
|||||||
speed_limit: z.number().optional(),
|
speed_limit: z.number().optional(),
|
||||||
device_limit: z.number().optional(),
|
device_limit: z.number().optional(),
|
||||||
expired_at: z.number().nullish().optional(),
|
expired_at: z.number().nullish().optional(),
|
||||||
|
upload: z.number().optional(),
|
||||||
|
download: z.number().optional(),
|
||||||
|
id: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function SubscriptionForm({ trigger, title, loading, initialData, onSubmit }: Props) {
|
export function SubscriptionForm({ trigger, title, loading, initialData, onSubmit }: Props) {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
import { Display } from '@/components/display';
|
import { Display } from '@/components/display';
|
||||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||||
|
import { getSubscribeList } from '@/services/admin/subscribe';
|
||||||
import { createUser, deleteUser, getUserList, updateUserBasicInfo } from '@/services/admin/user';
|
import { createUser, deleteUser, getUserList, updateUserBasicInfo } from '@/services/admin/user';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Badge } from '@workspace/ui/components/badge';
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
import { Switch } from '@workspace/ui/components/switch';
|
||||||
@@ -20,8 +22,19 @@ export default function Page() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const ref = useRef<ProTableActions>(null);
|
const ref = useRef<ProTableActions>(null);
|
||||||
|
|
||||||
|
const { data: subscribeList } = useQuery({
|
||||||
|
queryKey: ['getSubscribeList', 'all'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await getSubscribeList({
|
||||||
|
page: 1,
|
||||||
|
size: 9999,
|
||||||
|
});
|
||||||
|
return data.data?.list as API.SubscribeGroup[];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProTable<API.User, Record<string, unknown>>
|
<ProTable<API.User, API.GetUserListParams>
|
||||||
action={ref}
|
action={ref}
|
||||||
header={{
|
header={{
|
||||||
title: t('userList'),
|
title: t('userList'),
|
||||||
@@ -58,8 +71,21 @@ export default function Page() {
|
|||||||
<Switch
|
<Switch
|
||||||
defaultChecked={row.getValue('enable')}
|
defaultChecked={row.getValue('enable')}
|
||||||
onCheckedChange={async (checked) => {
|
onCheckedChange={async (checked) => {
|
||||||
|
const {
|
||||||
|
auth_methods,
|
||||||
|
user_devices,
|
||||||
|
enable_balance_notify,
|
||||||
|
enable_login_notify,
|
||||||
|
enable_subscribe_notify,
|
||||||
|
enable_trade_notify,
|
||||||
|
updated_at,
|
||||||
|
created_at,
|
||||||
|
id,
|
||||||
|
...rest
|
||||||
|
} = row.original;
|
||||||
await updateUserBasicInfo({
|
await updateUserBasicInfo({
|
||||||
...row.original,
|
user_id: id,
|
||||||
|
...rest,
|
||||||
enable: checked,
|
enable: checked,
|
||||||
} as unknown as API.UpdateUserBasiceInfoRequest);
|
} as unknown as API.UpdateUserBasiceInfoRequest);
|
||||||
toast.success(t('updateSuccess'));
|
toast.success(t('updateSuccess'));
|
||||||
@@ -119,13 +145,38 @@ export default function Page() {
|
|||||||
cell: ({ row }) => formatDate(row.getValue('created_at')),
|
cell: ({ row }) => formatDate(row.getValue('created_at')),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
request={async (pagination) => {
|
request={async (pagination, filter) => {
|
||||||
const { data } = await getUserList(pagination);
|
const { data } = await getUserList({
|
||||||
|
...pagination,
|
||||||
|
...filter,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
list: data.data?.list || [],
|
list: data.data?.list || [],
|
||||||
total: data.data?.total || 0,
|
total: data.data?.total || 0,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
|
params={[
|
||||||
|
{
|
||||||
|
key: 'subscribe_id',
|
||||||
|
placeholder: t('subscription'),
|
||||||
|
options: subscribeList?.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: String(item.id),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'search',
|
||||||
|
placeholder: 'Search',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'user_id',
|
||||||
|
placeholder: t('userId'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'user_subscribe_id',
|
||||||
|
placeholder: t('subscriptionId'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
actions={{
|
actions={{
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -1,15 +1,121 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Display } from '@/components/display';
|
import { Display } from '@/components/display';
|
||||||
import { getUserDetail } from '@/services/admin/user';
|
import { getUserDetail, getUserSubscribeById } from '@/services/admin/user';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@workspace/ui/components/hover-card';
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@workspace/ui/components/hover-card';
|
||||||
import { formatDate } from '@workspace/ui/utils';
|
import { formatBytes, formatDate } from '@workspace/ui/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export function UserSubscribeDetail({ id, enabled }: { id: number; enabled: boolean }) {
|
||||||
|
const t = useTranslations('user');
|
||||||
|
|
||||||
|
const { data } = useQuery({
|
||||||
|
enabled: id !== 0 && enabled,
|
||||||
|
queryKey: ['getUserSubscribeById', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await getUserSubscribeById({ id });
|
||||||
|
return data.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!id) return '--';
|
||||||
|
|
||||||
|
const usedTraffic = data ? data.upload + data.download : 0;
|
||||||
|
const totalTraffic = data?.traffic || 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='space-y-4'>
|
||||||
|
<div>
|
||||||
|
<h3 className='mb-2 text-sm font-medium'>{t('subscriptionInfo')}</h3>
|
||||||
|
<div className='bg-muted/30 rounded-lg p-3'>
|
||||||
|
<ul className='grid gap-3'>
|
||||||
|
<li className='flex items-center justify-between font-semibold'>
|
||||||
|
<span className='text-muted-foreground'>{t('subscriptionId')}</span>
|
||||||
|
<span>{data?.id || '--'}</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('subscriptionName')}</span>
|
||||||
|
<span>{data?.subscribe?.name || '--'}</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('token')}</span>
|
||||||
|
<div className='font-mono text-xs' title={data?.token || ''}>
|
||||||
|
{data?.token || '--'}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('trafficUsage')}</span>
|
||||||
|
<span>
|
||||||
|
{data
|
||||||
|
? totalTraffic === 0
|
||||||
|
? `${formatBytes(usedTraffic)} / ${t('unlimited')}`
|
||||||
|
: `${formatBytes(usedTraffic)} / ${formatBytes(totalTraffic)}`
|
||||||
|
: '--'}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('startTime')}</span>
|
||||||
|
<span>{data?.start_time ? formatDate(data.start_time) : '--'}</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('expireTime')}</span>
|
||||||
|
<span>{data?.expire_time ? formatDate(data.expire_time) : '--'}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className='mb-2 text-sm font-medium'>
|
||||||
|
{t('userInfo')}
|
||||||
|
{data?.user_id && (
|
||||||
|
<Button
|
||||||
|
variant='link'
|
||||||
|
size='sm'
|
||||||
|
className='text-primary ml-2 h-auto p-0 text-xs'
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Link href={`/dashboard/user/${data.user_id}`}>{t('viewDetails')}</Link>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</h3>
|
||||||
|
<ul className='grid gap-3'>
|
||||||
|
<li className='flex items-center justify-between font-semibold'>
|
||||||
|
<span className='text-muted-foreground'>{t('userId')}</span>
|
||||||
|
<span>{data?.user_id}</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between font-semibold'>
|
||||||
|
<span className='text-muted-foreground'>{t('balance')}</span>
|
||||||
|
<span>
|
||||||
|
<Display type='currency' value={data?.user.balance} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('giftAmount')}</span>
|
||||||
|
<span>
|
||||||
|
<Display type='currency' value={data?.user?.gift_amount} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('commission')}</span>
|
||||||
|
<span>
|
||||||
|
<Display type='currency' value={data?.user?.commission} />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className='flex items-center justify-between'>
|
||||||
|
<span className='text-muted-foreground'>{t('createdAt')}</span>
|
||||||
|
<span>{data?.user?.created_at && formatDate(data?.user?.created_at)}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function UserDetail({ id }: { id: number }) {
|
export function UserDetail({ id }: { id: number }) {
|
||||||
const t = useTranslations('user');
|
const t = useTranslations('user');
|
||||||
const [shouldFetch, setShouldFetch] = useState(false);
|
const [shouldFetch, setShouldFetch] = useState(false);
|
||||||
|
|||||||
+11
-10
@@ -9,20 +9,20 @@ import { NextIntlClientProvider } from 'next-intl';
|
|||||||
import { getLocale, getMessages } from 'next-intl/server';
|
import { getLocale, getMessages } from 'next-intl/server';
|
||||||
import { PublicEnvScript } from 'next-runtime-env';
|
import { PublicEnvScript } from 'next-runtime-env';
|
||||||
import { unstable_noStore as noStore } from 'next/cache';
|
import { unstable_noStore as noStore } from 'next/cache';
|
||||||
import { Geist, Geist_Mono } from 'next/font/google';
|
// import { Geist, Geist_Mono } from 'next/font/google';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import NextTopLoader from 'nextjs-toploader';
|
import NextTopLoader from 'nextjs-toploader';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const fontSans = Geist({
|
// const fontSans = Geist({
|
||||||
subsets: ['latin'],
|
// subsets: ['latin'],
|
||||||
variable: '--font-sans',
|
// variable: '--font-sans',
|
||||||
});
|
// });
|
||||||
|
|
||||||
const fontMono = Geist_Mono({
|
// const fontMono = Geist_Mono({
|
||||||
subsets: ['latin'],
|
// subsets: ['latin'],
|
||||||
variable: '--font-mono',
|
// variable: '--font-mono',
|
||||||
});
|
// });
|
||||||
|
|
||||||
export async function generateMetadata(): Promise<Metadata> {
|
export async function generateMetadata(): Promise<Metadata> {
|
||||||
noStore();
|
noStore();
|
||||||
@@ -111,7 +111,8 @@ export default async function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className={`${fontSans.variable} ${fontMono.variable} size-full min-h-[calc(100dvh-env(safe-area-inset-top))] font-sans antialiased`}
|
// ${fontSans.variable} ${fontMono.variable}
|
||||||
|
className={`size-full min-h-[calc(100dvh-env(safe-area-inset-top))] font-sans antialiased`}
|
||||||
>
|
>
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
<NextTopLoader showSpinner={false} />
|
<NextTopLoader showSpinner={false} />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
|
||||||
import { queryServerTotalData, queryTicketWaitReply } from '@/services/admin/console';
|
import { queryServerTotalData, queryTicketWaitReply } from '@/services/admin/console';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@workspace/ui/components/select';
|
} from '@workspace/ui/components/select';
|
||||||
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
import { formatBytes } from '@workspace/ui/utils';
|
import { formatBytes } from '@workspace/ui/utils';
|
||||||
@@ -59,15 +61,13 @@ export default function Statistics() {
|
|||||||
users: {
|
users: {
|
||||||
today:
|
today:
|
||||||
ServerTotal?.user_traffic_ranking_today?.map((item) => ({
|
ServerTotal?.user_traffic_ranking_today?.map((item) => ({
|
||||||
name: item.user_id,
|
name: item.sid,
|
||||||
traffic: item.download + item.upload,
|
traffic: item.download + item.upload,
|
||||||
email: item.email,
|
|
||||||
})) || [],
|
})) || [],
|
||||||
yesterday:
|
yesterday:
|
||||||
ServerTotal?.user_traffic_ranking_yesterday?.map((item) => ({
|
ServerTotal?.user_traffic_ranking_yesterday?.map((item) => ({
|
||||||
name: item.user_id,
|
name: item.sid,
|
||||||
traffic: item.download + item.upload,
|
traffic: item.download + item.upload,
|
||||||
email: item.email,
|
|
||||||
})) || [],
|
})) || [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -175,10 +175,6 @@ export default function Statistics() {
|
|||||||
label: t('type'),
|
label: t('type'),
|
||||||
color: 'hsl(var(--muted))',
|
color: 'hsl(var(--muted))',
|
||||||
},
|
},
|
||||||
email: {
|
|
||||||
label: t('email'),
|
|
||||||
color: 'hsl(var(--muted))',
|
|
||||||
},
|
|
||||||
label: {
|
label: {
|
||||||
color: 'hsl(var(--foreground))',
|
color: 'hsl(var(--foreground))',
|
||||||
},
|
},
|
||||||
@@ -204,13 +200,22 @@ export default function Statistics() {
|
|||||||
tickFormatter={(value, index) => String(index + 1)}
|
tickFormatter={(value, index) => String(index + 1)}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
|
trigger='hover'
|
||||||
content={
|
content={
|
||||||
<ChartTooltipContent
|
<ChartTooltipContent
|
||||||
label={true}
|
label={true}
|
||||||
labelFormatter={(label) =>
|
labelFormatter={(label, [payload]) =>
|
||||||
dataType === 'nodes'
|
dataType === 'nodes' ? (
|
||||||
? `${t('nodes')}: ${label}`
|
`${t('nodes')}: ${label}`
|
||||||
: `${t('users')}: ${label}`
|
) : (
|
||||||
|
<>
|
||||||
|
<div className='w-80'>
|
||||||
|
<UserSubscribeDetail id={payload?.payload.name} enabled={true} />
|
||||||
|
</div>
|
||||||
|
<Separator className='my-2' />
|
||||||
|
<div>{`${t('users')}: ${label}`}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
formatter={(value) => {
|
formatter={(value) => {
|
||||||
return formatBytes(Number(value) || 0);
|
return formatBytes(Number(value) || 0);
|
||||||
|
|||||||
@@ -81,16 +81,26 @@ export const navs = [
|
|||||||
url: '/dashboard/server',
|
url: '/dashboard/server',
|
||||||
icon: 'flat-color-icons:data-protection',
|
icon: 'flat-color-icons:data-protection',
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
|
title: 'Application Management',
|
||||||
|
url: '/dashboard/application',
|
||||||
|
icon: 'flat-color-icons:touchscreen-smartphone',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Finance',
|
title: 'Rule Management',
|
||||||
items: [
|
url: '/dashboard/rules',
|
||||||
|
icon: 'flat-color-icons:ruler',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Subscribe Management',
|
title: 'Subscribe Management',
|
||||||
url: '/dashboard/subscribe',
|
url: '/dashboard/subscribe',
|
||||||
icon: 'flat-color-icons:shop',
|
icon: 'flat-color-icons:shop',
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Finance',
|
||||||
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Order Management',
|
title: 'Order Management',
|
||||||
url: '/dashboard/order',
|
url: '/dashboard/order',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
|||||||
site_logo: '',
|
site_logo: '',
|
||||||
keywords: '',
|
keywords: '',
|
||||||
custom_html: '',
|
custom_html: '',
|
||||||
|
custom_data: '',
|
||||||
},
|
},
|
||||||
verify: {
|
verify: {
|
||||||
turnstile_site_key: '',
|
turnstile_site_key: '',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Zadejte platnou e-mailovou adresu.",
|
"email": "Zadejte platnou e-mailovou adresu.",
|
||||||
"existingAccount": "Již máte účet?",
|
"existingAccount": "Již máte účet?",
|
||||||
"get": "Získat",
|
"get": "Získat",
|
||||||
"invite": "Pozvánkový kód",
|
"invite": "Pozvánkový kód (volitelné)",
|
||||||
"message": "#### Vážený uživateli,\n\nDěkujeme za váš zájem a podporu. V důsledku úpravy provozní strategie jsme uzavřeli registraci nových uživatelů. Během této doby nebude používání stávajících účtů nijak ovlivněno.\n\nSnažíme se vám poskytnout lepší služby a zážitky, a proto během uzavření registrace provedeme komplexní optimalizaci systému a aktualizaci funkcí. V budoucnu vás přivítáme s kvalitnějším obsahem a službami.\n\nSledujte naše webové stránky a sociální média pro nejnovější aktualizace a oznámení. Děkujeme za vaše pochopení a podporu.\n\nPokud máte jakékoli dotazy nebo potřebujete pomoc, neváhejte kontaktovat náš zákaznický servis.\n\n**Ještě jednou děkujeme za vaši podporu a pochopení.**",
|
"message": "#### Vážený uživateli,\n\nDěkujeme za váš zájem a podporu. V důsledku úpravy provozní strategie jsme uzavřeli registraci nových uživatelů. Během této doby nebude používání stávajících účtů nijak ovlivněno.\n\nSnažíme se vám poskytnout lepší služby a zážitky, a proto během uzavření registrace provedeme komplexní optimalizaci systému a aktualizaci funkcí. V budoucnu vás přivítáme s kvalitnějším obsahem a službami.\n\nSledujte naše webové stránky a sociální média pro nejnovější aktualizace a oznámení. Děkujeme za vaše pochopení a podporu.\n\nPokud máte jakékoli dotazy nebo potřebujete pomoc, neváhejte kontaktovat náš zákaznický servis.\n\n**Ještě jednou děkujeme za vaši podporu a pochopení.**",
|
||||||
"passwordMismatch": "Hesla se neshodují",
|
"passwordMismatch": "Hesla se neshodují",
|
||||||
"success": "Registrace úspěšná, automaticky přihlášeno!",
|
"success": "Registrace úspěšná, automaticky přihlášeno!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Stav",
|
"status": "Stav",
|
||||||
"subject": "Předmět",
|
"subject": "Předmět",
|
||||||
"template": "Šablona",
|
"template": "Šablona",
|
||||||
|
"traffic_exceed_email_template": "Šablona oznámení o překročení provozu",
|
||||||
|
"traffic_exceed_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
||||||
"verify_email_template": "Šablona ověřovacího e-mailu",
|
"verify_email_template": "Šablona ověřovacího e-mailu",
|
||||||
"verify_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
"verify_email_templateDescription": "Zástupné symboly {after}.variable{before} budou nahrazeny skutečnými daty. Ujistěte se, že tyto proměnné zachováte.",
|
||||||
"whitelistSuffixes": "Povolené přípony",
|
"whitelistSuffixes": "Povolené přípony",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "Konfigurace ADS",
|
"ADS Config": "Konfigurace ADS",
|
||||||
"Announcement Management": "Správa oznámení",
|
"Announcement Management": "Správa oznámení",
|
||||||
"Apple": "Apple ID",
|
"Apple": "Apple ID",
|
||||||
|
"Application Management": "Správa aplikací",
|
||||||
"Auth Control": "Řízení ověřování",
|
"Auth Control": "Řízení ověřování",
|
||||||
"Coupon Management": "Správa kupónů",
|
"Coupon Management": "Správa kupónů",
|
||||||
"Dashboard": "Přístrojová deska",
|
"Dashboard": "Přístrojová deska",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Správa objednávek",
|
"Order Management": "Správa objednávek",
|
||||||
"Payment Config": "Konfigurace platby",
|
"Payment Config": "Konfigurace platby",
|
||||||
"Phone Number": "Telefonní číslo",
|
"Phone Number": "Telefonní číslo",
|
||||||
|
"Rule Management": "Správa pravidel",
|
||||||
"Server": "Server",
|
"Server": "Server",
|
||||||
"Server Management": "Správa serveru",
|
"Server Management": "Správa serveru",
|
||||||
"Settings": "Nastavení",
|
"Settings": "Nastavení",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Popis",
|
"description": "Popis",
|
||||||
"descriptionPlaceholder": "Zadejte popis platební metody",
|
"descriptionPlaceholder": "Zadejte popis platební metody",
|
||||||
"domain": "Doména",
|
"domain": "Doména",
|
||||||
"domainPlaceholder": "volitelné, např. {example}",
|
|
||||||
"edit": "Upravit",
|
"edit": "Upravit",
|
||||||
"editPayment": "Upravit platební metodu",
|
"editPayment": "Upravit platební metodu",
|
||||||
"enable": "Povolit",
|
"enable": "Povolit",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analyzuji...",
|
||||||
|
"appIcon": "Ikona pravidla",
|
||||||
|
"cancel": "Zrušit",
|
||||||
|
"confirm": "Potvrdit",
|
||||||
|
"confirmDelete": "Potvrdit smazání",
|
||||||
|
"create": "Přidat pravidlo",
|
||||||
|
"createRule": "Přidat pravidlo",
|
||||||
|
"createSuccess": "Pravidlo bylo úspěšně vytvořeno",
|
||||||
|
"createdAt": "Vytvořeno dne",
|
||||||
|
"delete": "Smazat",
|
||||||
|
"deleteSuccess": "Pravidlo bylo úspěšně smazáno",
|
||||||
|
"deleteWarning": "Opravdu chcete smazat toto pravidlo? Tato akce není vratná.",
|
||||||
|
"downloadTemplate": "Stáhnout šablonu",
|
||||||
|
"edit": "Upravit",
|
||||||
|
"editRule": "Upravit pravidlo",
|
||||||
|
"enable": "Povolit",
|
||||||
|
"enterIconUrl": "Zadejte nebo nahrajte ikonu",
|
||||||
|
"enterRuleName": "Zadejte název pravidla",
|
||||||
|
"enterRules": "Zadejte obsah pravidla, jedno na řádek",
|
||||||
|
"import": "Importovat",
|
||||||
|
"importFailed": "Import se nezdařil",
|
||||||
|
"importSuccess": "Import byl úspěšný",
|
||||||
|
"importYamlDescription": "Importujte pravidla ze souboru YAML, systém automaticky vytvoří skupiny pravidel na základě názvů skupin politiky",
|
||||||
|
"importYamlRules": "Importovat YAML pravidla",
|
||||||
|
"importing": "Importuji...",
|
||||||
|
"invalidYamlFormat": "Neplatný formát YAML, chybí pole pravidel",
|
||||||
|
"name": "Název pravidla",
|
||||||
|
"noValidRules": "Nebyly nalezeny žádné platné pravidla",
|
||||||
|
"pleaseUploadFile": "Prosím, nahrajte soubor YAML",
|
||||||
|
"preview": "Náhled",
|
||||||
|
"rulesFormat": "Formát pravidla: typ pravidla, obsah shody, [politika], kde politika je volitelná.\nPokud politika není specifikována, bude automaticky použito aktuální jméno skupiny pravidel. Příklady:",
|
||||||
|
"rulesLabel": "Obsah pravidla",
|
||||||
|
"searchRule": "Hledat název pravidla",
|
||||||
|
"selectTags": "Vybrat značky uzlu",
|
||||||
|
"tags": "Značky uzlu",
|
||||||
|
"tagsLabel": "Značky uzlu",
|
||||||
|
"updateSuccess": "Pravidlo bylo úspěšně aktualizováno"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Limit na nákup/čas",
|
"quota": "Limit na nákup/čas",
|
||||||
"replacement": "Obnovení ceny/krát",
|
"replacement": "Obnovení ceny/krát",
|
||||||
"sell": "Prodej",
|
"sell": "Prodej",
|
||||||
"show": "Zobrazit na hlavní stránce",
|
"show": "Zobrazit",
|
||||||
"sold": "Počet předplatných",
|
"sold": "Počet předplatných",
|
||||||
"subscribe": "Předplatit",
|
"subscribe": "Předplatit",
|
||||||
"subscribeGroup": "Přihlásit se ke skupině",
|
"subscribeGroup": "Přihlásit se ke skupině",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Zásady ochrany osobních údajů"
|
"title": "Zásady ochrany osobních údajů"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Vlastní data",
|
||||||
|
"customDataDescription": "Vlastní data, používaná pro vlastní data webu",
|
||||||
"customHtml": "Vlastní HTML",
|
"customHtml": "Vlastní HTML",
|
||||||
"customHtmlDescription": "Vlastní HTML kód, který bude vložen na konec tagu body na stránce.",
|
"customHtmlDescription": "Vlastní HTML kód, který bude vložen na konec tagu body na stránce.",
|
||||||
"keywords": "Klíčová slova",
|
"keywords": "Klíčová slova",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Potvrdit aktualizaci systému",
|
"confirmSystemUpgrade": "Potvrdit aktualizaci systému",
|
||||||
"confirmUpgrade": "Potvrdit aktualizaci",
|
"confirmUpgrade": "Potvrdit aktualizaci",
|
||||||
"currentVersion": "Aktuální verze systému:",
|
"currentVersion": "Aktuální verze systému:",
|
||||||
|
"developmentVersion": "Vývojová verze",
|
||||||
"errors": "Chyby",
|
"errors": "Chyby",
|
||||||
"ip": "IP",
|
"ip": "IP adresa",
|
||||||
"lastUpdated": "Poslední aktualizace:",
|
"lastUpdated": "Poslední aktualizace:",
|
||||||
|
"newVersionAvailable": "Nová verze",
|
||||||
"none": "Žádný",
|
"none": "Žádný",
|
||||||
"query": "Dotaz",
|
"query": "Dotaz",
|
||||||
"rebootDescription": "Opravdu chcete restartovat systém? Tato akce způsobí krátké přerušení služby.",
|
"rebootDescription": "Opravdu chcete restartovat systém? Tato akce způsobí krátké přerušení služby.",
|
||||||
"rebooting": "Systém se restartuje...",
|
"rebooting": "Systém se restartuje...",
|
||||||
"refreshLogs": "Obnovit protokoly",
|
"refreshLogs": "Obnovit protokoly",
|
||||||
"request": "Žádost",
|
"request": "Žádost",
|
||||||
|
"serverVersion": "Server",
|
||||||
"status": "Stav",
|
"status": "Stav",
|
||||||
"systemLogs": "Systémové protokoly",
|
"systemLogs": "Systémové protokoly",
|
||||||
"systemReboot": "Restart systému",
|
"systemReboot": "Restart systému",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "Aktualizace systému",
|
"systemUpgrade": "Aktualizace systému",
|
||||||
"upgradeDescription": "Jste si jisti, že chcete provést aktualizaci systému? Tato operace může trvat několik minut a během ní může být systém neodpovídající.",
|
"upgradeDescription": "Jste si jisti, že chcete provést aktualizaci systému? Tato operace může trvat několik minut a během ní může být systém neodpovídající.",
|
||||||
"userAgent": "Uživatelský agent",
|
"userAgent": "Uživatelský agent",
|
||||||
"viewLogsAndManage": "Zobrazit systémové protokoly, provádět aktualizace systému a restartovat operace"
|
"versionInfo": "Informace o verzi",
|
||||||
|
"viewLogsAndManage": "Zobrazit systémové protokoly, provádět aktualizace systému a restartovat operace",
|
||||||
|
"viewNewVersion": "Zobrazit novou verzi",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Povolení účtu",
|
"accountEnable": "Povolení účtu",
|
||||||
"actions": "akce",
|
"actions": "akce",
|
||||||
|
"active": "Aktivní",
|
||||||
"add": "Přidat",
|
"add": "Přidat",
|
||||||
"administrator": "Administrátor",
|
"administrator": "Administrátor",
|
||||||
"areaCode": "Směrový kód",
|
"areaCode": "Směrový kód",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "e-mail",
|
"email": "e-mail",
|
||||||
"enable": "Povolit",
|
"enable": "Povolit",
|
||||||
"expireTime": "Čas vypršení",
|
"expireTime": "Čas vypršení",
|
||||||
|
"expired": "Vypršelo",
|
||||||
"expiredAt": "Platnost vypršela",
|
"expiredAt": "Platnost vypršela",
|
||||||
"failed": "Neúspěch",
|
"failed": "Neúspěch",
|
||||||
"giftAmount": "Částka dárku",
|
"giftAmount": "Částka dárku",
|
||||||
"giftAmountPlaceholder": "Zadejte částku dárku",
|
"giftAmountPlaceholder": "Zadejte částku dárku",
|
||||||
|
"inactive": "Neaktivní",
|
||||||
"invalidEmailFormat": "Neplatný formát e-mailu",
|
"invalidEmailFormat": "Neplatný formát e-mailu",
|
||||||
"inviteCode": "Pozvánkový kód",
|
"inviteCode": "Pozvánkový kód",
|
||||||
"inviteCodePlaceholder": "Zadejte kód pozvánky (ponechte prázdné pro vygenerování)",
|
"inviteCodePlaceholder": "Zadejte kód pozvánky (ponechte prázdné pro vygenerování)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Zadejte nové heslo (volitelné)",
|
"passwordPlaceholder": "Zadejte nové heslo (volitelné)",
|
||||||
"permanent": "Trvalý",
|
"permanent": "Trvalý",
|
||||||
"pleaseEnterEmail": "Prosím, zadejte e-mail",
|
"pleaseEnterEmail": "Prosím, zadejte e-mail",
|
||||||
|
"price": "Cena",
|
||||||
"referer": "Doporučitel",
|
"referer": "Doporučitel",
|
||||||
"refererId": "ID doporučitele",
|
"refererId": "ID doporučitele",
|
||||||
"refererIdPlaceholder": "Zadejte ID doporučitele",
|
"refererIdPlaceholder": "Zadejte ID doporučitele",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "Vyhledat IP adresu",
|
"searchIp": "Vyhledat IP adresu",
|
||||||
"selectAuthType": "Vyberte typ ověření",
|
"selectAuthType": "Vyberte typ ověření",
|
||||||
"speedLimit": "Rychlostní limit",
|
"speedLimit": "Rychlostní limit",
|
||||||
|
"startTime": "Čas zahájení",
|
||||||
|
"status": "Stav",
|
||||||
"subscription": "Předplatné",
|
"subscription": "Předplatné",
|
||||||
"subscriptionDetails": "Podrobnosti o předplatném",
|
"subscriptionDetails": "Podrobnosti o předplatném",
|
||||||
|
"subscriptionId": "ID předplatného",
|
||||||
|
"subscriptionInfo": "Informace o předplatném",
|
||||||
"subscriptionList": "Seznam předplatných",
|
"subscriptionList": "Seznam předplatných",
|
||||||
"subscriptionLogs": "Záznamy o předplatném",
|
"subscriptionLogs": "Záznamy o předplatném",
|
||||||
"subscriptionName": "Název předplatného",
|
"subscriptionName": "Název předplatného",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Obchodní oznámení",
|
"tradeNotifications": "Obchodní oznámení",
|
||||||
"trafficLimit": "Limit provozu",
|
"trafficLimit": "Limit provozu",
|
||||||
"trafficLogs": "Protokoly provozu",
|
"trafficLogs": "Protokoly provozu",
|
||||||
|
"trafficUsage": "Využití dat",
|
||||||
|
"unknown": "Neznámé",
|
||||||
"unlimited": "Neomezený",
|
"unlimited": "Neomezený",
|
||||||
"unverified": "Neověřeno",
|
"unverified": "Neověřeno",
|
||||||
"update": "Aktualizovat",
|
"update": "Aktualizovat",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "Uživatelský agent",
|
"userAgent": "Uživatelský agent",
|
||||||
"userEmail": "Uživatelský e-mail",
|
"userEmail": "Uživatelský e-mail",
|
||||||
"userEmailPlaceholder": "Zadejte e-mail uživatele",
|
"userEmailPlaceholder": "Zadejte e-mail uživatele",
|
||||||
|
"userId": "ID uživatele",
|
||||||
|
"userInfo": "Informace o uživateli",
|
||||||
"userList": "Seznam uživatelů",
|
"userList": "Seznam uživatelů",
|
||||||
"userLogs": "Historie přihlášení",
|
"userLogs": "Historie přihlášení",
|
||||||
"userName": "Identifikátor uživatele",
|
"userName": "Identifikátor uživatele",
|
||||||
"userOrders": "Objednávky",
|
"userOrders": "Objednávky",
|
||||||
"userProfile": "Profil",
|
"userProfile": "Profil",
|
||||||
"userSubscriptions": "Předplatné",
|
"userSubscriptions": "Předplatné",
|
||||||
"verified": "Ověřeno"
|
"username": "Uživatelské jméno",
|
||||||
|
"verified": "Ověřeno",
|
||||||
|
"viewDetails": "Zobrazit podrobnosti"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
"email": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
||||||
"existingAccount": "Bereits ein Konto?",
|
"existingAccount": "Bereits ein Konto?",
|
||||||
"get": "Erhalten",
|
"get": "Erhalten",
|
||||||
"invite": "Einladungscode",
|
"invite": "Einladungscode (optional)",
|
||||||
"message": "#### Sehr geehrter Nutzer,\n\nVielen Dank für Ihr Interesse und Ihre Unterstützung. Aufgrund einer Anpassung unserer Betriebsstrategie haben wir die Registrierung für neue Nutzer geschlossen. Während dieser Zeit wird die Nutzung für bestehende Nutzer nicht beeinträchtigt.\n\nWir sind bestrebt, Ihnen einen besseren Service und ein besseres Erlebnis zu bieten, und werden daher während der Schließung der Registrierung umfassende Systemoptimierungen und Funktionsupgrades durchführen. In Zukunft werden wir Sie mit hochwertigeren Inhalten und Dienstleistungen willkommen heißen.\n\nBitte folgen Sie unserer Website und unseren Social-Media-Plattformen, um die neuesten Updates und Benachrichtigungen zu erhalten. Vielen Dank für Ihr Verständnis und Ihre Unterstützung.\n\nBei Fragen oder wenn Sie Hilfe benötigen, kontaktieren Sie bitte jederzeit unser Kundenserviceteam.\n\n**Nochmals vielen Dank für Ihre Unterstützung und Ihr Verständnis.**",
|
"message": "#### Sehr geehrter Nutzer,\n\nVielen Dank für Ihr Interesse und Ihre Unterstützung. Aufgrund einer Anpassung unserer Betriebsstrategie haben wir die Registrierung für neue Nutzer geschlossen. Während dieser Zeit wird die Nutzung für bestehende Nutzer nicht beeinträchtigt.\n\nWir sind bestrebt, Ihnen einen besseren Service und ein besseres Erlebnis zu bieten, und werden daher während der Schließung der Registrierung umfassende Systemoptimierungen und Funktionsupgrades durchführen. In Zukunft werden wir Sie mit hochwertigeren Inhalten und Dienstleistungen willkommen heißen.\n\nBitte folgen Sie unserer Website und unseren Social-Media-Plattformen, um die neuesten Updates und Benachrichtigungen zu erhalten. Vielen Dank für Ihr Verständnis und Ihre Unterstützung.\n\nBei Fragen oder wenn Sie Hilfe benötigen, kontaktieren Sie bitte jederzeit unser Kundenserviceteam.\n\n**Nochmals vielen Dank für Ihre Unterstützung und Ihr Verständnis.**",
|
||||||
"passwordMismatch": "Die eingegebenen Passwörter stimmen nicht überein",
|
"passwordMismatch": "Die eingegebenen Passwörter stimmen nicht überein",
|
||||||
"success": "Registrierung erfolgreich, Sie sind automatisch eingeloggt!",
|
"success": "Registrierung erfolgreich, Sie sind automatisch eingeloggt!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Betreff",
|
"subject": "Betreff",
|
||||||
"template": "Vorlage",
|
"template": "Vorlage",
|
||||||
|
"traffic_exceed_email_template": "Benachrichtigung über überschreitenden Verkehr",
|
||||||
|
"traffic_exceed_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass Sie diese Variablen beibehalten.",
|
||||||
"verify_email_template": "E-Mail-Bestätigungsvorlage",
|
"verify_email_template": "E-Mail-Bestätigungsvorlage",
|
||||||
"verify_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass diese Variablen beibehalten werden.",
|
"verify_email_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tatsächliche Daten ersetzt. Stellen Sie sicher, dass diese Variablen beibehalten werden.",
|
||||||
"whitelistSuffixes": "Whitelist-Suffixe",
|
"whitelistSuffixes": "Whitelist-Suffixe",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "ADS-Konfiguration",
|
"ADS Config": "ADS-Konfiguration",
|
||||||
"Announcement Management": "Ankündigungsverwaltung",
|
"Announcement Management": "Ankündigungsverwaltung",
|
||||||
"Apple": "Apple-ID",
|
"Apple": "Apple-ID",
|
||||||
|
"Application Management": "Anwendungsverwaltung",
|
||||||
"Auth Control": "Authentifizierungskontrolle",
|
"Auth Control": "Authentifizierungskontrolle",
|
||||||
"Coupon Management": "Gutscheinverwaltung",
|
"Coupon Management": "Gutscheinverwaltung",
|
||||||
"Dashboard": "Armaturenbrett",
|
"Dashboard": "Armaturenbrett",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Bestellverwaltung",
|
"Order Management": "Bestellverwaltung",
|
||||||
"Payment Config": "Zahlungskonfiguration",
|
"Payment Config": "Zahlungskonfiguration",
|
||||||
"Phone Number": "Telefonnummer",
|
"Phone Number": "Telefonnummer",
|
||||||
|
"Rule Management": "Regelverwaltung",
|
||||||
"Server": "Dienst",
|
"Server": "Dienst",
|
||||||
"Server Management": "Serververwaltung",
|
"Server Management": "Serververwaltung",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"descriptionPlaceholder": "Geben Sie die Beschreibung der Zahlungsmethode ein",
|
"descriptionPlaceholder": "Geben Sie die Beschreibung der Zahlungsmethode ein",
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"domainPlaceholder": "optional, z.B. {example}",
|
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"editPayment": "Zahlungsmethode bearbeiten",
|
"editPayment": "Zahlungsmethode bearbeiten",
|
||||||
"enable": "Aktivieren",
|
"enable": "Aktivieren",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analysiere...",
|
||||||
|
"appIcon": "Regel-Icon",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"confirmDelete": "Löschen bestätigen",
|
||||||
|
"create": "Regel hinzufügen",
|
||||||
|
"createRule": "Regel hinzufügen",
|
||||||
|
"createSuccess": "Regel erfolgreich erstellt",
|
||||||
|
"createdAt": "Erstellt am",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"deleteSuccess": "Regel erfolgreich gelöscht",
|
||||||
|
"deleteWarning": "Sind Sie sicher, dass Sie diese Regel löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
|
"downloadTemplate": "Vorlage herunterladen",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"editRule": "Regel bearbeiten",
|
||||||
|
"enable": "Aktivieren",
|
||||||
|
"enterIconUrl": "Geben Sie eine URL für das Icon ein oder laden Sie es hoch",
|
||||||
|
"enterRuleName": "Geben Sie den Regelname ein",
|
||||||
|
"enterRules": "Geben Sie den Regelinhalt ein, jeweils eine Regel pro Zeile",
|
||||||
|
"import": "Importieren",
|
||||||
|
"importFailed": "Import fehlgeschlagen",
|
||||||
|
"importSuccess": "Import erfolgreich",
|
||||||
|
"importYamlDescription": "Regeln aus einer YAML-Datei importieren, das System erstellt automatisch Regelgruppen basierend auf den Namen der Richtliniengruppen",
|
||||||
|
"importYamlRules": "YAML-Regeln importieren",
|
||||||
|
"importing": "Importiere...",
|
||||||
|
"invalidYamlFormat": "Ungültiges YAML-Format, das Feld 'Regeln' fehlt",
|
||||||
|
"name": "Regelname",
|
||||||
|
"noValidRules": "Keine gültigen Regeln gefunden",
|
||||||
|
"pleaseUploadFile": "Bitte laden Sie eine YAML-Datei hoch",
|
||||||
|
"preview": "Vorschau",
|
||||||
|
"rulesFormat": "Regelformat: Regeltyp, Übereinstimmungsinhalt, [Richtlinie], wobei die Richtlinie optional ist.\nWenn keine Richtlinie angegeben ist, wird automatisch der aktuelle Regelgruppenname verwendet. Beispiele:",
|
||||||
|
"rulesLabel": "Regelinhalt",
|
||||||
|
"searchRule": "Regelname suchen",
|
||||||
|
"selectTags": "Knoten-Tags auswählen",
|
||||||
|
"tags": "Knoten-Tags",
|
||||||
|
"tagsLabel": "Knoten-Tags",
|
||||||
|
"updateSuccess": "Regel erfolgreich aktualisiert"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Kaufbeschränkung/pro Einkauf",
|
"quota": "Kaufbeschränkung/pro Einkauf",
|
||||||
"replacement": "Preis pro Zurücksetzung",
|
"replacement": "Preis pro Zurücksetzung",
|
||||||
"sell": "Verkauf",
|
"sell": "Verkauf",
|
||||||
"show": "Startseite anzeigen",
|
"show": "Anzeigen",
|
||||||
"sold": "Abonnentenzahl",
|
"sold": "Abonnentenzahl",
|
||||||
"subscribe": "Abonnieren",
|
"subscribe": "Abonnieren",
|
||||||
"subscribeGroup": "Gruppe abonnieren",
|
"subscribeGroup": "Gruppe abonnieren",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Datenschutzrichtlinie"
|
"title": "Datenschutzrichtlinie"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Benutzerdefinierte Daten",
|
||||||
|
"customDataDescription": "Benutzerdefinierte Daten, die für benutzerdefinierte Daten der Website verwendet werden",
|
||||||
"customHtml": "Benutzerdefiniertes HTML",
|
"customHtml": "Benutzerdefiniertes HTML",
|
||||||
"customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingefügt werden soll.",
|
"customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingefügt werden soll.",
|
||||||
"keywords": "Schlüsselwörter",
|
"keywords": "Schlüsselwörter",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Systemaktualisierung bestätigen",
|
"confirmSystemUpgrade": "Systemaktualisierung bestätigen",
|
||||||
"confirmUpgrade": "Upgrade bestätigen",
|
"confirmUpgrade": "Upgrade bestätigen",
|
||||||
"currentVersion": "Aktuelle Systemversion:",
|
"currentVersion": "Aktuelle Systemversion:",
|
||||||
|
"developmentVersion": "Entwicklungsversion",
|
||||||
"errors": "Fehler",
|
"errors": "Fehler",
|
||||||
"ip": "IP",
|
"ip": "IP-Adresse",
|
||||||
"lastUpdated": "Zuletzt aktualisiert:",
|
"lastUpdated": "Zuletzt aktualisiert:",
|
||||||
|
"newVersionAvailable": "Neue Version",
|
||||||
"none": "Keine",
|
"none": "Keine",
|
||||||
"query": "Abfrage",
|
"query": "Abfrage",
|
||||||
"rebootDescription": "Möchten Sie das System wirklich neu starten? Dieser Vorgang führt zu einer kurzen Unterbrechung des Dienstes.",
|
"rebootDescription": "Möchten Sie das System wirklich neu starten? Dieser Vorgang führt zu einer kurzen Unterbrechung des Dienstes.",
|
||||||
"rebooting": "System wird neu gestartet...",
|
"rebooting": "System wird neu gestartet...",
|
||||||
"refreshLogs": "Protokolle aktualisieren",
|
"refreshLogs": "Protokolle aktualisieren",
|
||||||
"request": "Anfrage",
|
"request": "Anfrage",
|
||||||
|
"serverVersion": "Server",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"systemLogs": "Systemprotokolle",
|
"systemLogs": "Systemprotokolle",
|
||||||
"systemReboot": "Systemneustart",
|
"systemReboot": "Systemneustart",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "Systemaktualisierung",
|
"systemUpgrade": "Systemaktualisierung",
|
||||||
"upgradeDescription": "Möchten Sie das System-Upgrade wirklich durchführen? Dieser Vorgang kann einige Minuten dauern, währenddessen das System möglicherweise nicht reagiert.",
|
"upgradeDescription": "Möchten Sie das System-Upgrade wirklich durchführen? Dieser Vorgang kann einige Minuten dauern, währenddessen das System möglicherweise nicht reagiert.",
|
||||||
"userAgent": "Benutzeragent",
|
"userAgent": "Benutzeragent",
|
||||||
"viewLogsAndManage": "Systemprotokolle anzeigen, Systemaktualisierungen durchführen und Neustartvorgänge ausführen"
|
"versionInfo": "Versionsinformationen",
|
||||||
|
"viewLogsAndManage": "Systemprotokolle anzeigen, Systemaktualisierungen durchführen und Neustartvorgänge ausführen",
|
||||||
|
"viewNewVersion": "Neue Version anzeigen",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Konto aktivieren",
|
"accountEnable": "Konto aktivieren",
|
||||||
"actions": "Aktionen",
|
"actions": "Aktionen",
|
||||||
|
"active": "Aktiv",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"areaCode": "Vorwahl",
|
"areaCode": "Vorwahl",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"enable": "Aktivieren",
|
"enable": "Aktivieren",
|
||||||
"expireTime": "Ablaufzeit",
|
"expireTime": "Ablaufzeit",
|
||||||
|
"expired": "Abgelaufen",
|
||||||
"expiredAt": "Abgelaufen am",
|
"expiredAt": "Abgelaufen am",
|
||||||
"failed": "Fehlgeschlagen",
|
"failed": "Fehlgeschlagen",
|
||||||
"giftAmount": "Geschenkbetrag",
|
"giftAmount": "Geschenkbetrag",
|
||||||
"giftAmountPlaceholder": "Geben Sie den Geschenkbetrag ein",
|
"giftAmountPlaceholder": "Geben Sie den Geschenkbetrag ein",
|
||||||
|
"inactive": "Inaktiv",
|
||||||
"invalidEmailFormat": "Ungültiges E-Mail-Format",
|
"invalidEmailFormat": "Ungültiges E-Mail-Format",
|
||||||
"inviteCode": "Einladungscode",
|
"inviteCode": "Einladungscode",
|
||||||
"inviteCodePlaceholder": "Einladungscode eingeben (leer lassen, um zu generieren)",
|
"inviteCodePlaceholder": "Einladungscode eingeben (leer lassen, um zu generieren)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Neues Passwort eingeben (optional)",
|
"passwordPlaceholder": "Neues Passwort eingeben (optional)",
|
||||||
"permanent": "Dauerhaft",
|
"permanent": "Dauerhaft",
|
||||||
"pleaseEnterEmail": "Bitte E-Mail eingeben",
|
"pleaseEnterEmail": "Bitte E-Mail eingeben",
|
||||||
|
"price": "Preis",
|
||||||
"referer": "Empfehler",
|
"referer": "Empfehler",
|
||||||
"refererId": "Referenz-ID",
|
"refererId": "Referenz-ID",
|
||||||
"refererIdPlaceholder": "Geben Sie die Referenz-ID ein",
|
"refererIdPlaceholder": "Geben Sie die Referenz-ID ein",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "IP-Adresse suchen",
|
"searchIp": "IP-Adresse suchen",
|
||||||
"selectAuthType": "Authentifizierungstyp auswählen",
|
"selectAuthType": "Authentifizierungstyp auswählen",
|
||||||
"speedLimit": "Geschwindigkeitsbegrenzung",
|
"speedLimit": "Geschwindigkeitsbegrenzung",
|
||||||
|
"startTime": "Startzeit",
|
||||||
|
"status": "Status",
|
||||||
"subscription": "Abonnement",
|
"subscription": "Abonnement",
|
||||||
"subscriptionDetails": "Abonnementdetails",
|
"subscriptionDetails": "Abonnementdetails",
|
||||||
|
"subscriptionId": "Abonnement-ID",
|
||||||
|
"subscriptionInfo": "Abonnement-Informationen",
|
||||||
"subscriptionList": "Abonnementsliste",
|
"subscriptionList": "Abonnementsliste",
|
||||||
"subscriptionLogs": "Abonnementprotokolle",
|
"subscriptionLogs": "Abonnementprotokolle",
|
||||||
"subscriptionName": "Abonnementname",
|
"subscriptionName": "Abonnementname",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Handelsbenachrichtigungen",
|
"tradeNotifications": "Handelsbenachrichtigungen",
|
||||||
"trafficLimit": "Verkehrslimit",
|
"trafficLimit": "Verkehrslimit",
|
||||||
"trafficLogs": "Verkehrsprotokolle",
|
"trafficLogs": "Verkehrsprotokolle",
|
||||||
|
"trafficUsage": "Datenverbrauch",
|
||||||
|
"unknown": "Unbekannt",
|
||||||
"unlimited": "Unbegrenzt",
|
"unlimited": "Unbegrenzt",
|
||||||
"unverified": "Unbestätigt",
|
"unverified": "Unbestätigt",
|
||||||
"update": "Aktualisieren",
|
"update": "Aktualisieren",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "Benutzeragent",
|
"userAgent": "Benutzeragent",
|
||||||
"userEmail": "Benutzer-E-Mail",
|
"userEmail": "Benutzer-E-Mail",
|
||||||
"userEmailPlaceholder": "Benutzer-E-Mail eingeben",
|
"userEmailPlaceholder": "Benutzer-E-Mail eingeben",
|
||||||
|
"userId": "Benutzer-ID",
|
||||||
|
"userInfo": "Benutzerinformationen",
|
||||||
"userList": "Benutzerliste",
|
"userList": "Benutzerliste",
|
||||||
"userLogs": "Anmeldeverlauf",
|
"userLogs": "Anmeldeverlauf",
|
||||||
"userName": "Benutzerkennung",
|
"userName": "Benutzerkennung",
|
||||||
"userOrders": "Bestellungen",
|
"userOrders": "Bestellungen",
|
||||||
"userProfile": "Profil",
|
"userProfile": "Profil",
|
||||||
"userSubscriptions": "Abonnements",
|
"userSubscriptions": "Abonnements",
|
||||||
"verified": "Verifiziert"
|
"username": "Benutzername",
|
||||||
|
"verified": "Verifiziert",
|
||||||
|
"viewDetails": "Details anzeigen"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Please enter a valid email address.",
|
"email": "Please enter a valid email address.",
|
||||||
"existingAccount": "Already have an account?",
|
"existingAccount": "Already have an account?",
|
||||||
"get": "Get",
|
"get": "Get",
|
||||||
"invite": "Invitation code",
|
"invite": "Invitation code (optional)",
|
||||||
"message": "#### Dear User, Hello!\n\nThank you for your attention and support. Due to adjustments in site operation strategy, we have closed the new user registration function. During this period, existing users will not be affected.\n\nWe are committed to providing you with better service and experience, so we will conduct comprehensive system optimization and feature upgrades during the registration closure. In the future, we will welcome you with better content and services.\n\nPlease follow our website and social media platforms to get the latest updates and notifications. Thank you for your understanding and support.\n\nIf you have any questions or need assistance, please feel free to contact our customer service team.\n\n**Thank you again for your support and understanding.**",
|
"message": "#### Dear User, Hello!\n\nThank you for your attention and support. Due to adjustments in site operation strategy, we have closed the new user registration function. During this period, existing users will not be affected.\n\nWe are committed to providing you with better service and experience, so we will conduct comprehensive system optimization and feature upgrades during the registration closure. In the future, we will welcome you with better content and services.\n\nPlease follow our website and social media platforms to get the latest updates and notifications. Thank you for your understanding and support.\n\nIf you have any questions or need assistance, please feel free to contact our customer service team.\n\n**Thank you again for your support and understanding.**",
|
||||||
"passwordMismatch": "Passwords do not match",
|
"passwordMismatch": "Passwords do not match",
|
||||||
"success": "Registration successful, automatically logged in!",
|
"success": "Registration successful, automatically logged in!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"subject": "Subject",
|
"subject": "Subject",
|
||||||
"template": "Template",
|
"template": "Template",
|
||||||
|
"traffic_exceed_email_template": "Traffic Exceed Notice Template",
|
||||||
|
"traffic_exceed_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||||
"verify_email_template": "Verification Email Template",
|
"verify_email_template": "Verification Email Template",
|
||||||
"verify_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
"verify_email_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||||
"whitelistSuffixes": "Whitelist Suffixes",
|
"whitelistSuffixes": "Whitelist Suffixes",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "ADS Config",
|
"ADS Config": "ADS Config",
|
||||||
"Announcement Management": "Announcement Management",
|
"Announcement Management": "Announcement Management",
|
||||||
"Apple": "Apple ID",
|
"Apple": "Apple ID",
|
||||||
|
"Application Management": "Application Management",
|
||||||
"Auth Control": "Auth Control",
|
"Auth Control": "Auth Control",
|
||||||
"Coupon Management": "Coupon Management",
|
"Coupon Management": "Coupon Management",
|
||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Dashboard",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Order Management",
|
"Order Management": "Order Management",
|
||||||
"Payment Config": "Payment Config",
|
"Payment Config": "Payment Config",
|
||||||
"Phone Number": "Phone Number",
|
"Phone Number": "Phone Number",
|
||||||
|
"Rule Management": "Rule Management",
|
||||||
"Server": "Server",
|
"Server": "Server",
|
||||||
"Server Management": "Server Management",
|
"Server Management": "Server Management",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"descriptionPlaceholder": "Enter payment method description",
|
"descriptionPlaceholder": "Enter payment method description",
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"domainPlaceholder": "optional, e.g. {example}",
|
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"editPayment": "Edit Payment Method",
|
"editPayment": "Edit Payment Method",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analyzing...",
|
||||||
|
"appIcon": "Rule Icon",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"confirmDelete": "Confirm Deletion",
|
||||||
|
"create": "Add Rule",
|
||||||
|
"createRule": "Add Rule",
|
||||||
|
"createSuccess": "Rule created successfully",
|
||||||
|
"createdAt": "Created At",
|
||||||
|
"default": "Default",
|
||||||
|
"defaultRule": "Default Rule",
|
||||||
|
"delete": "Delete",
|
||||||
|
"deleteSuccess": "Rule deleted successfully",
|
||||||
|
"deleteWarning": "Are you sure you want to delete this rule? This action cannot be undone.",
|
||||||
|
"direct": "Direct",
|
||||||
|
"downloadTemplate": "Download Template",
|
||||||
|
"edit": "Edit",
|
||||||
|
"editRule": "Edit Rule",
|
||||||
|
"enable": "Enable",
|
||||||
|
"enterIconUrl": "Enter or upload an icon",
|
||||||
|
"enterRuleName": "Enter rule name",
|
||||||
|
"enterRules": "Enter rule content, one per line",
|
||||||
|
"import": "Import",
|
||||||
|
"importFailed": "Import failed",
|
||||||
|
"importSuccess": "Import successful",
|
||||||
|
"importYamlDescription": "Import rules from YAML file, the system will automatically create rule groups based on policy group names",
|
||||||
|
"importYamlRules": "Import YAML Rules",
|
||||||
|
"importing": "Importing...",
|
||||||
|
"invalidYamlFormat": "Invalid YAML format, missing rules field",
|
||||||
|
"name": "Rule Name",
|
||||||
|
"noValidRules": "No valid rules found",
|
||||||
|
"pleaseUploadFile": "Please upload a YAML file",
|
||||||
|
"preview": "Preview",
|
||||||
|
"reject": "Reject",
|
||||||
|
"rulesFormat": "Rule format: rule type,match content,[policy], where policy is optional.\nIf policy is not specified, the current rule group name will be used automatically. Examples:",
|
||||||
|
"rulesLabel": "Rule Content",
|
||||||
|
"searchRule": "Search rule name",
|
||||||
|
"selectTags": "Select node tags",
|
||||||
|
"selectType": "Select rule type",
|
||||||
|
"tags": "Node Tags",
|
||||||
|
"tagsLabel": "Node Tags",
|
||||||
|
"type": "Rule Type",
|
||||||
|
"updateSuccess": "Rule updated successfully"
|
||||||
|
}
|
||||||
@@ -63,16 +63,20 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"editNode": "Edit Node",
|
"editNode": "Edit Node",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
|
"id": "ID",
|
||||||
"form": {
|
"form": {
|
||||||
"allowInsecure": "Allow Insecure",
|
"allowInsecure": "Allow Insecure",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
|
"congestionController": "Congestion Controller",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
|
"disableSni": "Disable SNI",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"editSecurity": "Edit Security Configuration",
|
"editSecurity": "Edit Security Configuration",
|
||||||
"enableTLS": "Enable TLS",
|
"enableTLS": "Enable TLS",
|
||||||
"encryptionMethod": "Encryption Method",
|
"encryptionMethod": "Encryption Method",
|
||||||
|
"fingerprint": "Fingerprint",
|
||||||
"flow": "Flow Control Algorithm",
|
"flow": "Flow Control Algorithm",
|
||||||
"groupId": "Node Group",
|
"groupId": "Node Group",
|
||||||
"hopInterval": "Hop Interval",
|
"hopInterval": "Hop Interval",
|
||||||
@@ -85,6 +89,7 @@
|
|||||||
"pleaseSelect": "Please Select",
|
"pleaseSelect": "Please Select",
|
||||||
"port": "Server Port",
|
"port": "Server Port",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
|
"reduceRtt": "Reduce RTT",
|
||||||
"relayHost": "Relay Host",
|
"relayHost": "Relay Host",
|
||||||
"relayMode": "Relay Mode",
|
"relayMode": "Relay Mode",
|
||||||
"relayModeOptions": {
|
"relayModeOptions": {
|
||||||
@@ -128,6 +133,7 @@
|
|||||||
"transport": "Transport Protocol Configuration",
|
"transport": "Transport Protocol Configuration",
|
||||||
"transportConfig": "Transport Protocol Configuration",
|
"transportConfig": "Transport Protocol Configuration",
|
||||||
"transportHost": "Transport Server Address",
|
"transportHost": "Transport Server Address",
|
||||||
|
"udpRelayMode": "UDP Relay Mode",
|
||||||
"transportPath": "Transport Path",
|
"transportPath": "Transport Path",
|
||||||
"transportServerName": "Transport Server Name"
|
"transportServerName": "Transport Server Name"
|
||||||
},
|
},
|
||||||
@@ -136,6 +142,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"node": "Node",
|
"node": "Node",
|
||||||
"nodeGroup": "Node Group",
|
"nodeGroup": "Node Group",
|
||||||
|
"noData": "--",
|
||||||
"normal": "Normal",
|
"normal": "Normal",
|
||||||
"onlineCount": "Online users",
|
"onlineCount": "Online users",
|
||||||
"onlineUsers": "Online Users",
|
"onlineUsers": "Online Users",
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Purchase Limit/Time",
|
"quota": "Purchase Limit/Time",
|
||||||
"replacement": "Reset Price/Time",
|
"replacement": "Reset Price/Time",
|
||||||
"sell": "Sell",
|
"sell": "Sell",
|
||||||
"show": "Home display",
|
"show": "Display",
|
||||||
"sold": "Subscription Count",
|
"sold": "Subscription Count",
|
||||||
"subscribe": "Subscribe",
|
"subscribe": "Subscribe",
|
||||||
"subscribeGroup": "Subscription Group",
|
"subscribeGroup": "Subscription Group",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Privacy Policy"
|
"title": "Privacy Policy"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Custom Data",
|
||||||
|
"customDataDescription": "Custom Data, used for custom data of the site",
|
||||||
"customHtml": "Custom HTML",
|
"customHtml": "Custom HTML",
|
||||||
"customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.",
|
"customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.",
|
||||||
"keywords": "Keywords",
|
"keywords": "Keywords",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Confirm System Upgrade",
|
"confirmSystemUpgrade": "Confirm System Upgrade",
|
||||||
"confirmUpgrade": "Confirm Upgrade",
|
"confirmUpgrade": "Confirm Upgrade",
|
||||||
"currentVersion": "Current System Version:",
|
"currentVersion": "Current System Version:",
|
||||||
|
"developmentVersion": "Dev",
|
||||||
"errors": "Errors",
|
"errors": "Errors",
|
||||||
"ip": "IP",
|
"ip": "IP",
|
||||||
"lastUpdated": "Last Updated:",
|
"lastUpdated": "Last Updated:",
|
||||||
|
"newVersionAvailable": "New Version",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"query": "Query",
|
"query": "Query",
|
||||||
"rebootDescription": "Are you sure you want to reboot the system? This operation will cause a brief service interruption.",
|
"rebootDescription": "Are you sure you want to reboot the system? This operation will cause a brief service interruption.",
|
||||||
"rebooting": "Rebooting system...",
|
"rebooting": "Rebooting system...",
|
||||||
"refreshLogs": "Refresh Logs",
|
"refreshLogs": "Refresh Logs",
|
||||||
"request": "Request",
|
"request": "Request",
|
||||||
|
"serverVersion": "Server",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"systemLogs": "System Logs",
|
"systemLogs": "System Logs",
|
||||||
"systemReboot": "System Reboot",
|
"systemReboot": "System Reboot",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "System Upgrade",
|
"systemUpgrade": "System Upgrade",
|
||||||
"upgradeDescription": "Are you sure you want to perform a system upgrade? This operation may take a few minutes, during which the system may be unresponsive.",
|
"upgradeDescription": "Are you sure you want to perform a system upgrade? This operation may take a few minutes, during which the system may be unresponsive.",
|
||||||
"userAgent": "User Agent",
|
"userAgent": "User Agent",
|
||||||
"viewLogsAndManage": "View system logs, perform system upgrade and reboot operations"
|
"versionInfo": "Version Information",
|
||||||
|
"viewLogsAndManage": "View system logs, perform system upgrade and reboot operations",
|
||||||
|
"viewNewVersion": "View New Version",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Account Enable",
|
"accountEnable": "Account Enable",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
|
"active": "Active",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"areaCode": "Area Code",
|
"areaCode": "Area Code",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"enable": "Enable",
|
"enable": "Enable",
|
||||||
"expireTime": "Expire Time",
|
"expireTime": "Expire Time",
|
||||||
|
"expired": "Expired",
|
||||||
"expiredAt": "Expired At",
|
"expiredAt": "Expired At",
|
||||||
"failed": "Failed",
|
"failed": "Failed",
|
||||||
"giftAmount": "Gift Amount",
|
"giftAmount": "Gift Amount",
|
||||||
"giftAmountPlaceholder": "Enter gift amount",
|
"giftAmountPlaceholder": "Enter gift amount",
|
||||||
|
"inactive": "Inactive",
|
||||||
"invalidEmailFormat": "Invalid email format",
|
"invalidEmailFormat": "Invalid email format",
|
||||||
"inviteCode": "Invite Code",
|
"inviteCode": "Invite Code",
|
||||||
"inviteCodePlaceholder": "Enter invite code (leave blank to generate)",
|
"inviteCodePlaceholder": "Enter invite code (leave blank to generate)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Enter new password (optional)",
|
"passwordPlaceholder": "Enter new password (optional)",
|
||||||
"permanent": "Permanent",
|
"permanent": "Permanent",
|
||||||
"pleaseEnterEmail": "Please enter email",
|
"pleaseEnterEmail": "Please enter email",
|
||||||
|
"price": "Price",
|
||||||
"referer": "Referer",
|
"referer": "Referer",
|
||||||
"refererId": "Referer ID",
|
"refererId": "Referer ID",
|
||||||
"refererIdPlaceholder": "Enter referer ID",
|
"refererIdPlaceholder": "Enter referer ID",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "Search IP address",
|
"searchIp": "Search IP address",
|
||||||
"selectAuthType": "Select auth type",
|
"selectAuthType": "Select auth type",
|
||||||
"speedLimit": "Speed Limit",
|
"speedLimit": "Speed Limit",
|
||||||
|
"startTime": "Start Time",
|
||||||
|
"status": "Status",
|
||||||
"subscription": "Subscription",
|
"subscription": "Subscription",
|
||||||
"subscriptionDetails": "Subscription Details",
|
"subscriptionDetails": "Subscription Details",
|
||||||
|
"subscriptionId": "Subscription ID",
|
||||||
|
"subscriptionInfo": "Subscription Info",
|
||||||
"subscriptionList": "Subscription List",
|
"subscriptionList": "Subscription List",
|
||||||
"subscriptionLogs": "Subscription Logs",
|
"subscriptionLogs": "Subscription Logs",
|
||||||
"subscriptionName": "Subscription Name",
|
"subscriptionName": "Subscription Name",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Trade Notifications",
|
"tradeNotifications": "Trade Notifications",
|
||||||
"trafficLimit": "Traffic Limit",
|
"trafficLimit": "Traffic Limit",
|
||||||
"trafficLogs": "Traffic Logs",
|
"trafficLogs": "Traffic Logs",
|
||||||
|
"trafficUsage": "Traffic Usage",
|
||||||
|
"unknown": "Unknown",
|
||||||
"unlimited": "Unlimited",
|
"unlimited": "Unlimited",
|
||||||
"unverified": "Unverified",
|
"unverified": "Unverified",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "User Agent",
|
"userAgent": "User Agent",
|
||||||
"userEmail": "User Email",
|
"userEmail": "User Email",
|
||||||
"userEmailPlaceholder": "Enter user email",
|
"userEmailPlaceholder": "Enter user email",
|
||||||
|
"userId": "User ID",
|
||||||
|
"userInfo": "User Info",
|
||||||
"userList": "User List",
|
"userList": "User List",
|
||||||
"userLogs": "Login History",
|
"userLogs": "Login History",
|
||||||
"userName": "User Identifier",
|
"userName": "User Identifier",
|
||||||
"userOrders": "Orders",
|
"userOrders": "Orders",
|
||||||
"userProfile": "Profile",
|
"userProfile": "Profile",
|
||||||
"userSubscriptions": "Subscriptions",
|
"userSubscriptions": "Subscriptions",
|
||||||
"verified": "Verified"
|
"username": "Username",
|
||||||
|
"verified": "Verified",
|
||||||
|
"viewDetails": "View Details"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Por favor, introduce una dirección de correo electrónico válida.",
|
"email": "Por favor, introduce una dirección de correo electrónico válida.",
|
||||||
"existingAccount": "¿Ya tienes una cuenta?",
|
"existingAccount": "¿Ya tienes una cuenta?",
|
||||||
"get": "Obtener",
|
"get": "Obtener",
|
||||||
"invite": "Código de invitación",
|
"invite": "Código de invitación (opcional)",
|
||||||
"message": "#### ¡Estimado usuario!\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a brindarle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
"message": "#### ¡Estimado usuario!\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a brindarle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
||||||
"passwordMismatch": "Las contraseñas no coinciden",
|
"passwordMismatch": "Las contraseñas no coinciden",
|
||||||
"success": "¡Registro exitoso, sesión iniciada automáticamente!",
|
"success": "¡Registro exitoso, sesión iniciada automáticamente!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"subject": "Asunto",
|
"subject": "Asunto",
|
||||||
"template": "Plantilla",
|
"template": "Plantilla",
|
||||||
|
"traffic_exceed_email_template": "Plantilla de Notificación de Exceso de Tráfico",
|
||||||
|
"traffic_exceed_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||||
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
||||||
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrese de mantener estas variables.",
|
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrese de mantener estas variables.",
|
||||||
"whitelistSuffixes": "Sufijos de la lista blanca",
|
"whitelistSuffixes": "Sufijos de la lista blanca",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "Configuración de ADS",
|
"ADS Config": "Configuración de ADS",
|
||||||
"Announcement Management": "Gestión de Anuncios",
|
"Announcement Management": "Gestión de Anuncios",
|
||||||
"Apple": "ID de Apple",
|
"Apple": "ID de Apple",
|
||||||
|
"Application Management": "Gestión de Aplicaciones",
|
||||||
"Auth Control": "Control de Autenticación",
|
"Auth Control": "Control de Autenticación",
|
||||||
"Coupon Management": "Gestión de Cupones",
|
"Coupon Management": "Gestión de Cupones",
|
||||||
"Dashboard": "Tablero",
|
"Dashboard": "Tablero",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Gestión de Pedidos",
|
"Order Management": "Gestión de Pedidos",
|
||||||
"Payment Config": "Configuración de Pago",
|
"Payment Config": "Configuración de Pago",
|
||||||
"Phone Number": "Número de Teléfono",
|
"Phone Number": "Número de Teléfono",
|
||||||
|
"Rule Management": "Gestión de Reglas",
|
||||||
"Server": "Servidor",
|
"Server": "Servidor",
|
||||||
"Server Management": "Gestión de Servidores",
|
"Server Management": "Gestión de Servidores",
|
||||||
"Settings": "Configuración",
|
"Settings": "Configuración",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"descriptionPlaceholder": "Ingrese la descripción del método de pago",
|
"descriptionPlaceholder": "Ingrese la descripción del método de pago",
|
||||||
"domain": "Dominio",
|
"domain": "Dominio",
|
||||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"editPayment": "Editar Método de Pago",
|
"editPayment": "Editar Método de Pago",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analizando...",
|
||||||
|
"appIcon": "Icono de Regla",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"confirmDelete": "Confirmar Eliminación",
|
||||||
|
"create": "Agregar Regla",
|
||||||
|
"createRule": "Agregar Regla",
|
||||||
|
"createSuccess": "Regla creada con éxito",
|
||||||
|
"createdAt": "Creado En",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"deleteSuccess": "Regla eliminada con éxito",
|
||||||
|
"deleteWarning": "¿Está seguro de que desea eliminar esta regla? Esta acción no se puede deshacer.",
|
||||||
|
"downloadTemplate": "Descargar Plantilla",
|
||||||
|
"edit": "Editar",
|
||||||
|
"editRule": "Editar Regla",
|
||||||
|
"enable": "Habilitar",
|
||||||
|
"enterIconUrl": "Ingrese o suba un icono",
|
||||||
|
"enterRuleName": "Ingrese el nombre de la regla",
|
||||||
|
"enterRules": "Ingrese el contenido de la regla, uno por línea",
|
||||||
|
"import": "Importar",
|
||||||
|
"importFailed": "Importación fallida",
|
||||||
|
"importSuccess": "Importación exitosa",
|
||||||
|
"importYamlDescription": "Importar reglas desde un archivo YAML, el sistema creará automáticamente grupos de reglas basados en los nombres de los grupos de políticas",
|
||||||
|
"importYamlRules": "Importar Reglas YAML",
|
||||||
|
"importing": "Importando...",
|
||||||
|
"invalidYamlFormat": "Formato YAML inválido, falta el campo de reglas",
|
||||||
|
"name": "Nombre de la Regla",
|
||||||
|
"noValidRules": "No se encontraron reglas válidas",
|
||||||
|
"pleaseUploadFile": "Por favor, suba un archivo YAML",
|
||||||
|
"preview": "Vista Previa",
|
||||||
|
"rulesFormat": "Formato de regla: tipo de regla, contenido de coincidencia, [política], donde la política es opcional.\nSi no se especifica la política, se utilizará automáticamente el nombre del grupo de reglas actual. Ejemplos:",
|
||||||
|
"rulesLabel": "Contenido de la Regla",
|
||||||
|
"searchRule": "Buscar nombre de regla",
|
||||||
|
"selectTags": "Seleccionar etiquetas de nodo",
|
||||||
|
"tags": "Etiquetas de Nodo",
|
||||||
|
"tagsLabel": "Etiquetas de Nodo",
|
||||||
|
"updateSuccess": "Regla actualizada con éxito"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Límite de compra/vez",
|
"quota": "Límite de compra/vez",
|
||||||
"replacement": "Restablecer precio/vez",
|
"replacement": "Restablecer precio/vez",
|
||||||
"sell": "Venta",
|
"sell": "Venta",
|
||||||
"show": "Mostrar en la página de inicio",
|
"show": "Mostrar",
|
||||||
"sold": "Conteo de Suscripciones",
|
"sold": "Conteo de Suscripciones",
|
||||||
"subscribe": "Suscribirse",
|
"subscribe": "Suscribirse",
|
||||||
"subscribeGroup": "Suscribirse al grupo",
|
"subscribeGroup": "Suscribirse al grupo",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Política de Privacidad"
|
"title": "Política de Privacidad"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Datos Personalizados",
|
||||||
|
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||||
"customHtml": "HTML personalizado",
|
"customHtml": "HTML personalizado",
|
||||||
"customHtmlDescription": "Código HTML personalizado que se inyectará en la parte inferior de la etiqueta body del sitio.",
|
"customHtmlDescription": "Código HTML personalizado que se inyectará en la parte inferior de la etiqueta body del sitio.",
|
||||||
"keywords": "Palabras clave",
|
"keywords": "Palabras clave",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Confirmar actualización del sistema",
|
"confirmSystemUpgrade": "Confirmar actualización del sistema",
|
||||||
"confirmUpgrade": "Confirmar actualización",
|
"confirmUpgrade": "Confirmar actualización",
|
||||||
"currentVersion": "Versión actual del sistema:",
|
"currentVersion": "Versión actual del sistema:",
|
||||||
|
"developmentVersion": "Versión de desarrollo",
|
||||||
"errors": "Errores",
|
"errors": "Errores",
|
||||||
"ip": "IP",
|
"ip": "Dirección IP",
|
||||||
"lastUpdated": "Última actualización:",
|
"lastUpdated": "Última actualización:",
|
||||||
|
"newVersionAvailable": "Nueva versión",
|
||||||
"none": "Ninguno",
|
"none": "Ninguno",
|
||||||
"query": "Consulta",
|
"query": "Consulta",
|
||||||
"rebootDescription": "¿Está seguro de que desea reiniciar el sistema? Esta acción provocará una breve interrupción del servicio.",
|
"rebootDescription": "¿Está seguro de que desea reiniciar el sistema? Esta acción provocará una breve interrupción del servicio.",
|
||||||
"rebooting": "Reiniciando el sistema...",
|
"rebooting": "Reiniciando el sistema...",
|
||||||
"refreshLogs": "Actualizar registros",
|
"refreshLogs": "Actualizar registros",
|
||||||
"request": "Solicitud",
|
"request": "Solicitud",
|
||||||
|
"serverVersion": "Servidor",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"systemLogs": "Registros del sistema",
|
"systemLogs": "Registros del sistema",
|
||||||
"systemReboot": "Reinicio del sistema",
|
"systemReboot": "Reinicio del sistema",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "Actualización del sistema",
|
"systemUpgrade": "Actualización del sistema",
|
||||||
"upgradeDescription": "¿Está seguro de que desea realizar la actualización del sistema? Esta operación puede tardar unos minutos, durante los cuales el sistema podría no responder.",
|
"upgradeDescription": "¿Está seguro de que desea realizar la actualización del sistema? Esta operación puede tardar unos minutos, durante los cuales el sistema podría no responder.",
|
||||||
"userAgent": "Agente de Usuario",
|
"userAgent": "Agente de Usuario",
|
||||||
"viewLogsAndManage": "Ver los registros del sistema, realizar actualizaciones del sistema y reiniciar operaciones"
|
"versionInfo": "Información de versión",
|
||||||
|
"viewLogsAndManage": "Ver los registros del sistema, realizar actualizaciones del sistema y reiniciar operaciones",
|
||||||
|
"viewNewVersion": "Ver nueva versión",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Habilitar cuenta",
|
"accountEnable": "Habilitar cuenta",
|
||||||
"actions": "acciones",
|
"actions": "acciones",
|
||||||
|
"active": "Activo",
|
||||||
"add": "Agregar",
|
"add": "Agregar",
|
||||||
"administrator": "Administrador",
|
"administrator": "Administrador",
|
||||||
"areaCode": "Código de Área",
|
"areaCode": "Código de Área",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "correo electrónico",
|
"email": "correo electrónico",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
"expireTime": "Tiempo de Expiración",
|
"expireTime": "Tiempo de Expiración",
|
||||||
|
"expired": "Caducado",
|
||||||
"expiredAt": "Fecha de vencimiento",
|
"expiredAt": "Fecha de vencimiento",
|
||||||
"failed": "Fallido",
|
"failed": "Fallido",
|
||||||
"giftAmount": "Monto del Regalo",
|
"giftAmount": "Monto del Regalo",
|
||||||
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
||||||
|
"inactive": "Inactivo",
|
||||||
"invalidEmailFormat": "Formato de correo electrónico no válido",
|
"invalidEmailFormat": "Formato de correo electrónico no válido",
|
||||||
"inviteCode": "Código de invitación",
|
"inviteCode": "Código de invitación",
|
||||||
"inviteCodePlaceholder": "Introduce el código de invitación (déjalo en blanco para generar uno)",
|
"inviteCodePlaceholder": "Introduce el código de invitación (déjalo en blanco para generar uno)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Ingrese nueva contraseña (opcional)",
|
"passwordPlaceholder": "Ingrese nueva contraseña (opcional)",
|
||||||
"permanent": "Permanente",
|
"permanent": "Permanente",
|
||||||
"pleaseEnterEmail": "Por favor, introduce el correo electrónico",
|
"pleaseEnterEmail": "Por favor, introduce el correo electrónico",
|
||||||
|
"price": "Precio",
|
||||||
"referer": "Recomendador",
|
"referer": "Recomendador",
|
||||||
"refererId": "ID del Referente",
|
"refererId": "ID del Referente",
|
||||||
"refererIdPlaceholder": "Ingrese el ID del referente",
|
"refererIdPlaceholder": "Ingrese el ID del referente",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "Buscar dirección IP",
|
"searchIp": "Buscar dirección IP",
|
||||||
"selectAuthType": "Seleccionar tipo de autenticación",
|
"selectAuthType": "Seleccionar tipo de autenticación",
|
||||||
"speedLimit": "Límite de Velocidad",
|
"speedLimit": "Límite de Velocidad",
|
||||||
|
"startTime": "Hora de Inicio",
|
||||||
|
"status": "Estado",
|
||||||
"subscription": "Suscripción",
|
"subscription": "Suscripción",
|
||||||
"subscriptionDetails": "Detalles de la suscripción",
|
"subscriptionDetails": "Detalles de la suscripción",
|
||||||
|
"subscriptionId": "ID de Suscripción",
|
||||||
|
"subscriptionInfo": "Información de Suscripción",
|
||||||
"subscriptionList": "Lista de Suscripción",
|
"subscriptionList": "Lista de Suscripción",
|
||||||
"subscriptionLogs": "Registros de Suscripción",
|
"subscriptionLogs": "Registros de Suscripción",
|
||||||
"subscriptionName": "Nombre de la Suscripción",
|
"subscriptionName": "Nombre de la Suscripción",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Notificaciones de Comercio",
|
"tradeNotifications": "Notificaciones de Comercio",
|
||||||
"trafficLimit": "Límite de Tráfico",
|
"trafficLimit": "Límite de Tráfico",
|
||||||
"trafficLogs": "Registros de Tráfico",
|
"trafficLogs": "Registros de Tráfico",
|
||||||
|
"trafficUsage": "Uso de Tráfico",
|
||||||
|
"unknown": "Desconocido",
|
||||||
"unlimited": "Ilimitado",
|
"unlimited": "Ilimitado",
|
||||||
"unverified": "No verificado",
|
"unverified": "No verificado",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "Agente de Usuario",
|
"userAgent": "Agente de Usuario",
|
||||||
"userEmail": "Correo Electrónico del Usuario",
|
"userEmail": "Correo Electrónico del Usuario",
|
||||||
"userEmailPlaceholder": "Introduce el correo electrónico del usuario",
|
"userEmailPlaceholder": "Introduce el correo electrónico del usuario",
|
||||||
|
"userId": "ID de Usuario",
|
||||||
|
"userInfo": "Información de Usuario",
|
||||||
"userList": "Lista de usuarios",
|
"userList": "Lista de usuarios",
|
||||||
"userLogs": "Historial de inicio de sesión",
|
"userLogs": "Historial de inicio de sesión",
|
||||||
"userName": "Identificador de Usuario",
|
"userName": "Identificador de Usuario",
|
||||||
"userOrders": "Pedidos",
|
"userOrders": "Pedidos",
|
||||||
"userProfile": "Perfil",
|
"userProfile": "Perfil",
|
||||||
"userSubscriptions": "Suscripciones",
|
"userSubscriptions": "Suscripciones",
|
||||||
"verified": "Verificado"
|
"username": "Nombre de Usuario",
|
||||||
|
"verified": "Verificado",
|
||||||
|
"viewDetails": "Ver Detalles"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Por favor, ingresa una dirección de correo electrónico válida.",
|
"email": "Por favor, ingresa una dirección de correo electrónico válida.",
|
||||||
"existingAccount": "¿Ya tienes una cuenta?",
|
"existingAccount": "¿Ya tienes una cuenta?",
|
||||||
"get": "Obtener",
|
"get": "Obtener",
|
||||||
"invite": "Código de invitación",
|
"invite": "Código de invitación (opcional)",
|
||||||
"message": "#### Estimado usuario,\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a ofrecerle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
"message": "#### Estimado usuario,\n\nGracias por su interés y apoyo. Debido a un ajuste en la estrategia de operación del sitio, hemos cerrado la función de registro para nuevos usuarios. Durante este período, el uso por parte de los usuarios existentes no se verá afectado.\n\nNos comprometemos a ofrecerle un mejor servicio y experiencia, por lo que realizaremos una optimización completa del sistema y una actualización de funciones durante el cierre del registro. En el futuro, le daremos la bienvenida con contenido y servicios de mayor calidad.\n\nPor favor, siga nuestro sitio web y plataformas de redes sociales para obtener las últimas actualizaciones y notificaciones. Agradecemos su comprensión y apoyo.\n\nSi tiene alguna pregunta o necesita ayuda, no dude en ponerse en contacto con nuestro equipo de atención al cliente.\n\n**Nuevamente, gracias por su apoyo y comprensión.**",
|
||||||
"passwordMismatch": "Las contraseñas no coinciden",
|
"passwordMismatch": "Las contraseñas no coinciden",
|
||||||
"success": "¡Registro exitoso, has iniciado sesión automáticamente!",
|
"success": "¡Registro exitoso, has iniciado sesión automáticamente!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"subject": "Asunto",
|
"subject": "Asunto",
|
||||||
"template": "Plantilla",
|
"template": "Plantilla",
|
||||||
|
"traffic_exceed_email_template": "Plantilla de Notificación de Exceso de Tráfico",
|
||||||
|
"traffic_exceed_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||||
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
"verify_email_template": "Plantilla de Correo Electrónico de Verificación",
|
||||||
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
"verify_email_templateDescription": "Los marcadores de posición {after}.variable{before} serán reemplazados con datos reales. Asegúrate de mantener estas variables.",
|
||||||
"whitelistSuffixes": "Sufijos de Lista Blanca",
|
"whitelistSuffixes": "Sufijos de Lista Blanca",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "Configuración de ADS",
|
"ADS Config": "Configuración de ADS",
|
||||||
"Announcement Management": "Gestión de Anuncios",
|
"Announcement Management": "Gestión de Anuncios",
|
||||||
"Apple": "ID de Apple",
|
"Apple": "ID de Apple",
|
||||||
|
"Application Management": "Gestión de Aplicaciones",
|
||||||
"Auth Control": "Control de Autenticación",
|
"Auth Control": "Control de Autenticación",
|
||||||
"Coupon Management": "Gestión de Cupones",
|
"Coupon Management": "Gestión de Cupones",
|
||||||
"Dashboard": "Tablero",
|
"Dashboard": "Tablero",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Gestión de Pedidos",
|
"Order Management": "Gestión de Pedidos",
|
||||||
"Payment Config": "Configuración de Pago",
|
"Payment Config": "Configuración de Pago",
|
||||||
"Phone Number": "Número de Teléfono",
|
"Phone Number": "Número de Teléfono",
|
||||||
|
"Rule Management": "Gestión de Reglas",
|
||||||
"Server": "Servidor",
|
"Server": "Servidor",
|
||||||
"Server Management": "Gestión de Servidores",
|
"Server Management": "Gestión de Servidores",
|
||||||
"Settings": "Configuración",
|
"Settings": "Configuración",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"descriptionPlaceholder": "Ingresa la descripción del método de pago",
|
"descriptionPlaceholder": "Ingresa la descripción del método de pago",
|
||||||
"domain": "Dominio",
|
"domain": "Dominio",
|
||||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"editPayment": "Editar Método de Pago",
|
"editPayment": "Editar Método de Pago",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analizando...",
|
||||||
|
"appIcon": "Ícono de Regla",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"confirmDelete": "Confirmar Eliminación",
|
||||||
|
"create": "Agregar Regla",
|
||||||
|
"createRule": "Agregar Regla",
|
||||||
|
"createSuccess": "Regla creada con éxito",
|
||||||
|
"createdAt": "Creado En",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"deleteSuccess": "Regla eliminada con éxito",
|
||||||
|
"deleteWarning": "¿Está seguro de que desea eliminar esta regla? Esta acción no se puede deshacer.",
|
||||||
|
"downloadTemplate": "Descargar Plantilla",
|
||||||
|
"edit": "Editar",
|
||||||
|
"editRule": "Editar Regla",
|
||||||
|
"enable": "Habilitar",
|
||||||
|
"enterIconUrl": "Ingrese o suba un ícono",
|
||||||
|
"enterRuleName": "Ingrese el nombre de la regla",
|
||||||
|
"enterRules": "Ingrese el contenido de la regla, uno por línea",
|
||||||
|
"import": "Importar",
|
||||||
|
"importFailed": "Importación fallida",
|
||||||
|
"importSuccess": "Importación exitosa",
|
||||||
|
"importYamlDescription": "Importar reglas desde un archivo YAML, el sistema creará automáticamente grupos de reglas basados en los nombres de los grupos de políticas",
|
||||||
|
"importYamlRules": "Importar Reglas YAML",
|
||||||
|
"importing": "Importando...",
|
||||||
|
"invalidYamlFormat": "Formato YAML inválido, falta el campo de reglas",
|
||||||
|
"name": "Nombre de la Regla",
|
||||||
|
"noValidRules": "No se encontraron reglas válidas",
|
||||||
|
"pleaseUploadFile": "Por favor, suba un archivo YAML",
|
||||||
|
"preview": "Vista Previa",
|
||||||
|
"rulesFormat": "Formato de regla: tipo de regla, contenido de coincidencia, [política], donde la política es opcional.\nSi no se especifica la política, se utilizará automáticamente el nombre del grupo de reglas actual. Ejemplos:",
|
||||||
|
"rulesLabel": "Contenido de la Regla",
|
||||||
|
"searchRule": "Buscar nombre de regla",
|
||||||
|
"selectTags": "Seleccionar etiquetas de nodo",
|
||||||
|
"tags": "Etiquetas de Nodo",
|
||||||
|
"tagsLabel": "Etiquetas de Nodo",
|
||||||
|
"updateSuccess": "Regla actualizada con éxito"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Límite de compra/vez",
|
"quota": "Límite de compra/vez",
|
||||||
"replacement": "Restablecer precio/vez",
|
"replacement": "Restablecer precio/vez",
|
||||||
"sell": "venta",
|
"sell": "venta",
|
||||||
"show": "Mostrar en la página de inicio",
|
"show": "Mostrar",
|
||||||
"sold": "Conteo de Suscripciones",
|
"sold": "Conteo de Suscripciones",
|
||||||
"subscribe": "Suscribirse",
|
"subscribe": "Suscribirse",
|
||||||
"subscribeGroup": "Suscribirse al grupo",
|
"subscribeGroup": "Suscribirse al grupo",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Política de Privacidad"
|
"title": "Política de Privacidad"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Datos Personalizados",
|
||||||
|
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||||
"customHtml": "HTML personalizado",
|
"customHtml": "HTML personalizado",
|
||||||
"customHtmlDescription": "Código HTML personalizado que se inyectará al final de la etiqueta body del sitio.",
|
"customHtmlDescription": "Código HTML personalizado que se inyectará al final de la etiqueta body del sitio.",
|
||||||
"keywords": "Palabras clave",
|
"keywords": "Palabras clave",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Confirmar actualización del sistema",
|
"confirmSystemUpgrade": "Confirmar actualización del sistema",
|
||||||
"confirmUpgrade": "Confirmar actualización",
|
"confirmUpgrade": "Confirmar actualización",
|
||||||
"currentVersion": "Versión actual del sistema:",
|
"currentVersion": "Versión actual del sistema:",
|
||||||
|
"developmentVersion": "Versión de desarrollo",
|
||||||
"errors": "Errores",
|
"errors": "Errores",
|
||||||
"ip": "IP",
|
"ip": "Dirección IP",
|
||||||
"lastUpdated": "Última actualización:",
|
"lastUpdated": "Última actualización:",
|
||||||
|
"newVersionAvailable": "Nueva versión",
|
||||||
"none": "Ninguno",
|
"none": "Ninguno",
|
||||||
"query": "Consulta",
|
"query": "Consulta",
|
||||||
"rebootDescription": "¿Está seguro de que desea reiniciar el sistema? Esta acción provocará una breve interrupción del servicio.",
|
"rebootDescription": "¿Está seguro de que desea reiniciar el sistema? Esta acción provocará una breve interrupción del servicio.",
|
||||||
"rebooting": "Reiniciando el sistema...",
|
"rebooting": "Reiniciando el sistema...",
|
||||||
"refreshLogs": "Actualizar registros",
|
"refreshLogs": "Actualizar registros",
|
||||||
"request": "Solicitud",
|
"request": "Solicitud",
|
||||||
|
"serverVersion": "Servidor",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"systemLogs": "Registros del sistema",
|
"systemLogs": "Registros del sistema",
|
||||||
"systemReboot": "Reinicio del sistema",
|
"systemReboot": "Reinicio del sistema",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "Actualización del sistema",
|
"systemUpgrade": "Actualización del sistema",
|
||||||
"upgradeDescription": "¿Está seguro de que desea realizar la actualización del sistema? Esta operación puede tardar unos minutos, durante los cuales el sistema podría no responder.",
|
"upgradeDescription": "¿Está seguro de que desea realizar la actualización del sistema? Esta operación puede tardar unos minutos, durante los cuales el sistema podría no responder.",
|
||||||
"userAgent": "Agente de Usuario",
|
"userAgent": "Agente de Usuario",
|
||||||
"viewLogsAndManage": "Ver los registros del sistema, realizar actualizaciones del sistema y reiniciar operaciones"
|
"versionInfo": "Información de versión",
|
||||||
|
"viewLogsAndManage": "Ver los registros del sistema, realizar actualizaciones del sistema y reiniciar operaciones",
|
||||||
|
"viewNewVersion": "Ver nueva versión",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Habilitar Cuenta",
|
"accountEnable": "Habilitar Cuenta",
|
||||||
"actions": "acciones",
|
"actions": "acciones",
|
||||||
|
"active": "Activo",
|
||||||
"add": "Agregar",
|
"add": "Agregar",
|
||||||
"administrator": "Administrador",
|
"administrator": "Administrador",
|
||||||
"areaCode": "Código de Área",
|
"areaCode": "Código de Área",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "correo electrónico",
|
"email": "correo electrónico",
|
||||||
"enable": "Habilitar",
|
"enable": "Habilitar",
|
||||||
"expireTime": "Tiempo de Expiración",
|
"expireTime": "Tiempo de Expiración",
|
||||||
|
"expired": "Expirado",
|
||||||
"expiredAt": "Fecha de vencimiento",
|
"expiredAt": "Fecha de vencimiento",
|
||||||
"failed": "Fallido",
|
"failed": "Fallido",
|
||||||
"giftAmount": "Monto del Regalo",
|
"giftAmount": "Monto del Regalo",
|
||||||
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
||||||
|
"inactive": "Inactivo",
|
||||||
"invalidEmailFormat": "Formato de correo electrónico no válido",
|
"invalidEmailFormat": "Formato de correo electrónico no válido",
|
||||||
"inviteCode": "Código de Invitación",
|
"inviteCode": "Código de Invitación",
|
||||||
"inviteCodePlaceholder": "Ingresa el código de invitación (déjalo en blanco para generar uno)",
|
"inviteCodePlaceholder": "Ingresa el código de invitación (déjalo en blanco para generar uno)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Ingresa nueva contraseña (opcional)",
|
"passwordPlaceholder": "Ingresa nueva contraseña (opcional)",
|
||||||
"permanent": "Permanente",
|
"permanent": "Permanente",
|
||||||
"pleaseEnterEmail": "Por favor, ingresa el correo electrónico",
|
"pleaseEnterEmail": "Por favor, ingresa el correo electrónico",
|
||||||
|
"price": "Precio",
|
||||||
"referer": "Recomendador",
|
"referer": "Recomendador",
|
||||||
"refererId": "ID del Referente",
|
"refererId": "ID del Referente",
|
||||||
"refererIdPlaceholder": "Ingrese ID del referente",
|
"refererIdPlaceholder": "Ingrese ID del referente",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "Buscar dirección IP",
|
"searchIp": "Buscar dirección IP",
|
||||||
"selectAuthType": "Seleccionar tipo de autenticación",
|
"selectAuthType": "Seleccionar tipo de autenticación",
|
||||||
"speedLimit": "Límite de Velocidad",
|
"speedLimit": "Límite de Velocidad",
|
||||||
|
"startTime": "Hora de Inicio",
|
||||||
|
"status": "Estado",
|
||||||
"subscription": "Suscripción",
|
"subscription": "Suscripción",
|
||||||
"subscriptionDetails": "Detalles de la Suscripción",
|
"subscriptionDetails": "Detalles de la Suscripción",
|
||||||
|
"subscriptionId": "ID de Suscripción",
|
||||||
|
"subscriptionInfo": "Información de Suscripción",
|
||||||
"subscriptionList": "Lista de Suscripción",
|
"subscriptionList": "Lista de Suscripción",
|
||||||
"subscriptionLogs": "Registros de Suscripción",
|
"subscriptionLogs": "Registros de Suscripción",
|
||||||
"subscriptionName": "Nombre de la Suscripción",
|
"subscriptionName": "Nombre de la Suscripción",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Notificaciones de Comercio",
|
"tradeNotifications": "Notificaciones de Comercio",
|
||||||
"trafficLimit": "Límite de Tráfico",
|
"trafficLimit": "Límite de Tráfico",
|
||||||
"trafficLogs": "Registros de Tráfico",
|
"trafficLogs": "Registros de Tráfico",
|
||||||
|
"trafficUsage": "Uso de Tráfico",
|
||||||
|
"unknown": "Desconocido",
|
||||||
"unlimited": "Ilimitado",
|
"unlimited": "Ilimitado",
|
||||||
"unverified": "No verificado",
|
"unverified": "No verificado",
|
||||||
"update": "Actualizar",
|
"update": "Actualizar",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "Agente de Usuario",
|
"userAgent": "Agente de Usuario",
|
||||||
"userEmail": "Correo Electrónico del Usuario",
|
"userEmail": "Correo Electrónico del Usuario",
|
||||||
"userEmailPlaceholder": "Ingresa el correo electrónico del usuario",
|
"userEmailPlaceholder": "Ingresa el correo electrónico del usuario",
|
||||||
|
"userId": "ID de Usuario",
|
||||||
|
"userInfo": "Información de Usuario",
|
||||||
"userList": "Lista de usuarios",
|
"userList": "Lista de usuarios",
|
||||||
"userLogs": "Historial de Inicio de Sesión",
|
"userLogs": "Historial de Inicio de Sesión",
|
||||||
"userName": "Identificador de Usuario",
|
"userName": "Identificador de Usuario",
|
||||||
"userOrders": "Pedidos",
|
"userOrders": "Pedidos",
|
||||||
"userProfile": "Perfil",
|
"userProfile": "Perfil",
|
||||||
"userSubscriptions": "Suscripciones",
|
"userSubscriptions": "Suscripciones",
|
||||||
"verified": "Verificado"
|
"username": "Nombre de Usuario",
|
||||||
|
"verified": "Verificado",
|
||||||
|
"viewDetails": "Ver Detalles"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "لطفاً یک آدرس ایمیل معتبر وارد کنید.",
|
"email": "لطفاً یک آدرس ایمیل معتبر وارد کنید.",
|
||||||
"existingAccount": "آیا قبلاً حساب کاربری دارید؟",
|
"existingAccount": "آیا قبلاً حساب کاربری دارید؟",
|
||||||
"get": "دریافت",
|
"get": "دریافت",
|
||||||
"invite": "کد دعوت",
|
"invite": "کد دعوت (اختیاری)",
|
||||||
"message": "#### کاربر گرامی، سلام!\n\nاز توجه و حمایت شما سپاسگزاریم. به دلیل تغییرات در استراتژی عملیاتی سایت، عملکرد ثبتنام کاربران جدید را متوقف کردهایم. در این مدت، کاربران فعلی تحت تأثیر قرار نخواهند گرفت.\n\nما متعهد به ارائه خدمات و تجربه بهتر به شما هستیم، بنابراین در طول این دوره، بهینهسازی جامع سیستم و ارتقاء ویژگیها را انجام خواهیم داد. در آینده، با محتوای و خدمات بهتر از شما استقبال خواهیم کرد.\n\nلطفاً برای دریافت آخرین بهروزرسانیها و اطلاعیهها، وبسایت و پلتفرمهای اجتماعی ما را دنبال کنید. از درک و حمایت شما سپاسگزاریم.\n\nاگر سوالی دارید یا به کمک نیاز دارید، لطفاً با تیم خدمات مشتری ما تماس بگیرید.\n\n**بار دیگر از حمایت و درک شما سپاسگزاریم.**",
|
"message": "#### کاربر گرامی، سلام!\n\nاز توجه و حمایت شما سپاسگزاریم. به دلیل تغییرات در استراتژی عملیاتی سایت، عملکرد ثبتنام کاربران جدید را متوقف کردهایم. در این مدت، کاربران فعلی تحت تأثیر قرار نخواهند گرفت.\n\nما متعهد به ارائه خدمات و تجربه بهتر به شما هستیم، بنابراین در طول این دوره، بهینهسازی جامع سیستم و ارتقاء ویژگیها را انجام خواهیم داد. در آینده، با محتوای و خدمات بهتر از شما استقبال خواهیم کرد.\n\nلطفاً برای دریافت آخرین بهروزرسانیها و اطلاعیهها، وبسایت و پلتفرمهای اجتماعی ما را دنبال کنید. از درک و حمایت شما سپاسگزاریم.\n\nاگر سوالی دارید یا به کمک نیاز دارید، لطفاً با تیم خدمات مشتری ما تماس بگیرید.\n\n**بار دیگر از حمایت و درک شما سپاسگزاریم.**",
|
||||||
"passwordMismatch": "رمزهای عبور مطابقت ندارند",
|
"passwordMismatch": "رمزهای عبور مطابقت ندارند",
|
||||||
"success": "ثبتنام موفقیتآمیز بود، بهطور خودکار وارد شدید!",
|
"success": "ثبتنام موفقیتآمیز بود، بهطور خودکار وارد شدید!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "وضعیت",
|
"status": "وضعیت",
|
||||||
"subject": "موضوع",
|
"subject": "موضوع",
|
||||||
"template": "الگو",
|
"template": "الگو",
|
||||||
|
"traffic_exceed_email_template": "الگوی اعلان ترافیک بیش از حد",
|
||||||
|
"traffic_exceed_email_templateDescription": "جایگزینهای {after}.variable{before} با دادههای واقعی خواهد شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
||||||
"verify_email_template": "قالب ایمیل تأیید",
|
"verify_email_template": "قالب ایمیل تأیید",
|
||||||
"verify_email_templateDescription": "جایگذاریهای {after}.variable{before} با دادههای واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
"verify_email_templateDescription": "جایگذاریهای {after}.variable{before} با دادههای واقعی جایگزین خواهند شد. اطمینان حاصل کنید که این متغیرها را حفظ کنید.",
|
||||||
"whitelistSuffixes": "پسوندهای لیست سفید",
|
"whitelistSuffixes": "پسوندهای لیست سفید",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "تنظیمات ADS",
|
"ADS Config": "تنظیمات ADS",
|
||||||
"Announcement Management": "مدیریت اطلاعیهها",
|
"Announcement Management": "مدیریت اطلاعیهها",
|
||||||
"Apple": "شناسه اپل",
|
"Apple": "شناسه اپل",
|
||||||
|
"Application Management": "مدیریت برنامه",
|
||||||
"Auth Control": "کنترل احراز هویت",
|
"Auth Control": "کنترل احراز هویت",
|
||||||
"Coupon Management": "مدیریت کوپن",
|
"Coupon Management": "مدیریت کوپن",
|
||||||
"Dashboard": "داشبورد",
|
"Dashboard": "داشبورد",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "مدیریت سفارش",
|
"Order Management": "مدیریت سفارش",
|
||||||
"Payment Config": "پیکربندی پرداخت",
|
"Payment Config": "پیکربندی پرداخت",
|
||||||
"Phone Number": "شماره تلفن",
|
"Phone Number": "شماره تلفن",
|
||||||
|
"Rule Management": "مدیریت قوانین",
|
||||||
"Server": "سرور",
|
"Server": "سرور",
|
||||||
"Server Management": "مدیریت سرور",
|
"Server Management": "مدیریت سرور",
|
||||||
"Settings": "تنظیمات",
|
"Settings": "تنظیمات",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "توضیحات",
|
"description": "توضیحات",
|
||||||
"descriptionPlaceholder": "توضیحات روش پرداخت را وارد کنید",
|
"descriptionPlaceholder": "توضیحات روش پرداخت را وارد کنید",
|
||||||
"domain": "دامنه",
|
"domain": "دامنه",
|
||||||
"domainPlaceholder": "اختیاری، به عنوان مثال {example}",
|
|
||||||
"edit": "ویرایش",
|
"edit": "ویرایش",
|
||||||
"editPayment": "ویرایش روش پرداخت",
|
"editPayment": "ویرایش روش پرداخت",
|
||||||
"enable": "فعال کردن",
|
"enable": "فعال کردن",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "در حال تجزیه و تحلیل...",
|
||||||
|
"appIcon": "آیکون قانون",
|
||||||
|
"cancel": "لغو",
|
||||||
|
"confirm": "تأیید",
|
||||||
|
"confirmDelete": "تأیید حذف",
|
||||||
|
"create": "افزودن قانون",
|
||||||
|
"createRule": "افزودن قانون",
|
||||||
|
"createSuccess": "قانون با موفقیت ایجاد شد",
|
||||||
|
"createdAt": "تاریخ ایجاد",
|
||||||
|
"delete": "حذف",
|
||||||
|
"deleteSuccess": "قانون با موفقیت حذف شد",
|
||||||
|
"deleteWarning": "آیا مطمئن هستید که میخواهید این قانون را حذف کنید؟ این عمل قابل بازگشت نیست.",
|
||||||
|
"downloadTemplate": "دانلود الگو",
|
||||||
|
"edit": "ویرایش",
|
||||||
|
"editRule": "ویرایش قانون",
|
||||||
|
"enable": "فعالسازی",
|
||||||
|
"enterIconUrl": "آدرس یا بارگذاری آیکون را وارد کنید",
|
||||||
|
"enterRuleName": "نام قانون را وارد کنید",
|
||||||
|
"enterRules": "محتوای قانون را وارد کنید، هر کدام در یک خط",
|
||||||
|
"import": "وارد کردن",
|
||||||
|
"importFailed": "وارد کردن ناموفق بود",
|
||||||
|
"importSuccess": "وارد کردن با موفقیت انجام شد",
|
||||||
|
"importYamlDescription": "وارد کردن قوانین از فایل YAML، سیستم به طور خودکار گروههای قانونی را بر اساس نامهای گروه سیاست ایجاد خواهد کرد",
|
||||||
|
"importYamlRules": "وارد کردن قوانین YAML",
|
||||||
|
"importing": "در حال وارد کردن...",
|
||||||
|
"invalidYamlFormat": "فرمت YAML نامعتبر است، فیلد قوانین گم شده است",
|
||||||
|
"name": "نام قانون",
|
||||||
|
"noValidRules": "هیچ قانونی یافت نشد",
|
||||||
|
"pleaseUploadFile": "لطفاً یک فایل YAML بارگذاری کنید",
|
||||||
|
"preview": "پیشنمایش",
|
||||||
|
"rulesFormat": "فرمت قانون: نوع قانون، محتوای تطابق، [سیاست]، که سیاست اختیاری است.\nاگر سیاست مشخص نشود، نام گروه قانونی فعلی به طور خودکار استفاده خواهد شد. مثالها:",
|
||||||
|
"rulesLabel": "محتوای قانون",
|
||||||
|
"searchRule": "جستجوی نام قانون",
|
||||||
|
"selectTags": "انتخاب برچسبهای گره",
|
||||||
|
"tags": "برچسبهای گره",
|
||||||
|
"tagsLabel": "برچسبهای گره",
|
||||||
|
"updateSuccess": "قانون با موفقیت بهروزرسانی شد"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "محدودیت خرید/زمان",
|
"quota": "محدودیت خرید/زمان",
|
||||||
"replacement": "تنظیم مجدد قیمت/زمان",
|
"replacement": "تنظیم مجدد قیمت/زمان",
|
||||||
"sell": "فروش",
|
"sell": "فروش",
|
||||||
"show": "نمایش خانه",
|
"show": "نمایش",
|
||||||
"sold": "تعداد اشتراک",
|
"sold": "تعداد اشتراک",
|
||||||
"subscribe": "اشتراک",
|
"subscribe": "اشتراک",
|
||||||
"subscribeGroup": "گروه اشتراک",
|
"subscribeGroup": "گروه اشتراک",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "سیاست حفظ حریم خصوصی"
|
"title": "سیاست حفظ حریم خصوصی"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "دادههای سفارشی",
|
||||||
|
"customDataDescription": "دادههای سفارشی، استفاده شده برای دادههای سفارشی سایت",
|
||||||
"customHtml": "HTML سفارشی",
|
"customHtml": "HTML سفارشی",
|
||||||
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.",
|
"customHtmlDescription": "کد HTML سفارشی که باید در انتهای تگ body سایت وارد شود.",
|
||||||
"keywords": "کلمات کلیدی",
|
"keywords": "کلمات کلیدی",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "تأیید ارتقاء سیستم",
|
"confirmSystemUpgrade": "تأیید ارتقاء سیستم",
|
||||||
"confirmUpgrade": "تأیید ارتقاء",
|
"confirmUpgrade": "تأیید ارتقاء",
|
||||||
"currentVersion": "نسخه فعلی سیستم:",
|
"currentVersion": "نسخه فعلی سیستم:",
|
||||||
|
"developmentVersion": "نسخه توسعه",
|
||||||
"errors": "خطاها",
|
"errors": "خطاها",
|
||||||
"ip": "آیپی",
|
"ip": "آدرس آیپی",
|
||||||
"lastUpdated": "آخرین بهروزرسانی:",
|
"lastUpdated": "آخرین بهروزرسانی:",
|
||||||
|
"newVersionAvailable": "نسخه جدید",
|
||||||
"none": "هیچ",
|
"none": "هیچ",
|
||||||
"query": "پرس و جو",
|
"query": "پرس و جو",
|
||||||
"rebootDescription": "آیا مطمئن هستید که میخواهید سیستم را راهاندازی مجدد کنید؟ این عملیات باعث وقفه کوتاهی در خدمات خواهد شد.",
|
"rebootDescription": "آیا مطمئن هستید که میخواهید سیستم را راهاندازی مجدد کنید؟ این عملیات باعث وقفه کوتاهی در خدمات خواهد شد.",
|
||||||
"rebooting": "در حال راهاندازی مجدد سیستم...",
|
"rebooting": "در حال راهاندازی مجدد سیستم...",
|
||||||
"refreshLogs": "تازهسازی گزارشها",
|
"refreshLogs": "تازهسازی گزارشها",
|
||||||
"request": "درخواست",
|
"request": "درخواست",
|
||||||
|
"serverVersion": "سرور",
|
||||||
"status": "وضعیت",
|
"status": "وضعیت",
|
||||||
"systemLogs": "گزارشهای سیستم",
|
"systemLogs": "گزارشهای سیستم",
|
||||||
"systemReboot": "راهاندازی مجدد سیستم",
|
"systemReboot": "راهاندازی مجدد سیستم",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "ارتقاء سیستم",
|
"systemUpgrade": "ارتقاء سیستم",
|
||||||
"upgradeDescription": "آیا مطمئن هستید که میخواهید ارتقاء سیستم را انجام دهید؟ این عملیات ممکن است چند دقیقه طول بکشد و در این مدت سیستم ممکن است پاسخگو نباشد.",
|
"upgradeDescription": "آیا مطمئن هستید که میخواهید ارتقاء سیستم را انجام دهید؟ این عملیات ممکن است چند دقیقه طول بکشد و در این مدت سیستم ممکن است پاسخگو نباشد.",
|
||||||
"userAgent": "عامل کاربر",
|
"userAgent": "عامل کاربر",
|
||||||
"viewLogsAndManage": "مشاهده گزارشهای سیستم، انجام ارتقاء سیستم و عملیات راهاندازی مجدد"
|
"versionInfo": "اطلاعات نسخه",
|
||||||
|
"viewLogsAndManage": "مشاهده گزارشهای سیستم، انجام ارتقاء سیستم و عملیات راهاندازی مجدد",
|
||||||
|
"viewNewVersion": "مشاهده نسخه جدید",
|
||||||
|
"webVersion": "وب/مدیریت"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "فعالسازی حساب",
|
"accountEnable": "فعالسازی حساب",
|
||||||
"actions": "اقدامات",
|
"actions": "اقدامات",
|
||||||
|
"active": "فعال",
|
||||||
"add": "افزودن",
|
"add": "افزودن",
|
||||||
"administrator": "مدیر",
|
"administrator": "مدیر",
|
||||||
"areaCode": "کد منطقه",
|
"areaCode": "کد منطقه",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "ایمیل",
|
"email": "ایمیل",
|
||||||
"enable": "فعال کردن",
|
"enable": "فعال کردن",
|
||||||
"expireTime": "زمان انقضا",
|
"expireTime": "زمان انقضا",
|
||||||
|
"expired": "منقضی شده",
|
||||||
"expiredAt": "منقضی شده در",
|
"expiredAt": "منقضی شده در",
|
||||||
"failed": "ناموفق",
|
"failed": "ناموفق",
|
||||||
"giftAmount": "مقدار هدیه",
|
"giftAmount": "مقدار هدیه",
|
||||||
"giftAmountPlaceholder": "مبلغ هدیه را وارد کنید",
|
"giftAmountPlaceholder": "مبلغ هدیه را وارد کنید",
|
||||||
|
"inactive": "غیرفعال",
|
||||||
"invalidEmailFormat": "فرمت ایمیل نامعتبر است",
|
"invalidEmailFormat": "فرمت ایمیل نامعتبر است",
|
||||||
"inviteCode": "کد دعوت",
|
"inviteCode": "کد دعوت",
|
||||||
"inviteCodePlaceholder": "کد دعوت را وارد کنید (برای تولید خالی بگذارید)",
|
"inviteCodePlaceholder": "کد دعوت را وارد کنید (برای تولید خالی بگذارید)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "رمز عبور جدید را وارد کنید (اختیاری)",
|
"passwordPlaceholder": "رمز عبور جدید را وارد کنید (اختیاری)",
|
||||||
"permanent": "دائمی",
|
"permanent": "دائمی",
|
||||||
"pleaseEnterEmail": "لطفاً ایمیل خود را وارد کنید",
|
"pleaseEnterEmail": "لطفاً ایمیل خود را وارد کنید",
|
||||||
|
"price": "قیمت",
|
||||||
"referer": "ارجاعدهنده",
|
"referer": "ارجاعدهنده",
|
||||||
"refererId": "شناسه معرف",
|
"refererId": "شناسه معرف",
|
||||||
"refererIdPlaceholder": "شناسه معرف را وارد کنید",
|
"refererIdPlaceholder": "شناسه معرف را وارد کنید",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "جستجوی آدرس IP",
|
"searchIp": "جستجوی آدرس IP",
|
||||||
"selectAuthType": "نوع احراز هویت را انتخاب کنید",
|
"selectAuthType": "نوع احراز هویت را انتخاب کنید",
|
||||||
"speedLimit": "محدودیت سرعت",
|
"speedLimit": "محدودیت سرعت",
|
||||||
|
"startTime": "زمان شروع",
|
||||||
|
"status": "وضعیت",
|
||||||
"subscription": "اشتراک",
|
"subscription": "اشتراک",
|
||||||
"subscriptionDetails": "جزئیات اشتراک",
|
"subscriptionDetails": "جزئیات اشتراک",
|
||||||
|
"subscriptionId": "شناسه اشتراک",
|
||||||
|
"subscriptionInfo": "اطلاعات اشتراک",
|
||||||
"subscriptionList": "لیست اشتراک",
|
"subscriptionList": "لیست اشتراک",
|
||||||
"subscriptionLogs": "گزارشهای اشتراک",
|
"subscriptionLogs": "گزارشهای اشتراک",
|
||||||
"subscriptionName": "نام اشتراک",
|
"subscriptionName": "نام اشتراک",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "اعلانهای تجارت",
|
"tradeNotifications": "اعلانهای تجارت",
|
||||||
"trafficLimit": "محدودیت ترافیک",
|
"trafficLimit": "محدودیت ترافیک",
|
||||||
"trafficLogs": "گزارشهای ترافیک",
|
"trafficLogs": "گزارشهای ترافیک",
|
||||||
|
"trafficUsage": "استفاده از ترافیک",
|
||||||
|
"unknown": "نامشخص",
|
||||||
"unlimited": "نامحدود",
|
"unlimited": "نامحدود",
|
||||||
"unverified": "تأیید نشده",
|
"unverified": "تأیید نشده",
|
||||||
"update": "بهروزرسانی",
|
"update": "بهروزرسانی",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "عامل کاربر",
|
"userAgent": "عامل کاربر",
|
||||||
"userEmail": "ایمیل کاربر",
|
"userEmail": "ایمیل کاربر",
|
||||||
"userEmailPlaceholder": "ایمیل کاربر را وارد کنید",
|
"userEmailPlaceholder": "ایمیل کاربر را وارد کنید",
|
||||||
|
"userId": "شناسه کاربر",
|
||||||
|
"userInfo": "اطلاعات کاربر",
|
||||||
"userList": "فهرست کاربران",
|
"userList": "فهرست کاربران",
|
||||||
"userLogs": "تاریخچه ورود",
|
"userLogs": "تاریخچه ورود",
|
||||||
"userName": "شناسه کاربر",
|
"userName": "شناسه کاربر",
|
||||||
"userOrders": "سفارشها",
|
"userOrders": "سفارشها",
|
||||||
"userProfile": "پروفایل",
|
"userProfile": "پروفایل",
|
||||||
"userSubscriptions": "اشتراکها",
|
"userSubscriptions": "اشتراکها",
|
||||||
"verified": "تأیید شده"
|
"username": "نام کاربری",
|
||||||
|
"verified": "تأیید شده",
|
||||||
|
"viewDetails": "مشاهده جزئیات"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Anna kelvollinen sähköpostiosoite.",
|
"email": "Anna kelvollinen sähköpostiosoite.",
|
||||||
"existingAccount": "Onko sinulla jo tili?",
|
"existingAccount": "Onko sinulla jo tili?",
|
||||||
"get": "Hanki",
|
"get": "Hanki",
|
||||||
"invite": "Kutsukoodi",
|
"invite": "Kutsukoodi (valinnainen)",
|
||||||
"message": "#### Arvoisa käyttäjä,\n\nKiitos mielenkiinnostasi ja tuestasi. Sivuston toimintastrategian muutosten vuoksi olemme sulkeneet uusien käyttäjien rekisteröinnin. Tänä aikana nykyisten käyttäjien käyttö ei vaikutu millään tavalla.\n\nOlemme sitoutuneet tarjoamaan sinulle parempaa palvelua ja kokemusta, joten rekisteröinnin sulkemisen aikana suoritamme kattavan järjestelmän optimoinnin ja toiminnallisuuden päivityksen. Tulevaisuudessa toivotamme sinut tervetulleeksi entistä laadukkaamman sisällön ja palvelun kera.\n\nSeuraa verkkosivustoamme ja sosiaalisen median alustoja saadaksesi uusimmat päivitykset ja ilmoitukset. Kiitos ymmärryksestäsi ja tuestasi.\n\nJos sinulla on kysyttävää tai tarvitset apua, ota yhteyttä asiakaspalvelutiimiimme.\n\n**Kiitos vielä kerran tuestasi ja ymmärryksestäsi.**",
|
"message": "#### Arvoisa käyttäjä,\n\nKiitos mielenkiinnostasi ja tuestasi. Sivuston toimintastrategian muutosten vuoksi olemme sulkeneet uusien käyttäjien rekisteröinnin. Tänä aikana nykyisten käyttäjien käyttö ei vaikutu millään tavalla.\n\nOlemme sitoutuneet tarjoamaan sinulle parempaa palvelua ja kokemusta, joten rekisteröinnin sulkemisen aikana suoritamme kattavan järjestelmän optimoinnin ja toiminnallisuuden päivityksen. Tulevaisuudessa toivotamme sinut tervetulleeksi entistä laadukkaamman sisällön ja palvelun kera.\n\nSeuraa verkkosivustoamme ja sosiaalisen median alustoja saadaksesi uusimmat päivitykset ja ilmoitukset. Kiitos ymmärryksestäsi ja tuestasi.\n\nJos sinulla on kysyttävää tai tarvitset apua, ota yhteyttä asiakaspalvelutiimiimme.\n\n**Kiitos vielä kerran tuestasi ja ymmärryksestäsi.**",
|
||||||
"passwordMismatch": "Salasanat eivät täsmää",
|
"passwordMismatch": "Salasanat eivät täsmää",
|
||||||
"success": "Rekisteröinti onnistui, olet kirjautunut automaattisesti!",
|
"success": "Rekisteröinti onnistui, olet kirjautunut automaattisesti!",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Tila",
|
"status": "Tila",
|
||||||
"subject": "Aihe",
|
"subject": "Aihe",
|
||||||
"template": "Malline",
|
"template": "Malline",
|
||||||
|
"traffic_exceed_email_template": "Liikenteen Ylitysilmoitusmalli",
|
||||||
|
"traffic_exceed_email_templateDescription": "{after}.variable{before} -paikat korvataan todellisilla tiedoilla. Varmista, että säilytät nämä muuttujat.",
|
||||||
"verify_email_template": "Sähköpostin Vahvistusmalli",
|
"verify_email_template": "Sähköpostin Vahvistusmalli",
|
||||||
"verify_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.",
|
"verify_email_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, että pidät nämä muuttujat.",
|
||||||
"whitelistSuffixes": "Sallittujen luetteloiden päätteet",
|
"whitelistSuffixes": "Sallittujen luetteloiden päätteet",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "ADS-asetukset",
|
"ADS Config": "ADS-asetukset",
|
||||||
"Announcement Management": "Ilmoitusten hallinta",
|
"Announcement Management": "Ilmoitusten hallinta",
|
||||||
"Apple": "Apple ID",
|
"Apple": "Apple ID",
|
||||||
|
"Application Management": "Sovellushallinta",
|
||||||
"Auth Control": "Todennuksen hallinta",
|
"Auth Control": "Todennuksen hallinta",
|
||||||
"Coupon Management": "Kuponkien hallinta",
|
"Coupon Management": "Kuponkien hallinta",
|
||||||
"Dashboard": "Kojelauta",
|
"Dashboard": "Kojelauta",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Tilausten hallinta",
|
"Order Management": "Tilausten hallinta",
|
||||||
"Payment Config": "Maksukonfiguraatio",
|
"Payment Config": "Maksukonfiguraatio",
|
||||||
"Phone Number": "Puhelinnumero",
|
"Phone Number": "Puhelinnumero",
|
||||||
|
"Rule Management": "Sääntöhallinta",
|
||||||
"Server": "Palvelu",
|
"Server": "Palvelu",
|
||||||
"Server Management": "Palvelimen hallinta",
|
"Server Management": "Palvelimen hallinta",
|
||||||
"Settings": "Asetukset",
|
"Settings": "Asetukset",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Kuvaus",
|
"description": "Kuvaus",
|
||||||
"descriptionPlaceholder": "Syötä maksutavan kuvaus",
|
"descriptionPlaceholder": "Syötä maksutavan kuvaus",
|
||||||
"domain": "Verkkotunnus",
|
"domain": "Verkkotunnus",
|
||||||
"domainPlaceholder": "valinnainen, esim. {esimerkki}",
|
|
||||||
"edit": "Muokkaa",
|
"edit": "Muokkaa",
|
||||||
"editPayment": "Muokkaa maksutapaa",
|
"editPayment": "Muokkaa maksutapaa",
|
||||||
"enable": "Ota käyttöön",
|
"enable": "Ota käyttöön",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analysoidaan...",
|
||||||
|
"appIcon": "Sääntökuvake",
|
||||||
|
"cancel": "Peruuta",
|
||||||
|
"confirm": "Vahvista",
|
||||||
|
"confirmDelete": "Vahvista poisto",
|
||||||
|
"create": "Lisää sääntö",
|
||||||
|
"createRule": "Lisää sääntö",
|
||||||
|
"createSuccess": "Sääntö luotiin onnistuneesti",
|
||||||
|
"createdAt": "Luotu",
|
||||||
|
"delete": "Poista",
|
||||||
|
"deleteSuccess": "Sääntö poistettiin onnistuneesti",
|
||||||
|
"deleteWarning": "Oletko varma, että haluat poistaa tämän säännön? Tätä toimintoa ei voi peruuttaa.",
|
||||||
|
"downloadTemplate": "Lataa malli",
|
||||||
|
"edit": "Muokkaa",
|
||||||
|
"editRule": "Muokkaa sääntöä",
|
||||||
|
"enable": "Ota käyttöön",
|
||||||
|
"enterIconUrl": "Syötä tai lataa kuvake",
|
||||||
|
"enterRuleName": "Syötä säännön nimi",
|
||||||
|
"enterRules": "Syötä sääntöjen sisältö, yksi per rivi",
|
||||||
|
"import": "Tuoda",
|
||||||
|
"importFailed": "Tuonti epäonnistui",
|
||||||
|
"importSuccess": "Tuonti onnistui",
|
||||||
|
"importYamlDescription": "Tuo sääntöjä YAML-tiedostosta, järjestelmä luo automaattisesti sääntöryhmiä politiikkaryhmän nimien perusteella",
|
||||||
|
"importYamlRules": "Tuo YAML-säännöt",
|
||||||
|
"importing": "Tuodaan...",
|
||||||
|
"invalidYamlFormat": "Virheellinen YAML-muoto, puuttuu sääntöjen kenttä",
|
||||||
|
"name": "Säännön nimi",
|
||||||
|
"noValidRules": "Ei voimassa olevia sääntöjä löytynyt",
|
||||||
|
"pleaseUploadFile": "Lataa YAML-tiedosto",
|
||||||
|
"preview": "Esikatselu",
|
||||||
|
"rulesFormat": "Sääntömuoto: sääntötyyppi, vastaavuus, [politiikka], jossa politiikka on valinnainen.\nJos politiikkaa ei ole määritelty, nykyistä sääntöryhmän nimeä käytetään automaattisesti. Esimerkkejä:",
|
||||||
|
"rulesLabel": "Säännön sisältö",
|
||||||
|
"searchRule": "Etsi säännön nimeä",
|
||||||
|
"selectTags": "Valitse solmun tunnisteet",
|
||||||
|
"tags": "Solmun tunnisteet",
|
||||||
|
"tagsLabel": "Solmun tunnisteet",
|
||||||
|
"updateSuccess": "Sääntö päivitettiin onnistuneesti"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Ostorajoitus/kerta",
|
"quota": "Ostorajoitus/kerta",
|
||||||
"replacement": "Nollaa hinta/kerta",
|
"replacement": "Nollaa hinta/kerta",
|
||||||
"sell": "myydä",
|
"sell": "myydä",
|
||||||
"show": "Etusivun näyttö",
|
"show": "Näytä",
|
||||||
"sold": "Tilausmäärä",
|
"sold": "Tilausmäärä",
|
||||||
"subscribe": "Tilaa",
|
"subscribe": "Tilaa",
|
||||||
"subscribeGroup": "Tilaa ryhmä",
|
"subscribeGroup": "Tilaa ryhmä",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Tietosuojakäytäntö"
|
"title": "Tietosuojakäytäntö"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Mukautettu tieto",
|
||||||
|
"customDataDescription": "Mukautettu tieto, jota käytetään sivuston mukautettuun tietoon",
|
||||||
"customHtml": "Mukautettu HTML",
|
"customHtml": "Mukautettu HTML",
|
||||||
"customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston body -tagin loppuun.",
|
"customHtmlDescription": "Mukautettu HTML-koodi, joka lisätään sivuston body -tagin loppuun.",
|
||||||
"keywords": "Avainsanat",
|
"keywords": "Avainsanat",
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
"confirmSystemUpgrade": "Vahvista järjestelmän päivitys",
|
"confirmSystemUpgrade": "Vahvista järjestelmän päivitys",
|
||||||
"confirmUpgrade": "Vahvista päivitys",
|
"confirmUpgrade": "Vahvista päivitys",
|
||||||
"currentVersion": "Nykyinen järjestelmäversio:",
|
"currentVersion": "Nykyinen järjestelmäversio:",
|
||||||
|
"developmentVersion": "Kehitysversio",
|
||||||
"errors": "Virheet",
|
"errors": "Virheet",
|
||||||
"ip": "IP",
|
"ip": "IP-osoite",
|
||||||
"lastUpdated": "Viimeksi päivitetty:",
|
"lastUpdated": "Viimeksi päivitetty:",
|
||||||
|
"newVersionAvailable": "Uusi versio",
|
||||||
"none": "Ei mitään",
|
"none": "Ei mitään",
|
||||||
"query": "Kysely",
|
"query": "Kysely",
|
||||||
"rebootDescription": "Oletko varma, että haluat käynnistää järjestelmän uudelleen? Tämä toimenpide aiheuttaa lyhyen palvelukatkon.",
|
"rebootDescription": "Oletko varma, että haluat käynnistää järjestelmän uudelleen? Tämä toimenpide aiheuttaa lyhyen palvelukatkon.",
|
||||||
"rebooting": "Järjestelmä käynnistyy uudelleen...",
|
"rebooting": "Järjestelmä käynnistyy uudelleen...",
|
||||||
"refreshLogs": "Päivitä lokit",
|
"refreshLogs": "Päivitä lokit",
|
||||||
"request": "Pyyntö",
|
"request": "Pyyntö",
|
||||||
|
"serverVersion": "Palvelin",
|
||||||
"status": "Tila",
|
"status": "Tila",
|
||||||
"systemLogs": "Järjestelmän lokit",
|
"systemLogs": "Järjestelmän lokit",
|
||||||
"systemReboot": "Järjestelmän uudelleenkäynnistys",
|
"systemReboot": "Järjestelmän uudelleenkäynnistys",
|
||||||
@@ -22,5 +25,8 @@
|
|||||||
"systemUpgrade": "Järjestelmän päivitys",
|
"systemUpgrade": "Järjestelmän päivitys",
|
||||||
"upgradeDescription": "Haluatko varmasti suorittaa järjestelmäpäivityksen? Tämä toimenpide voi kestää muutaman minuutin, ja sen aikana järjestelmä ei välttämättä vastaa.",
|
"upgradeDescription": "Haluatko varmasti suorittaa järjestelmäpäivityksen? Tämä toimenpide voi kestää muutaman minuutin, ja sen aikana järjestelmä ei välttämättä vastaa.",
|
||||||
"userAgent": "Käyttäjäagentti",
|
"userAgent": "Käyttäjäagentti",
|
||||||
"viewLogsAndManage": "Tarkastele järjestelmän lokitietoja, suorita järjestelmäpäivityksiä ja uudelleenkäynnistyksiä"
|
"versionInfo": "Versiotiedot",
|
||||||
|
"viewLogsAndManage": "Tarkastele järjestelmän lokitietoja, suorita järjestelmäpäivityksiä ja uudelleenkäynnistyksiä",
|
||||||
|
"viewNewVersion": "Näytä uusi versio",
|
||||||
|
"webVersion": "Web/Admin"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"accountEnable": "Tilin aktivointi",
|
"accountEnable": "Tilin aktivointi",
|
||||||
"actions": "toiminnot",
|
"actions": "toiminnot",
|
||||||
|
"active": "Aktiivinen",
|
||||||
"add": "Lisää",
|
"add": "Lisää",
|
||||||
"administrator": "Ylläpitäjä",
|
"administrator": "Ylläpitäjä",
|
||||||
"areaCode": "Alueen koodi",
|
"areaCode": "Alueen koodi",
|
||||||
@@ -41,10 +42,12 @@
|
|||||||
"email": "sähköposti",
|
"email": "sähköposti",
|
||||||
"enable": "Ota käyttöön",
|
"enable": "Ota käyttöön",
|
||||||
"expireTime": "Vanhentumisaika",
|
"expireTime": "Vanhentumisaika",
|
||||||
|
"expired": "Vanhentunut",
|
||||||
"expiredAt": "Vanhentunut",
|
"expiredAt": "Vanhentunut",
|
||||||
"failed": "Epäonnistui",
|
"failed": "Epäonnistui",
|
||||||
"giftAmount": "Lahjan määrä",
|
"giftAmount": "Lahjan määrä",
|
||||||
"giftAmountPlaceholder": "Syötä lahjan määrä",
|
"giftAmountPlaceholder": "Syötä lahjan määrä",
|
||||||
|
"inactive": "Epäaktiivinen",
|
||||||
"invalidEmailFormat": "Virheellinen sähköpostimuoto",
|
"invalidEmailFormat": "Virheellinen sähköpostimuoto",
|
||||||
"inviteCode": "Kutsukoodi",
|
"inviteCode": "Kutsukoodi",
|
||||||
"inviteCodePlaceholder": "Syötä kutsukoodi (jätä tyhjäksi luodaksesi uuden)",
|
"inviteCodePlaceholder": "Syötä kutsukoodi (jätä tyhjäksi luodaksesi uuden)",
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
"passwordPlaceholder": "Syötä uusi salasana (valinnainen)",
|
"passwordPlaceholder": "Syötä uusi salasana (valinnainen)",
|
||||||
"permanent": "Pysyvä",
|
"permanent": "Pysyvä",
|
||||||
"pleaseEnterEmail": "Ole hyvä ja syötä sähköpostiosoite",
|
"pleaseEnterEmail": "Ole hyvä ja syötä sähköpostiosoite",
|
||||||
|
"price": "Hinta",
|
||||||
"referer": "Suosittelija",
|
"referer": "Suosittelija",
|
||||||
"refererId": "Viittaajan ID",
|
"refererId": "Viittaajan ID",
|
||||||
"refererIdPlaceholder": "Syötä suosittelijan ID",
|
"refererIdPlaceholder": "Syötä suosittelijan ID",
|
||||||
@@ -74,8 +78,12 @@
|
|||||||
"searchIp": "Etsi IP-osoite",
|
"searchIp": "Etsi IP-osoite",
|
||||||
"selectAuthType": "Valitse todennustyyppi",
|
"selectAuthType": "Valitse todennustyyppi",
|
||||||
"speedLimit": "Nopeusrajoitus",
|
"speedLimit": "Nopeusrajoitus",
|
||||||
|
"startTime": "Aloitusaika",
|
||||||
|
"status": "Tila",
|
||||||
"subscription": "Tilaus",
|
"subscription": "Tilaus",
|
||||||
"subscriptionDetails": "Tilaustiedot",
|
"subscriptionDetails": "Tilaustiedot",
|
||||||
|
"subscriptionId": "Tilauksen ID",
|
||||||
|
"subscriptionInfo": "Tilauksen tiedot",
|
||||||
"subscriptionList": "Tilauksen lista",
|
"subscriptionList": "Tilauksen lista",
|
||||||
"subscriptionLogs": "Tilaushistoria",
|
"subscriptionLogs": "Tilaushistoria",
|
||||||
"subscriptionName": "Tilauksen nimi",
|
"subscriptionName": "Tilauksen nimi",
|
||||||
@@ -89,6 +97,8 @@
|
|||||||
"tradeNotifications": "Kauppailmoitukset",
|
"tradeNotifications": "Kauppailmoitukset",
|
||||||
"trafficLimit": "Liikenteen rajoitus",
|
"trafficLimit": "Liikenteen rajoitus",
|
||||||
"trafficLogs": "Liikennelokit",
|
"trafficLogs": "Liikennelokit",
|
||||||
|
"trafficUsage": "Liikenteen käyttö",
|
||||||
|
"unknown": "Tuntematon",
|
||||||
"unlimited": "Rajoittamaton",
|
"unlimited": "Rajoittamaton",
|
||||||
"unverified": "Vahvistamaton",
|
"unverified": "Vahvistamaton",
|
||||||
"update": "Päivitä",
|
"update": "Päivitä",
|
||||||
@@ -98,11 +108,15 @@
|
|||||||
"userAgent": "Käyttäjäagentti",
|
"userAgent": "Käyttäjäagentti",
|
||||||
"userEmail": "Käyttäjän sähköposti",
|
"userEmail": "Käyttäjän sähköposti",
|
||||||
"userEmailPlaceholder": "Syötä käyttäjän sähköpostiosoite",
|
"userEmailPlaceholder": "Syötä käyttäjän sähköpostiosoite",
|
||||||
|
"userId": "Käyttäjän ID",
|
||||||
|
"userInfo": "Käyttäjän tiedot",
|
||||||
"userList": "Käyttäjälista",
|
"userList": "Käyttäjälista",
|
||||||
"userLogs": "Kirjautumishistoria",
|
"userLogs": "Kirjautumishistoria",
|
||||||
"userName": "Käyttäjän tunniste",
|
"userName": "Käyttäjän tunniste",
|
||||||
"userOrders": "Tilaukset",
|
"userOrders": "Tilaukset",
|
||||||
"userProfile": "Profiili",
|
"userProfile": "Profiili",
|
||||||
"userSubscriptions": "Tilaukset",
|
"userSubscriptions": "Tilaukset",
|
||||||
"verified": "Vahvistettu"
|
"username": "Käyttäjänimi",
|
||||||
|
"verified": "Vahvistettu",
|
||||||
|
"viewDetails": "Näytä tiedot"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"email": "Veuillez entrer une adresse e-mail valide.",
|
"email": "Veuillez entrer une adresse e-mail valide.",
|
||||||
"existingAccount": "Vous avez déjà un compte ?",
|
"existingAccount": "Vous avez déjà un compte ?",
|
||||||
"get": "Obtenir",
|
"get": "Obtenir",
|
||||||
"invite": "Code d'invitation",
|
"invite": "Code d'invitation (facultatif)",
|
||||||
"message": "#### Cher utilisateur,\n\nMerci pour votre intérêt et votre soutien. En raison d'un ajustement de notre stratégie opérationnelle, nous avons fermé la fonction d'inscription pour les nouveaux utilisateurs. Pendant cette période, l'utilisation par les utilisateurs existants ne sera pas affectée.\n\nNous nous engageons à vous offrir un meilleur service et une meilleure expérience, c'est pourquoi nous procéderons à une optimisation complète du système et à une mise à niveau des fonctionnalités pendant la fermeture des inscriptions. À l'avenir, nous vous accueillerons avec un contenu et des services de meilleure qualité.\n\nVeuillez suivre notre site web et nos plateformes de médias sociaux pour obtenir les dernières nouvelles et notifications. Merci pour votre compréhension et votre soutien.\n\nSi vous avez des questions ou avez besoin d'aide, n'hésitez pas à contacter notre équipe de service client.\n\n**Merci encore pour votre soutien et votre compréhension.**",
|
"message": "#### Cher utilisateur,\n\nMerci pour votre intérêt et votre soutien. En raison d'un ajustement de notre stratégie opérationnelle, nous avons fermé la fonction d'inscription pour les nouveaux utilisateurs. Pendant cette période, l'utilisation par les utilisateurs existants ne sera pas affectée.\n\nNous nous engageons à vous offrir un meilleur service et une meilleure expérience, c'est pourquoi nous procéderons à une optimisation complète du système et à une mise à niveau des fonctionnalités pendant la fermeture des inscriptions. À l'avenir, nous vous accueillerons avec un contenu et des services de meilleure qualité.\n\nVeuillez suivre notre site web et nos plateformes de médias sociaux pour obtenir les dernières nouvelles et notifications. Merci pour votre compréhension et votre soutien.\n\nSi vous avez des questions ou avez besoin d'aide, n'hésitez pas à contacter notre équipe de service client.\n\n**Merci encore pour votre soutien et votre compréhension.**",
|
||||||
"passwordMismatch": "Les mots de passe saisis ne correspondent pas",
|
"passwordMismatch": "Les mots de passe saisis ne correspondent pas",
|
||||||
"success": "Inscription réussie, vous êtes maintenant connecté automatiquement !",
|
"success": "Inscription réussie, vous êtes maintenant connecté automatiquement !",
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"subject": "Sujet",
|
"subject": "Sujet",
|
||||||
"template": "Modèle",
|
"template": "Modèle",
|
||||||
|
"traffic_exceed_email_template": "Modèle de notification de dépassement de trafic",
|
||||||
|
"traffic_exceed_email_templateDescription": "Les espaces réservés {after}.variable{before} seront remplacés par des données réelles. Assurez-vous de conserver ces variables.",
|
||||||
"verify_email_template": "Modèle d'Email de Vérification",
|
"verify_email_template": "Modèle d'Email de Vérification",
|
||||||
"verify_email_templateDescription": "Les espaces réservés {after}.variable{before} seront remplacés par des données réelles. Assurez-vous de conserver ces variables.",
|
"verify_email_templateDescription": "Les espaces réservés {after}.variable{before} seront remplacés par des données réelles. Assurez-vous de conserver ces variables.",
|
||||||
"whitelistSuffixes": "Suffixes de la liste blanche",
|
"whitelistSuffixes": "Suffixes de la liste blanche",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"ADS Config": "Configuration ADS",
|
"ADS Config": "Configuration ADS",
|
||||||
"Announcement Management": "Gestion des annonces",
|
"Announcement Management": "Gestion des annonces",
|
||||||
"Apple": "Identifiant Apple",
|
"Apple": "Identifiant Apple",
|
||||||
|
"Application Management": "Gestion des applications",
|
||||||
"Auth Control": "Contrôle d'authentification",
|
"Auth Control": "Contrôle d'authentification",
|
||||||
"Coupon Management": "Gestion des coupons",
|
"Coupon Management": "Gestion des coupons",
|
||||||
"Dashboard": "Tableau de bord",
|
"Dashboard": "Tableau de bord",
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
"Order Management": "Gestion des commandes",
|
"Order Management": "Gestion des commandes",
|
||||||
"Payment Config": "Configuration de paiement",
|
"Payment Config": "Configuration de paiement",
|
||||||
"Phone Number": "Numéro de téléphone",
|
"Phone Number": "Numéro de téléphone",
|
||||||
|
"Rule Management": "Gestion des règles",
|
||||||
"Server": "Serveur",
|
"Server": "Serveur",
|
||||||
"Server Management": "Gestion des services",
|
"Server Management": "Gestion des services",
|
||||||
"Settings": "Paramètres",
|
"Settings": "Paramètres",
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"description": "Description",
|
"description": "Description",
|
||||||
"descriptionPlaceholder": "Entrez la description du mode de paiement",
|
"descriptionPlaceholder": "Entrez la description du mode de paiement",
|
||||||
"domain": "Domaine",
|
"domain": "Domaine",
|
||||||
"domainPlaceholder": "facultatif, par exemple {exemple}",
|
|
||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"editPayment": "Modifier le mode de paiement",
|
"editPayment": "Modifier le mode de paiement",
|
||||||
"enable": "Activer",
|
"enable": "Activer",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analyse en cours...",
|
||||||
|
"appIcon": "Icône de règle",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"confirm": "Confirmer",
|
||||||
|
"confirmDelete": "Confirmer la suppression",
|
||||||
|
"create": "Ajouter une règle",
|
||||||
|
"createRule": "Ajouter une règle",
|
||||||
|
"createSuccess": "Règle créée avec succès",
|
||||||
|
"createdAt": "Créé le",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"deleteSuccess": "Règle supprimée avec succès",
|
||||||
|
"deleteWarning": "Êtes-vous sûr de vouloir supprimer cette règle ? Cette action ne peut pas être annulée.",
|
||||||
|
"downloadTemplate": "Télécharger le modèle",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"editRule": "Modifier la règle",
|
||||||
|
"enable": "Activer",
|
||||||
|
"enterIconUrl": "Entrez ou téléchargez une icône",
|
||||||
|
"enterRuleName": "Entrez le nom de la règle",
|
||||||
|
"enterRules": "Entrez le contenu de la règle, un par ligne",
|
||||||
|
"import": "Importer",
|
||||||
|
"importFailed": "Échec de l'importation",
|
||||||
|
"importSuccess": "Importation réussie",
|
||||||
|
"importYamlDescription": "Importer des règles à partir d'un fichier YAML, le système créera automatiquement des groupes de règles en fonction des noms de groupes de politiques",
|
||||||
|
"importYamlRules": "Importer des règles YAML",
|
||||||
|
"importing": "Importation en cours...",
|
||||||
|
"invalidYamlFormat": "Format YAML invalide, champ de règles manquant",
|
||||||
|
"name": "Nom de la règle",
|
||||||
|
"noValidRules": "Aucune règle valide trouvée",
|
||||||
|
"pleaseUploadFile": "Veuillez télécharger un fichier YAML",
|
||||||
|
"preview": "Aperçu",
|
||||||
|
"rulesFormat": "Format de la règle : type de règle, contenu de correspondance, [politique], où la politique est optionnelle.\nSi la politique n'est pas spécifiée, le nom du groupe de règles actuel sera utilisé automatiquement. Exemples :",
|
||||||
|
"rulesLabel": "Contenu de la règle",
|
||||||
|
"searchRule": "Rechercher le nom de la règle",
|
||||||
|
"selectTags": "Sélectionner des balises de nœud",
|
||||||
|
"tags": "Balises de nœud",
|
||||||
|
"tagsLabel": "Balises de nœud",
|
||||||
|
"updateSuccess": "Règle mise à jour avec succès"
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"quota": "Limite d'achat/par fois",
|
"quota": "Limite d'achat/par fois",
|
||||||
"replacement": "Réinitialiser le prix/par fois",
|
"replacement": "Réinitialiser le prix/par fois",
|
||||||
"sell": "Vente",
|
"sell": "Vente",
|
||||||
"show": "Afficher sur la page d'accueil",
|
"show": "Afficher",
|
||||||
"sold": "Nombre d'abonnements",
|
"sold": "Nombre d'abonnements",
|
||||||
"subscribe": "S'abonner",
|
"subscribe": "S'abonner",
|
||||||
"subscribeGroup": "S'abonner au groupe",
|
"subscribeGroup": "S'abonner au groupe",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"title": "Politique de confidentialité"
|
"title": "Politique de confidentialité"
|
||||||
},
|
},
|
||||||
"site": {
|
"site": {
|
||||||
|
"customData": "Données personnalisées",
|
||||||
|
"customDataDescription": "Données personnalisées, utilisées pour les données personnalisées du site",
|
||||||
"customHtml": "HTML personnalisé",
|
"customHtml": "HTML personnalisé",
|
||||||
"customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise body du site.",
|
"customHtmlDescription": "Code HTML personnalisé à injecter dans le bas de la balise body du site.",
|
||||||
"keywords": "Mots-clés",
|
"keywords": "Mots-clés",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user