Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
+537
-2
@@ -1,13 +1,548 @@
|
||||
<a name="readme-top"></a>
|
||||
# Changelog
|
||||
|
||||
## [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)
|
||||
|
||||
### тЬи 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
|
||||
|
||||
* **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))
|
||||
- **input**: Add minimum value constraint and enhance number handling in EnhancedInput ([94822d9](https://github.com/perfect-panel/ppanel-web/commit/94822d9))
|
||||
|
||||
# [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>
|
||||
|
||||
|
||||
+25
-13
@@ -232,16 +232,6 @@ export default function SubscribeAppForm<
|
||||
name: 'is_default',
|
||||
type: 'boolean',
|
||||
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',
|
||||
@@ -252,10 +242,32 @@ export default function SubscribeAppForm<
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={(value) => {
|
||||
form.setValue(
|
||||
field.name,
|
||||
value.filter((item) => item.url),
|
||||
const filteredValue = 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>
|
||||
@@ -286,31 +286,34 @@ export default function Page() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='template'>
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
||||
{['verify_email_template', 'expiration_email_template', 'maintenance_email_template'].map(
|
||||
(templateKey) => (
|
||||
<Card key={templateKey}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
||||
<CardDescription>
|
||||
{t(`${templateKey}Description`, { after: '{{', before: '}}' })}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HTMLEditor
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config?.[templateKey] as string}
|
||||
onBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
[templateKey]: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
),
|
||||
)}
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
|
||||
{[
|
||||
'verify_email_template',
|
||||
'expiration_email_template',
|
||||
'maintenance_email_template',
|
||||
'traffic_exceed_email_template',
|
||||
].map((templateKey) => (
|
||||
<Card key={templateKey}>
|
||||
<CardHeader>
|
||||
<CardTitle>{t(`${templateKey}`)}</CardTitle>
|
||||
<CardDescription>
|
||||
{t(`${templateKey}Description`, { after: '{{', before: '}}' })}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HTMLEditor
|
||||
placeholder={t('inputPlaceholder')}
|
||||
value={data?.config?.[templateKey] as string}
|
||||
onBlur={(value) =>
|
||||
updateConfig('config', {
|
||||
...data?.config,
|
||||
[templateKey]: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
|
||||
@@ -136,14 +136,14 @@ export function Register() {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<TableRow className='flex flex-col'>
|
||||
<TableCell className='w-full'>
|
||||
<Label>{t('trialSubscribePlan')}</Label>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{t('trialSubscribePlanDescription')}
|
||||
</p>
|
||||
</TableCell>
|
||||
<TableCell className='text-right'>
|
||||
<TableCell className='max-w-96'>
|
||||
<EnhancedInput
|
||||
placeholder={t('trialDuration')}
|
||||
type='number'
|
||||
|
||||
@@ -307,6 +307,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.countPlaceholder')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -327,6 +328,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.userLimitPlaceholder')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
|
||||
@@ -57,8 +57,13 @@ export default function Page(props: any) {
|
||||
{
|
||||
accessorKey: 'subscribe_id',
|
||||
header: t('subscribe'),
|
||||
cell: ({ row }) =>
|
||||
subscribeList?.find((item) => item.id === row.getValue('subscribe_id'))?.name,
|
||||
cell: ({ row }) => {
|
||||
const name = subscribeList?.find(
|
||||
(item) => item.id === row.getValue('subscribe_id'),
|
||||
)?.name;
|
||||
const quantity = row.original.quantity;
|
||||
return name ? `${name} ├Ч ${quantity}` : '';
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
|
||||
@@ -45,6 +45,7 @@ interface PaymentFormProps<T> {
|
||||
loading?: boolean;
|
||||
initialValues?: T;
|
||||
onSubmit: (values: T) => Promise<boolean>;
|
||||
isEdit?: boolean;
|
||||
}
|
||||
|
||||
export default function PaymentForm<T>({
|
||||
@@ -53,6 +54,7 @@ export default function PaymentForm<T>({
|
||||
loading,
|
||||
initialValues,
|
||||
onSubmit,
|
||||
isEdit,
|
||||
}: PaymentFormProps<T>) {
|
||||
const t = useTranslations('payment');
|
||||
const { common } = useGlobalStore();
|
||||
@@ -199,7 +201,7 @@ export default function PaymentForm<T>({
|
||||
<FormLabel>{t('domain')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
placeholder={t('domainPlaceholder', { example: 'http(s)://example.com' })}
|
||||
placeholder='http(s)://example.com'
|
||||
value={field.value}
|
||||
onValueChange={(value) => form.setValue('domain', value as string)}
|
||||
/>
|
||||
@@ -317,6 +319,8 @@ export default function PaymentForm<T>({
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
value={field.value}
|
||||
// @ts-expect-error
|
||||
disabled={isEdit && Boolean(initialValues?.platform)}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
@@ -367,6 +371,7 @@ export default function PaymentForm<T>({
|
||||
? configValues[fieldKey]
|
||||
: ''
|
||||
}
|
||||
disabled={fieldKey === 'webhook_secret'}
|
||||
onValueChange={(value) => {
|
||||
const newConfig = { ...configValues };
|
||||
newConfig[fieldKey] = value;
|
||||
|
||||
@@ -136,6 +136,7 @@ export default function PaymentTable() {
|
||||
actions={{
|
||||
render: (row) => [
|
||||
<PaymentForm<API.UpdatePaymentMethodRequest>
|
||||
isEdit
|
||||
key='edit'
|
||||
trigger={<Button>{t('edit')}</Button>}
|
||||
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';
|
||||
|
||||
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 portSchema = z.number().max(65535).nullish();
|
||||
@@ -58,7 +58,15 @@ const hysteria2Schema = z.object({
|
||||
|
||||
const tuicSchema = z.object({
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -87,6 +95,10 @@ const protocolConfigSchema = z.discriminatedUnion('protocol', [
|
||||
protocol: z.literal('tuic'),
|
||||
config: tuicSchema,
|
||||
}),
|
||||
z.object({
|
||||
protocol: z.literal('anytls'),
|
||||
config: anytlsSchema,
|
||||
}),
|
||||
]);
|
||||
|
||||
const baseFormSchema = z.object({
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
{trigger}
|
||||
</Button>
|
||||
</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>
|
||||
<SheetTitle>{title}</SheetTitle>
|
||||
</SheetHeader>
|
||||
@@ -290,7 +290,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
if (['trojan', 'hysteria2', 'tuic'].includes(value)) {
|
||||
if (['trojan', 'hysteria2', 'tuic', 'anytls'].includes(value)) {
|
||||
form.setValue('config.security', 'tls');
|
||||
}
|
||||
}}
|
||||
@@ -362,9 +362,10 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
placeholder='1-65535'
|
||||
step={1}
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder='1-65535'
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
@@ -401,11 +402,11 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic'].includes(protocol) && (
|
||||
{['vmess', 'vless', 'trojan', 'hysteria2', 'tuic', 'anytls'].includes(protocol) && (
|
||||
<div className='grid gap-4'>
|
||||
<div
|
||||
className={cn('flex gap-4 *:flex-1', {
|
||||
'grid grid-cols-2': ['hysteria2'].includes(protocol),
|
||||
'grid grid-cols-2': ['hysteria2', 'tuic'].includes(protocol),
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
@@ -418,6 +419,9 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
step={1}
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder='1-65535'
|
||||
onValueChange={(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>
|
||||
{['vmess', 'vless', 'trojan'].includes(protocol) && (
|
||||
<Card>
|
||||
@@ -626,171 +733,17 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between p-3'>
|
||||
<CardTitle>{t('form.securityConfig')}</CardTitle>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security'
|
||||
render={({ field }) => (
|
||||
<FormItem className='!mt-0 min-w-32'>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('form.pleaseSelect')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{['vmess', 'vless'].includes(protocol) && (
|
||||
<SelectItem value='none'>NONE</SelectItem>
|
||||
)}
|
||||
<SelectItem value='tls'>TLS</SelectItem>
|
||||
{protocol === 'vless' && (
|
||||
<SelectItem value='reality'>Reality</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardHeader>
|
||||
{security !== 'none' && (
|
||||
<CardContent className='grid grid-cols-2 gap-4 p-3'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.sni'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server Name(SNI)</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{protocol === 'vless' && security === 'reality' && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_server_addr'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.serverAddress')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t(
|
||||
'form.security_config.serverAddressPlaceholder',
|
||||
)}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_server_port'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.serverPort')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder={t('form.security_config.serverPortPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_private_key'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.privateKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t('form.security_config.privateKeyPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_public_key'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.publicKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t('form.security_config.publicKeyPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_short_id'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.shortId')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t('form.security_config.shortIdPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{protocol === 'vless' && (
|
||||
{(['vmess', 'vless', 'trojan'].includes(protocol) ||
|
||||
['anytls', 'tuic', 'hysteria2'].includes(protocol)) && (
|
||||
<Card>
|
||||
<CardHeader className='flex flex-row items-center justify-between p-3'>
|
||||
<CardTitle>{t('form.securityConfig')}</CardTitle>
|
||||
{['vmess', 'vless', 'trojan'].includes(protocol) && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.fingerprint'
|
||||
name='config.security'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.fingerprint')}</FormLabel>
|
||||
<FormItem className='!mt-0 min-w-32'>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
@@ -803,45 +756,213 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value='chrome'>Chrome</SelectItem>
|
||||
<SelectItem value='firefox'>Firefox</SelectItem>
|
||||
<SelectItem value='safari'>Safari</SelectItem>
|
||||
<SelectItem value='ios'>IOS</SelectItem>
|
||||
<SelectItem value='android'>Android</SelectItem>
|
||||
<SelectItem value='edge'>edge</SelectItem>
|
||||
<SelectItem value='360'>360</SelectItem>
|
||||
<SelectItem value='qq'>QQ</SelectItem>
|
||||
{['vmess', 'vless'].includes(protocol) && (
|
||||
<SelectItem value='none'>NONE</SelectItem>
|
||||
)}
|
||||
<SelectItem value='tls'>TLS</SelectItem>
|
||||
{protocol === 'vless' && (
|
||||
<SelectItem value='reality'>Reality</SelectItem>
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.allow_insecure'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Allow Insecure</FormLabel>
|
||||
<FormControl>
|
||||
<div className='pt-2'>
|
||||
<Switch
|
||||
checked={!!field.value}
|
||||
onCheckedChange={(value) => {
|
||||
</CardHeader>
|
||||
{(['anytls', 'tuic', 'hysteria2'].includes(protocol) ||
|
||||
(['vmess', 'vless', 'trojan'].includes(protocol) &&
|
||||
security !== 'none')) && (
|
||||
<CardContent className='grid grid-cols-2 gap-4 p-3'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.sni'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Server Name(SNI)</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Reality чЙ╣цоКщЕНч╜охПкхЬи vless + reality цЧ╢цШ╛чд║ */}
|
||||
{protocol === 'vless' && security === 'reality' && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_server_addr'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.serverAddress')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t(
|
||||
'form.security_config.serverAddressPlaceholder',
|
||||
)}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_server_port'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.serverPort')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
type='number'
|
||||
min={1}
|
||||
max={65535}
|
||||
placeholder={t(
|
||||
'form.security_config.serverPortPlaceholder',
|
||||
)}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_private_key'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.privateKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t(
|
||||
'form.security_config.privateKeyPlaceholder',
|
||||
)}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_public_key'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.publicKey')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t('form.security_config.publicKeyPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.reality_short_id'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.shortId')}</FormLabel>
|
||||
<FormControl>
|
||||
<EnhancedInput
|
||||
{...field}
|
||||
placeholder={t('form.security_config.shortIdPlaceholder')}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{protocol === 'vless' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.fingerprint'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.security_config.fingerprint')}</FormLabel>
|
||||
<Select
|
||||
value={field.value}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
}}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('form.pleaseSelect')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value='chrome'>Chrome</SelectItem>
|
||||
<SelectItem value='firefox'>Firefox</SelectItem>
|
||||
<SelectItem value='safari'>Safari</SelectItem>
|
||||
<SelectItem value='ios'>IOS</SelectItem>
|
||||
<SelectItem value='android'>Android</SelectItem>
|
||||
<SelectItem value='edge'>edge</SelectItem>
|
||||
<SelectItem value='360'>360</SelectItem>
|
||||
<SelectItem value='qq'>QQ</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='config.security_config.allow_insecure'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Allow Insecure</FormLabel>
|
||||
<FormControl>
|
||||
<div className='pt-2'>
|
||||
<Switch
|
||||
checked={!!field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
form.setValue(field.name, checked);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -899,6 +1020,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
|
||||
{
|
||||
name: 'port',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
min: 1,
|
||||
max: 65535,
|
||||
placeholder: t('form.relayPort'),
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@workspace/ui/components/accordion';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Progress } from '@workspace/ui/components/progress';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
@@ -11,6 +17,8 @@ import {
|
||||
} from '@workspace/ui/components/tooltip';
|
||||
import { formatDate } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { UserSubscribeDetail } from '../user/user-detail';
|
||||
|
||||
export function formatPercentage(value: number): string {
|
||||
return `${value.toFixed(1)}%`;
|
||||
@@ -18,25 +26,19 @@ export function formatPercentage(value: number): string {
|
||||
|
||||
export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
||||
const t = useTranslations('server.node');
|
||||
const [openItem, setOpenItem] = useState<string | null>(null);
|
||||
|
||||
const {
|
||||
last_at,
|
||||
online_users,
|
||||
status: serverStatus,
|
||||
} = status || {
|
||||
online_users: [],
|
||||
status: {
|
||||
cpu: 0,
|
||||
mem: 0,
|
||||
disk: 0,
|
||||
updated_at: 0,
|
||||
},
|
||||
last_at: 0,
|
||||
const { online, cpu, mem, disk, updated_at } = status || {
|
||||
online: {},
|
||||
cpu: 0,
|
||||
mem: 0,
|
||||
disk: 0,
|
||||
updated_at: 0,
|
||||
};
|
||||
const isOnline = last_at > 0;
|
||||
const isOnline = updated_at > 0;
|
||||
const badgeVariant = isOnline ? 'default' : 'destructive';
|
||||
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 (
|
||||
<TooltipProvider>
|
||||
@@ -52,45 +54,56 @@ export function NodeStatusCell({ status }: { status: API.NodeStatus }) {
|
||||
<div className='flex flex-col space-y-1'>
|
||||
<div className='flex justify-between'>
|
||||
<span>CPU</span>
|
||||
<span>{formatPercentage(serverStatus?.cpu ?? 0)}</span>
|
||||
<span>{formatPercentage(cpu ?? 0)}</span>
|
||||
</div>
|
||||
<Progress value={serverStatus?.cpu ?? 0} className='h-2' max={100} />
|
||||
<Progress value={cpu ?? 0} className='h-2' max={100} />
|
||||
</div>
|
||||
<div className='flex flex-col space-y-1'>
|
||||
<div className='flex justify-between'>
|
||||
<span>{t('memory')}</span>
|
||||
<span>{formatPercentage(serverStatus?.mem ?? 0)}</span>
|
||||
<span>{formatPercentage(mem ?? 0)}</span>
|
||||
</div>
|
||||
<Progress value={serverStatus?.mem ?? 0} className='h-2' max={100} />
|
||||
<Progress value={mem ?? 0} className='h-2' max={100} />
|
||||
</div>
|
||||
<div className='flex flex-col space-y-1'>
|
||||
<div className='flex justify-between'>
|
||||
<span>{t('disk')}</span>
|
||||
<span>{formatPercentage(serverStatus?.disk ?? 0)}</span>
|
||||
<span>{formatPercentage(disk ?? 0)}</span>
|
||||
</div>
|
||||
<Progress value={serverStatus?.disk ?? 0} className='h-2' max={100} />
|
||||
<Progress value={disk ?? 0} className='h-2' max={100} />
|
||||
</div>
|
||||
{isOnline && (
|
||||
<div>
|
||||
{t('lastUpdated')}: {formatDate(serverStatus?.updated_at ?? 0)}
|
||||
{t('lastUpdated')}: {formatDate(updated_at ?? 0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
{isOnline && onlineCount > 0 && (
|
||||
<TooltipContent className='bg-muted text-foreground w-80'>
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-2'>
|
||||
<h4 className='text-sm font-semibold'>{t('onlineUsers')}</h4>
|
||||
<ScrollArea className='h-[400px] rounded-md border p-2'>
|
||||
{online_users.map((user, index) => (
|
||||
<div key={user.uid} className='py-1 text-xs'>
|
||||
{user.ip} (UID: {user.uid})
|
||||
</div>
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
<TooltipContent className='bg-card text-foreground w-96'>
|
||||
<ScrollArea className='h-[540px] rounded-md border px-4 py-2'>
|
||||
<h4 className='py-1 text-sm font-semibold'>{t('onlineUsers')}</h4>
|
||||
<Accordion
|
||||
type='single'
|
||||
collapsible
|
||||
className='w-full'
|
||||
onValueChange={(value) => setOpenItem(value)}
|
||||
>
|
||||
{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>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function NodeTable() {
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
header: t('id'),
|
||||
cell: ({ row }) => (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
@@ -87,6 +87,7 @@ export default function NodeTable() {
|
||||
'bg-yellow-500': row.original.protocol === 'trojan',
|
||||
'bg-purple-500': row.original.protocol === 'hysteria2',
|
||||
'bg-cyan-500': row.original.protocol === 'tuic',
|
||||
'bg-gray-500': row.original.protocol === 'anytls',
|
||||
})}
|
||||
>
|
||||
{row.getValue('id')}
|
||||
@@ -159,7 +160,7 @@ export default function NodeTable() {
|
||||
header: t('nodeGroup'),
|
||||
cell: ({ row }) => {
|
||||
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>
|
||||
) : (
|
||||
'--'
|
||||
t('noData')
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -272,17 +273,17 @@ export default function NodeTable() {
|
||||
<ConfirmButton
|
||||
key='delete'
|
||||
trigger={<Button variant='destructive'>{t('delete')}</Button>}
|
||||
title={t('group.confirmDelete')}
|
||||
description={t('group.deleteWarning')}
|
||||
title={t('confirmDelete')}
|
||||
description={t('deleteWarning')}
|
||||
onConfirm={async () => {
|
||||
await batchDeleteNode({
|
||||
ids: rows.map((item) => item.id),
|
||||
});
|
||||
toast.success(t('group.deleteSuccess'));
|
||||
toast.success(t('deleteSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
cancelText={t('group.cancel')}
|
||||
confirmText={t('group.confirm')}
|
||||
cancelText={t('cancel')}
|
||||
confirmText={t('confirm')}
|
||||
/>,
|
||||
];
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import { getTranslations } from 'next-intl/server';
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
|
||||
import SubscribeApp from './app/table';
|
||||
import GroupTable from './group/table';
|
||||
import SubscribeConfig from './subscribe-config';
|
||||
import SubscribeTable from './subscribe-table';
|
||||
@@ -16,7 +15,6 @@ export default async function Page() {
|
||||
<TabsTrigger value='subscribe'>{t('tabs.subscribe')}</TabsTrigger>
|
||||
<TabsTrigger value='group'>{t('tabs.subscribeGroup')}</TabsTrigger>
|
||||
<TabsTrigger value='config'>{t('tabs.subscribeConfig')}</TabsTrigger>
|
||||
<TabsTrigger value='app'>{t('tabs.subscribeApp')}</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='subscribe'>
|
||||
<SubscribeTable />
|
||||
@@ -27,9 +25,6 @@ export default async function Page() {
|
||||
<TabsContent value='config'>
|
||||
<SubscribeConfig />
|
||||
</TabsContent>
|
||||
<TabsContent value='app'>
|
||||
<SubscribeApp />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ import { evaluateWithPrecision, unitConversion } from '@workspace/ui/utils';
|
||||
import { CreditCard, Server, Settings } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { assign, shake } from 'radash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
@@ -81,6 +81,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
}: Readonly<SubscribeFormProps<T>>) {
|
||||
const t = useTranslations('subscribe');
|
||||
const [open, setOpen] = useState(false);
|
||||
const updateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const formSchema = z.object({
|
||||
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(() => {
|
||||
form?.reset(
|
||||
assign(defaultValues, shake(initialValues, (value) => value === null) as Record<string, any>),
|
||||
);
|
||||
}, [form, initialValues]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (updateTimeoutRef.current) {
|
||||
clearTimeout(updateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function handleSubmit(data: { [x: string]: any }) {
|
||||
const bool = await onSubmit(data as T);
|
||||
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_price = form.watch('unit_price');
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
@@ -298,6 +399,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -321,7 +423,8 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
value={field.value === -1 ? 0 : field.value}
|
||||
step={1}
|
||||
value={field.value}
|
||||
min={0}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -343,6 +446,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<EnhancedInput
|
||||
placeholder={t('form.noLimit')}
|
||||
type='number'
|
||||
step={1}
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
@@ -540,53 +644,75 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
||||
<FormItem>
|
||||
<FormLabel>{t('form.discount')}</FormLabel>
|
||||
<FormControl>
|
||||
<ArrayInput<API.SubscribeDiscount>
|
||||
<ArrayInput<API.SubscribeDiscount & { price?: number }>
|
||||
fields={[
|
||||
{
|
||||
name: 'quantity',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
min: 1,
|
||||
suffix: unit_time && t(`form.${unit_time}`),
|
||||
},
|
||||
{
|
||||
name: 'discount',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
min: 0.01,
|
||||
max: 100,
|
||||
step: 0.01,
|
||||
placeholder: t('form.discountPercent'),
|
||||
suffix: '%',
|
||||
calculateValue: function (data) {
|
||||
const { unit_price } = form.getValues();
|
||||
return {
|
||||
...data,
|
||||
price: evaluateWithPrecision(
|
||||
`${unit_price} * ${data.quantity} * ${data.discount} / 100`,
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
placeholder: t('form.discount_price'),
|
||||
type: 'number',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
formatInput: (value) => unitConversion('centsToDollars', 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}
|
||||
onChange={(value) => {
|
||||
form.setValue(field.name, value);
|
||||
onChange={(
|
||||
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>
|
||||
|
||||
@@ -5,18 +5,21 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { Label } from '@workspace/ui/components/label';
|
||||
import { Table, TableBody, TableCell, TableRow } from '@workspace/ui/components/table';
|
||||
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 { UploadImage } from '@workspace/ui/custom-components/upload-image';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useRef } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function Site() {
|
||||
const t = useTranslations('system.site');
|
||||
|
||||
const ref = useRef<API.SiteConfig | undefined>(undefined);
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ['getSiteConfig'],
|
||||
queryFn: async () => {
|
||||
const { data } = await getSiteConfig();
|
||||
ref.current = data.data;
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
@@ -25,7 +28,7 @@ export default function Site() {
|
||||
if (data?.[key] === value) return;
|
||||
try {
|
||||
await updateSiteConfig({
|
||||
...data,
|
||||
...ref.current,
|
||||
[key]: value,
|
||||
} as API.SiteConfig);
|
||||
toast.success(t('saveSuccess'));
|
||||
@@ -130,6 +133,49 @@ export default function Site() {
|
||||
/>
|
||||
</TableCell>
|
||||
</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>
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { getSystemLog, restartSystem } from '@/services/admin/tool';
|
||||
import { getSystemLog, getVersion, restartSystem } from '@/services/admin/tool';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
Accordion,
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '@workspace/ui/components/accordion';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
@@ -30,8 +29,10 @@ import {
|
||||
} from '@workspace/ui/components/card';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { formatDate } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import packageJson from '../../../../../package.json';
|
||||
|
||||
const getLogLevelColor = (level: string) => {
|
||||
const colorMap: { [key: string]: string } = {
|
||||
@@ -59,6 +60,91 @@ export default function Page() {
|
||||
const [openRestart, setOpenRestart] = 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 (
|
||||
<Card className='border-none'>
|
||||
<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>
|
||||
</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'>
|
||||
<AlertDialog>
|
||||
{/* <AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button>{t('systemUpgrade')}</Button>
|
||||
</AlertDialogTrigger>
|
||||
@@ -81,7 +167,7 @@ export default function Page() {
|
||||
<AlertDialogAction>{t('confirmUpgrade')}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</AlertDialog> */}
|
||||
<AlertDialog open={openRestart} onOpenChange={setOpenRestart}>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant='destructive'>{t('systemReboot')}</Button>
|
||||
@@ -112,13 +198,88 @@ export default function Page() {
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='space-y-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='text-lg font-semibold'>
|
||||
{t('currentVersion')} <span>V1.0.0</span>
|
||||
<div className='space-y-4'>
|
||||
{/* чЙИцЬмф┐бцБпч┤зхЗСцШ╛чд║ */}
|
||||
<div className='flex flex-col space-y-2 sm:flex-row sm:space-x-3 sm:space-y-0'>
|
||||
{/* чФицИ╖члп/чобчРЖчлпчЙИцЬм */}
|
||||
<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='text-muted-foreground text-sm'>
|
||||
{t('lastUpdated')} <span>2024-12-16 12:00:00</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: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>
|
||||
<Card className='overflow-hidden'>
|
||||
@@ -146,20 +307,20 @@ export default function Page() {
|
||||
<AccordionItem key={index} value={`item-${index}`} className='px-4'>
|
||||
<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'>
|
||||
<Badge
|
||||
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>
|
||||
<span className='text-xs font-medium sm:text-sm'>{log.timestamp}</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<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>{log.ip}</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='font-medium'>{t('userAgent')}:</div>
|
||||
<div className='break-all'>{log['user-agent']}</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
|
||||
@@ -45,6 +45,9 @@ const formSchema = z.object({
|
||||
speed_limit: z.number().optional(),
|
||||
device_limit: z.number().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) {
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
import { Display } from '@/components/display';
|
||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||
import { getSubscribeList } from '@/services/admin/subscribe';
|
||||
import { createUser, deleteUser, getUserList, updateUserBasicInfo } from '@/services/admin/user';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
@@ -20,8 +22,19 @@ export default function Page() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
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 (
|
||||
<ProTable<API.User, Record<string, unknown>>
|
||||
<ProTable<API.User, API.GetUserListParams>
|
||||
action={ref}
|
||||
header={{
|
||||
title: t('userList'),
|
||||
@@ -58,8 +71,21 @@ export default function Page() {
|
||||
<Switch
|
||||
defaultChecked={row.getValue('enable')}
|
||||
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({
|
||||
...row.original,
|
||||
user_id: id,
|
||||
...rest,
|
||||
enable: checked,
|
||||
} as unknown as API.UpdateUserBasiceInfoRequest);
|
||||
toast.success(t('updateSuccess'));
|
||||
@@ -119,13 +145,38 @@ export default function Page() {
|
||||
cell: ({ row }) => formatDate(row.getValue('created_at')),
|
||||
},
|
||||
]}
|
||||
request={async (pagination) => {
|
||||
const { data } = await getUserList(pagination);
|
||||
request={async (pagination, filter) => {
|
||||
const { data } = await getUserList({
|
||||
...pagination,
|
||||
...filter,
|
||||
});
|
||||
return {
|
||||
list: data.data?.list || [],
|
||||
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={{
|
||||
render: (row) => {
|
||||
return [
|
||||
|
||||
@@ -1,15 +1,121 @@
|
||||
'use client';
|
||||
|
||||
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 { Button } from '@workspace/ui/components/button';
|
||||
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 Link from 'next/link';
|
||||
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 }) {
|
||||
const t = useTranslations('user');
|
||||
const [shouldFetch, setShouldFetch] = useState(false);
|
||||
|
||||
+11
-10
@@ -9,20 +9,20 @@ import { NextIntlClientProvider } from 'next-intl';
|
||||
import { getLocale, getMessages } from 'next-intl/server';
|
||||
import { PublicEnvScript } from 'next-runtime-env';
|
||||
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 NextTopLoader from 'nextjs-toploader';
|
||||
import React from 'react';
|
||||
|
||||
const fontSans = Geist({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-sans',
|
||||
});
|
||||
// const fontSans = Geist({
|
||||
// subsets: ['latin'],
|
||||
// variable: '--font-sans',
|
||||
// });
|
||||
|
||||
const fontMono = Geist_Mono({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-mono',
|
||||
});
|
||||
// const fontMono = Geist_Mono({
|
||||
// subsets: ['latin'],
|
||||
// variable: '--font-mono',
|
||||
// });
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
noStore();
|
||||
@@ -111,7 +111,8 @@ export default async function RootLayout({
|
||||
</head>
|
||||
<body
|
||||
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}>
|
||||
<NextTopLoader showSpinner={false} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
|
||||
import { queryServerTotalData, queryTicketWaitReply } from '@/services/admin/console';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@workspace/ui/components/select';
|
||||
import { Separator } from '@workspace/ui/components/separator';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||
import { formatBytes } from '@workspace/ui/utils';
|
||||
@@ -59,15 +61,13 @@ export default function Statistics() {
|
||||
users: {
|
||||
today:
|
||||
ServerTotal?.user_traffic_ranking_today?.map((item) => ({
|
||||
name: item.user_id,
|
||||
name: item.sid,
|
||||
traffic: item.download + item.upload,
|
||||
email: item.email,
|
||||
})) || [],
|
||||
yesterday:
|
||||
ServerTotal?.user_traffic_ranking_yesterday?.map((item) => ({
|
||||
name: item.user_id,
|
||||
name: item.sid,
|
||||
traffic: item.download + item.upload,
|
||||
email: item.email,
|
||||
})) || [],
|
||||
},
|
||||
};
|
||||
@@ -175,10 +175,6 @@ export default function Statistics() {
|
||||
label: t('type'),
|
||||
color: 'hsl(var(--muted))',
|
||||
},
|
||||
email: {
|
||||
label: t('email'),
|
||||
color: 'hsl(var(--muted))',
|
||||
},
|
||||
label: {
|
||||
color: 'hsl(var(--foreground))',
|
||||
},
|
||||
@@ -204,13 +200,22 @@ export default function Statistics() {
|
||||
tickFormatter={(value, index) => String(index + 1)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
trigger='hover'
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
label={true}
|
||||
labelFormatter={(label) =>
|
||||
dataType === 'nodes'
|
||||
? `${t('nodes')}: ${label}`
|
||||
: `${t('users')}: ${label}`
|
||||
labelFormatter={(label, [payload]) =>
|
||||
dataType === 'nodes' ? (
|
||||
`${t('nodes')}: ${label}`
|
||||
) : (
|
||||
<>
|
||||
<div className='w-80'>
|
||||
<UserSubscribeDetail id={payload?.payload.name} enabled={true} />
|
||||
</div>
|
||||
<Separator className='my-2' />
|
||||
<div>{`${t('users')}: ${label}`}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
formatter={(value) => {
|
||||
return formatBytes(Number(value) || 0);
|
||||
|
||||
@@ -81,16 +81,26 @@ export const navs = [
|
||||
url: '/dashboard/server',
|
||||
icon: 'flat-color-icons:data-protection',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Finance',
|
||||
items: [
|
||||
{
|
||||
title: 'Application Management',
|
||||
url: '/dashboard/application',
|
||||
icon: 'flat-color-icons:touchscreen-smartphone',
|
||||
},
|
||||
{
|
||||
title: 'Rule Management',
|
||||
url: '/dashboard/rules',
|
||||
icon: 'flat-color-icons:ruler',
|
||||
},
|
||||
{
|
||||
title: 'Subscribe Management',
|
||||
url: '/dashboard/subscribe',
|
||||
icon: 'flat-color-icons:shop',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Finance',
|
||||
items: [
|
||||
{
|
||||
title: 'Order Management',
|
||||
url: '/dashboard/order',
|
||||
|
||||
@@ -16,6 +16,7 @@ export const useGlobalStore = create<GlobalStore>((set) => ({
|
||||
site_logo: '',
|
||||
keywords: '',
|
||||
custom_html: '',
|
||||
custom_data: '',
|
||||
},
|
||||
verify: {
|
||||
turnstile_site_key: '',
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Zadejte platnou e-mailovou adresu.",
|
||||
"existingAccount": "Ji┼╛ m├бte ├║─Нet?",
|
||||
"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├н.**",
|
||||
"passwordMismatch": "Hesla se neshoduj├н",
|
||||
"success": "Registrace ├║sp─Ы┼бn├б, automaticky p┼Щihl├б┼бeno!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Stav",
|
||||
"subject": "P┼Щedm─Ыt",
|
||||
"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_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",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "Konfigurace ADS",
|
||||
"Announcement Management": "Spr├бva ozn├бmen├н",
|
||||
"Apple": "Apple ID",
|
||||
"Application Management": "Spr├бva aplikac├н",
|
||||
"Auth Control": "┼Ш├нzen├н ov─Ы┼Щov├бn├н",
|
||||
"Coupon Management": "Spr├бva kup├│n┼п",
|
||||
"Dashboard": "P┼Щ├нstrojov├б deska",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Spr├бva objedn├бvek",
|
||||
"Payment Config": "Konfigurace platby",
|
||||
"Phone Number": "Telefonn├н ─Н├нslo",
|
||||
"Rule Management": "Spr├бva pravidel",
|
||||
"Server": "Server",
|
||||
"Server Management": "Spr├бva serveru",
|
||||
"Settings": "Nastaven├н",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Popis",
|
||||
"descriptionPlaceholder": "Zadejte popis platebn├н metody",
|
||||
"domain": "Dom├йna",
|
||||
"domainPlaceholder": "voliteln├й, nap┼Щ. {example}",
|
||||
"edit": "Upravit",
|
||||
"editPayment": "Upravit platebn├н metodu",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Z├бsady ochrany osobn├нch ├║daj┼п"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Vlastn├н data",
|
||||
"customDataDescription": "Vlastn├н data, pou┼╛├нvan├б pro vlastn├н data webu",
|
||||
"customHtml": "Vlastn├н HTML",
|
||||
"customHtmlDescription": "Vlastn├н HTML k├│d, kter├╜ bude vlo┼╛en na konec tagu body na str├бnce.",
|
||||
"keywords": "Kl├н─Нov├б slova",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Potvrdit aktualizaci syst├йmu",
|
||||
"confirmUpgrade": "Potvrdit aktualizaci",
|
||||
"currentVersion": "Aktu├бln├н verze syst├йmu:",
|
||||
"developmentVersion": "V├╜vojov├б verze",
|
||||
"errors": "Chyby",
|
||||
"ip": "IP",
|
||||
"ip": "IP adresa",
|
||||
"lastUpdated": "Posledn├н aktualizace:",
|
||||
"newVersionAvailable": "Nov├б verze",
|
||||
"none": "┼╜├бdn├╜",
|
||||
"query": "Dotaz",
|
||||
"rebootDescription": "Opravdu chcete restartovat syst├йm? Tato akce zp┼пsob├н kr├бtk├й p┼Щeru┼бen├н slu┼╛by.",
|
||||
"rebooting": "Syst├йm se restartuje...",
|
||||
"refreshLogs": "Obnovit protokoly",
|
||||
"request": "┼╜├бdost",
|
||||
"serverVersion": "Server",
|
||||
"status": "Stav",
|
||||
"systemLogs": "Syst├йmov├й protokoly",
|
||||
"systemReboot": "Restart syst├йmu",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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├н.",
|
||||
"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",
|
||||
"actions": "akce",
|
||||
"active": "Aktivn├н",
|
||||
"add": "P┼Щidat",
|
||||
"administrator": "Administr├бtor",
|
||||
"areaCode": "Sm─Ыrov├╜ k├│d",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "e-mail",
|
||||
"enable": "Povolit",
|
||||
"expireTime": "─Мas vypr┼бen├н",
|
||||
"expired": "Vypr┼бelo",
|
||||
"expiredAt": "Platnost vypr┼бela",
|
||||
"failed": "Ne├║sp─Ыch",
|
||||
"giftAmount": "─М├бstka d├бrku",
|
||||
"giftAmountPlaceholder": "Zadejte ─Н├бstku d├бrku",
|
||||
"inactive": "Neaktivn├н",
|
||||
"invalidEmailFormat": "Neplatn├╜ form├бt e-mailu",
|
||||
"inviteCode": "Pozv├бnkov├╜ k├│d",
|
||||
"inviteCodePlaceholder": "Zadejte k├│d pozv├бnky (ponechte pr├бzdn├й pro vygenerov├бn├н)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "Zadejte nov├й heslo (voliteln├й)",
|
||||
"permanent": "Trval├╜",
|
||||
"pleaseEnterEmail": "Pros├нm, zadejte e-mail",
|
||||
"price": "Cena",
|
||||
"referer": "Doporu─Нitel",
|
||||
"refererId": "ID doporu─Нitele",
|
||||
"refererIdPlaceholder": "Zadejte ID doporu─Нitele",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Vyhledat IP adresu",
|
||||
"selectAuthType": "Vyberte typ ov─Ы┼Щen├н",
|
||||
"speedLimit": "Rychlostn├н limit",
|
||||
"startTime": "─Мas zah├бjen├н",
|
||||
"status": "Stav",
|
||||
"subscription": "P┼Щedplatn├й",
|
||||
"subscriptionDetails": "Podrobnosti o p┼Щedplatn├йm",
|
||||
"subscriptionId": "ID p┼Щedplatn├йho",
|
||||
"subscriptionInfo": "Informace o p┼Щedplatn├йm",
|
||||
"subscriptionList": "Seznam p┼Щedplatn├╜ch",
|
||||
"subscriptionLogs": "Z├бznamy o p┼Щedplatn├йm",
|
||||
"subscriptionName": "N├бzev p┼Щedplatn├йho",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Obchodn├н ozn├бmen├н",
|
||||
"trafficLimit": "Limit provozu",
|
||||
"trafficLogs": "Protokoly provozu",
|
||||
"trafficUsage": "Vyu┼╛it├н dat",
|
||||
"unknown": "Nezn├бm├й",
|
||||
"unlimited": "Neomezen├╜",
|
||||
"unverified": "Neov─Ы┼Щeno",
|
||||
"update": "Aktualizovat",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "U┼╛ivatelsk├╜ agent",
|
||||
"userEmail": "U┼╛ivatelsk├╜ e-mail",
|
||||
"userEmailPlaceholder": "Zadejte e-mail u┼╛ivatele",
|
||||
"userId": "ID u┼╛ivatele",
|
||||
"userInfo": "Informace o u┼╛ivateli",
|
||||
"userList": "Seznam u┼╛ivatel┼п",
|
||||
"userLogs": "Historie p┼Щihl├б┼бen├н",
|
||||
"userName": "Identifik├бtor u┼╛ivatele",
|
||||
"userOrders": "Objedn├бvky",
|
||||
"userProfile": "Profil",
|
||||
"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.",
|
||||
"existingAccount": "Bereits ein Konto?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Die eingegebenen Passw├╢rter stimmen nicht ├╝berein",
|
||||
"success": "Registrierung erfolgreich, Sie sind automatisch eingeloggt!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Betreff",
|
||||
"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_templateDescription": "Die Platzhalter {after}.variable{before} werden durch tats├дchliche Daten ersetzt. Stellen Sie sicher, dass diese Variablen beibehalten werden.",
|
||||
"whitelistSuffixes": "Whitelist-Suffixe",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "ADS-Konfiguration",
|
||||
"Announcement Management": "Ank├╝ndigungsverwaltung",
|
||||
"Apple": "Apple-ID",
|
||||
"Application Management": "Anwendungsverwaltung",
|
||||
"Auth Control": "Authentifizierungskontrolle",
|
||||
"Coupon Management": "Gutscheinverwaltung",
|
||||
"Dashboard": "Armaturenbrett",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Bestellverwaltung",
|
||||
"Payment Config": "Zahlungskonfiguration",
|
||||
"Phone Number": "Telefonnummer",
|
||||
"Rule Management": "Regelverwaltung",
|
||||
"Server": "Dienst",
|
||||
"Server Management": "Serververwaltung",
|
||||
"Settings": "Einstellungen",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie die Beschreibung der Zahlungsmethode ein",
|
||||
"domain": "Domain",
|
||||
"domainPlaceholder": "optional, z.B. {example}",
|
||||
"edit": "Bearbeiten",
|
||||
"editPayment": "Zahlungsmethode bearbeiten",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Datenschutzrichtlinie"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Benutzerdefinierte Daten",
|
||||
"customDataDescription": "Benutzerdefinierte Daten, die f├╝r benutzerdefinierte Daten der Website verwendet werden",
|
||||
"customHtml": "Benutzerdefiniertes HTML",
|
||||
"customHtmlDescription": "Benutzerdefinierter HTML-Code, der am Ende des body-Tags der Seite eingef├╝gt werden soll.",
|
||||
"keywords": "Schl├╝sselw├╢rter",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Systemaktualisierung best├дtigen",
|
||||
"confirmUpgrade": "Upgrade best├дtigen",
|
||||
"currentVersion": "Aktuelle Systemversion:",
|
||||
"developmentVersion": "Entwicklungsversion",
|
||||
"errors": "Fehler",
|
||||
"ip": "IP",
|
||||
"ip": "IP-Adresse",
|
||||
"lastUpdated": "Zuletzt aktualisiert:",
|
||||
"newVersionAvailable": "Neue Version",
|
||||
"none": "Keine",
|
||||
"query": "Abfrage",
|
||||
"rebootDescription": "M├╢chten Sie das System wirklich neu starten? Dieser Vorgang f├╝hrt zu einer kurzen Unterbrechung des Dienstes.",
|
||||
"rebooting": "System wird neu gestartet...",
|
||||
"refreshLogs": "Protokolle aktualisieren",
|
||||
"request": "Anfrage",
|
||||
"serverVersion": "Server",
|
||||
"status": "Status",
|
||||
"systemLogs": "Systemprotokolle",
|
||||
"systemReboot": "Systemneustart",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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.",
|
||||
"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",
|
||||
"actions": "Aktionen",
|
||||
"active": "Aktiv",
|
||||
"add": "Hinzuf├╝gen",
|
||||
"administrator": "Administrator",
|
||||
"areaCode": "Vorwahl",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "E-Mail",
|
||||
"enable": "Aktivieren",
|
||||
"expireTime": "Ablaufzeit",
|
||||
"expired": "Abgelaufen",
|
||||
"expiredAt": "Abgelaufen am",
|
||||
"failed": "Fehlgeschlagen",
|
||||
"giftAmount": "Geschenkbetrag",
|
||||
"giftAmountPlaceholder": "Geben Sie den Geschenkbetrag ein",
|
||||
"inactive": "Inaktiv",
|
||||
"invalidEmailFormat": "Ung├╝ltiges E-Mail-Format",
|
||||
"inviteCode": "Einladungscode",
|
||||
"inviteCodePlaceholder": "Einladungscode eingeben (leer lassen, um zu generieren)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "Neues Passwort eingeben (optional)",
|
||||
"permanent": "Dauerhaft",
|
||||
"pleaseEnterEmail": "Bitte E-Mail eingeben",
|
||||
"price": "Preis",
|
||||
"referer": "Empfehler",
|
||||
"refererId": "Referenz-ID",
|
||||
"refererIdPlaceholder": "Geben Sie die Referenz-ID ein",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "IP-Adresse suchen",
|
||||
"selectAuthType": "Authentifizierungstyp ausw├дhlen",
|
||||
"speedLimit": "Geschwindigkeitsbegrenzung",
|
||||
"startTime": "Startzeit",
|
||||
"status": "Status",
|
||||
"subscription": "Abonnement",
|
||||
"subscriptionDetails": "Abonnementdetails",
|
||||
"subscriptionId": "Abonnement-ID",
|
||||
"subscriptionInfo": "Abonnement-Informationen",
|
||||
"subscriptionList": "Abonnementsliste",
|
||||
"subscriptionLogs": "Abonnementprotokolle",
|
||||
"subscriptionName": "Abonnementname",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Handelsbenachrichtigungen",
|
||||
"trafficLimit": "Verkehrslimit",
|
||||
"trafficLogs": "Verkehrsprotokolle",
|
||||
"trafficUsage": "Datenverbrauch",
|
||||
"unknown": "Unbekannt",
|
||||
"unlimited": "Unbegrenzt",
|
||||
"unverified": "Unbest├дtigt",
|
||||
"update": "Aktualisieren",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "Benutzeragent",
|
||||
"userEmail": "Benutzer-E-Mail",
|
||||
"userEmailPlaceholder": "Benutzer-E-Mail eingeben",
|
||||
"userId": "Benutzer-ID",
|
||||
"userInfo": "Benutzerinformationen",
|
||||
"userList": "Benutzerliste",
|
||||
"userLogs": "Anmeldeverlauf",
|
||||
"userName": "Benutzerkennung",
|
||||
"userOrders": "Bestellungen",
|
||||
"userProfile": "Profil",
|
||||
"userSubscriptions": "Abonnements",
|
||||
"verified": "Verifiziert"
|
||||
"username": "Benutzername",
|
||||
"verified": "Verifiziert",
|
||||
"viewDetails": "Details anzeigen"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Please enter a valid email address.",
|
||||
"existingAccount": "Already have an account?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Passwords do not match",
|
||||
"success": "Registration successful, automatically logged in!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Status",
|
||||
"subject": "Subject",
|
||||
"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_templateDescription": "The {after}.variable{before} placeholders will be replaced with actual data. Ensure to keep these variables.",
|
||||
"whitelistSuffixes": "Whitelist Suffixes",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "ADS Config",
|
||||
"Announcement Management": "Announcement Management",
|
||||
"Apple": "Apple ID",
|
||||
"Application Management": "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": "Rule Management",
|
||||
"Server": "Server",
|
||||
"Server Management": "Server Management",
|
||||
"Settings": "Settings",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Enter payment method description",
|
||||
"domain": "Domain",
|
||||
"domainPlaceholder": "optional, e.g. {example}",
|
||||
"edit": "Edit",
|
||||
"editPayment": "Edit Payment Method",
|
||||
"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",
|
||||
"editNode": "Edit Node",
|
||||
"enable": "Enable",
|
||||
"id": "ID",
|
||||
"form": {
|
||||
"allowInsecure": "Allow Insecure",
|
||||
"cancel": "Cancel",
|
||||
"city": "City",
|
||||
"confirm": "Confirm",
|
||||
"congestionController": "Congestion Controller",
|
||||
"country": "Country",
|
||||
"disableSni": "Disable SNI",
|
||||
"edit": "Edit",
|
||||
"editSecurity": "Edit Security Configuration",
|
||||
"enableTLS": "Enable TLS",
|
||||
"encryptionMethod": "Encryption Method",
|
||||
"fingerprint": "Fingerprint",
|
||||
"flow": "Flow Control Algorithm",
|
||||
"groupId": "Node Group",
|
||||
"hopInterval": "Hop Interval",
|
||||
@@ -85,6 +89,7 @@
|
||||
"pleaseSelect": "Please Select",
|
||||
"port": "Server Port",
|
||||
"protocol": "Protocol",
|
||||
"reduceRtt": "Reduce RTT",
|
||||
"relayHost": "Relay Host",
|
||||
"relayMode": "Relay Mode",
|
||||
"relayModeOptions": {
|
||||
@@ -128,6 +133,7 @@
|
||||
"transport": "Transport Protocol Configuration",
|
||||
"transportConfig": "Transport Protocol Configuration",
|
||||
"transportHost": "Transport Server Address",
|
||||
"udpRelayMode": "UDP Relay Mode",
|
||||
"transportPath": "Transport Path",
|
||||
"transportServerName": "Transport Server Name"
|
||||
},
|
||||
@@ -136,6 +142,7 @@
|
||||
"name": "Name",
|
||||
"node": "Node",
|
||||
"nodeGroup": "Node Group",
|
||||
"noData": "--",
|
||||
"normal": "Normal",
|
||||
"onlineCount": "Online users",
|
||||
"onlineUsers": "Online Users",
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Privacy Policy"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Custom Data",
|
||||
"customDataDescription": "Custom Data, used for custom data of the site",
|
||||
"customHtml": "Custom HTML",
|
||||
"customHtmlDescription": "Custom HTML code to be injected into the bottom of the site's body tag.",
|
||||
"keywords": "Keywords",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Confirm System Upgrade",
|
||||
"confirmUpgrade": "Confirm Upgrade",
|
||||
"currentVersion": "Current System Version:",
|
||||
"developmentVersion": "Dev",
|
||||
"errors": "Errors",
|
||||
"ip": "IP",
|
||||
"lastUpdated": "Last Updated:",
|
||||
"newVersionAvailable": "New Version",
|
||||
"none": "None",
|
||||
"query": "Query",
|
||||
"rebootDescription": "Are you sure you want to reboot the system? This operation will cause a brief service interruption.",
|
||||
"rebooting": "Rebooting system...",
|
||||
"refreshLogs": "Refresh Logs",
|
||||
"request": "Request",
|
||||
"serverVersion": "Server",
|
||||
"status": "Status",
|
||||
"systemLogs": "System Logs",
|
||||
"systemReboot": "System Reboot",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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.",
|
||||
"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",
|
||||
"actions": "Actions",
|
||||
"active": "Active",
|
||||
"add": "Add",
|
||||
"administrator": "Administrator",
|
||||
"areaCode": "Area Code",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "Email",
|
||||
"enable": "Enable",
|
||||
"expireTime": "Expire Time",
|
||||
"expired": "Expired",
|
||||
"expiredAt": "Expired At",
|
||||
"failed": "Failed",
|
||||
"giftAmount": "Gift Amount",
|
||||
"giftAmountPlaceholder": "Enter gift amount",
|
||||
"inactive": "Inactive",
|
||||
"invalidEmailFormat": "Invalid email format",
|
||||
"inviteCode": "Invite Code",
|
||||
"inviteCodePlaceholder": "Enter invite code (leave blank to generate)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "Enter new password (optional)",
|
||||
"permanent": "Permanent",
|
||||
"pleaseEnterEmail": "Please enter email",
|
||||
"price": "Price",
|
||||
"referer": "Referer",
|
||||
"refererId": "Referer ID",
|
||||
"refererIdPlaceholder": "Enter referer ID",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Search IP address",
|
||||
"selectAuthType": "Select auth type",
|
||||
"speedLimit": "Speed Limit",
|
||||
"startTime": "Start Time",
|
||||
"status": "Status",
|
||||
"subscription": "Subscription",
|
||||
"subscriptionDetails": "Subscription Details",
|
||||
"subscriptionId": "Subscription ID",
|
||||
"subscriptionInfo": "Subscription Info",
|
||||
"subscriptionList": "Subscription List",
|
||||
"subscriptionLogs": "Subscription Logs",
|
||||
"subscriptionName": "Subscription Name",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Trade Notifications",
|
||||
"trafficLimit": "Traffic Limit",
|
||||
"trafficLogs": "Traffic Logs",
|
||||
"trafficUsage": "Traffic Usage",
|
||||
"unknown": "Unknown",
|
||||
"unlimited": "Unlimited",
|
||||
"unverified": "Unverified",
|
||||
"update": "Update",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "User Agent",
|
||||
"userEmail": "User Email",
|
||||
"userEmailPlaceholder": "Enter user email",
|
||||
"userId": "User ID",
|
||||
"userInfo": "User Info",
|
||||
"userList": "User List",
|
||||
"userLogs": "Login History",
|
||||
"userName": "User Identifier",
|
||||
"userOrders": "Orders",
|
||||
"userProfile": "Profile",
|
||||
"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.",
|
||||
"existingAccount": "┬┐Ya tienes una cuenta?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Las contrase├▒as no coinciden",
|
||||
"success": "┬бRegistro exitoso, sesi├│n iniciada autom├бticamente!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Estado",
|
||||
"subject": "Asunto",
|
||||
"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_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",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "Configuraci├│n de ADS",
|
||||
"Announcement Management": "Gesti├│n de Anuncios",
|
||||
"Apple": "ID de Apple",
|
||||
"Application Management": "Gesti├│n de Aplicaciones",
|
||||
"Auth Control": "Control de Autenticaci├│n",
|
||||
"Coupon Management": "Gesti├│n de Cupones",
|
||||
"Dashboard": "Tablero",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Gesti├│n de Pedidos",
|
||||
"Payment Config": "Configuraci├│n de Pago",
|
||||
"Phone Number": "N├║mero de Tel├йfono",
|
||||
"Rule Management": "Gesti├│n de Reglas",
|
||||
"Server": "Servidor",
|
||||
"Server Management": "Gesti├│n de Servidores",
|
||||
"Settings": "Configuraci├│n",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Descripci├│n",
|
||||
"descriptionPlaceholder": "Ingrese la descripci├│n del m├йtodo de pago",
|
||||
"domain": "Dominio",
|
||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||
"edit": "Editar",
|
||||
"editPayment": "Editar M├йtodo de Pago",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Pol├нtica de Privacidad"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Datos Personalizados",
|
||||
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||
"customHtml": "HTML personalizado",
|
||||
"customHtmlDescription": "C├│digo HTML personalizado que se inyectar├б en la parte inferior de la etiqueta body del sitio.",
|
||||
"keywords": "Palabras clave",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Confirmar actualizaci├│n del sistema",
|
||||
"confirmUpgrade": "Confirmar actualizaci├│n",
|
||||
"currentVersion": "Versi├│n actual del sistema:",
|
||||
"developmentVersion": "Versi├│n de desarrollo",
|
||||
"errors": "Errores",
|
||||
"ip": "IP",
|
||||
"ip": "Direcci├│n IP",
|
||||
"lastUpdated": "├Ъltima actualizaci├│n:",
|
||||
"newVersionAvailable": "Nueva versi├│n",
|
||||
"none": "Ninguno",
|
||||
"query": "Consulta",
|
||||
"rebootDescription": "┬┐Est├б seguro de que desea reiniciar el sistema? Esta acci├│n provocar├б una breve interrupci├│n del servicio.",
|
||||
"rebooting": "Reiniciando el sistema...",
|
||||
"refreshLogs": "Actualizar registros",
|
||||
"request": "Solicitud",
|
||||
"serverVersion": "Servidor",
|
||||
"status": "Estado",
|
||||
"systemLogs": "Registros del sistema",
|
||||
"systemReboot": "Reinicio del sistema",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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.",
|
||||
"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",
|
||||
"actions": "acciones",
|
||||
"active": "Activo",
|
||||
"add": "Agregar",
|
||||
"administrator": "Administrador",
|
||||
"areaCode": "C├│digo de ├Бrea",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "correo electr├│nico",
|
||||
"enable": "Habilitar",
|
||||
"expireTime": "Tiempo de Expiraci├│n",
|
||||
"expired": "Caducado",
|
||||
"expiredAt": "Fecha de vencimiento",
|
||||
"failed": "Fallido",
|
||||
"giftAmount": "Monto del Regalo",
|
||||
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
||||
"inactive": "Inactivo",
|
||||
"invalidEmailFormat": "Formato de correo electr├│nico no v├бlido",
|
||||
"inviteCode": "C├│digo de invitaci├│n",
|
||||
"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)",
|
||||
"permanent": "Permanente",
|
||||
"pleaseEnterEmail": "Por favor, introduce el correo electr├│nico",
|
||||
"price": "Precio",
|
||||
"referer": "Recomendador",
|
||||
"refererId": "ID del Referente",
|
||||
"refererIdPlaceholder": "Ingrese el ID del referente",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Buscar direcci├│n IP",
|
||||
"selectAuthType": "Seleccionar tipo de autenticaci├│n",
|
||||
"speedLimit": "L├нmite de Velocidad",
|
||||
"startTime": "Hora de Inicio",
|
||||
"status": "Estado",
|
||||
"subscription": "Suscripci├│n",
|
||||
"subscriptionDetails": "Detalles de la suscripci├│n",
|
||||
"subscriptionId": "ID de Suscripci├│n",
|
||||
"subscriptionInfo": "Informaci├│n de Suscripci├│n",
|
||||
"subscriptionList": "Lista de Suscripci├│n",
|
||||
"subscriptionLogs": "Registros de Suscripci├│n",
|
||||
"subscriptionName": "Nombre de la Suscripci├│n",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Notificaciones de Comercio",
|
||||
"trafficLimit": "L├нmite de Tr├бfico",
|
||||
"trafficLogs": "Registros de Tr├бfico",
|
||||
"trafficUsage": "Uso de Tr├бfico",
|
||||
"unknown": "Desconocido",
|
||||
"unlimited": "Ilimitado",
|
||||
"unverified": "No verificado",
|
||||
"update": "Actualizar",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "Agente de Usuario",
|
||||
"userEmail": "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",
|
||||
"userLogs": "Historial de inicio de sesi├│n",
|
||||
"userName": "Identificador de Usuario",
|
||||
"userOrders": "Pedidos",
|
||||
"userProfile": "Perfil",
|
||||
"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.",
|
||||
"existingAccount": "┬┐Ya tienes una cuenta?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Las contrase├▒as no coinciden",
|
||||
"success": "┬бRegistro exitoso, has iniciado sesi├│n autom├бticamente!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Estado",
|
||||
"subject": "Asunto",
|
||||
"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_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",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "Configuraci├│n de ADS",
|
||||
"Announcement Management": "Gesti├│n de Anuncios",
|
||||
"Apple": "ID de Apple",
|
||||
"Application Management": "Gesti├│n de Aplicaciones",
|
||||
"Auth Control": "Control de Autenticaci├│n",
|
||||
"Coupon Management": "Gesti├│n de Cupones",
|
||||
"Dashboard": "Tablero",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Gesti├│n de Pedidos",
|
||||
"Payment Config": "Configuraci├│n de Pago",
|
||||
"Phone Number": "N├║mero de Tel├йfono",
|
||||
"Rule Management": "Gesti├│n de Reglas",
|
||||
"Server": "Servidor",
|
||||
"Server Management": "Gesti├│n de Servidores",
|
||||
"Settings": "Configuraci├│n",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Descripci├│n",
|
||||
"descriptionPlaceholder": "Ingresa la descripci├│n del m├йtodo de pago",
|
||||
"domain": "Dominio",
|
||||
"domainPlaceholder": "opcional, p. ej. {example}",
|
||||
"edit": "Editar",
|
||||
"editPayment": "Editar M├йtodo de Pago",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Pol├нtica de Privacidad"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Datos Personalizados",
|
||||
"customDataDescription": "Datos Personalizados, utilizados para datos personalizados del sitio",
|
||||
"customHtml": "HTML personalizado",
|
||||
"customHtmlDescription": "C├│digo HTML personalizado que se inyectar├б al final de la etiqueta body del sitio.",
|
||||
"keywords": "Palabras clave",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Confirmar actualizaci├│n del sistema",
|
||||
"confirmUpgrade": "Confirmar actualizaci├│n",
|
||||
"currentVersion": "Versi├│n actual del sistema:",
|
||||
"developmentVersion": "Versi├│n de desarrollo",
|
||||
"errors": "Errores",
|
||||
"ip": "IP",
|
||||
"ip": "Direcci├│n IP",
|
||||
"lastUpdated": "├Ъltima actualizaci├│n:",
|
||||
"newVersionAvailable": "Nueva versi├│n",
|
||||
"none": "Ninguno",
|
||||
"query": "Consulta",
|
||||
"rebootDescription": "┬┐Est├б seguro de que desea reiniciar el sistema? Esta acci├│n provocar├б una breve interrupci├│n del servicio.",
|
||||
"rebooting": "Reiniciando el sistema...",
|
||||
"refreshLogs": "Actualizar registros",
|
||||
"request": "Solicitud",
|
||||
"serverVersion": "Servidor",
|
||||
"status": "Estado",
|
||||
"systemLogs": "Registros del sistema",
|
||||
"systemReboot": "Reinicio del sistema",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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.",
|
||||
"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",
|
||||
"actions": "acciones",
|
||||
"active": "Activo",
|
||||
"add": "Agregar",
|
||||
"administrator": "Administrador",
|
||||
"areaCode": "C├│digo de ├Бrea",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "correo electr├│nico",
|
||||
"enable": "Habilitar",
|
||||
"expireTime": "Tiempo de Expiraci├│n",
|
||||
"expired": "Expirado",
|
||||
"expiredAt": "Fecha de vencimiento",
|
||||
"failed": "Fallido",
|
||||
"giftAmount": "Monto del Regalo",
|
||||
"giftAmountPlaceholder": "Ingrese el monto del regalo",
|
||||
"inactive": "Inactivo",
|
||||
"invalidEmailFormat": "Formato de correo electr├│nico no v├бlido",
|
||||
"inviteCode": "C├│digo de Invitaci├│n",
|
||||
"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)",
|
||||
"permanent": "Permanente",
|
||||
"pleaseEnterEmail": "Por favor, ingresa el correo electr├│nico",
|
||||
"price": "Precio",
|
||||
"referer": "Recomendador",
|
||||
"refererId": "ID del Referente",
|
||||
"refererIdPlaceholder": "Ingrese ID del referente",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Buscar direcci├│n IP",
|
||||
"selectAuthType": "Seleccionar tipo de autenticaci├│n",
|
||||
"speedLimit": "L├нmite de Velocidad",
|
||||
"startTime": "Hora de Inicio",
|
||||
"status": "Estado",
|
||||
"subscription": "Suscripci├│n",
|
||||
"subscriptionDetails": "Detalles de la Suscripci├│n",
|
||||
"subscriptionId": "ID de Suscripci├│n",
|
||||
"subscriptionInfo": "Informaci├│n de Suscripci├│n",
|
||||
"subscriptionList": "Lista de Suscripci├│n",
|
||||
"subscriptionLogs": "Registros de Suscripci├│n",
|
||||
"subscriptionName": "Nombre de la Suscripci├│n",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Notificaciones de Comercio",
|
||||
"trafficLimit": "L├нmite de Tr├бfico",
|
||||
"trafficLogs": "Registros de Tr├бfico",
|
||||
"trafficUsage": "Uso de Tr├бfico",
|
||||
"unknown": "Desconocido",
|
||||
"unlimited": "Ilimitado",
|
||||
"unverified": "No verificado",
|
||||
"update": "Actualizar",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "Agente de Usuario",
|
||||
"userEmail": "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",
|
||||
"userLogs": "Historial de Inicio de Sesi├│n",
|
||||
"userName": "Identificador de Usuario",
|
||||
"userOrders": "Pedidos",
|
||||
"userProfile": "Perfil",
|
||||
"userSubscriptions": "Suscripciones",
|
||||
"verified": "Verificado"
|
||||
"username": "Nombre de Usuario",
|
||||
"verified": "Verificado",
|
||||
"viewDetails": "Ver Detalles"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "┘Д╪╖┘Б╪з┘Л █М┌й ╪в╪п╪▒╪│ ╪з█М┘Е█М┘Д ┘Е╪╣╪к╪и╪▒ ┘И╪з╪▒╪п ┌й┘Ж█М╪п.",
|
||||
"existingAccount": "╪в█М╪з ┘В╪и┘Д╪з┘Л ╪н╪│╪з╪и ┌й╪з╪▒╪и╪▒█М ╪п╪з╪▒█М╪п╪Я",
|
||||
"get": "╪п╪▒█М╪з┘Б╪к",
|
||||
"invite": "┌й╪п ╪п╪╣┘И╪к",
|
||||
"invite": "┌й╪п ╪п╪╣┘И╪к (╪з╪о╪к█М╪з╪▒█М)",
|
||||
"message": "#### ┌й╪з╪▒╪и╪▒ ┌п╪▒╪з┘Е█М╪М ╪│┘Д╪з┘Е!\n\n╪з╪▓ ╪к┘И╪м┘З ┘И ╪н┘Е╪з█М╪к ╪┤┘Е╪з ╪│┘╛╪з╪│┌п╪▓╪з╪▒█М┘Е. ╪и┘З ╪п┘Д█М┘Д ╪к╪║█М█М╪▒╪з╪к ╪п╪▒ ╪з╪│╪к╪▒╪з╪к┌Ш█М ╪╣┘Е┘Д█М╪з╪к█М ╪│╪з█М╪к╪М ╪╣┘Е┘Д┌й╪▒╪п ╪л╪и╪ктАМ┘Ж╪з┘Е ┌й╪з╪▒╪и╪▒╪з┘Ж ╪м╪п█М╪п ╪▒╪з ┘Е╪к┘И┘В┘Б ┌й╪▒╪п┘ЗтАМ╪з█М┘Е. ╪п╪▒ ╪з█М┘Ж ┘Е╪п╪к╪М ┌й╪з╪▒╪и╪▒╪з┘Ж ┘Б╪╣┘Д█М ╪к╪н╪к ╪к╪г╪л█М╪▒ ┘В╪▒╪з╪▒ ┘Ж╪о┘И╪з┘З┘Ж╪п ┌п╪▒┘Б╪к.\n\n┘Е╪з ┘Е╪к╪╣┘З╪п ╪и┘З ╪з╪▒╪з╪ж┘З ╪о╪п┘Е╪з╪к ┘И ╪к╪м╪▒╪и┘З ╪и┘З╪к╪▒ ╪и┘З ╪┤┘Е╪з ┘З╪│╪к█М┘Е╪М ╪и┘Ж╪з╪и╪▒╪з█М┘Ж ╪п╪▒ ╪╖┘И┘Д ╪з█М┘Ж ╪п┘И╪▒┘З╪М ╪и┘З█М┘Ж┘ЗтАМ╪│╪з╪▓█М ╪м╪з┘Е╪╣ ╪│█М╪│╪к┘Е ┘И ╪з╪▒╪к┘В╪з╪б ┘И█М┌Ш┌п█МтАМ┘З╪з ╪▒╪з ╪з┘Ж╪м╪з┘Е ╪о┘И╪з┘З█М┘Е ╪п╪з╪п. ╪п╪▒ ╪в█М┘Ж╪п┘З╪М ╪и╪з ┘Е╪н╪к┘И╪з█М ┘И ╪о╪п┘Е╪з╪к ╪и┘З╪к╪▒ ╪з╪▓ ╪┤┘Е╪з ╪з╪│╪к┘В╪и╪з┘Д ╪о┘И╪з┘З█М┘Е ┌й╪▒╪п.\n\n┘Д╪╖┘Б╪з┘Л ╪и╪▒╪з█М ╪п╪▒█М╪з┘Б╪к ╪в╪о╪▒█М┘Ж ╪и┘ЗтАМ╪▒┘И╪▓╪▒╪│╪з┘Ж█МтАМ┘З╪з ┘И ╪з╪╖┘Д╪з╪╣█М┘ЗтАМ┘З╪з╪М ┘И╪итАМ╪│╪з█М╪к ┘И ┘╛┘Д╪к┘Б╪▒┘ЕтАМ┘З╪з█М ╪з╪м╪к┘Е╪з╪╣█М ┘Е╪з ╪▒╪з ╪п┘Ж╪и╪з┘Д ┌й┘Ж█М╪п. ╪з╪▓ ╪п╪▒┌й ┘И ╪н┘Е╪з█М╪к ╪┤┘Е╪з ╪│┘╛╪з╪│┌п╪▓╪з╪▒█М┘Е.\n\n╪з┌п╪▒ ╪│┘И╪з┘Д█М ╪п╪з╪▒█М╪п █М╪з ╪и┘З ┌й┘Е┌й ┘Ж█М╪з╪▓ ╪п╪з╪▒█М╪п╪М ┘Д╪╖┘Б╪з┘Л ╪и╪з ╪к█М┘Е ╪о╪п┘Е╪з╪к ┘Е╪┤╪к╪▒█М ┘Е╪з ╪к┘Е╪з╪│ ╪и┌п█М╪▒█М╪п.\n\n**╪и╪з╪▒ ╪п█М┌п╪▒ ╪з╪▓ ╪н┘Е╪з█М╪к ┘И ╪п╪▒┌й ╪┤┘Е╪з ╪│┘╛╪з╪│┌п╪▓╪з╪▒█М┘Е.**",
|
||||
"passwordMismatch": "╪▒┘Е╪▓┘З╪з█М ╪╣╪и┘И╪▒ ┘Е╪╖╪з╪и┘В╪к ┘Ж╪п╪з╪▒┘Ж╪п",
|
||||
"success": "╪л╪и╪ктАМ┘Ж╪з┘Е ┘Е┘И┘Б┘В█М╪ктАМ╪в┘Е█М╪▓ ╪и┘И╪п╪М ╪и┘ЗтАМ╪╖┘И╪▒ ╪о┘И╪п┌й╪з╪▒ ┘И╪з╪▒╪п ╪┤╪п█М╪п!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "┘И╪╢╪╣█М╪к",
|
||||
"subject": "┘Е┘И╪╢┘И╪╣",
|
||||
"template": "╪з┘Д┌п┘И",
|
||||
"traffic_exceed_email_template": "╪з┘Д┌п┘И█М ╪з╪╣┘Д╪з┘Ж ╪к╪▒╪з┘Б█М┌й ╪и█М╪┤ ╪з╪▓ ╪н╪п",
|
||||
"traffic_exceed_email_templateDescription": "╪м╪з█М┌п╪▓█М┘ЖтАМ┘З╪з█М {after}.variable{before} ╪и╪з ╪п╪з╪п┘ЗтАМ┘З╪з█М ┘И╪з┘В╪╣█М ╪о┘И╪з┘З╪п ╪┤╪п. ╪з╪╖┘Е█М┘Ж╪з┘Ж ╪н╪з╪╡┘Д ┌й┘Ж█М╪п ┌й┘З ╪з█М┘Ж ┘Е╪к╪║█М╪▒┘З╪з ╪▒╪з ╪н┘Б╪╕ ┌й┘Ж█М╪п.",
|
||||
"verify_email_template": "┘В╪з┘Д╪и ╪з█М┘Е█М┘Д ╪к╪г█М█М╪п",
|
||||
"verify_email_templateDescription": "╪м╪з█МтАМ┌п╪░╪з╪▒█МтАМ┘З╪з█М {after}.variable{before} ╪и╪з ╪п╪з╪п┘ЗтАМ┘З╪з█М ┘И╪з┘В╪╣█М ╪м╪з█М┌п╪▓█М┘Ж ╪о┘И╪з┘З┘Ж╪п ╪┤╪п. ╪з╪╖┘Е█М┘Ж╪з┘Ж ╪н╪з╪╡┘Д ┌й┘Ж█М╪п ┌й┘З ╪з█М┘Ж ┘Е╪к╪║█М╪▒┘З╪з ╪▒╪з ╪н┘Б╪╕ ┌й┘Ж█М╪п.",
|
||||
"whitelistSuffixes": "┘╛╪│┘И┘Ж╪п┘З╪з█М ┘Д█М╪│╪к ╪│┘Б█М╪п",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "╪к┘Ж╪╕█М┘Е╪з╪к ADS",
|
||||
"Announcement Management": "┘Е╪п█М╪▒█М╪к ╪з╪╖┘Д╪з╪╣█М┘ЗтАМ┘З╪з",
|
||||
"Apple": "╪┤┘Ж╪з╪│┘З ╪з┘╛┘Д",
|
||||
"Application Management": "┘Е╪п█М╪▒█М╪к ╪и╪▒┘Ж╪з┘Е┘З",
|
||||
"Auth Control": "┌й┘Ж╪к╪▒┘Д ╪з╪н╪▒╪з╪▓ ┘З┘И█М╪к",
|
||||
"Coupon Management": "┘Е╪п█М╪▒█М╪к ┌й┘И┘╛┘Ж",
|
||||
"Dashboard": "╪п╪з╪┤╪и┘И╪▒╪п",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "┘Е╪п█М╪▒█М╪к ╪│┘Б╪з╪▒╪┤",
|
||||
"Payment Config": "┘╛█М┌й╪▒╪и┘Ж╪п█М ┘╛╪▒╪п╪з╪о╪к",
|
||||
"Phone Number": "╪┤┘Е╪з╪▒┘З ╪к┘Д┘Б┘Ж",
|
||||
"Rule Management": "┘Е╪п█М╪▒█М╪к ┘В┘И╪з┘Ж█М┘Ж",
|
||||
"Server": "╪│╪▒┘И╪▒",
|
||||
"Server Management": "┘Е╪п█М╪▒█М╪к ╪│╪▒┘И╪▒",
|
||||
"Settings": "╪к┘Ж╪╕█М┘Е╪з╪к",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "╪к┘И╪╢█М╪н╪з╪к",
|
||||
"descriptionPlaceholder": "╪к┘И╪╢█М╪н╪з╪к ╪▒┘И╪┤ ┘╛╪▒╪п╪з╪о╪к ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п",
|
||||
"domain": "╪п╪з┘Е┘Ж┘З",
|
||||
"domainPlaceholder": "╪з╪о╪к█М╪з╪▒█М╪М ╪и┘З ╪╣┘Ж┘И╪з┘Ж ┘Е╪л╪з┘Д {example}",
|
||||
"edit": "┘И█М╪▒╪з█М╪┤",
|
||||
"editPayment": "┘И█М╪▒╪з█М╪┤ ╪▒┘И╪┤ ┘╛╪▒╪п╪з╪о╪к",
|
||||
"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": "┘В╪з┘Ж┘И┘Ж ╪и╪з ┘Е┘И┘Б┘В█М╪к ╪и┘ЗтАМ╪▒┘И╪▓╪▒╪│╪з┘Ж█М ╪┤╪п"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "╪│█М╪з╪│╪к ╪н┘Б╪╕ ╪н╪▒█М┘Е ╪о╪╡┘И╪╡█М"
|
||||
},
|
||||
"site": {
|
||||
"customData": "╪п╪з╪п┘ЗтАМ┘З╪з█М ╪│┘Б╪з╪▒╪┤█М",
|
||||
"customDataDescription": "╪п╪з╪п┘ЗтАМ┘З╪з█М ╪│┘Б╪з╪▒╪┤█М╪М ╪з╪│╪к┘Б╪з╪п┘З ╪┤╪п┘З ╪и╪▒╪з█М ╪п╪з╪п┘ЗтАМ┘З╪з█М ╪│┘Б╪з╪▒╪┤█М ╪│╪з█М╪к",
|
||||
"customHtml": "HTML ╪│┘Б╪з╪▒╪┤█М",
|
||||
"customHtmlDescription": "┌й╪п HTML ╪│┘Б╪з╪▒╪┤█М ┌й┘З ╪и╪з█М╪п ╪п╪▒ ╪з┘Ж╪к┘З╪з█М ╪к┌п body ╪│╪з█М╪к ┘И╪з╪▒╪п ╪┤┘И╪п.",
|
||||
"keywords": "┌й┘Д┘Е╪з╪к ┌й┘Д█М╪п█М",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "╪к╪г█М█М╪п ╪з╪▒╪к┘В╪з╪б ╪│█М╪│╪к┘Е",
|
||||
"confirmUpgrade": "╪к╪г█М█М╪п ╪з╪▒╪к┘В╪з╪б",
|
||||
"currentVersion": "┘Ж╪│╪о┘З ┘Б╪╣┘Д█М ╪│█М╪│╪к┘Е:",
|
||||
"developmentVersion": "┘Ж╪│╪о┘З ╪к┘И╪│╪╣┘З",
|
||||
"errors": "╪о╪╖╪з┘З╪з",
|
||||
"ip": "╪в█МтАМ┘╛█М",
|
||||
"ip": "╪в╪п╪▒╪│ ╪в█МтАМ┘╛█М",
|
||||
"lastUpdated": "╪в╪о╪▒█М┘Ж ╪и┘ЗтАМ╪▒┘И╪▓╪▒╪│╪з┘Ж█М:",
|
||||
"newVersionAvailable": "┘Ж╪│╪о┘З ╪м╪п█М╪п",
|
||||
"none": "┘З█М┌Ж",
|
||||
"query": "┘╛╪▒╪│ ┘И ╪м┘И",
|
||||
"rebootDescription": "╪в█М╪з ┘Е╪╖┘Е╪ж┘Ж ┘З╪│╪к█М╪п ┌й┘З ┘Е█МтАМ╪о┘И╪з┘З█М╪п ╪│█М╪│╪к┘Е ╪▒╪з ╪▒╪з┘ЗтАМ╪з┘Ж╪п╪з╪▓█М ┘Е╪м╪п╪п ┌й┘Ж█М╪п╪Я ╪з█М┘Ж ╪╣┘Е┘Д█М╪з╪к ╪и╪з╪╣╪л ┘И┘В┘Б┘З ┌й┘И╪к╪з┘З█М ╪п╪▒ ╪о╪п┘Е╪з╪к ╪о┘И╪з┘З╪п ╪┤╪п.",
|
||||
"rebooting": "╪п╪▒ ╪н╪з┘Д ╪▒╪з┘ЗтАМ╪з┘Ж╪п╪з╪▓█М ┘Е╪м╪п╪п ╪│█М╪│╪к┘Е...",
|
||||
"refreshLogs": "╪к╪з╪▓┘ЗтАМ╪│╪з╪▓█М ┌п╪▓╪з╪▒╪┤тАМ┘З╪з",
|
||||
"request": "╪п╪▒╪о┘И╪з╪│╪к",
|
||||
"serverVersion": "╪│╪▒┘И╪▒",
|
||||
"status": "┘И╪╢╪╣█М╪к",
|
||||
"systemLogs": "┌п╪▓╪з╪▒╪┤тАМ┘З╪з█М ╪│█М╪│╪к┘Е",
|
||||
"systemReboot": "╪▒╪з┘ЗтАМ╪з┘Ж╪п╪з╪▓█М ┘Е╪м╪п╪п ╪│█М╪│╪к┘Е",
|
||||
@@ -22,5 +25,8 @@
|
||||
"systemUpgrade": "╪з╪▒╪к┘В╪з╪б ╪│█М╪│╪к┘Е",
|
||||
"upgradeDescription": "╪в█М╪з ┘Е╪╖┘Е╪ж┘Ж ┘З╪│╪к█М╪п ┌й┘З ┘Е█МтАМ╪о┘И╪з┘З█М╪п ╪з╪▒╪к┘В╪з╪б ╪│█М╪│╪к┘Е ╪▒╪з ╪з┘Ж╪м╪з┘Е ╪п┘З█М╪п╪Я ╪з█М┘Ж ╪╣┘Е┘Д█М╪з╪к ┘Е┘Е┌й┘Ж ╪з╪│╪к ┌Ж┘Ж╪п ╪п┘В█М┘В┘З ╪╖┘И┘Д ╪и┌й╪┤╪п ┘И ╪п╪▒ ╪з█М┘Ж ┘Е╪п╪к ╪│█М╪│╪к┘Е ┘Е┘Е┌й┘Ж ╪з╪│╪к ┘╛╪з╪│╪о┌п┘И ┘Ж╪и╪з╪┤╪п.",
|
||||
"userAgent": "╪╣╪з┘Е┘Д ┌й╪з╪▒╪и╪▒",
|
||||
"viewLogsAndManage": "┘Е╪┤╪з┘З╪п┘З ┌п╪▓╪з╪▒╪┤тАМ┘З╪з█М ╪│█М╪│╪к┘Е╪М ╪з┘Ж╪м╪з┘Е ╪з╪▒╪к┘В╪з╪б ╪│█М╪│╪к┘Е ┘И ╪╣┘Е┘Д█М╪з╪к ╪▒╪з┘ЗтАМ╪з┘Ж╪п╪з╪▓█М ┘Е╪м╪п╪п"
|
||||
"versionInfo": "╪з╪╖┘Д╪з╪╣╪з╪к ┘Ж╪│╪о┘З",
|
||||
"viewLogsAndManage": "┘Е╪┤╪з┘З╪п┘З ┌п╪▓╪з╪▒╪┤тАМ┘З╪з█М ╪│█М╪│╪к┘Е╪М ╪з┘Ж╪м╪з┘Е ╪з╪▒╪к┘В╪з╪б ╪│█М╪│╪к┘Е ┘И ╪╣┘Е┘Д█М╪з╪к ╪▒╪з┘ЗтАМ╪з┘Ж╪п╪з╪▓█М ┘Е╪м╪п╪п",
|
||||
"viewNewVersion": "┘Е╪┤╪з┘З╪п┘З ┘Ж╪│╪о┘З ╪м╪п█М╪п",
|
||||
"webVersion": "┘И╪и/┘Е╪п█М╪▒█М╪к"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accountEnable": "┘Б╪╣╪з┘ДтАМ╪│╪з╪▓█М ╪н╪│╪з╪и",
|
||||
"actions": "╪з┘В╪п╪з┘Е╪з╪к",
|
||||
"active": "┘Б╪╣╪з┘Д",
|
||||
"add": "╪з┘Б╪▓┘И╪п┘Ж",
|
||||
"administrator": "┘Е╪п█М╪▒",
|
||||
"areaCode": "┌й╪п ┘Е┘Ж╪╖┘В┘З",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "╪з█М┘Е█М┘Д",
|
||||
"enable": "┘Б╪╣╪з┘Д ┌й╪▒╪п┘Ж",
|
||||
"expireTime": "╪▓┘Е╪з┘Ж ╪з┘Ж┘В╪╢╪з",
|
||||
"expired": "┘Е┘Ж┘В╪╢█М ╪┤╪п┘З",
|
||||
"expiredAt": "┘Е┘Ж┘В╪╢█М ╪┤╪п┘З ╪п╪▒",
|
||||
"failed": "┘Ж╪з┘Е┘И┘Б┘В",
|
||||
"giftAmount": "┘Е┘В╪п╪з╪▒ ┘З╪п█М┘З",
|
||||
"giftAmountPlaceholder": "┘Е╪и┘Д╪║ ┘З╪п█М┘З ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п",
|
||||
"inactive": "╪║█М╪▒┘Б╪╣╪з┘Д",
|
||||
"invalidEmailFormat": "┘Б╪▒┘Е╪к ╪з█М┘Е█М┘Д ┘Ж╪з┘Е╪╣╪к╪и╪▒ ╪з╪│╪к",
|
||||
"inviteCode": "┌й╪п ╪п╪╣┘И╪к",
|
||||
"inviteCodePlaceholder": "┌й╪п ╪п╪╣┘И╪к ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п (╪и╪▒╪з█М ╪к┘И┘Д█М╪п ╪о╪з┘Д█М ╪и┌п╪░╪з╪▒█М╪п)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "╪▒┘Е╪▓ ╪╣╪и┘И╪▒ ╪м╪п█М╪п ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п (╪з╪о╪к█М╪з╪▒█М)",
|
||||
"permanent": "╪п╪з╪ж┘Е█М",
|
||||
"pleaseEnterEmail": "┘Д╪╖┘Б╪з┘Л ╪з█М┘Е█М┘Д ╪о┘И╪п ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п",
|
||||
"price": "┘В█М┘Е╪к",
|
||||
"referer": "╪з╪▒╪м╪з╪╣тАМ╪п┘З┘Ж╪п┘З",
|
||||
"refererId": "╪┤┘Ж╪з╪│┘З ┘Е╪╣╪▒┘Б",
|
||||
"refererIdPlaceholder": "╪┤┘Ж╪з╪│┘З ┘Е╪╣╪▒┘Б ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "╪м╪│╪к╪м┘И█М ╪в╪п╪▒╪│ IP",
|
||||
"selectAuthType": "┘Ж┘И╪╣ ╪з╪н╪▒╪з╪▓ ┘З┘И█М╪к ╪▒╪з ╪з┘Ж╪к╪о╪з╪и ┌й┘Ж█М╪п",
|
||||
"speedLimit": "┘Е╪н╪п┘И╪п█М╪к ╪│╪▒╪╣╪к",
|
||||
"startTime": "╪▓┘Е╪з┘Ж ╪┤╪▒┘И╪╣",
|
||||
"status": "┘И╪╢╪╣█М╪к",
|
||||
"subscription": "╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionDetails": "╪м╪▓╪ж█М╪з╪к ╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionId": "╪┤┘Ж╪з╪│┘З ╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionInfo": "╪з╪╖┘Д╪з╪╣╪з╪к ╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionList": "┘Д█М╪│╪к ╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionLogs": "┌п╪▓╪з╪▒╪┤тАМ┘З╪з█М ╪з╪┤╪к╪▒╪з┌й",
|
||||
"subscriptionName": "┘Ж╪з┘Е ╪з╪┤╪к╪▒╪з┌й",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "╪з╪╣┘Д╪з┘ЖтАМ┘З╪з█М ╪к╪м╪з╪▒╪к",
|
||||
"trafficLimit": "┘Е╪н╪п┘И╪п█М╪к ╪к╪▒╪з┘Б█М┌й",
|
||||
"trafficLogs": "┌п╪▓╪з╪▒╪┤тАМ┘З╪з█М ╪к╪▒╪з┘Б█М┌й",
|
||||
"trafficUsage": "╪з╪│╪к┘Б╪з╪п┘З ╪з╪▓ ╪к╪▒╪з┘Б█М┌й",
|
||||
"unknown": "┘Ж╪з┘Е╪┤╪о╪╡",
|
||||
"unlimited": "┘Ж╪з┘Е╪н╪п┘И╪п",
|
||||
"unverified": "╪к╪г█М█М╪п ┘Ж╪┤╪п┘З",
|
||||
"update": "╪и┘ЗтАМ╪▒┘И╪▓╪▒╪│╪з┘Ж█М",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "╪╣╪з┘Е┘Д ┌й╪з╪▒╪и╪▒",
|
||||
"userEmail": "╪з█М┘Е█М┘Д ┌й╪з╪▒╪и╪▒",
|
||||
"userEmailPlaceholder": "╪з█М┘Е█М┘Д ┌й╪з╪▒╪и╪▒ ╪▒╪з ┘И╪з╪▒╪п ┌й┘Ж█М╪п",
|
||||
"userId": "╪┤┘Ж╪з╪│┘З ┌й╪з╪▒╪и╪▒",
|
||||
"userInfo": "╪з╪╖┘Д╪з╪╣╪з╪к ┌й╪з╪▒╪и╪▒",
|
||||
"userList": "┘Б┘З╪▒╪│╪к ┌й╪з╪▒╪и╪▒╪з┘Ж",
|
||||
"userLogs": "╪к╪з╪▒█М╪о┌Ж┘З ┘И╪▒┘И╪п",
|
||||
"userName": "╪┤┘Ж╪з╪│┘З ┌й╪з╪▒╪и╪▒",
|
||||
"userOrders": "╪│┘Б╪з╪▒╪┤тАМ┘З╪з",
|
||||
"userProfile": "┘╛╪▒┘И┘Б╪з█М┘Д",
|
||||
"userSubscriptions": "╪з╪┤╪к╪▒╪з┌йтАМ┘З╪з",
|
||||
"verified": "╪к╪г█М█М╪п ╪┤╪п┘З"
|
||||
"username": "┘Ж╪з┘Е ┌й╪з╪▒╪и╪▒█М",
|
||||
"verified": "╪к╪г█М█М╪п ╪┤╪п┘З",
|
||||
"viewDetails": "┘Е╪┤╪з┘З╪п┘З ╪м╪▓╪ж█М╪з╪к"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "Anna kelvollinen s├дhk├╢postiosoite.",
|
||||
"existingAccount": "Onko sinulla jo tili?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Salasanat eiv├дt t├дsm├д├д",
|
||||
"success": "Rekister├╢inti onnistui, olet kirjautunut automaattisesti!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Tila",
|
||||
"subject": "Aihe",
|
||||
"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_templateDescription": "Paikkamerkit {after}.variable{before} korvataan todellisilla tiedoilla. Varmista, ett├д pid├дt n├дm├д muuttujat.",
|
||||
"whitelistSuffixes": "Sallittujen luetteloiden p├д├дtteet",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "ADS-asetukset",
|
||||
"Announcement Management": "Ilmoitusten hallinta",
|
||||
"Apple": "Apple ID",
|
||||
"Application Management": "Sovellushallinta",
|
||||
"Auth Control": "Todennuksen hallinta",
|
||||
"Coupon Management": "Kuponkien hallinta",
|
||||
"Dashboard": "Kojelauta",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Tilausten hallinta",
|
||||
"Payment Config": "Maksukonfiguraatio",
|
||||
"Phone Number": "Puhelinnumero",
|
||||
"Rule Management": "S├д├дnt├╢hallinta",
|
||||
"Server": "Palvelu",
|
||||
"Server Management": "Palvelimen hallinta",
|
||||
"Settings": "Asetukset",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Kuvaus",
|
||||
"descriptionPlaceholder": "Sy├╢t├д maksutavan kuvaus",
|
||||
"domain": "Verkkotunnus",
|
||||
"domainPlaceholder": "valinnainen, esim. {esimerkki}",
|
||||
"edit": "Muokkaa",
|
||||
"editPayment": "Muokkaa maksutapaa",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Tietosuojak├дyt├дnt├╢"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Mukautettu tieto",
|
||||
"customDataDescription": "Mukautettu tieto, jota k├дytet├д├дn sivuston mukautettuun tietoon",
|
||||
"customHtml": "Mukautettu HTML",
|
||||
"customHtmlDescription": "Mukautettu HTML-koodi, joka lis├дt├д├дn sivuston body -tagin loppuun.",
|
||||
"keywords": "Avainsanat",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Vahvista j├дrjestelm├дn p├дivitys",
|
||||
"confirmUpgrade": "Vahvista p├дivitys",
|
||||
"currentVersion": "Nykyinen j├дrjestelm├дversio:",
|
||||
"developmentVersion": "Kehitysversio",
|
||||
"errors": "Virheet",
|
||||
"ip": "IP",
|
||||
"ip": "IP-osoite",
|
||||
"lastUpdated": "Viimeksi p├дivitetty:",
|
||||
"newVersionAvailable": "Uusi versio",
|
||||
"none": "Ei mit├д├дn",
|
||||
"query": "Kysely",
|
||||
"rebootDescription": "Oletko varma, ett├д haluat k├дynnist├д├д j├дrjestelm├дn uudelleen? T├дm├д toimenpide aiheuttaa lyhyen palvelukatkon.",
|
||||
"rebooting": "J├дrjestelm├д k├дynnistyy uudelleen...",
|
||||
"refreshLogs": "P├дivit├д lokit",
|
||||
"request": "Pyynt├╢",
|
||||
"serverVersion": "Palvelin",
|
||||
"status": "Tila",
|
||||
"systemLogs": "J├дrjestelm├дn lokit",
|
||||
"systemReboot": "J├дrjestelm├дn uudelleenk├дynnistys",
|
||||
@@ -22,5 +25,8 @@
|
||||
"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.",
|
||||
"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",
|
||||
"actions": "toiminnot",
|
||||
"active": "Aktiivinen",
|
||||
"add": "Lis├д├д",
|
||||
"administrator": "Yll├дpit├дj├д",
|
||||
"areaCode": "Alueen koodi",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "s├дhk├╢posti",
|
||||
"enable": "Ota k├дytt├╢├╢n",
|
||||
"expireTime": "Vanhentumisaika",
|
||||
"expired": "Vanhentunut",
|
||||
"expiredAt": "Vanhentunut",
|
||||
"failed": "Ep├дonnistui",
|
||||
"giftAmount": "Lahjan m├д├дr├д",
|
||||
"giftAmountPlaceholder": "Sy├╢t├д lahjan m├д├дr├д",
|
||||
"inactive": "Ep├дaktiivinen",
|
||||
"invalidEmailFormat": "Virheellinen s├дhk├╢postimuoto",
|
||||
"inviteCode": "Kutsukoodi",
|
||||
"inviteCodePlaceholder": "Sy├╢t├д kutsukoodi (j├дt├д tyhj├дksi luodaksesi uuden)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "Sy├╢t├д uusi salasana (valinnainen)",
|
||||
"permanent": "Pysyv├д",
|
||||
"pleaseEnterEmail": "Ole hyv├д ja sy├╢t├д s├дhk├╢postiosoite",
|
||||
"price": "Hinta",
|
||||
"referer": "Suosittelija",
|
||||
"refererId": "Viittaajan ID",
|
||||
"refererIdPlaceholder": "Sy├╢t├д suosittelijan ID",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Etsi IP-osoite",
|
||||
"selectAuthType": "Valitse todennustyyppi",
|
||||
"speedLimit": "Nopeusrajoitus",
|
||||
"startTime": "Aloitusaika",
|
||||
"status": "Tila",
|
||||
"subscription": "Tilaus",
|
||||
"subscriptionDetails": "Tilaustiedot",
|
||||
"subscriptionId": "Tilauksen ID",
|
||||
"subscriptionInfo": "Tilauksen tiedot",
|
||||
"subscriptionList": "Tilauksen lista",
|
||||
"subscriptionLogs": "Tilaushistoria",
|
||||
"subscriptionName": "Tilauksen nimi",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Kauppailmoitukset",
|
||||
"trafficLimit": "Liikenteen rajoitus",
|
||||
"trafficLogs": "Liikennelokit",
|
||||
"trafficUsage": "Liikenteen k├дytt├╢",
|
||||
"unknown": "Tuntematon",
|
||||
"unlimited": "Rajoittamaton",
|
||||
"unverified": "Vahvistamaton",
|
||||
"update": "P├дivit├д",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "K├дytt├дj├дagentti",
|
||||
"userEmail": "K├дytt├дj├дn s├дhk├╢posti",
|
||||
"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",
|
||||
"userLogs": "Kirjautumishistoria",
|
||||
"userName": "K├дytt├дj├дn tunniste",
|
||||
"userOrders": "Tilaukset",
|
||||
"userProfile": "Profiili",
|
||||
"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.",
|
||||
"existingAccount": "Vous avez d├йj├а un compte ?",
|
||||
"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.**",
|
||||
"passwordMismatch": "Les mots de passe saisis ne correspondent pas",
|
||||
"success": "Inscription r├йussie, vous ├кtes maintenant connect├й automatiquement !",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "Statut",
|
||||
"subject": "Sujet",
|
||||
"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_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",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "Configuration ADS",
|
||||
"Announcement Management": "Gestion des annonces",
|
||||
"Apple": "Identifiant Apple",
|
||||
"Application Management": "Gestion des applications",
|
||||
"Auth Control": "Contr├┤le d'authentification",
|
||||
"Coupon Management": "Gestion des coupons",
|
||||
"Dashboard": "Tableau de bord",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "Gestion des commandes",
|
||||
"Payment Config": "Configuration de paiement",
|
||||
"Phone Number": "Num├йro de t├йl├йphone",
|
||||
"Rule Management": "Gestion des r├иgles",
|
||||
"Server": "Serveur",
|
||||
"Server Management": "Gestion des services",
|
||||
"Settings": "Param├иtres",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "Description",
|
||||
"descriptionPlaceholder": "Entrez la description du mode de paiement",
|
||||
"domain": "Domaine",
|
||||
"domainPlaceholder": "facultatif, par exemple {exemple}",
|
||||
"edit": "Modifier",
|
||||
"editPayment": "Modifier le mode de paiement",
|
||||
"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"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "Politique de confidentialit├й"
|
||||
},
|
||||
"site": {
|
||||
"customData": "Donn├йes personnalis├йes",
|
||||
"customDataDescription": "Donn├йes personnalis├йes, utilis├йes pour les donn├йes personnalis├йes du site",
|
||||
"customHtml": "HTML personnalis├й",
|
||||
"customHtmlDescription": "Code HTML personnalis├й ├а injecter dans le bas de la balise body du site.",
|
||||
"keywords": "Mots-cl├йs",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "Confirmer la mise ├а niveau du syst├иme",
|
||||
"confirmUpgrade": "Confirmer la mise ├а niveau",
|
||||
"currentVersion": "Version actuelle du syst├иme :",
|
||||
"developmentVersion": "Version de d├йveloppement",
|
||||
"errors": "Erreurs",
|
||||
"ip": "IP",
|
||||
"ip": "Adresse IP",
|
||||
"lastUpdated": "Derni├иre mise ├а jour :",
|
||||
"newVersionAvailable": "Nouvelle version",
|
||||
"none": "Aucun",
|
||||
"query": "Requ├кte",
|
||||
"rebootDescription": "├Кtes-vous s├╗r de vouloir red├йmarrer le syst├иme ? Cette op├йration entra├оnera une interruption temporaire du service.",
|
||||
"rebooting": "Red├йmarrage du syst├иme...",
|
||||
"refreshLogs": "Rafra├оchir les journaux",
|
||||
"request": "Demande",
|
||||
"serverVersion": "Serveur",
|
||||
"status": "Statut",
|
||||
"systemLogs": "Journaux syst├иme",
|
||||
"systemReboot": "Red├йmarrage du syst├иme",
|
||||
@@ -22,5 +25,8 @@
|
||||
"systemUpgrade": "Mise ├а niveau du syst├иme",
|
||||
"upgradeDescription": "├Кtes-vous s├╗r de vouloir effectuer la mise ├а niveau du syst├иme ? Cette op├йration peut prendre quelques minutes, pendant lesquelles le syst├иme peut ne pas r├йpondre.",
|
||||
"userAgent": "Agent utilisateur",
|
||||
"viewLogsAndManage": "Afficher les journaux syst├иme, effectuer des mises ├а niveau et red├йmarrer le syst├иme"
|
||||
"versionInfo": "Informations de version",
|
||||
"viewLogsAndManage": "Afficher les journaux syst├иme, effectuer des mises ├а niveau et red├йmarrer le syst├иme",
|
||||
"viewNewVersion": "Voir la nouvelle version",
|
||||
"webVersion": "Web/Admin"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"accountEnable": "Activation du compte",
|
||||
"actions": "actions",
|
||||
"active": "Actif",
|
||||
"add": "Ajouter",
|
||||
"administrator": "Administrateur",
|
||||
"areaCode": "Indicatif r├йgional",
|
||||
@@ -41,10 +42,12 @@
|
||||
"email": "e-mail",
|
||||
"enable": "Activer",
|
||||
"expireTime": "Date d'expiration",
|
||||
"expired": "Expir├й",
|
||||
"expiredAt": "Expir├й le",
|
||||
"failed": "├Йchou├й",
|
||||
"giftAmount": "Montant du cadeau",
|
||||
"giftAmountPlaceholder": "Entrez le montant du cadeau",
|
||||
"inactive": "Inactif",
|
||||
"invalidEmailFormat": "Format d'email invalide",
|
||||
"inviteCode": "Code d'invitation",
|
||||
"inviteCodePlaceholder": "Entrez le code d'invitation (laissez vide pour g├йn├йrer)",
|
||||
@@ -63,6 +66,7 @@
|
||||
"passwordPlaceholder": "Entrez un nouveau mot de passe (facultatif)",
|
||||
"permanent": "Permanent",
|
||||
"pleaseEnterEmail": "Veuillez entrer votre adresse e-mail",
|
||||
"price": "Prix",
|
||||
"referer": "R├йf├йrent",
|
||||
"refererId": "ID du r├йf├йrent",
|
||||
"refererIdPlaceholder": "Entrez l'ID du r├йf├йrent",
|
||||
@@ -74,8 +78,12 @@
|
||||
"searchIp": "Rechercher une adresse IP",
|
||||
"selectAuthType": "S├йlectionner le type d'authentification",
|
||||
"speedLimit": "Limite de vitesse",
|
||||
"startTime": "Date de d├йbut",
|
||||
"status": "Statut",
|
||||
"subscription": "Abonnement",
|
||||
"subscriptionDetails": "D├йtails de l'abonnement",
|
||||
"subscriptionId": "ID d'abonnement",
|
||||
"subscriptionInfo": "Informations sur l'abonnement",
|
||||
"subscriptionList": "Liste des abonnements",
|
||||
"subscriptionLogs": "Journaux d'abonnement",
|
||||
"subscriptionName": "Nom de l'abonnement",
|
||||
@@ -89,6 +97,8 @@
|
||||
"tradeNotifications": "Notifications de commerce",
|
||||
"trafficLimit": "Limite de trafic",
|
||||
"trafficLogs": "Journaux de trafic",
|
||||
"trafficUsage": "Utilisation du trafic",
|
||||
"unknown": "Inconnu",
|
||||
"unlimited": "Illimit├й",
|
||||
"unverified": "Non v├йrifi├й",
|
||||
"update": "Mettre ├а jour",
|
||||
@@ -98,11 +108,15 @@
|
||||
"userAgent": "Agent utilisateur",
|
||||
"userEmail": "E-mail de l'utilisateur",
|
||||
"userEmailPlaceholder": "Entrez l'email de l'utilisateur",
|
||||
"userId": "ID utilisateur",
|
||||
"userInfo": "Informations sur l'utilisateur",
|
||||
"userList": "Liste des utilisateurs",
|
||||
"userLogs": "Historique des connexions",
|
||||
"userName": "Identifiant Utilisateur",
|
||||
"userOrders": "Commandes",
|
||||
"userProfile": "Profil",
|
||||
"userSubscriptions": "Abonnements",
|
||||
"verified": "V├йrifi├й"
|
||||
"username": "Nom d'utilisateur",
|
||||
"verified": "V├йrifi├й",
|
||||
"viewDetails": "Voir les d├йtails"
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"email": "рдХреГрдкрдпрд╛ рдПрдХ рдорд╛рдиреНрдп рдИрдореЗрд▓ рдкрддрд╛ рджрд░реНрдЬ рдХрд░реЗрдВред",
|
||||
"existingAccount": "рдкрд╣рд▓реЗ рд╕реЗ рдЦрд╛рддрд╛ рд╣реИ?",
|
||||
"get": "рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ",
|
||||
"invite": "рдЖрдордВрддреНрд░рдг рдХреЛрдб",
|
||||
"invite": "рдЖрдордВрддреНрд░рдг рдХреЛрдб (рд╡реИрдХрд▓реНрдкрд┐рдХ)",
|
||||
"message": "#### рдкреНрд░рд┐рдп рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛, рдирдорд╕реНрддреЗ!\n\nрд╣рдорд╛рд░реЗ рдкреНрд░рддрд┐ рдЖрдкрдХреА рд░реБрдЪрд┐ рдФрд░ рд╕рдорд░реНрдерди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред рд╕рд╛рдЗрдЯ рд╕рдВрдЪрд╛рд▓рди рд░рдгрдиреАрддрд┐ рдореЗрдВ рдмрджрд▓рд╛рд╡ рдХреЗ рдХрд╛рд░рдг, рд╣рдордиреЗ рдирдП рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкрдВрдЬреАрдХрд░рдг рдХреЛ рдмрдВрдж рдХрд░ рджрд┐рдпрд╛ рд╣реИред рдЗрд╕ рджреМрд░рд╛рди, рдореМрдЬреВрджрд╛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЗ рдЙрдкрдпреЛрдЧ рдкрд░ рдХреЛрдИ рдкреНрд░рднрд╛рд╡ рдирд╣реАрдВ рдкрдбрд╝реЗрдЧрд╛ред\n\nрд╣рдо рдЖрдкрдХреЛ рдмреЗрд╣рддрд░ рд╕реЗрд╡рд╛ рдФрд░ рдЕрдиреБрднрд╡ рдкреНрд░рджрд╛рди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░рддрд┐рдмрджреНрдз рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рдкрдВрдЬреАрдХрд░рдг рдмрдВрдж рд╣реЛрдиреЗ рдХреЗ рджреМрд░рд╛рди рд╣рдо рд╡реНрдпрд╛рдкрдХ рдкреНрд░рдгрд╛рд▓реА рдЕрдиреБрдХреВрд▓рди рдФрд░ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдЙрдиреНрдирдпрди рдХрд░реЗрдВрдЧреЗред рднрд╡рд┐рд╖реНрдп рдореЗрдВ, рд╣рдо рдЖрдкрдХреЛ рдмреЗрд╣рддрд░ рд╕рд╛рдордЧреНрд░реА рдФрд░ рд╕реЗрд╡рд╛рдУрдВ рдХреЗ рд╕рд╛рде рд╕реНрд╡рд╛рдЧрдд рдХрд░реЗрдВрдЧреЗред\n\nрд╣рдорд╛рд░реА рд╡реЗрдмрд╕рд╛рдЗрдЯ рдФрд░ рд╕реЛрд╢рд▓ рдореАрдбрд┐рдпрд╛ рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо рдкрд░ рдирд╡реАрдирддрдо рдЕрдкрдбреЗрдЯ рдФрд░ рд╕реВрдЪрдирд╛рдПрдВ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдзреНрдпрд╛рди рджреЗрдВред рдЖрдкрдХреА рд╕рдордЭ рдФрд░ рд╕рдорд░реНрдерди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред\n\nрдпрджрд┐ рдЖрдкрдХреЗ рдХреЛрдИ рдкреНрд░рд╢реНрди рд╣реИрдВ рдпрд╛ рд╕рд╣рд╛рдпрддрд╛ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рддреЛ рдХреГрдкрдпрд╛ рд╣рдорд╛рд░реА рдЧреНрд░рд╛рд╣рдХ рд╕реЗрд╡рд╛ рдЯреАрдо рд╕реЗ рд╕рдВрдкрд░реНрдХ рдХрд░реЗрдВред\n\n**рдЖрдкрдХреЗ рд╕рдорд░реНрдерди рдФрд░ рд╕рдордЭ рдХреЗ рд▓рд┐рдП рдПрдХ рдмрд╛рд░ рдлрд┐рд░ рдзрдиреНрдпрд╡рд╛рджред**",
|
||||
"passwordMismatch": "рджреЛрдиреЛрдВ рдмрд╛рд░ рджрд░реНрдЬ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдкрд╛рд╕рд╡рд░реНрдб рдореЗрд▓ рдирд╣реАрдВ рдЦрд╛ рд░рд╣рд╛ рд╣реИ",
|
||||
"success": "рдкрдВрдЬреАрдХрд░рдг рд╕рдлрд▓, рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рд▓реЙрдЧ рдЗрди рд╣реЛ рдЧрдпрд╛!",
|
||||
|
||||
@@ -39,6 +39,8 @@
|
||||
"status": "рд╕реНрдерд┐рддрд┐",
|
||||
"subject": "рд╡рд┐рд╖рдп",
|
||||
"template": "рдЯреЗрдореНрдкрд▓реЗрдЯ",
|
||||
"traffic_exceed_email_template": "рдЯреНрд░реИрдлрд┐рдХ рдЕрдзрд┐рдХрддрд╛ рд╕реВрдЪрдирд╛ рдЯреЗрдореНрдкрд▓реЗрдЯ",
|
||||
"traffic_exceed_email_templateDescription": "{after}.variable{before} рдкреНрд▓реЗрд╕рд╣реЛрд▓реНрдбрд░реНрд╕ рдХреЛ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдЗрди рд╡реЗрд░рд┐рдПрдмрд▓реНрд╕ рдХреЛ рдмрдирд╛рдП рд░рдЦреЗрдВред",
|
||||
"verify_email_template": "рд╕рддреНрдпрд╛рдкрди рдИрдореЗрд▓ рдЯреЗрдореНрдкрд▓реЗрдЯ",
|
||||
"verify_email_templateDescription": "{after}.variable{before} рдкреНрд▓реЗрд╕рд╣реЛрд▓реНрдбрд░реНрд╕ рдХреЛ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдбреЗрдЯрд╛ рд╕реЗ рдмрджрд▓рд╛ рдЬрд╛рдПрдЧрд╛ред рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рдЗрди рд╡реЗрд░рд┐рдПрдмрд▓реНрд╕ рдХреЛ рдмрдирд╛рдП рд░рдЦреЗрдВред",
|
||||
"whitelistSuffixes": "рд╢реНрд╡реЗрддрд╕реВрдЪреА рдкреНрд░рддреНрдпрдп",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"ADS Config": "рдПрдбреАрдПрд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧ",
|
||||
"Announcement Management": "рдШреЛрд╖рдгрд╛ рдкреНрд░рдмрдВрдзрди",
|
||||
"Apple": "рдПрдкреНрдкрд▓ рдЖрдИрдбреА",
|
||||
"Application Management": "рдРрдкреНрд▓рд┐рдХреЗрд╢рди рдкреНрд░рдмрдВрдзрди",
|
||||
"Auth Control": "рдкреНрд░рдорд╛рдгрд┐рдХрд░рдг рдирд┐рдпрдВрддреНрд░рдг",
|
||||
"Coupon Management": "рдХреВрдкрди рдкреНрд░рдмрдВрдзрди",
|
||||
"Dashboard": "рдбреИрд╢рдмреЛрд░реНрдб",
|
||||
@@ -16,6 +17,7 @@
|
||||
"Order Management": "рдСрд░реНрдбрд░ рдкреНрд░рдмрдВрдзрди",
|
||||
"Payment Config": "рднреБрдЧрддрд╛рди рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди",
|
||||
"Phone Number": "рдлрд╝реЛрди рдирдВрдмрд░",
|
||||
"Rule Management": "рдирд┐рдпрдо рдкреНрд░рдмрдВрдзрди",
|
||||
"Server": "рд╕рд░реНрд╡рд░",
|
||||
"Server Management": "рд╕рд░реНрд╡рд░ рдкреНрд░рдмрдВрдзрди",
|
||||
"Settings": "рд╕реЗрдЯрд┐рдВрдЧреНрд╕",
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
"description": "рд╡рд┐рд╡рд░рдг",
|
||||
"descriptionPlaceholder": "рднреБрдЧрддрд╛рди рд╡рд┐рдзрд┐ рдХрд╛ рд╡рд┐рд╡рд░рдг рджрд░реНрдЬ рдХрд░реЗрдВ",
|
||||
"domain": "рдбреЛрдореЗрди",
|
||||
"domainPlaceholder": "рд╡реИрдХрд▓реНрдкрд┐рдХ, рдЬреИрд╕реЗ рдХрд┐ {example}",
|
||||
"edit": "рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||
"editPayment": "рднреБрдЧрддрд╛рди рд╡рд┐рдзрд┐ рд╕рдВрдкрд╛рджрд┐рдд рдХрд░реЗрдВ",
|
||||
"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": "рдирд┐рдпрдо рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЕрдкрдбреЗрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛"
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
"title": "рдЧреЛрдкрдиреАрдпрддрд╛ рдиреАрддрд┐"
|
||||
},
|
||||
"site": {
|
||||
"customData": "рдХрд╕реНрдЯрдо рдбреЗрдЯрд╛",
|
||||
"customDataDescription": "рдХрд╕реНрдЯрдо рдбреЗрдЯрд╛, рд╕рд╛рдЗрдЯ рдХреЗ рдХрд╕реНрдЯрдо рдбреЗрдЯрд╛ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ",
|
||||
"customHtml": "рдХрд╕реНрдЯрдо HTML",
|
||||
"customHtmlDescription": "рд╕рд╛рдЗрдЯ рдХреЗ bodyреИрдЧ рдХреЗ рдиреАрдЪреЗ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд╕реНрдЯрдо HTML рдХреЛрдбред",
|
||||
"keywords": "рдХреАрд╡рд░реНрдб",
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
"confirmSystemUpgrade": "рд╕рд┐рд╕реНрдЯрдо рдЙрдиреНрдирдпрди рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ",
|
||||
"confirmUpgrade": "рдЙрдиреНрдирдпрди рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░реЗрдВ",
|
||||
"currentVersion": "рд╡рд░реНрддрдорд╛рди рд╕рд┐рд╕реНрдЯрдо рд╕рдВрд╕реНрдХрд░рдг:",
|
||||
"developmentVersion": "рд╡рд┐рдХрд╛рд╕ рд╕рдВрд╕реНрдХрд░рдг",
|
||||
"errors": "рддреНрд░реБрдЯрд┐рдпрд╛рдБ",
|
||||
"ip": "рдЖрдИрдкреА",
|
||||
"ip": "рдЖрдИрдкреА рдкрддрд╛",
|
||||
"lastUpdated": "рдЕрдВрддрд┐рдо рдЕрдкрдбреЗрдЯ:",
|
||||
"newVersionAvailable": "рдирдпрд╛ рд╕рдВрд╕реНрдХрд░рдг",
|
||||
"none": "рдХреЛрдИ рдирд╣реАрдВ",
|
||||
"query": "рдкреНрд░рд╢реНрди",
|
||||
"rebootDescription": "рдХреНрдпрд╛ рдЖрдк рд╡рд╛рдХрдИ рд╕рд┐рд╕реНрдЯрдо рдХреЛ рдкреБрдирдГ рдЖрд░рдВрдн рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ? рдЗрд╕ рдХреНрд░рд┐рдпрд╛ рд╕реЗ рдереЛрдбрд╝реЗ рд╕рдордп рдХреЗ рд▓рд┐рдП рд╕реЗрд╡рд╛ рдореЗрдВ рдмрд╛рдзрд╛ рдЖ рд╕рдХрддреА рд╣реИред",
|
||||
"rebooting": "рд╕рд┐рд╕реНрдЯрдо рдХреЛ рдкреБрдирдГ рдЖрд░рдВрдн рдХрд┐рдпрд╛ рдЬрд╛ рд░рд╣рд╛ рд╣реИ...",
|
||||
"refreshLogs": "рд▓реЙрдЧреНрд╕ рдХреЛ рддрд╛рдЬрд╝рд╛ рдХрд░реЗрдВ",
|
||||
"request": "рдЕрдиреБрд░реЛрдз",
|
||||
"serverVersion": "рд╕рд░реНрд╡рд░",
|
||||
"status": "рд╕реНрдерд┐рддрд┐",
|
||||
"systemLogs": "рд╕рд┐рд╕реНрдЯрдо рд▓реЙрдЧреНрд╕",
|
||||
"systemReboot": "рд╕рд┐рд╕реНрдЯрдо рдкреБрдирд░рд╛рд░рдВрдн",
|
||||
@@ -22,5 +25,8 @@
|
||||
"systemUpgrade": "рд╕рд┐рд╕реНрдЯрдо рдЙрдиреНрдирдпрди",
|
||||
"upgradeDescription": "рдХреНрдпрд╛ рдЖрдк рд╡рд╛рдХрдИ рд╕рд┐рд╕реНрдЯрдо рдЕрдкрдЧреНрд░реЗрдб рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ? рдЗрд╕ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рдХреБрдЫ рдорд┐рдирдЯ рд▓рдЧ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕ рджреМрд░рд╛рди рд╕рд┐рд╕реНрдЯрдо рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рдирд╣реАрдВ рджреЗ рд╕рдХрддрд╛ рд╣реИред",
|
||||
"userAgent": "рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдПрдЬреЗрдВрдЯ",
|
||||
"viewLogsAndManage": "рд╕рд┐рд╕реНрдЯрдо рд▓реЙрдЧ рджреЗрдЦреЗрдВ, рд╕рд┐рд╕реНрдЯрдо рдЕрдкрдЧреНрд░реЗрдб рдФрд░ рдкреБрдирд░рд╛рд░рдВрдн рд╕рдВрдЪрд╛рд▓рди рдХрд░реЗрдВ"
|
||||
"versionInfo": "рд╕рдВрд╕реНрдХрд░рдг рдЬрд╛рдирдХрд╛рд░реА",
|
||||
"viewLogsAndManage": "рд╕рд┐рд╕реНрдЯрдо рд▓реЙрдЧ рджреЗрдЦреЗрдВ, рд╕рд┐рд╕реНрдЯрдо рдЕрдкрдЧреНрд░реЗрдб рдФрд░ рдкреБрдирд░рд╛рд░рдВрдн рд╕рдВрдЪрд╛рд▓рди рдХрд░реЗрдВ",
|
||||
"viewNewVersion": "рдирдпрд╛ рд╕рдВрд╕реНрдХрд░рдг рджреЗрдЦреЗрдВ",
|
||||
"webVersion": "рд╡реЗрдм/рдПрдбрдорд┐рди"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user