Compare commits

..

2447 Commits
alpha ... main

Author SHA1 Message Date
wonfen
84fbccbfd9 release v2.2.3 2025-04-09 13:37:17 +08:00
TianHua Liu
49c81f6201
fix: the first function call creates multiple axios instances (#3273) 2025-04-07 16:50:53 +08:00
Tunglies
c894a15d13 chore: update UPDATELOG 2025-04-06 02:03:12 +08:00
Tunglies
196b887381 Adjust text and shadow colors in tray speed rate display with colorful
icon
2025-04-06 02:00:40 +08:00
Tunglies
2ad20ed239 Merge branch 'tunglies' into dev 2025-04-05 20:00:55 +08:00
Tunglies
98de91771e Simplify tray icon template logic and adjust text colors 2025-04-05 19:54:32 +08:00
Tunglies
9dfd9bad20 Remove color detection for tray icons
Always use white text with black shadow regardless of icon type
2025-04-05 16:07:16 +08:00
Tunglies
0b8d08d13b Update logging macros usage in timer module
Replace log macros with custom logging macros in timer module
2025-04-05 11:36:38 +08:00
Tunglies
0de304d4e3 Fix and Optimize
fix: #3187 and #3096, silent startup can not trigger lightweight mode
fix: optimize startup initializing processing
2025-04-05 10:28:27 +08:00
Tunglies
55f1766ebc Change logging level from info to debug for hotkeys 2025-04-05 09:19:44 +08:00
Langning Chen
6d1a8fb264
Fix traffic graph time mismatch (#3201) 2025-04-04 17:52:18 +08:00
wonfen
d3958594d9 fix: #2609, avoid URL encoding issues by directly parsing query parameters 2025-04-04 13:50:21 +08:00
wonfen
b5952f320b fix: #3244, remove test directory, simplify resource initialization 2025-04-04 13:18:17 +08:00
wonfen
98be9621a6 feat: retry subscription fetch using Clash proxy on failure 2025-04-03 14:55:28 +08:00
wonfen
e4eb13ce22 chore: update default DNS override configuration 2025-04-03 14:55:28 +08:00
Tunglies
ecf2da7c0a chore: update UPDATELOG 2025-04-03 07:35:37 +08:00
wonfen
7d7c8988d7 fix: resolve rendering issue caused by duplicate node names 2025-04-02 12:48:17 +08:00
Tunglies
5e11d36972 Remove real-time window position and size saving 2025-04-01 17:26:27 +08:00
wonfen
3039f39d40 fix: #3227 show traffic chart only when data is available 2025-04-01 13:51:35 +08:00
wonfen
7b5fd104de fix: #3227, use async operations to avoid blocking on 2025-04-01 12:52:59 +08:00
wonfen
30ea408019 feat: enhance system info card with combo mode display 2025-04-01 01:44:59 +08:00
Tunglies
3fa130695c rm: setup_window_state_monitor for temporary due to unkown window control behavior 2025-03-31 21:52:50 +08:00
Tunglies
fece31438a chore: update UPDATELOG 2025-03-31 21:47:06 +08:00
Tunglies
ad1f2bea3b fix: unused Result 2025-03-31 21:44:12 +08:00
Tunglies
cd4bfdd743 feat: change proxy mode to close connections if enable auto close connections 2025-03-31 21:43:29 +08:00
wonfen
cde8c4004f feat: add is_admin field to export_diagnostic_info 2025-03-31 10:34:11 +08:00
wonfen
c53514e060 feat: unify runtime mode detection; support TUN and service installation in admin mode 2025-03-31 08:16:14 +08:00
wonfen
8e99672265 feat: show node count in proxy groups 2025-03-31 04:35:27 +08:00
wonfen
5b9b5cb6a8 fix: add macos missing libc deps 2025-03-31 03:36:58 +08:00
wonfen
62141380d8 chore: update dependencies and bug report template 2025-03-31 03:24:36 +08:00
wonfen
52a15bb281 feat: detect admin mode and warn about auto-start unavailability 2025-03-31 03:22:24 +08:00
Tunglies
b092f74c88 fix(dir): wrong logs dir path 2025-03-30 13:12:42 +08:00
Tunglies
937f43c270 fix: unable to switch subscribtion profile 2025-03-30 12:53:16 +08:00
Tunglies
9b02088918 chore: update UPDATELOG 2025-03-30 12:49:07 +08:00
wonfen
1bd503a654 feat: add error prompt for initial config loading to prevent switching to invalid subscription 2025-03-30 10:56:30 +08:00
Tunglies
c6477dfda4 Update build target from esnext to es2020 2025-03-30 04:19:35 +08:00
Tunglies
4831d88467 rm(lightweight): unused logging 2025-03-29 13:05:58 +08:00
Tunglies
9ebde802d4 Update alpha workflow to trigger on src directory changes 2025-03-29 12:35:49 +08:00
wonfen
a9cccc7b97 feat: add error prompt for loading initial config file 2025-03-29 07:52:46 +08:00
Tunglies
d54a765bd6 chore: update UPDATELOG 2025-03-28 19:41:46 +08:00
Tunglies
5a2751162f fix(linux): can not connect to mihomo core 2025-03-28 19:39:11 +08:00
Tunglies
492a5a6de7 feat(cmd): service return message i18 language support 2025-03-28 11:51:52 +08:00
Tunglies
f6c0f144a6 fix(clippy): clippy warning codes 2025-03-28 11:43:21 +08:00
Tunglies
1c046f3ca3 fix(cmd): service error message return with shorter message 2025-03-28 11:35:50 +08:00
wonfen
4a47c5bb6f fix: unused code on non-MacOS platform 2025-03-28 05:58:02 +08:00
wonfen
b7e01aefb4 perf: optimize homepage traffic chart code, refine UI, and complete i18n 2025-03-28 05:53:18 +08:00
Tunglies
e8e16f7d57 refactor(logging): replace log_err! with structured logging_error! calls
refactor(cm-service): better error handling from Backend to Frontend
2025-03-28 03:39:34 +08:00
wonfen
59caa22431 feat: enhance service startup logic with user preference-based sidecar fallback 2025-03-28 03:20:13 +08:00
Tunglies
e2046f3e48 refact(profile+core): replace println with logging! macros for structured logging 2025-03-28 01:48:55 +08:00
Tunglies
8fdcffc731 refactor(CoreManager) combine duplicated logical 2025-03-28 00:58:34 +08:00
Tunglies
3ec77c6256 chore: update UPDATELOG 2025-03-27 20:52:50 +08:00
Tunglies
b09313756e refactor: optimize total width calculation and format bytes speed function 2025-03-27 20:45:51 +08:00
wonfen
f800e2e3b6 refactor: simplify graph code and adjust text margins 2025-03-27 13:11:39 +08:00
wonfen
daad623855 fix: resolve toggle flicker for "Auto Start" and "DNS Override" 2025-03-27 12:30:15 +08:00
Tunglies
7716e2bc87 rm: unused trait filed 2025-03-27 12:14:27 +08:00
Tunglies
70bd5ec03c refactor: simplify tray icon handling and update return types for icon functions
fix: custom tray icon with speedrate display large gap
2025-03-27 12:04:52 +08:00
Tunglies
ce5c86c3b0 fix: load custom tray icon failed due to #2886 2025-03-27 11:12:08 +08:00
Tunglies
a6a6d9d036 feat: initial and restart core checks service version if available
chore: update UPDATELOG.md
2025-03-27 09:20:15 +08:00
Tunglies
971dd6a2cf fea: optimize vite chunk splitting 2025-03-27 05:09:36 +08:00
Tunglies
42d0ea7e36 chore: remove unused dependencies and notification plugin from the project 2025-03-27 02:59:48 +08:00
Tunglies
006bcffe8c chore: update rust dependence and remove unused dependence, minimized feature requred 2025-03-27 01:04:43 +08:00
Tunglies
ff4101fa47 Revert "feat: front-end use RunningMode enum instead of string literals"
This reverts commit 72806357412d5ac5ce052be573713f26505b4f8f.
2025-03-26 22:17:29 +08:00
Tunglies
7280635741 feat: front-end use RunningMode enum instead of string literals 2025-03-26 22:10:42 +08:00
Tunglies
7ede91599c fix: standardize RunningMode handling between frontend and backend
This commit improves the type consistency between the Rust backend and TypeScript frontend by:

1. Modifying the Rust `get_running_mode()` command to return a String instead of RunningMode enum directly
2. Removing the RunningMode enum and IRunningMode interface from TypeScript types
3. Using string literals for mode comparison in frontend components
4. Standardizing on capitalized mode names (e.g., "Sidecar" instead of "sidecar")

These changes ensure proper serialization/deserialization between backend and frontend,
making the code more maintainable and reducing potential inconsistencies.
2025-03-26 22:04:16 +08:00
Tunglies
6e40dd9862 fix: tray icon and tray rate display expression logic bug 2025-03-26 19:07:09 +08:00
Tunglies
42db9ea0bb chore: enable pre-commit for Rust formatting, pre-push for Rust linter check 2025-03-26 18:59:31 +08:00
Tunglies
ca0cf4552c add: RunningMode Display implementation and TypeScript enum 2025-03-26 17:01:48 +08:00
Tunglies
d91653b218 feat: add config log type and improve window logging 2025-03-26 16:43:26 +08:00
Tunglies
1ace560531 feat: add front-end system service management functions 2025-03-26 16:22:13 +08:00
Tunglies
81968a579d feat: reorganize service commands and implement logging for service management 2025-03-26 15:02:08 +08:00
wonfen
5a0eb56f70 feat: add AppDataProvider for centralized app data management and optimized refresh logic 2025-03-26 13:26:32 +08:00
wonfen
804fad6083 fix: reduce CPU usage caused by repeated refresh of "Current Proxy" card 2025-03-26 11:59:20 +08:00
Tunglies
98d3a48710 refactor: replace println with logging in core validation and tray quit function 2025-03-26 04:31:38 +08:00
Tunglies
0ec4f46052 chore: better install service prompt translation 2025-03-26 03:44:30 +08:00
Tunglies
a891341e35 fix: alpha version 2025-03-26 03:03:50 +08:00
Tunglies
10426af3ad chore: update UPDATELOG 2025-03-26 01:57:44 +08:00
Tunglies
5be1d604ee chore: fix release-alpha-version 2025-03-26 01:54:45 +08:00
Tunglies
1baa840160 v2.2.3-alpha begin 2025-03-26 01:51:19 +08:00
Tunglies
14347f60d5 Refactor hotkey logging with structured logging macro 2025-03-26 01:18:28 +08:00
Tunglies
df5424d55e feat: add logging module and update running mode terminology 2025-03-25 23:05:09 +08:00
wonfen
12065330e1 Release 2.2.2 2025-03-25 14:11:40 +08:00
wonfen
e054ac67fb feat: improve mihomo core and service keep-alive and reinstallation logic 2025-03-25 06:41:00 +08:00
Tunglies
31a7750482 chore: rename updater alpha release json names 2025-03-25 01:54:44 +08:00
Tunglies
2e38307f65 chore: updater channel logic 2025-03-25 01:49:51 +08:00
Tunglies
47accdd2b1 Revert "chore: updater channel combined"
This reverts commit cb0146573f1d1c41c9b4d8f7dba26cdabad1d9ae.
2025-03-25 01:38:52 +08:00
Tunglies
cb0146573f chore: updater channel combined 2025-03-25 01:32:37 +08:00
wonfen
cf78bb3686 refactor: service reinstallation logic on detection failure 2025-03-25 00:51:38 +08:00
Tunglies
b5b5ae4e7b chore: remove update endpoints temporary 2025-03-25 00:28:28 +08:00
Tunglies
b99bc7fcd1 feat: add default update log resolution and improve error handling 2025-03-25 00:23:19 +08:00
Tunglies
f50fe9159d refactor: remove deprecated lightweight module and adjust macOS activation policy 2025-03-24 20:11:38 +08:00
Tunglies
09f6917638 fix: update lightweight mode implementation and fix MacOS dock icon visibility 2025-03-24 19:16:21 +08:00
Tunglies
e330d75a89 fix: correct spelling of "Lightweight" in tray menu 2025-03-24 18:54:39 +08:00
Skrepysh
4f0ce7458e Update russian translation (#3109) 2025-03-24 18:34:50 +08:00
wonfen
a2811c4803 chore: update required service version 2025-03-24 03:59:55 +08:00
Tunglies
1c233783a7 chore: alpha update cron 2025-03-23 23:04:40 +08:00
Tunglies
3e45cc4650 chore: update bug_report.yml 2025-03-23 22:56:42 +08:00
Tunglies
1a7c076e07 feat: add option to enable/disable tray icon display on MacOS 2025-03-23 16:37:27 +08:00
Tunglies
da4fddf150 chore: remove unused import 2025-03-23 14:28:28 +08:00
wonfen
970eb62aa6 perf: improve Clash mode switch responsiveness on home card 2025-03-23 04:54:18 +08:00
Tunglies
d669650758 feat: add lightweight mode entry and related hotkey support 2025-03-23 03:10:48 +08:00
wonfen
69347160e9 perf: simplify code logic and improve efficiency 2025-03-23 02:16:06 +08:00
wonfen
a345b54a77 chore: update required service version 2025-03-23 00:16:32 +08:00
Tunglies
8aabcd77a5 feat: enhance tray icon handling with caching and speed rate rendering 2025-03-22 23:00:45 +08:00
Tunglies
c30f54609d chore: update UPDATELOG 2025-03-22 17:23:10 +08:00
Tunglies
0830236a73 fix: update alpha workflow to correctly extract alpha logs from UPDATELOG.md 2025-03-22 17:21:45 +08:00
Tunglies
44f21444bb feat: update versioning in package.json, Cargo.toml, and tauri.conf.json to append '-alpha' 2025-03-22 17:19:11 +08:00
Tunglies
1d88d98ea1 feat: ensure Mihomo and Verge services are running before executing commands 2025-03-22 17:05:42 +08:00
wonfen
86f69fd574 feat: add singleton check after core startup in sidecar mode 2025-03-22 15:01:55 +08:00
wonfen
e21846a2ce chore: update preview img 2025-03-22 11:07:20 +08:00
wonfen
d5981ca94f fix: alpha workflow 2025-03-22 08:45:55 +08:00
wonfen
8c5eb3b550 chore: remove unused code of current proxy card 2025-03-22 06:25:10 +08:00
Tunglies
55dc416109 feat: add scripts for managing alpha versioning and update workflow 2025-03-22 05:11:32 +08:00
Tunglies
ec30b888d1 chore: update UPDATELOG 2025-03-22 04:53:27 +08:00
Tunglies
2a92755e65 fix: linux hanlding mihomo-core and verge-service communication 2025-03-22 04:51:38 +08:00
wonfen
6976ea3c09 perf: optimize proxy refresh mechanism for home page current proxy card 2025-03-22 04:34:19 +08:00
wonfen
b07ed2dbf5 fix: theme color on connection detail card
fix: home page clash info card proxy address
2025-03-22 04:26:28 +08:00
wonfen
2ab923da87 fix: port setting sync problem 2025-03-21 12:28:51 +08:00
wonfen
9799d4f747 chore: add missing i18n 2025-03-21 10:29:20 +08:00
wonfen
f739836891 refactor: auto-truncate long text on home profile card
fix: sync system proxy and TUN mode status indicators on home proxy mode card
2025-03-21 05:23:45 +08:00
Tunglies
a28887be8e fix: add libxslt1.1 dependency to Ubuntu installation in workflows 2025-03-20 23:36:51 +08:00
Tunglies
0f13691ae0 v2.2.1 2025-03-20 23:22:34 +08:00
Tunglies
ae72b83dbe v2.2.1-alpha.1 2025-03-20 23:20:07 +08:00
Tunglies
2e38404434 fix: homepage entry lightweight mode exiting Macos tray icon
fix: lightweight mode better handling and logging logic
2025-03-20 23:17:37 +08:00
Tunglies
11b8c8be45 chore: v2.2.1 2025-03-20 22:33:20 +08:00
Tunglies
a06597a3a6 fix: homepage proxy card handle direct mode 2025-03-20 21:51:12 +08:00
Tunglies
108840c4be feat: enhance alpha release workflow to fetch update logs and generate release notes 2025-03-20 19:42:04 +08:00
Tunglies
16c8672aeb fix: update workflow to delete old release assets instead of the release itself 2025-03-20 19:33:26 +08:00
Tunglies
167edcf8ef chore: update version to 2.2.1-alpha and add alpha update logs
refactor: simplify version change detection in alpha workflow

chore: alpha delete old release then release new one

fix: update alpha workflow to handle missing ALPHA_LOGS and improve release notes generation

fix: update job dependencies in alpha workflow to include delete_otld_release
2025-03-20 19:11:48 +08:00
Tunglies
d6dd89b674 fix: remove macOS application menu setup due to CMD+C/V/A issues 2025-03-20 18:14:18 +08:00
Tunglies
fac2ee6374 chore: bug report template remove os label 2025-03-20 16:39:29 +08:00
Tunglies
dd7876845a chore: UPDATELOG update 2025-03-20 15:49:00 +08:00
Tunglies
56e6139c2b fix: ensure main window title is set correctly on macOS 2025-03-20 15:43:59 +08:00
wonfen
04bdd48a2a release 2.2.0 2025-03-20 14:44:26 +08:00
Tunglies
5b47fe5b88 Revert "fix: update permission config with app icon and name"
This reverts commit 618ba52bca8c270caca6bc9269ce7455a87751af.
2025-03-20 14:17:41 +08:00
Tunglies
84a5cf6b89 feat(hotkey): macos support CMD+W to close window as default 2025-03-20 13:02:26 +08:00
wonfen
618ba52bca fix: update permission config with app icon and name 2025-03-20 12:43:22 +08:00
Tunglies
5c0cde517f fix: update system architecture retrieval method in PlatformSpecification 2025-03-20 06:20:19 +08:00
Tunglies
1b249564a3 fix: update service response check for correct status code and message 2025-03-20 06:18:14 +08:00
Tunglies
81b5501b0e feat: implement auto lightweight mode timer functionality
This commit implements the automatic lightweight mode feature with timer functionality:

- Rename configuration properties from auto_enter_lite_mode to enable_auto_light_weight_mode and auto_enter_lite_mode_delay to auto_light_weight_minutes for better clarity
- Add window event listeners to detect when window is closed or gets focus
- Implement timer system to automatically enter lightweight mode after configured time
- Remove exit_lightweight_mode function as it's no longer needed with the new implementation
- Update UI components to reflect the new property names
- Add logging for lightweight mode operations
- Initialize lightweight mode based on user configuration at startup

The feature now allows users to set a timer that will automatically enter lightweight mode
after closing the main window, which can be cancelled by focusing the window again.
2025-03-20 06:01:38 +08:00
Tunglies
91ccb3045c feat: implement lightweight mode functionality and update related settings 2025-03-20 03:23:14 +08:00
wonfen
e31f176c25 feat: lite mode settings 2025-03-20 01:44:43 +08:00
Tunglies
ad45485009 fix: hotkeys on windows crash 2025-03-19 18:41:26 +08:00
wonfen
25e5cf2ac2 chore: update deps 2025-03-19 11:07:57 +08:00
wonfen
bd58d935c6 feat: add up/down name to home traffic graph card 2025-03-19 10:38:21 +08:00
wonfen
da2705ff7d chore: change defaut start page to home 2025-03-19 05:48:20 +08:00
wonfen
61f019f194 fix: cannot detect service mode on home card 2025-03-19 05:25:38 +08:00
Tunglies
74e441df5b feat: add lite mode toggle to home page 2025-03-19 02:18:20 +08:00
Tunglies
772ecdd3b0 refactor: improve proxy retrieval and add window destruction method 2025-03-19 02:04:01 +08:00
Tunglies
baa535b609 feat: add macOS application menu integration 2025-03-18 18:40:53 +08:00
Tunglies
a2ff0a7e20 chore: remove tray icon configuration from webview JSON files 2025-03-18 15:31:23 +08:00
Tunglies
84732f9835 Revert "feat: add Rust installation step and configure alpha release details"
This reverts commit fe1227618acefba5b4788dac26ac278e2a1eb4e2.
2025-03-18 14:37:56 +08:00
wonfen
dd17bcb0d6 chore: add missing colorful svg for home and unlock menu 2025-03-18 10:37:00 +08:00
wonfen
cab8e613a6 refactor: revise data retrieval for homepage traffic stats 2025-03-18 09:05:44 +08:00
Tunglies
fe1227618a feat: add Rust installation step and configure alpha release details 2025-03-18 01:08:46 +08:00
wonfen
596c52de87 feat: persist graph data after page reload 2025-03-18 00:37:10 +08:00
wonfen
ba5d5e9f86 feat: limit max url lenght on home profile card 2025-03-18 00:18:26 +08:00
wonfen
530669d288 fix: auto launch 2025-03-17 13:51:52 +08:00
wonfen
70b0f9a03a fix: resolve Netflix detection error 2025-03-17 11:57:12 +08:00
wonfen
105de99d06 perf: optimize all home page components 2025-03-17 11:47:02 +08:00
wonfen
6239f81f36 feat: sync auto-start status 2025-03-17 09:48:44 +08:00
wonfen
697d200ffe chore: update i18n for unlock test 2025-03-17 07:45:49 +08:00
wonfen
16d5077f55 perf: optimize CPU and memory usage of homepage traffic chart 2025-03-16 14:34:29 +08:00
wonfen
e0e1a05448 fix: sync proxy node selection 2025-03-16 14:24:58 +08:00
wonfen
bcaafa67a3 feat: unlock test page 2025-03-16 12:15:35 +08:00
Tunglies
36142656a4 refactor(timer): improve timer management with robust error handling
This commit improves the timer management system with the following enhancements:

Replace Mutex with RwLock for better read concurrency in timer state
Add structured TimerTask type to store task metadata
Use atomic boolean for initialization flag instead of mutex
Implement comprehensive error handling with detailed logging
Add rollback capability when task operations fail
Reduce lock contention by generating task diffs outside locks
Add timing metrics for task execution
Improve code organization and documentation
2025-03-15 18:58:12 +08:00
Tunglies
d6a48deb5a refactor(config): use bitflags for tracking update operations
Replace multiple boolean variables with a bitflag approach for tracking
required update operations in the patch_verge function. This improves
code maintainability and potentially performance by:

1. Using a single integer variable with bit operations instead of multiple booleans
2. Defining clear flags as enum variants for better code readability
3. Simplifying flag checks with bitwise operations

The UpdateFlags enum provides a clear and type-safe way to represent
different types of updates needed when patching Verge configuration.
2025-03-15 18:42:57 +08:00
Tunglies
e98ce0c2ae Optimize hotkey management to reduce lock contention and improve performance
- Minimize mutex lock durations in update() by processing data outside critical sections
- Pre-allocate collections to avoid unnecessary reallocations
- Replace forEach-style loops with more efficient for loops
- Add defensive null checks when accessing app_handle
- Improve error handling with more robust Option unwrapping
- Enhance code readability with descriptive comments
2025-03-15 17:52:14 +08:00
Tunglies
8118fc754c structure: move out crate_mihomo_api 2025-03-15 14:47:02 +08:00
Tunglies
1ec7a0f23c refactor: update request method handling to use reqwest::Method enum
fix: duplicated checks tray menu
2025-03-15 13:23:17 +08:00
Tunglies
488e8ef1d5 fix: optimize speed rate update logic for small values 2025-03-14 22:40:56 +08:00
wonfen
1f99cee78b feat: home page 2025-03-14 13:31:34 +08:00
Tunglies
c25015ed54 Revert "feat: add trigger updater workflow to GitHub actions for release and alpha workflows"
This reverts commit aaefc5b479149a48740fbcc7c0e4a1370bf1d903.
2025-03-14 02:00:55 +08:00
Tunglies
aaefc5b479 feat: add trigger updater workflow to GitHub actions for release and alpha workflows 2025-03-14 01:31:19 +08:00
GKarbon
1c58816c73 chore: correct typo in src/locales/en.json (#2978)
This pull request corrects a typo in src/locales/en.json by replacing “than” with “then”, gives more accurate instructions.
2025-03-14 01:04:44 +08:00
Christine.
0fd99358aa fix: build failed due to vite unexpected output dir config. (#2981) 2025-03-14 00:57:17 +08:00
Tunglies
d4012bace9 chore: remove tray icon configuration from Linux app settings 2025-03-13 19:03:27 +08:00
Tunglies
af7660686d fix: increase request timeout to 60 seconds for better reliability 2025-03-13 15:16:54 +08:00
Tunglies
b57c6e408a chore: git hooks for linter and formatter 2025-03-13 12:51:20 +08:00
Mimi
124934b012 feat: additional macos tray event handling option for menu display (#2958) 2025-03-13 10:08:38 +08:00
MaqicXu
1bef6d085d refactor(timer): enhance timer initialization and task handling (#2956)
- Add initialization flag to prevent duplicate timer initialization
- Improve logging for better debugging and monitoring
- Refactor async task function with proper error handling
- Add more detailed log messages throughout the timer lifecycle
2025-03-12 22:36:25 +08:00
Christine.
c73927c5ba chore: Change default TUN stack from 'mixed' to 'gvisor' (#2967) 2025-03-12 21:45:14 +08:00
Tunglies
692deb6012 fix: windows unmatched tray 2025-03-12 13:55:11 +08:00
Tunglies
8ec499f631 fix: windows different tray icon display 2025-03-12 13:28:04 +08:00
Tunglies
2bcd653a56 feat: update systray creation to use TrayIconBuilder and pass app reference
fix: macos systray duplicated icon
2025-03-12 13:04:15 +08:00
Tunglies
0f10952979 feat: add alpha update endpoints to tauri configuration 2025-03-11 01:31:31 +08:00
Tunglies
58fa67100f feat: add support for alpha updates and enhance updater functionality
feat: improve release handling by adding creation logic for non-existent releases
2025-03-11 01:27:17 +08:00
Tunglies
8e294916c4 rm: label issues workflow 2025-03-10 01:01:12 +08:00
Tunglies
6877e0c95d chore: rename "DNS Settings" to "DNS Overwrite" in UI for consistency
This change updates the label for the DNS toggle setting from "DNS Settings" to "DNS Overwrite"
in the ClashVerge settings interface. The change provides better consistency with the translation
keys and more clearly communicates the function of the setting, which is to override system DNS.
The corresponding translation keys have been updated in both English and Chinese localization files.
2025-03-10 00:57:55 +08:00
Tunglies
37a333a023 feat: add scheduled workflow and commit change check to alpha build 2025-03-09 17:42:13 +08:00
Tunglies
48f1da963a refactor: update MihomoManager to handle traffic WebSocket URL and authorization 2025-03-09 14:44:15 +08:00
wonfen
e1905aced4 feat: enhance latency test logging and error handling 2025-03-09 04:22:34 +08:00
wonfen
f18202a3a4 refactor: improve webSocket connection handling and error recovery 2025-03-09 04:22:01 +08:00
wonfen
c1a9de4d66 feat: add icon file content check to prevent saving failed downloads 2025-03-09 02:34:57 +08:00
Tunglies
18f86874ee refactor: migrate clash client info retrieval to MihomoManager 2025-03-09 00:40:16 +08:00
Tunglies
e6686e0b82 rm: clash-api dead code 2025-03-09 00:29:14 +08:00
Tunglies
4bf166986d refactor: improve request handling and response processing in MihomoManager 2025-03-09 00:27:12 +08:00
Tunglies
0f60d84f6c refactor: streamline clash delay test and improve API interactions 2025-03-09 00:04:48 +08:00
Tunglies
15e54df67c refactor: streamline clash mode handling and improve API interactions 2025-03-08 22:41:14 +08:00
wonfen
4cb6ad7736 feat: optimize icon cache download and DNS view styling 2025-03-08 13:31:20 +08:00
wonfen
e27a32395a refactor: restructure DNS setting logic 2025-03-08 11:25:00 +08:00
wonfen
eddcf209c1 refactor: refine DNS handling to follow config and merge settings 2025-03-08 03:34:25 +08:00
wonfen
10a151d411 fix: regex for first character matching 2025-03-07 14:07:10 +08:00
wonfen
54d5586a60 perf: faster app exit 2025-03-07 13:44:07 +08:00
TianHua Liu
30ca547e50 fix: Notice @ts-ignore (#2896)
thx
2025-03-07 12:46:30 +08:00
0XE
a1944d1a90 feat: Add x-data-grid component localization (#2925)
thx
2025-03-07 12:45:56 +08:00
wonfen
c2b35fdaa5 test: remove entitlements.plist items 2025-03-07 06:49:24 +08:00
Tunglies
805b54d81e Update dependencies and refactor encryption logic
Updates multiple dependencies to their latest versions in Cargo.lock and Cargo.toml.
Refactors encryption logic to use updated getrandom API.
Improves tray speed rate display by using ab_glyph for font rendering.
2025-03-06 18:56:31 +08:00
wonfen
e3579dac65 feat: enable dns settings by default 2025-03-06 14:40:35 +08:00
wonfen
f80591242e feat: add dns settings 2025-03-06 14:30:43 +08:00
Christine.
69cb9769c1 add: missing i18n text. (#2917) 2025-03-05 22:28:28 +08:00
0XE
efd42d9da0 fix: ISSUES #2727 (#2913) 2025-03-05 14:20:07 +08:00
Christine.
21a6340095 workflow: remove renaming behavior. (#2909) 2025-03-05 11:41:15 +08:00
Tunglies
6c96724dce feat(mihomo): refactor MihomoManager for global access and improve proxy retrieval (#2906) 2025-03-05 10:58:54 +08:00
wonfen
ebb194d2a2 feat: add admin permission prompt for system service 2025-03-05 10:22:57 +08:00
Tunglies
1a51a92b70 test: crate_mihomo_api additional headers 2025-03-05 08:14:37 +08:00
Tunglies
5760f16272 fix: extern controler api secert with headers 2025-03-05 08:09:42 +08:00
Tunglies
4ed36f6223 refacture: Mihomo API integration (#2900)
* feat: add mihomo_api crate as a workspace member

Added a new mihomo_api crate to handle interactions with the Mihomo API. This modular approach provides a dedicated interface for fetching and managing proxy data from Mihomo servers. The implementation includes functionality to refresh and retrieve both proxies and provider proxies with proper error handling and timeouts. Added this crate as a workspace member and included it as a dependency in the main project.

* Refactors Mihomo API integration

Simplifies proxy fetching by removing the MihomoManager structure.

Updates the get_proxies and get_providers_proxies functions to directly use the mihomo_api module.

Removes unused Mihomo API related files and modules for cleaner codebase.

Enhances overall maintainability and performance.
2025-03-05 00:45:08 +08:00
0XE
7ea7ca1415 fix: issues #2838 (#2886) 2025-03-04 20:46:17 +08:00
Tunglies
1ee8786ab7 feat(sysinfo): Add diagnostic information enhancements (#2880)
Enhanced the PlatformSpecification struct with additional diagnostic information including:
- Added Verge version information to diagnostic output
- Added running mode information (Service/Sidecar/Not Running)
- Improved Debug implementation to display all diagnostic fields
- Implemented asynchronous detection of core running mode

This change helps users provide more complete system information when reporting issues.
2025-03-04 11:52:22 +08:00
wonfen
44ca513241 fix: sync system proxy status indicator with hotkey 2025-03-04 06:57:42 +08:00
wonfen
73310b466b fix: correct type declarations for getProxiesInner and getProxyProviders 2025-03-04 02:26:26 +08:00
Tunglies
1ba688727e feat(proxy): add proxy commands and integrate with API
Add new proxy.rs module with get_proxies and get_providers_proxies commands.
Update mod.rs and lib.rs to re-export and register proxy commands.
Update API.ts to use invoke for proxy commands.
Minor formatting improvements in module/mihomo.rs.
2025-03-04 01:01:24 +08:00
Tunglies
3b69465016 feat: add Mihomo API modules and manager (#2869)
• Introduce new API caller implementations for Mihomo in model and module layers.
• Add configuration and API integration files under /src-tauri/src/config/api and /src-tauri/src/model/api.
• Implement a singleton MihomoAPICaller with async API call support and integration tests.
• Create a new MihomoManager module to refresh and fetch proxies from the API.
• Update Cargo.lock and Cargo.toml with additional dependencies (async-trait, env_logger, mockito, tempfile, etc.) related to the Mihomo API support.
2025-03-03 19:31:44 +08:00
wonfen
3e53ea7209 Revert "refactor: improve proxy group UI and spacing (#2835)"
This reverts commit 520c33557eb37fcdc8ea89f3aeb5740e2deb0c7a.
2025-03-03 14:47:05 +08:00
wonfen
07bdc108ed feat: show service mode installation prompts in user mode 2025-03-03 14:42:31 +08:00
Tunglies
a18efb0e71 fix: speed format runns by docs 2025-03-03 11:36:21 +08:00
Tunglies
de1c825ad3 Revert "style: update box styling in settings page for improved layout (#2857)"
This reverts commit de2cff824e5502420dc7ff32f901af0a7a0fd191.
2025-03-03 08:00:10 +08:00
Tunglies
de2cff824e style: update box styling in settings page for improved layout (#2857) 2025-03-03 06:38:32 +08:00
Tunglies
aff504bddc feat: add export diagnostic info functionality (#2856) 2025-03-03 05:58:12 +08:00
wonfen
277390e597 feat: Add sidecar mode as an alternative to service mode
- Auto-fallback to sidecar mode if service mode fails
2025-03-03 03:34:34 +08:00
Tunglies
fdcefe458e fix: windows/linux runtime crash 2025-03-03 02:27:45 +08:00
Christine.
9bb2160abe workflow: remove 32-bit platform (#2855)
* chore: build portable by self

* chore: remove 32bit platform

* Update CONTRIBUTING.md

* update alpha version
2025-03-03 01:16:33 +08:00
Tunglies
97d683541d Revert "chore: alpha ci should remove old builds"
This reverts commit 9f7ffb80e1b74fdb91b92f5d56f0bad399948f76.
2025-03-03 00:16:17 +08:00
Tunglies
9f7ffb80e1 chore: alpha ci should remove old builds 2025-03-02 23:46:20 +08:00
Tunglies
c957ea7b24 feat: fish env export support 2025-03-02 23:20:10 +08:00
Tunglies
181fce16b1 version: 2.1.3 a 2025-03-02 20:46:53 +08:00
Tunglies
825f023505 version: 2.1.3 alpha 2025-03-02 20:43:54 +08:00
Tunglies
347ea53b32 version: 2.1.3 alpha (#2851)
* version: 2.1.3 alpha
2025-03-02 19:08:27 +08:00
Tunglies
d525e0dd70 chore: automatically label issues (#2844) 2025-03-02 18:50:31 +08:00
Tunglies
365e844b83 docs: add fast build and clean commands to contributing guide (#2842)
docs: add fast build and clean commands to contributing guide

- Added documentation for the `pnpm build:fast` command which uses Rust's fast-release profile to reduce compilation time
- Added explanation that fast builds disable optimization and LTO, resulting in larger but quicker builds
- Added documentation for the `pnpm clean` command to clean Rust build files
2025-03-02 15:13:17 +08:00
Tunglies
44bdeb555a chore: fast-dev and fast-build profile (#2841)
* refactor: improve proxy group UI and spacing

- Increased spacing in proxy-groups.tsx by adjusting the right position
  of the alphabet selector to provide better visual separation
- Enhanced spacing in proxy-render.tsx with larger margins and padding
  - Increased group item margins from 8px to 10px with 16px horizontal spacing
  - Expanded border radius from 8px to 10px for smoother appearance
  - Improved ProxyHead component spacing with pl: 3, pr: 3.5
  - Enhanced grid spacing in proxy collection items from 1 to 1.5
  - Adjusted padding for better visual hierarchy

These changes create a more polished, spacious layout with improved
readability and touch targets.

* - Update package.json with improved dev and build scripts:
  - Add fast-dev profile to development scripts
  - Configure build:fast with fast-release profile
  - Add clean command for cargo cleaning
2025-03-02 14:58:59 +08:00
Tunglies
520c33557e refactor: improve proxy group UI and spacing (#2835)
- Increased spacing in proxy-groups.tsx by adjusting the right position
  of the alphabet selector to provide better visual separation
- Enhanced spacing in proxy-render.tsx with larger margins and padding
  - Increased group item margins from 8px to 10px with 16px horizontal spacing
  - Expanded border radius from 8px to 10px for smoother appearance
  - Improved ProxyHead component spacing with pl: 3, pr: 3.5
  - Enhanced grid spacing in proxy collection items from 1 to 1.5
  - Adjusted padding for better visual hierarchy

These changes create a more polished, spacious layout with improved
readability and touch targets.
2025-03-02 05:36:18 +08:00
Tunglies
bfad5ac091 fix: macos frameless title 2025-03-02 04:30:58 +08:00
wonfen
3ecf4bc238 feat: refactor logging system into a global service 2025-03-02 04:20:38 +08:00
wonfen
028e4012aa fix: remove macos window title 2025-03-02 04:09:50 +08:00
wonfen
dc6d429b9c fix: add bottom padding to prevent jitter 2025-03-02 04:08:13 +08:00
Tunglies
625cf1a803 feat: add fast compilation options for development and release (#2831)
- Added fast compilation profiles in Cargo.toml
  - fast-dev profile with max codegen units and disabled optimizations
  - fast-release profile with debugging support and faster build time
- Added new npm scripts for quick development iterations
  - dev:fast command for standard development without extra features
  - build:fast command for quick release builds
- Updated default dev command to use verge-dev feature flag
- Both profiles retain debug symbols and disable stripping for better debugging
2025-03-02 00:40:07 +08:00
Tunglies
9ee011514a refactor: rename cmds module to cmd for better consistency (#2830)
- Renamed `cmds` module to `cmd` for better naming consistency
- Reorganized command modules into separate files under src/cmd/
- Updated all imports and references to use the new module name
- Fixed missing dependency in webdav.rs to reference core::backup
- Updated tray module to use new cmd namespace
- Improved uwp.rs module structure using platform-specific implementations
- Removed unnecessary imports from various command files
2025-03-01 22:52:43 +08:00
Tunglies
184fd4a1ba refactor: reorganize feat.rs into modular structure (#2827)
Split the monolithic feat.rs file into specialized modules:
- backup.rs: WebDAV backup and restore functions
- clash.rs: Core management and testing functions
- config.rs: Configuration handling
- profile.rs: Profile management
- proxy.rs: Proxy and TUN mode controls
- window.rs: Dashboard window management

This improves code organization, readability, and maintainability
by grouping related functionality into logical modules.
2025-03-01 20:44:35 +08:00
Christine.
23dcfd9401 fix: build failed with Windows (#2825) 2025-03-01 19:52:42 +08:00
Tunglies
1cdba297fb add: issues type template 2025-03-01 19:12:12 +08:00
Tunglies
1ed6743bbb Issues label options template (#2820)
* fix: macos dock display icon and text

* add: issues label option template
2025-03-01 02:46:16 -08:00
Tunglies
41c42bba32 fix: macos dock display icon and text (#2818) 2025-03-01 02:29:40 -08:00
wonfen
8fb66ea32c perf: improve scrolling performance and interaction in proxy group list 2025-03-01 08:31:31 +08:00
wonfen
51d4c1c4a5 fix: v2 action file rename 2025-03-01 08:04:31 +08:00
wonfen
86e069994e chore: updatelog 2025-03-01 03:45:03 +08:00
wonfen
1cb923b6d8 feat: add exit status check in core config validation 2025-03-01 03:39:13 +08:00
wonfen
b1d003b073 Release - 2.1.2 真·臻 2025-03-01 01:49:01 +08:00
Christine.
0fb4254481 add: i18n text for settings page. (#2815) 2025-03-01 01:36:36 +08:00
Tunglies
ae7f456011 feat: better setting UI layout (#2814) 2025-03-01 01:29:23 +08:00
wonfen
cd4bec6bfd chore: v2 updater 2025-03-01 01:29:23 +08:00
Tunglies
5906e0126d feat: better setting layout ui (#2809) 2025-03-01 01:29:23 +08:00
Tunglies
dce1395af1 feat: quiting when enable tun mode no more blocking system network (#2805) 2025-03-01 01:29:23 +08:00
wonfen
e7db13af37 test updater 2025-03-01 01:29:23 +08:00
wonfen
2eee8cd7d3 chore: updater v2 Capabilities 2025-02-28 10:55:53 +08:00
wonfen
836a2abae1 fix: updater 2025-02-28 09:33:50 +08:00
wonfen
a68a86d6db chore: updatelog 2025-02-28 07:34:56 +08:00
wonfen
ee9f0990fd feat: add ability to check service version and auto-reinstall 2025-02-28 06:45:30 +08:00
wonfen
59d0629e3f feat: enhance updater script with comprehensive platform support and logging 2025-02-27 17:19:04 +08:00
Christine.
17af292761 fix: connection details (#2778) 2025-02-27 14:53:17 +08:00
wonfen
a4dd4bcc8a feat: enhance merge config validation and error handling 2025-02-27 14:49:55 +08:00
wonfen
1a9b0a476b style: UI tweak 2025-02-27 13:04:46 +08:00
wonfen
bb015506e7 Release - 2.1.1 臻fix 2025-02-27 03:18:23 +08:00
Tunglies
76be5d8469 feat: macos display colorful icon with speed rate (#2771) 2025-02-27 01:51:52 +08:00
wonfen
1258e187f5 feat: improve file type detection for better script recognition 2025-02-26 15:59:19 +08:00
wonfen
4056a4c35f chore: downgrade Tauri updater plugin and add i18n for core switching 2025-02-26 15:04:47 +08:00
wonfen
b6677f0f72 feat: optimize hotkey behavior and window management logic 2025-02-26 11:03:50 +08:00
wonfen
e8c1e6f241 fix: CI 2025-02-26 09:52:15 +08:00
wonfen
618595ac4c Release 2.1.0 - 臻 2025-02-26 08:36:02 +08:00
wonfen
d54ba48c11 feat: enhance script validation and error handling 2025-02-26 05:21:14 +08:00
wonfen
a489012a0c feat: Add window state monitoring and auto-save in real-time 2025-02-26 00:36:02 +08:00
wonfen
a5acdc04e3 perf: Improve config validation error messages and handling 2025-02-25 13:47:29 +08:00
wonfen
709a20ed7b chore: revise translation 2025-02-24 23:26:04 +08:00
wonfen
c88f2099c1 chore: Change default TUN stack from 'mixed' to 'gvisor' 2025-02-24 23:10:09 +08:00
Christine.
f72a2a943b add: i18n text for config check (#2750) 2025-02-24 22:23:42 +08:00
wonfen
c51199719d fix: remove node related group info when deleting a node 2025-02-24 11:40:28 +08:00
wonfen
bf374f2e85 fix: menu switching issue 2025-02-24 11:37:23 +08:00
wonfen
34f450fcdb feat: Improve core change configuration validation and error handling 2025-02-24 07:34:03 +08:00
wonfen
23f75598e5 feat: Enhance configuration validation and error handling during app startup 2025-02-24 06:21:32 +08:00
zhaoyuan
afc238d60e feat: 通过添加CLASH_VERGE_REV_IP环境变量的方式,修改复制环境变量按钮的IP (#2734)
Co-authored-by: zymouse <zymouse@pixmoving.net>
2025-02-24 03:42:40 +08:00
wonfen
1291c38d58 feat: Enhance configuration validation and error handling
- Improve config validation process with detailed logging and error tracking
- Add more robust error handling in profile updates and config patches
- Implement comprehensive config validation using clash core subprocess
2025-02-23 10:53:09 +08:00
Christine.
16caccde51 feat: auto rename alpha version (#2740) 2025-02-23 02:30:15 +08:00
wonfen
33f199fcd2 chore: Disable automatic alpha tag update in workflow 2025-02-20 14:25:58 +08:00
wonfen
2b534e0d51 refactor: Optimize proxy rendering and layout calculation 2025-02-20 14:21:55 +08:00
wonfen
23d1d210c7 refactor: Simplify tray icon event handling across platforms 2025-02-20 07:03:28 +08:00
wonfen
39a1d6202a chore: Update Node.js version and remove updater file generation 2025-02-20 02:55:47 +08:00
wonfen
f00a5af6c9 feat: Add system proxy status indicators 2025-02-19 13:56:22 +08:00
wonfen
48d68f5766 refactor: Simplify log data management and improve search functionality 2025-02-19 13:06:15 +08:00
wonfen
f948da748e fix: connections page traffic calculation 2025-02-19 02:24:21 +08:00
Tunglies
8b25d45109 rm dead code (#2718)
* rm: verge service takes full control of mihomo process. no more required.

* rm: dead code
2025-02-19 01:13:52 +08:00
wonfen
c4ddc35746 style: tweak 2025-02-18 11:40:41 +08:00
wonfen
0122e2bdcf feat: Add persistent column width settings for connection table & process filtering 2025-02-18 11:04:22 +08:00
wonfen
94b75f463b refactor: Remove unused routing select component from settings page 2025-02-18 09:04:23 +08:00
wonfen
3c2e04290c feat: Improve letter item hover interaction with debounce mechanism 2025-02-18 09:00:03 +08:00
wonfen
f0331ec2d9 feat: Add pause/resume functionality for connection tracking 2025-02-18 08:14:09 +08:00
wonfen
3b4013a1b0 fix: resolve deprecated warnings in console 2025-02-18 07:10:28 +08:00
wonfen
31ddccd3e1 perf: Improve proxy list interaction and UI responsiveness 2025-02-18 05:54:16 +08:00
wonfen
d29fe4cb6c feat: Enhance alphabet selector with dynamic tooltip and scrolling 2025-02-18 01:55:44 +08:00
wonfen
6763537f22 refactor: Improve proxy data update mechanism with optimistic UI and error handling 2025-02-18 00:37:55 +08:00
wonfen
8ab4bd6293 fix: Refine TypeScript types for proxy groups component 2025-02-17 16:27:06 +08:00
wonfen
31bc644763 feat: Enhance proxy groups with Initials navigation and performance optimizations 2025-02-17 16:07:46 +08:00
wonfen
fcd672abeb feat: Optimize tray speed rate rendering and update logic 2025-02-17 15:08:19 +08:00
wonfen
5f550da0bb feat: Improve Virtuoso list rendering for proxy groups 2025-02-17 14:30:21 +08:00
wonfen
e865a86eef feat: Add persistent scroll position for proxy groups 2025-02-17 14:24:33 +08:00
wonfen
80ee2e4289 fix: alpha build win webview pack rename issue 2025-02-17 12:06:57 +08:00
wonfen
4d327594d3 chore: Update updater artifacts configuration 2025-02-17 11:22:14 +08:00
Akioe Yu
3363c37457 fix: check script error (#2453)
(cherry picked from commit 97bde64fd6516019a190d52f05af54ade623d57c)
2025-02-17 03:10:19 +08:00
wonfen
cee9be81bf chore: Add macOS-specific test for format_bytes_speed function 2025-02-17 03:10:03 +08:00
wonfen
932d36462f Revert "perf: Improve kernel management logic & add more dev mode logs"
This reverts commit ff2cf30238bcb5211f9b78e5fe65531e359d8d89.
2025-02-15 05:51:46 +08:00
Christine.
75c930f7ef add: i18n text for lightweight mode (#2692) 2025-02-13 14:27:12 +08:00
Tunglies
bdb178d893 fix: build front cannot find IvergeConfig.enable_lite_mode and macos port switching causes crash (#2691)
* fix: macos switch protocol port or enable protocol port causes crash

* fix: build time front cannot find IVergeConfig attribute enable_lite_mode
2025-02-12 18:14:13 -08:00
wonfen
3b0635e8a1 feat: add lightweight mode 2025-02-13 02:18:17 +08:00
wonfen
f5760784bf fix(macos): add missing required dependencies 2025-02-12 15:06:42 +08:00
wonfen
67f3554095 fix: remove unused imports to resolve compile warnings 2025-02-12 14:35:49 +08:00
wonfen
3bb3872e38 refactor: improve hotkey management, logging, and error handling; fix tray freeze and hotkey failure on silent startup 2025-02-12 14:23:42 +08:00
wonfen
c98330ea1f fix(tray): resolve blank icon issue on Windows and optimize creation logic 2025-02-12 13:56:33 +08:00
Christine.
d895b68f04 fix: use remoteDestination replace DestinationIP in connection page, #2668 (#2679)
* fix: use remoteDestination replace DestinationIP  in connection page, #2668

* add: missing i18n text

* fix: display the target address details in connection page
2025-02-11 14:52:27 +08:00
wonfen
e230981ac4 refactor: Improve tray icon and event handling across platforms & unify click behavior 2025-02-11 14:48:31 +08:00
wonfen
20763a741a fix: app crash without a default global_hotkey vaule 2025-02-11 01:50:48 +08:00
wonfen
4babcd9442 fix: deprecated CLI warnings 2025-02-11 01:05:51 +08:00
wonfen
d75f36066a feat: Add independent alpha update channel 2025-02-11 00:53:44 +08:00
wonfen
26ca4670ad fix: try to fix linux CI updater problem 2025-02-11 00:53:44 +08:00
Christine.
c4d6c167a2 add: i18n text (#2675) 2025-02-10 00:29:43 -08:00
wonfen
e5af9541da perf: Optimize kernel shutdown speed & logic 2025-02-10 13:18:00 +08:00
wonfen
89d9f47191 fix: crash caused by global_hotkey 2025-02-10 12:39:07 +08:00
wonfen
ff2cf30238 perf: Improve kernel management logic & add more dev mode logs 2025-02-09 14:19:15 +08:00
Tunglies
ebe0899eb1 perf: imporve clash mode switching performance on the main window (#2667) 2025-02-09 07:45:46 +08:00
Tunglies
a3d0a38b1e feat: option to enable global hotkey (#2665) 2025-02-09 07:45:22 +08:00
Tunglies
63bd0c87b2 fix: duplicate checked tray menu when profile name are same (#2660) 2025-02-08 09:36:04 +08:00
wonfen
db593fb188 feat: Optimize UI layout and page hierarchy 2025-02-07 09:13:30 +08:00
wonfen
67ae10b593 perf: optimize node latency refresh rate for faster updates 2025-02-06 07:58:42 +08:00
wonfen
c8d91c9e14 chore: update deps 2025-02-05 14:19:43 +08:00
Tunglies
6c54f5e9b4 Feature: Switch Proxy Profile from Tray Menu (#2644) 2025-02-05 08:52:47 +08:00
wonfen
8749648d97 fix: restore hotkey functionality after silent startup 2025-02-02 11:37:10 +08:00
wonfen
0b75b5ef26 feat: allow users to customize enhanced-mode and fakeip-range in tun mode by wonfen 2025-02-02 05:12:16 +08:00
wonfen
fbcadd0493 style: refine tray speed display 2025-02-02 03:43:15 +08:00
wonfen
75bb7a4dd7 fix: windows rounded corners 2025-01-19 09:47:43 +08:00
wonfen
4604fe4841 add donation 2025-01-18 16:08:24 +08:00
wonfen
e8badb0c0f release 2.0.3 2025-01-16 03:30:07 +08:00
白铭骢 (Mingcong Bai)
8906a8f3c6 fix(locales): clean up typesetting (#2539)
Following localisation guidelines from 《大陆简中自由软件本地化工作指南》
1.5.4 版 (Simplified Chinese - Mainland China FOSS Localization
Guidelines, version 1.5.4).

Link: https://repo.aosc.io/aosc-l10n/zh_CN_l10n_1.5.4.pdf
2025-01-14 13:00:30 +08:00
huzibaca
4e32990a5d chore: update 2025-01-14 12:56:08 +08:00
huzibaca
9f2583d1f2 revert: update deps 2025-01-14 12:41:52 +08:00
huzibaca
a9b3d8885d chore: avoid duplicate updates when tray rate is off 2025-01-14 11:48:43 +08:00
huzibaca
29ec4dc546 feat: maoos tray speed can be closed 2025-01-13 20:48:25 +08:00
huzibaca
d0d5204cbc fix: fix: try to fix the language pack issue(2) 2025-01-13 17:09:38 +08:00
huzibaca
3d84acd7ac fix: windows tray icon color not updated 2025-01-13 13:49:56 +08:00
huzibaca
5057221f59 chore: update husky/pre-commit 2025-01-13 13:03:46 +08:00
huzibaca
3ddfbc5d2f fix: tray tooltip not updating 2025-01-13 13:01:12 +08:00
huzibaca
362270e3ea chore: update 2025-01-13 12:41:07 +08:00
huzibaca
db91177e90 chore: update deps 2025-01-12 23:10:15 +08:00
huzibaca
d2f51ce509 fix: try to fix the language pack issue 2025-01-12 22:22:06 +08:00
huzibaca
146a66fb09 Merge branch 'languagefixes' into dev
# Conflicts:
#	src-tauri/Cargo.lock
#	src-tauri/Cargo.toml
#	src-tauri/src/core/tray/mod.rs
2025-01-11 15:07:30 +08:00
huzibaca
03305f03c1 fix: fixes #2502 2025-01-11 12:55:20 +08:00
lucidhz
82e76bc58e fix: put_configs response add detail error message (#2492)
感谢pr
2025-01-05 01:27:12 -08:00
huzibaca
e8ff6c785a fix: modify the external control access key, the tray rate display is abnormal 2025-01-02 15:27:28 +08:00
huzibaca
a8fafb469a chore: update deps 2025-01-02 15:02:07 +08:00
huzibaca
b9a220cb63 chore: update 2025-01-01 08:25:31 +08:00
huzibaca
0b44d40b39 feat: the tray displays the shortcut keys that have been set 2025-01-01 08:14:15 +08:00
huzibaca
6b349eda45 chore: update 2024-12-31 11:11:29 +08:00
huzibaca
ad335ba005 fix: unused code 2024-12-31 08:08:52 +08:00
huzibaca
04d766884a fix: syntax issues 2024-12-31 04:50:12 +08:00
huzibaca
44d1ec433d Merge branch 'feat-macos-spped-rate-icon' into dev 2024-12-31 04:43:54 +08:00
huzibaca
97864e8df3 feat: macos system tray addition rate display 2024-12-31 04:42:55 +08:00
blagodaren
3916293e8f fix: small refactor 2024-12-28 13:57:30 +03:00
blagodaren
a527177b67 fix: add more lang and lang fix for tray 2024-12-28 10:05:30 +03:00
blagodaren
9c027b10b2 fix: add system language sup 2024-12-28 08:12:46 +03:00
huzibaca
5da7086475 fix: fixes #2460 2024-12-28 06:05:09 +08:00
huzibaca
0006012ae7 chore:add macos entitlements.list 2024-12-27 04:38:34 +08:00
huzibaca
a3f46ec037 chore: update 2024-12-27 02:42:35 +08:00
huzibaca
bfea52f9dd chore: secret is empty and no parameters are passed 2024-12-25 02:15:06 +08:00
huzibaca
53334f05b8 chore: update 2024-12-24 06:03:23 +08:00
huzibaca
ba195c41b6 refactor: when updating the tray, the logic is split to improve performance. 2024-12-24 04:52:14 +08:00
huzibaca
cca2f1ce61 chore: after saving the configuration file, restart the core 2024-12-24 02:22:46 +08:00
huzibaca
a51191c661 chore: update default_bypass
1. add 172.29.0.0/16
2024-12-23 06:06:46 +08:00
huzibaca
4cdb5f93b9 fix: turn off window shadow fixer, fixes #2425 2024-12-23 04:36:41 +08:00
huzibaca
fae658c9c2 chore: update 2024-12-15 01:14:16 +08:00
huzibaca
886a469634 fix: fixer #2346 2024-12-14 17:30:58 +08:00
huzibaca
c3c1394e86 chore: update deps 2024-12-14 16:21:16 +08:00
Langning Chen
6a00255fff Fix l10n issues (#2315)
* Complete the missing translation

* Match the Chinese language file order with others
2024-12-11 01:02:21 +08:00
huzibaca
4f0aae0879 chorea: update deps 2024-12-10 22:36:13 +08:00
huzibaca
4f4fe4c41c feat: improve system bypass settings 2024-12-10 14:37:11 +08:00
huzibaca
2a4a3c8250 chore: enable default proxy bypass and hide custom bypass settings 2024-12-10 13:59:13 +08:00
huzibaca
7864acbadb chore: update deps(tauri-plugin-autostart) 2024-12-08 22:10:15 +08:00
huzibaca
ba18e64be0 chore: deeplink uses the latest API 2024-12-08 15:54:46 +08:00
huzibaca
ba8c1e5eb2 chore: update deps 2024-12-08 09:58:59 +08:00
huzibaca
2737fb2d87 fix: when the window is hidden, close the websocket connection, reduce the risk of memory leaks 2024-12-07 16:47:41 +08:00
wonfen
899285735f chore: change fakeip range 2024-12-06 15:34:41 +08:00
huzibaca
f561d12d35 chore:set the sysproxy_rs version to a fixed version 2024-12-06 15:03:04 +08:00
huzibaca
be258b13e0 fix: extension script dns is overwritten fixer #2235 2024-12-06 12:35:09 +08:00
Myles Mo
dbce6b5f1a feat: add nullshell env variable (#2285) 2024-12-04 19:01:13 +08:00
huzibaca
30f0c99a58 fix: shift hotkey conversion fixer #2278 2024-12-04 13:19:56 +08:00
huzibaca
49880c05d9 chore: update version 2024-12-04 00:30:46 +08:00
huzibaca
dc2fc84f58 fix: set fontLigatures to false fixer #2267 2024-12-04 00:28:33 +08:00
lollapalooza
78c2a1694f feat: add bypass check feature (#2272) 2024-12-03 16:18:07 +08:00
Christine.
baf34dd0d3 fix: #2126 (#2233) 2024-12-03 16:03:21 +08:00
wonfen
48f9dede7b release 2.0.2 2024-12-01 11:00:09 +08:00
huzibaca
a1f2a621ef fix: resource file initialization failed 2024-12-01 09:38:29 +08:00
huzibaca
a1e8ddb461 fix: resolve service permission failed(2) 2024-11-30 10:45:53 +08:00
huzibaca
d33d90a36e chore: update 2024-11-30 10:38:15 +08:00
huzibaca
c3114b876f fix: resolve service permission failed 2024-11-30 10:29:16 +08:00
huzibaca
50285aebde feat: user uploaded icons can use templates, provided they are monochrome icons fixer #2213 2024-11-30 09:49:43 +08:00
huzibaca
0eb5ee6ea8 chore: update 2024-11-30 07:04:09 +08:00
huzibaca
16c9c95e19 refactor: update_core_config, simplify logic and delete invalid notifications 2024-11-30 05:43:59 +08:00
yyhhyy
3bc4da3e85 🐛 fix: Dynamically set IPv6 DNS configuration based on existing config (#2198)
thanks
2024-11-30 00:55:15 +08:00
wonfen
a028a2e1cc chore: change mac tun dns 2024-11-29 11:10:41 +08:00
huzibaca
9675a35dff chore: update 2024-11-29 03:46:37 +08:00
huzibaca
c1546fdd64 chore: remove debug code 2024-11-28 14:44:13 +08:00
Yu-Haifeng
a109efc1d6 fix: macOS tray icon not changed when use system proxy or tun mode (#2176) 2024-11-28 14:42:05 +08:00
huzibaca
0782b25830 fix: the deadlock caused by incorrect call of window_state due to document error 2024-11-28 12:54:55 +08:00
huzibaca
0041ff13b8 chore: clean startup registry keys for older versions(2) 2024-11-28 06:40:44 +08:00
huzibaca
4693a25aa0 chore: clean startup registry keys for older versions 2024-11-28 05:34:56 +08:00
wonfen
609df5b4a6 chore: set tun default stack 2024-11-28 04:22:13 +08:00
huzibaca
6df8140cb1 chore: update 2024-11-27 14:27:16 +08:00
wonfen
65b4cb3191 release 2.0.2 2024-11-27 12:41:51 +08:00
huzibaca
e1de481349 chore: update 2024-11-27 11:34:52 +08:00
huzibaca
6e1cc80b91 fix: when tun is closed, the full profile configuration is not restored 2024-11-27 10:35:42 +08:00
huzibaca
b658ce7e75 refactor: backup implementation
1. timeouts can be set for different operations
2. performance optimization
2024-11-27 07:34:34 +08:00
huzibaca
f91f374dfa chore: update 2024-11-27 07:09:03 +08:00
huzibaca
56f6de5410 fix: linux build failed 2024-11-27 05:54:48 +08:00
huzibaca
94d22ecfc3 fix: fixer #2158 2024-11-27 05:47:08 +08:00
huzibaca
e25d71c6c8 chore: update version 2024-11-27 05:09:46 +08:00
huzibaca
bb1b156d2f fix: build failed. #2156 2024-11-27 05:07:02 +08:00
huzibaca
1b80ddf1e9 chore: timeout adjusted to 15 seconds 2024-11-26 15:09:58 +08:00
huzibaca
66d2fe9074 fix: add scroll bar and scroll to top button to the test page . fixer #2118 2024-11-26 14:44:43 +08:00
wonfen
6e36910734 style: adjust 2024-11-26 12:11:20 +08:00
huzibaca
a553a33c46 chore: remove window title 2024-11-26 10:11:55 +08:00
huzibaca
2a6f8b401b fix: kernel-caused silent mode failure to start windows 2024-11-26 09:00:01 +08:00
huzibaca
fa30567140 chore: update 2024-11-26 05:46:34 +08:00
huzibaca
243f685b83 refactor: Implement using third-party libraries 2024-11-26 04:51:19 +08:00
huzibaca
6cf2373b34 chore: update 2024-11-26 03:30:15 +08:00
huzibaca
e842ea745a fix: file drag and drop import cannot be used 2024-11-26 03:07:25 +08:00
wonfen
9696c7cec0 style: Increased light color contrast to prevent blurring on some displays 2024-11-26 01:05:30 +08:00
huzibaca
c4986eec50 chore: replace mui grid with mui grid2 2024-11-25 12:51:13 +08:00
wonfen
61079e769e update change log 2024-11-25 06:46:29 +08:00
huzibaca
00d2c915d1 fix: Try to fix vcruntime duplicate installation 2024-11-25 06:33:47 +08:00
huzibaca
6ad975c420 fix: update failed(updater.install called before updater.download) 2024-11-25 05:27:26 +08:00
huzibaca
1cd1a2d907 fix: when restoring webdav, the current username, password and url are not preserved 2024-11-25 03:41:35 +08:00
huzibaca
39a3c3d3a7 fix: password failed due to character escaping 2024-11-25 02:48:56 +08:00
huzibaca
dfefcf03ad fix: remove comments from svg icons to prevent front-end crashes. fixer #2093 2024-11-25 01:58:02 +08:00
wonfen
825b00e618 fix: fakeip dns 2024-11-25 01:50:30 +08:00
huzibaca
ae562e1e92 chore: replace mui grid with mui grid2 2024-11-25 01:34:18 +08:00
huzibaca
21c7888595 fix: tun allocates the wrong private network segment, causing conflicts. 2024-11-25 01:06:51 +08:00
wonfen
3b87a4f9d0 release 2.0.1 2024-11-24 09:00:20 +08:00
huzibaca
8564a58eab fix: If an older version of the executable exists, delete it(2) 2024-11-24 08:25:46 +08:00
huzibaca
c5d009c2cd feat: try to use vscode first, if not found then use system default app 2024-11-24 08:20:00 +08:00
huzibaca
c2e165d825 fix: If an older version of the executable exists, delete it 2024-11-24 01:24:51 +08:00
huzibaca
922020c57a Merge branch 'fix-migrate-tauri2-errors'
* fix-migrate-tauri2-errors: (288 commits)

# Conflicts:
#	.github/ISSUE_TEMPLATE/bug_report.yml
2024-11-24 00:14:46 +08:00
huzibaca
8cdc33beab fix: windows cannot open yaml file 2024-11-23 19:46:53 +08:00
wonfen
23eafdfe00 fix: release workflow 2024-11-23 12:36:16 +08:00
wonfen
e72e8ea631 release 2.0.0 2024-11-23 11:34:17 +08:00
huzibaca
a610a43db0 chore: set the request timeout to 3 seconds 2024-11-23 06:50:28 +08:00
huzibaca
4a90ffe619 chore: remove notes 2024-11-23 06:47:43 +08:00
huzibaca
18e8357b6a chore: update 2024-11-23 06:42:05 +08:00
huzibaca
df39347b19 fix: restore the window state first, then set the window size 2024-11-23 06:39:25 +08:00
huzibaca
a36261d705 chore: update 2024-11-23 06:22:46 +08:00
huzibaca
f133d22124 chore: add debug codes & logs(2) 2024-11-23 06:20:13 +08:00
huzibaca
6ba276b43f chore: add debug codes & logs 2024-11-23 05:43:13 +08:00
huzibaca
44db98f260 chore: set the request timeout to 5 seconds 2024-11-22 14:15:31 +08:00
huzibaca
8873526619 feat: added scroll top button for agent and rule pages 2024-11-22 09:22:44 +08:00
huzibaca
37c2599754 chore: remove debug code 2024-11-22 09:08:25 +08:00
huzibaca
a079b470b8 fix: macOS DNS restore failed 2024-11-22 03:09:39 +08:00
huzibaca
0f9ed02bf0 chore: hide DNS cache file 2024-11-22 02:42:55 +08:00
huzibaca
9aeb68205c fix: husky - install command is DEPRECATED 2024-11-21 12:06:52 +08:00
huzibaca
82b4cf259c chore: remove unused code 2024-11-21 11:44:19 +08:00
huzibaca
566fd3e88b chore: remove unused code 2024-11-21 11:24:19 +08:00
huzibaca
fbecf4f47b fix: password should not be trimmed 2024-11-21 11:14:40 +08:00
huzibaca
52899d4def chore: remove unused code 2024-11-21 11:13:11 +08:00
huzibaca
a89a828b35 fix: serde::json passing IVerge to the front end without deserialization 2024-11-21 06:01:56 +08:00
huzibaca
4d0dbdaced chore: update 2024-11-20 07:37:30 +08:00
huzibaca
8003f9902e chore: remove notes 2024-11-20 07:37:03 +08:00
huzibaca
15bd7324fe feat: encryption configuration properties 2024-11-20 07:27:42 +08:00
huzibaca
bb44fc51bd fix: auto launch does not worki 2024-11-20 03:52:19 +08:00
huzibaca
67a32e60c7 chore: update 2024-11-20 01:15:03 +08:00
huzibaca
960725777c fix: exit_app does not work 2024-11-20 01:04:55 +08:00
huzibaca
98c6e0311b fix: windows cannot save window state(2) 2024-11-20 00:27:53 +08:00
huzibaca
95b7641f9c fix: windows cannot save window state 2024-11-19 23:32:32 +08:00
huzibaca
18b0c3f7aa chore: update deps 2024-11-19 13:35:57 +08:00
huzibaca
49d3644d6a chore: format 2024-11-19 13:28:17 +08:00
wonfen
ee9d12d933 feat: improve set dns logic 2024-11-19 11:38:23 +08:00
huzibaca
5d37015f4d chore: update web:build comman, use tsc --noEmit 2024-11-19 04:42:37 +08:00
huzibaca
dca25637c9 feat: add logger highlighting, support regular and case matching 2024-11-19 04:10:10 +08:00
huzibaca
a7020fd46c feat: add logger highlighting 2024-11-19 03:29:44 +08:00
huzibaca
1ef2b1aaf1 fix: log pause button not working 2024-11-19 02:56:58 +08:00
huzibaca
a7a661e60f feat: logger support all level filters 2024-11-19 01:40:45 +08:00
huzibaca
2a9e2d47f5 chore: update deps 2024-11-18 23:51:43 +08:00
huzibaca
e33b3043df chore: remove unused code 2024-11-18 23:48:02 +08:00
Christine.
3f41618aa1 fix: field error, #2044 (#2045) 2024-11-18 07:34:57 -08:00
huzibaca
a507d7567f refactor: remove useSWRSubscription and use useEffect 2024-11-18 16:41:17 +08:00
huzibaca
62a6f58705 chore: remove unused code 2024-11-18 08:16:31 +08:00
huzibaca
77dd074fc3 feat: Log level status is saved to local storage 2024-11-18 08:14:21 +08:00
huzibaca
e8c0051be3 refactor: use zustand store, rewrite log clearing logic 2024-11-18 06:48:23 +08:00
huzibaca
b3923eafc7 chore: typo warn -> warning 2024-11-18 06:01:51 +08:00
huzibaca
9ebd96611a refactor: logger fetch logic 2024-11-18 05:58:06 +08:00
huzibaca
824325a2eb fix: the CJS build of Vite's Node API is deprecated, part 2 2024-11-18 01:21:00 +08:00
huzibaca
e8b3bd5bdc fix: the CJS build of Vite's Node API is deprecated 2024-11-18 01:07:16 +08:00
Christine.
a59fda512c chore: Replace test URL to support iPv4&iPv6 (#2033) 2024-11-18 00:20:21 +08:00
huzibaca
ae181f6835 fix: webdav list interface compatibility issue 2024-11-17 23:57:28 +08:00
huzibaca
67bb242778 fix: webdav refreshes data and clears the original data when an error occurs. 2024-11-17 23:50:34 +08:00
huzibaca
2028c189aa chore: update 2024-11-17 01:01:36 +08:00
huzibaca
ba0dc4fb81 chore: update 2024-11-17 00:46:35 +08:00
huzibaca
c40db417d2 fix: fixes #1968 2024-11-16 23:09:10 +08:00
wonfen
0eb776cdd3 release rc.7 2024-11-16 10:15:50 +08:00
huzibaca
c79a7a7f6f chore: remove debug code 2024-11-16 07:35:34 +08:00
huzibaca
1e3c995e6a fix: fixes #1940 2024-11-16 06:08:49 +08:00
huzibaca
3f79e42628 fix: When the shortcut key closes the window, the window is minimized 2024-11-16 04:46:20 +08:00
huzibaca
c16ae89a3d fix: windows arm64 vsruntime is not installed 2024-11-16 03:12:11 +08:00
huzibaca
7132eaeb11 Merge branch 'fix-tray-menu-flash-on-windows' into fix-migrate-tauri2-errors
* fix-tray-menu-flash-on-windows:
  chore: update
2024-11-16 02:54:24 +08:00
Chenx Dust
aef96f0d27 feat: support mptcp and smux display (#1995)
Corresponding pull request in mihomo: https://github.com/MetaCubeX/mihomo/pull/1646
2024-11-16 01:35:22 +08:00
huzibaca
b20a56f1de chore: update 2024-11-15 17:19:14 +08:00
huzibaca
3073b4e48e chore: unified code format 2024-11-14 03:21:18 +08:00
huzibaca
7b53752ccd fix: call parameter error 2024-11-14 03:16:32 +08:00
wonfen
03eedf6175 chore: update info & i18n 2024-11-14 03:01:37 +08:00
huzibaca
2330a4bc93 chore: update dep(tauri-bubild) 2024-11-13 21:49:53 +08:00
huzibaca
36afae50b1 chore: update deps 2024-11-13 21:39:55 +08:00
huzibaca
272ee7577c feat: add refresh button 2024-11-13 00:53:52 +08:00
huzibaca
7f34073da6 fix: InputProps is deprecated 2024-11-13 00:30:30 +08:00
huzibaca
f46ee2a0a3 fix: mui grid has been deprecated 2024-11-13 00:21:22 +08:00
huzibaca
4a79f0c75d fix: application restart, window status not saved 2024-11-12 22:49:08 +08:00
huzibaca
27a78af269 fix: syntax issues caused by upgrading mui5 2024-11-12 20:05:28 +08:00
huzibaca
586af67829 chore: update 2024-11-12 19:44:57 +08:00
huzibaca
575d8c4240 fix: import error caused by dependency upgrade 2024-11-12 19:35:56 +08:00
huzibaca
d32734214b fix: undefined error 2024-11-12 19:34:53 +08:00
huzibaca
22ce5aab25 chore: update deps 2024-11-12 19:06:04 +08:00
wonfen
9f90a1c58e release rc.6 2024-11-12 03:49:21 +08:00
huzibaca
b5e0374946 feat: add webdav backup 2024-11-12 02:55:02 +08:00
huzibaca
44cb1c7f3e chore: update 2024-11-09 12:13:50 +08:00
huzibaca
80aba859e7 Merge branch 'feat-webdav-backup' of ssh://github.com/clash-verge-rev/clash-verge-rev into feat-webdav-backup
# Conflicts:
#	src/locales/fa.json
#	src/locales/ru.json
#	src/locales/zh.json
2024-11-09 11:54:33 +08:00
wonfen
08360edd26 update: Readme 2024-11-09 09:00:53 +08:00
wonfen
6e69b3f032 fix: translation 2024-11-09 08:59:30 +08:00
huzibaca
19bb9c7f50 chore: update 2024-11-09 06:56:58 +08:00
huzibaca
f5dee51e9c chore: update 2024-11-09 05:50:51 +08:00
huzibaca
bd37fef720 chore: update 2024-11-08 23:42:00 +08:00
huzibaca
c22e4e5e2c chore: update 2024-11-08 21:46:15 +08:00
huzibaca
2887a2b6d3 Merge branch 'feat-add-unified-delay' into fix-migrate-tauri2-errors 2024-11-06 13:56:36 +08:00
huzibaca
01bde19701 chore: update 2024-11-06 13:52:36 +08:00
huzibaca
792f1826ee chore: update 2024-11-06 10:07:02 +08:00
huzibaca
c16795dce9 fix: @use rules must be written before any other rules. 2024-11-06 09:48:31 +08:00
huzibaca
c1597a0968 fix: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0. 2024-11-06 09:46:17 +08:00
huzibaca
590aa950df chore: update 2024-11-05 21:40:02 +08:00
huzibaca
402018b95c chore: update 2024-11-05 21:35:17 +08:00
huzibaca
d5101ac2f3 chore: update 2024-11-05 18:38:50 +08:00
huzibaca
251942c91d chore: update 2024-11-05 18:30:28 +08:00
huzibaca
fe86b812cd chore: add tooltip 2024-11-05 17:39:59 +08:00
huzibaca
cb3bff589f feat: add unified delay 2024-11-05 16:24:58 +08:00
huzibaca
ec7d7ec559 fix: wrong window state save point 2024-11-04 09:53:40 +08:00
huzibaca
24c7a5b805 chore: update deps 2024-11-02 10:25:38 +08:00
huzibaca
0a4ecb1507 chore: update 2024-11-02 07:27:08 +08:00
huzibaca
d736dace50 chore: update (+1 squashed commit)
Squashed commits:
[78899ef] fix: bbr2 not working in windows 11(24H2)
2024-11-02 07:10:47 +08:00
huzibaca
70bbab909f chore: update 2024-11-01 06:03:01 +08:00
The1111mp
6625f78e4f chore: added support for compiling linux arm architecture (#1945)
* chore: added support for compiling linux arm architecture

Signed-off-by: The1111mp <The1111mp@outlook.com>

* chore: remove debug code

Signed-off-by: The1111mp <The1111mp@outlook.com>

---------

Signed-off-by: The1111mp <The1111mp@outlook.com>
2024-10-31 17:20:16 +08:00
downer
da2b4c8858 fix: fix TUN icon is overwritten by system proxy icon (#1961)
Co-authored-by: 周晓亮 <zhouxl@jiyitech.com>
2024-10-31 09:07:21 +08:00
huzibaca
12df415dfd feat: use tauri_plugin_window_state 2024-10-30 18:52:53 +08:00
huzibaca
2493f463f3 revert: feat: use tauri-plugin-persisted-scope 2024-10-30 18:28:55 +08:00
huzibaca
f4238b1fb9 feat: use tauri-plugin-persisted-scope 2024-10-30 16:37:47 +08:00
huzibaca
794783ab4e chore: window hide replaces window minimize 2024-10-30 13:51:58 +08:00
wonfen
02634622a5 release: rc.5 2024-10-30 13:08:37 +08:00
wonfen
ac24501e76 chore: update bug report template 2024-10-30 12:59:19 +08:00
huzibaca
e40ea38112 chore: remove useless hooks
the window is not closed, it is minimized, so the position still exists
2024-10-30 10:06:54 +08:00
huzibaca
b809b9bb80 fix: linux file permissions problem
check-config configuration file moved from temp_dir to home_dir, resolved
2024-10-30 09:08:44 +08:00
huzibaca
73bad8f355 chore: restore linux custom close button 2024-10-30 06:30:49 +08:00
huzibaca
ac884da56b fix: performance issues caused by closing windows on mac 2024-10-30 06:27:29 +08:00
huzibaca
c35ab2e1cd chore: update 2024-10-29 10:15:07 +08:00
huzibaca
ed3907c273 chore: update 2024-10-29 10:13:53 +08:00
huzibaca
5e00287045 chore: disable websocket logging by default to reduce performance consumption. 2024-10-29 09:19:21 +08:00
huzibaca
95c6578911 fix incorrect usage of useCustomTheme 2024-10-29 09:07:08 +08:00
huzibaca
d22097ee33 chore: optimised the logic of dns processing 2024-10-28 13:09:55 +08:00
huzibaca
74251af163 fix: dns not restored when exiting the app 2024-10-28 00:24:57 +08:00
Christine.
7c1b11851f add more contribution details for Windows (#1939)
* doc: add more building details

Some friends said the build failed.

* chore: replace test url

replace default delay check URL, some user say that the delay numbers seen in CVR are very different from those in other software, and this default test result is not a valid reference.
2024-10-27 13:00:47 +08:00
huzibaca
f8724c4cb9 fix: unused variable 2024-10-27 10:19:42 +08:00
huzibaca
3baac034e5 chore: update 2024-10-27 06:47:16 +08:00
huzibaca
114f1426f3 feat: macos service installation query, can't avoid it, add service run check, if installation not confirmed, exit automatically after 10 seconds 2024-10-27 06:32:25 +08:00
huzibaca
7de63cea5c Revert "chore: update resource"
This reverts commit da907d0eeabfb825d9cce8fa95a1ade64efb805a.
2024-10-27 05:10:26 +08:00
huzibaca
34e3af2b38 chore: update 2024-10-27 05:06:42 +08:00
huzibaca
da907d0eea chore: update resource 2024-10-27 04:46:24 +08:00
huzibaca
9cbc2d9206 chore: update deps 2024-10-27 01:24:13 +08:00
huzibaca
db2e466d60 chore: update 2024-10-26 19:23:49 +08:00
wonfen
e5cdbf7361 release rc.4 2024-10-26 12:05:09 +08:00
huzibaca
a919f493d6 fix:public DNS not set in macos, tun+fake-ip 2024-10-26 08:55:00 +08:00
huzibaca
3660298683 chore: update 2024-10-26 01:44:06 +08:00
huzibaca
c845efe475 chore: update 2024-10-25 21:25:06 +08:00
huzibaca
38eb47132d chore: update 2024-10-25 14:13:30 +08:00
huzibaca
b1f097f32b chore: update 2024-10-25 14:12:27 +08:00
huzibaca
d3123253b3 fix: the tray icon does not display the correct colours on macos. 2024-10-25 13:58:22 +08:00
wonfen
7b1ec1ec22 chore: add mac sun ico 2024-10-25 12:27:22 +08:00
wonfen
4cefacfe73 release 2.0rc3 2024-10-24 12:52:20 +08:00
huzibaca
978acfa471 chore: update 2024-10-24 11:14:18 +08:00
huzibaca
fb2d138cbf Revert "chore: global mode doesn't show proxy groups, use placeholder images instead"
This reverts commit 0edd63edb575929e0802225b314e9c32174080f7.
2024-10-24 07:44:21 +08:00
huzibaca
0edd63edb5 chore: global mode doesn't show proxy groups, use placeholder images instead 2024-10-24 07:30:30 +08:00
huzibaca
97b730668c chore: update 2024-10-24 06:58:21 +08:00
huzibaca
26b8cf6d52 fix: proxy view display error 2024-10-24 06:54:27 +08:00
huzibaca
a979638368 refactor: the logic of profiles activation 2024-10-24 05:02:47 +08:00
huzibaca
97f434ad4a fix: restart app failure 2024-10-24 02:54:57 +08:00
huzibaca
34af040c48 fix: tun mode switch is not effective 2024-10-24 02:16:28 +08:00
huzibaca
cc81b443be refactor: IRuntime::patch_config() 2024-10-23 10:34:14 +08:00
huzibaca
d44f3c22c7 chore: update 2024-10-23 09:26:14 +08:00
huzibaca
3795b537f6 chore: update 2024-10-23 05:48:01 +08:00
huzibaca
ecb5f0885c fix: failed to install service on macos 2024-10-23 04:46:47 +08:00
huzibaca
86d2234713 feat: add windows uninstall script 2024-10-22 04:46:08 +08:00
huzibaca
62ddf26150 feat: add linux uninstall script 2024-10-22 03:17:04 +08:00
huzibaca
ec14b7c52f fix: debian executable file has no permissions 2024-10-22 02:37:36 +08:00
huzibaca
5f9cc38e82 chore: update 2024-10-22 01:34:26 +08:00
huzibaca
f48c58f299 fix: change the window close to minimize the window.
there is currently an error in the tauri library.
2024-10-21 23:16:45 +08:00
huzibaca
c2843f3c4b fix: pac url error ,fixes #1889 2024-10-21 22:52:10 +08:00
wonfen
5d33df4e12 chore: update bug_report template 2024-10-21 11:30:07 +08:00
huzibaca
c030fb47ca Merge branch 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev into fix-migrate-tauri2-errors
* 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev:
  chore: update to 2.0 rc2
2024-10-21 04:35:42 +08:00
huzibaca
964daadb18 chore: update 2024-10-21 04:35:21 +08:00
wonfen
71a5698ac7 chore: update to 2.0 rc2 2024-10-21 03:15:02 +08:00
huzibaca
d41d74d0f8 chore: replace sudo with pkexec execution 2024-10-21 02:31:43 +08:00
huzibaca
f6c7a611a3 chore: update 2024-10-21 02:17:41 +08:00
huzibaca
06f4e79e5c chore: update 2024-10-21 01:35:28 +08:00
huzibaca
154cf44f0a chore: update 2024-10-21 01:30:13 +08:00
huzibaca
f6e2ff0e44 chore: update 2024-10-21 01:12:52 +08:00
huzibaca
2aba616f7f chore: update 2024-10-21 00:50:43 +08:00
huzibaca
95e21386b8 chore: update deps 2024-10-21 00:45:06 +08:00
huzibaca
250e908d9a chore: update 2024-10-21 00:34:28 +08:00
huzibaca
9742fb296c fix: failed to start system proxy with PAC mode 2024-10-20 23:13:23 +08:00
huzibaca
9ff3c2c0d4 chore: update deps 2024-10-20 22:35:16 +08:00
huzibaca
9dd7bd9530 Merge branch 'fix-linux-errors' into fix-migrate-tauri2-errors
* fix-linux-errors:
  chore: try to fix service not started on linux
2024-10-20 21:57:41 +08:00
huzibaca
6f477b7147 fix: mac commad+q global hijack is not released 2024-10-20 06:06:47 +08:00
huzibaca
8389826e30 chore: try to fix service not started on linux 2024-10-20 04:48:42 +08:00
huzibaca
aa31fb7470 chore: update 2024-10-18 22:50:03 +08:00
huzibaca
a013fe663c fix: try to fix service not started on linux 2024-10-18 07:08:34 +08:00
wonfen
89ce497431 chore: adjust translation 2024-10-17 05:09:00 +08:00
huzibaca
60c0b649e8 chore: update deps 2024-10-16 22:49:24 +08:00
huzibaca
2bbb5ea23b chore: update 2024-10-16 02:55:23 +08:00
huzibaca
4f9c1533c1 fix: system proxy cannot be closed on mac 2024-10-16 02:52:48 +08:00
huzibaca
a6a3847e30 chore: update 2024-10-16 01:55:22 +08:00
huzibaca
118f38dba3 chore : update version 2024-10-16 01:53:33 +08:00
huzibaca
879f946b28 chore: t emporarily cancel window closure and change to minimize 2024-10-16 01:53:16 +08:00
huzibaca
6acb8a5a91 Merge branch 'fix-migrate-tauri2-errors' of ssh://github.com/clash-verge-rev/clash-verge-rev into fix-migrate-tauri2-errors 2024-10-15 02:38:57 +08:00
huzibaca
a38f1e92e3 fix: path escape issue 2024-10-15 02:35:50 +08:00
wonfen
4ca977466e chore: update changelog and texts 2024-10-15 02:32:25 +08:00
huzibaca
ec45dc56fb chore: remove windows esc shortcut key 2024-10-14 13:45:26 +08:00
huzibaca
5686302653 chore: update 2024-10-13 03:01:32 +08:00
huzibaca
6322773513 chore: update deps 2024-10-13 02:55:35 +08:00
huzibaca
8e845fc919 chore: adjust tun default parameters 2024-10-13 02:42:37 +08:00
huzibaca
f90c8f2ae5 fox: external-controller cors error 2024-10-11 22:01:57 +08:00
huzibaca
b5af06529f chore: complete field identifier 2024-10-11 14:33:11 +08:00
huzibaca
fac3669f8e Merge branch 'main' into fix-migrate-tauri2-errors 2024-10-11 02:56:30 +08:00
Lvc Revincx
28ff8d6dcc fix: task bar icon misssing under linux wayland (#1816) 2024-10-11 02:52:57 +08:00
huzibaca
d0e7f6673c fix: installation service error 2024-10-11 00:59:34 +08:00
huzibaca
800dc21202 chore: update 2024-10-10 23:34:55 +08:00
huzibaca
f52089a674 chore:update 2024-10-10 18:52:20 +08:00
huzibaca
82543de95e chore: update 2024-10-10 18:40:39 +08:00
huzibaca
12db69407e chore: update 2024-10-10 02:21:22 +08:00
huzibaca
9b2b447b8b feat: Modify startup logic and install services by default 2024-10-10 00:34:36 +08:00
huzibaca
35f5e4ca41 chore: update 2024-10-09 01:14:03 +08:00
huzibaca
8a69713f6c refactor: core logic 2024-10-08 02:39:17 +08:00
huzibaca
efd8ef0380 chore: update 2024-10-06 02:03:32 +08:00
huzibaca
c5eacd1627 chore: revert 2024-10-06 01:09:59 +08:00
huzibaca
5fdb52d8d0 refactor: tun mode is turned on and off, does not depend on services, and does not affect other configurations 2024-10-05 22:45:59 +08:00
huzibaca
7869ce060f refactor: init_log 2024-10-05 12:19:44 +08:00
huzibaca
e0d96c0ce1 refactor: get_bypass func 2024-10-05 02:58:41 +08:00
huzibaca
30678904ee chore: update 2024-10-05 00:03:11 +08:00
huzibaca
953be61d89 chore: update deps 2024-10-04 23:56:50 +08:00
huzibaca
d8c85007d4 feat: add color to log 2024-10-04 22:38:06 +08:00
huzibaca
591c1cb454 chore: update 2024-10-04 05:37:53 +08:00
huzibaca
0ca90ed082 chore: update 2024-10-04 05:27:59 +08:00
huzibaca
4c963b3978 chore: update 2024-10-03 14:31:40 +08:00
huzibaca
071665f0c3 chore: update 2024-10-03 12:01:06 +08:00
huzibaca
9a7826752f feat: windows uses sysproxy.exe for system proxy 2024-10-03 02:09:22 +08:00
huzibaca
44b4187365 refactor: simplify sysproxy logic
1. close all proxies directly when reset_proxy
2. init_sysproxy and update_sysproxy combined into one
3. optimize lock usage
4 ptimize the thread loop of guard_sysproxy,
2024-10-03 01:31:37 +08:00
huzibaca
148807543f chore: replace sysproxy-rs repo 2024-10-02 17:43:14 +08:00
huzibaca
2b9fa09293 feat: test item, when icon is svg, add svg format check 2024-10-01 20:49:03 +08:00
huzibaca
10211d1d03 chore: optimize lock 2024-10-01 00:27:08 +08:00
huzibaca
46811f33ad chore: remove the manual release lock 2024-09-30 23:07:46 +08:00
wonfen
32b16790d3 chore: add service mode info 2024-09-30 01:04:15 +08:00
huzibaca
10592ca5a8 chore: update 2024-09-28 20:20:40 +08:00
huzibaca
dc5cb2e1b8 refactor: logic optimization 2024-09-28 12:37:01 +08:00
huzibaca
c10d782524 ,fix: when updating the verge configuration, notification error 2024-09-27 22:25:23 +08:00
wonfen
d73366984f chore: update bug report template 2024-09-27 14:08:50 +08:00
huzibaca
60b1e47ae6 chore: update 2024-09-27 10:05:31 +08:00
huzibaca
9591fb2c21 chore: remove compatibility code 2024-09-27 10:04:23 +08:00
huzibaca
c3cba03ac6 refactor: logic optimization 2024-09-27 00:24:05 +08:00
huzibaca
ce7818c436 chore: update 2024-09-26 19:47:25 +08:00
huzibaca
f367a81e44 chore: remove compatibility code 2024-09-26 19:47:00 +08:00
huzibaca
de507f7ec9 chore: update 2024-09-26 19:23:16 +08:00
huzibaca
0a8be603c8 chore: update 2024-09-26 12:20:57 +08:00
huzibaca
d7f033bd46 chore: update 2024-09-25 21:20:36 +08:00
huzibaca
1fb3b87697 chore: update 2024-09-25 21:07:01 +08:00
huzibaca
b9c8fa61b2 Revert "chore: cover panic error"
This reverts commit 0bacfa9286cc650a06fc4943d40114222f3d6aed.
2024-09-25 20:22:49 +08:00
huzibaca
99ea6d5080 chore: unified hotkey registration 2024-09-25 15:45:12 +08:00
huzibaca
0bacfa9286 chore: cover panic error 2024-09-25 15:44:05 +08:00
huzibaca
b350b605a8 fix: the save_indow_size_position method has been triggered twice, at the same time
1. remove windowEvent::destoryed  hook
2024-09-25 13:54:05 +08:00
huzibaca
d1eeeab7b1 chore: remove useless exit codes 2024-09-25 11:47:01 +08:00
huzibaca
54296ba84a fix: code lint 2024-09-24 20:11:33 +08:00
huzibaca
f82b0f259c fix: code lint 2024-09-24 20:09:30 +08:00
huzibaca
57f1c005e6 fix: code lint 2024-09-24 20:06:25 +08:00
huzibaca
d9e5387bff chore: add cross-env dev dependency 2024-09-24 16:35:19 +08:00
huzibaca
3154b8ce55 chore: update 2024-09-23 23:57:08 +08:00
huzibaca
45b48ede44 refactor: unify and simplify the call of app_handle(2) 2024-09-23 23:15:51 +08:00
huzibaca
961b86dcd2 chore: update 2024-09-23 17:15:28 +08:00
huzibaca
4d57c64b0d chore: update 2024-09-23 16:53:00 +08:00
huzibaca
1c894f3cfa refactor: unify and simplify the call of app_handle 2024-09-23 16:31:58 +08:00
huzibaca
3d6faecaed chore: update 2024-09-22 22:15:36 +08:00
huzibaca
2263ade187 chore: rename method 2024-09-22 20:29:43 +08:00
huzibaca
2cdf33d8a1 Revert "chore: update"
This reverts commit f6fce6bd317796983dcd5876dce7219bbcf78756.
2024-09-22 16:46:24 +08:00
huzibaca
f6fce6bd31 chore: update 2024-09-22 15:59:59 +08:00
huzibaca
f9f1721d66 chore: update 2024-09-22 15:39:52 +08:00
huzibaca
0792ac7de8 chore: update 2024-09-22 14:08:08 +08:00
huzibaca
98edb048b7 Revert "chore: remove useless exit codes and hooks"
This reverts commit d9671faca710ade167d10020466341fda10c1fa1.
2024-09-22 00:41:20 +08:00
huzibaca
52fcdf28fa fix: remove unused variable 2024-09-22 00:32:40 +08:00
huzibaca
d9671faca7 chore: remove useless exit codes and hooks 2024-09-21 21:05:34 +08:00
huzibaca
f456004543 chore: try adjusting the triggering of the tray mouse button 2024-09-21 15:38:14 +08:00
huzibaca
a38040d0ea chore : update 2024-09-20 18:02:19 +08:00
huzibaca
f18cd92318 fix: tray menu text was not aligned 2024-09-20 17:59:24 +08:00
huzibaca
06e1d0f8da chore: update 2024-09-20 17:49:31 +08:00
huzibaca
dffd663d7a Fix: Custom tray icon not working
1. Contains potential deadlock
2024-09-20 16:26:23 +08:00
huzibaca
414f9e9e96 fix: function parameter call error 2024-09-20 08:27:06 +08:00
Sukka
c17ea74856 chore: drop fs-extra (#1739)
nb!
2024-09-20 02:01:36 +08:00
huzibaca
9a08740e5b chore: update dev dependencis 2024-09-18 21:34:20 +08:00
huzibaca
8bea0db843 fix: exit hotkey conflict 2024-09-18 15:16:43 +08:00
huzibaca
2c612e371f fix: whether the window starts as fullscreen or not. 2024-09-18 13:23:27 +08:00
huzibaca
2f61dc9bc6 chore: revert 2024-09-18 12:24:52 +08:00
huzibaca
d18b78c11c refactor: exit app 2024-09-18 02:48:55 +08:00
huzibaca
95fb4f2e50 fix: the shortcut key to exit cannot be used 2024-09-18 01:41:46 +08:00
huzibaca
f31fe1440d fix: hotkey status not accurately processed, resulting in two triggers 2024-09-17 10:59:39 +08:00
huzibaca
10cd0a1f30 chore: update 2024-09-17 08:28:13 +08:00
huzibaca
659f854f62 chore: update 2024-09-17 08:02:29 +08:00
huzibaca
4a7f8dbe09 chore: add window to allow full screen configuration 2024-09-16 15:25:01 +08:00
huzibaca
3e19e574e6 chore: update 2024-09-16 07:08:22 +08:00
huzibaca
2af2d3664f chore: update 2024-09-16 06:43:25 +08:00
huzibaca
7dad46adb4 chore: update 2024-09-16 06:37:39 +08:00
huzibaca
b6fc6a751a chore: update 2024-09-16 06:17:22 +08:00
huzibaca
934674a8d7 chore: update 2024-09-16 05:50:05 +08:00
huzibaca
c66986f065 chore: modify application description definition 2024-09-16 05:38:52 +08:00
huzibaca
fc49e4a0da fox: try to restore the processing logic of windows custom scheme 2024-09-16 05:30:29 +08:00
huzibaca
b6e1d71b81 chore: update dev workflow 2024-09-15 07:57:34 +08:00
huzibaca
b3626a786d chore: add log 2024-09-15 07:03:04 +08:00
huzibaca
a398e28ac0 chore: follow official standards and adjust the main file 2024-09-15 06:24:53 +08:00
wonfen
892d4e597e chore: update change log and texts 2024-09-15 00:33:54 +08:00
huzibaca
e8311dd306 fix: when importing subscriptions, force the window to open 2024-09-14 10:03:19 +08:00
huzibaca
a0b266fef8 fix: after importing a subscription, it cannot be automatically switched to the current subscription. 2024-09-13 18:17:14 +08:00
huzibaca
983d1ea361 chore: update action(rust-toolchain) 2024-09-13 14:21:51 +08:00
huzibaca
db615b932c chore: update action(rust-toolchain) 2024-09-13 14:14:27 +08:00
wonfen
07415e512f chore: change folder name 2024-09-13 12:03:28 +08:00
huzibaca
0c6d417d8c feat: Added web notification of successful subscription import 2024-09-13 11:24:49 +08:00
huzibaca
772e01ad40 fix: when the service is not installed, the tray hides the Tun mode 2024-09-13 04:12:25 +08:00
huzibaca
1b2509d5bc refactor: url scheme implementation 2024-09-13 03:21:55 +08:00
huzibaca
fd963a8e66 chore: delete useless emebd server api 2024-09-12 19:01:08 +08:00
huzibaca
3c17fca369 fix:tray click event 2024-09-12 17:02:17 +08:00
huzibaca
4a76997044 feat: Optimize kernel startup logic
1. tun mode startup logic
2. Remove invalid creation process PID logic
2024-09-12 15:35:08 +08:00
huzibaca
894960ef5a feat: log panic 2024-09-12 07:59:51 +08:00
huzibaca
3c24d4bc4e chore: update deps 2024-09-12 06:14:23 +08:00
huzibaca
ed7e6a3495 fix: missing items in system tray 2024-09-12 05:45:15 +08:00
huzibaca
07de032e62 feat: migrate tauri 2.0 2024-09-11 08:15:03 +08:00
huzibaca
c07165531a chore: update 2024-09-04 07:53:16 +08:00
huzibaca
b1a22d4412 chore: update capabilities 2024-09-04 05:57:37 +08:00
wonfen
6734e5ef57 chore: 2.0.0 beta release 2024-09-03 00:00:12 +08:00
huzibaca
6cc81fe6b8 chore: update 2024-09-02 19:33:17 +08:00
wonfen
ad80d21e89 chore: use in-house update proxy 2024-08-24 02:02:17 +08:00
wonfen
97689c6cbb Style: fix macos new version btn position 2024-08-22 07:33:51 +08:00
wonfen
41c83dabde Release 1.7.7 2024-08-22 03:47:33 +08:00
wonfen
f909f0dcf9 chore: Shorten text to prevent Windows tooltip truncation. 2024-08-22 03:41:16 +08:00
wonfen
5c9bf30c79 fix: profiles will not be selected after import 2024-08-22 00:57:04 +08:00
wonfen
6efc518eed Release 1.7.6 2024-08-18 00:49:23 +08:00
tony-sung
198bd3a3dc Update web-ui-viewer.tsx Fix yacd parameter hostname error (#1484) 2024-08-18 00:23:24 +08:00
MystiPanda
7b1055702b feat: auto select profile 2024-07-30 09:06:12 +08:00
MystiPanda
d21bcce3c4 chore: prepend new added rules 2024-07-24 14:25:06 +08:00
MystiPanda
b5a26941ef fix: wrap the password in quotes
#1460
2024-07-24 14:22:23 +08:00
Avan
6ab7131378 style: AddressDisplay padding (#1457) 2024-07-23 22:17:51 +08:00
nhsmw
22bcc2e438 fix: update test url 2024-07-19 10:32:41 +08:00
MystiPanda
32edc0f1fe chore: remove unnecessary console log 2024-07-18 23:37:54 +08:00
MystiPanda
8faa0ce2c2 fix: use group testUrl
#1384
2024-07-18 15:04:55 +08:00
MystiPanda
bf05e5999b fix: windows x86 updater url 2024-07-18 14:38:31 +08:00
MystiPanda
3d7bdded31 fix: error command status 2024-07-16 18:37:56 +08:00
MystiPanda
7507182097 fix: MacOS service install error 2024-07-16 13:01:27 +08:00
MystiPanda
6853b3c531 fix(#1335): support cidr 2024-07-16 12:22:11 +08:00
MystiPanda
f2372a13e8 fix: try to fix install service 2024-07-16 12:07:23 +08:00
MystiPanda
3f321c8801 chore: disable okBtn 2024-07-15 23:38:49 +08:00
MystiPanda
8b47107df8 ci: fix winget uploader 2024-07-15 23:27:38 +08:00
MystiPanda
97e7136293 Release 1.7.5 2024-07-15 22:35:33 +08:00
MystiPanda
c8db58150e ci: fix winget uploader 2024-07-15 22:33:57 +08:00
MystiPanda
5e1067df59 fix: copy necessary files 2024-07-15 22:25:04 +08:00
MystiPanda
175444c59f Release 1.7.4 2024-07-15 20:56:18 +08:00
MystiPanda
b09d5ff3c9 fix: unified switch style 2024-07-15 20:42:00 +08:00
MystiPanda
43fc97137e feat: optimize the service mode interaction logic. 2024-07-15 20:02:05 +08:00
MystiPanda
c67eee57d6 Revert "fix: try to fix abnormal stuck"
This reverts commit 7edbae7b4c36a3b10e6e8400e43d7ef94d4495c7.
2024-07-14 12:49:06 +08:00
Sukka
607aa78059 fix(#1387): avoid catastrophic backtracking (#1396) 2024-07-14 12:26:02 +08:00
MystiPanda
7edbae7b4c fix: try to fix abnormal stuck
#1387
2024-07-13 21:41:04 +08:00
MystiPanda
232ff38084 chore: update locale 2024-07-13 19:15:07 +08:00
MystiPanda
d9d9ca67cd chore: unified icon style 2024-07-13 19:01:16 +08:00
MystiPanda
57fa48aef4 feat: display network interface 2024-07-13 14:15:13 +08:00
Avan
5a8e0749c2 feature: copy clash env (#1391)
* feat: copy clash env

* style: use ContentCopyRounded replace CopyAll
2024-07-13 01:03:46 +08:00
Avan
f834f069cd style: EditorViewer title align center (#1390) 2024-07-12 23:29:55 +08:00
MystiPanda
9b2dc10da2 fix: service install failed on macos 2024-07-12 20:54:31 +08:00
dongchengjie
c3730b7efd chore: checkbox items' title & button icons 2024-07-12 18:24:19 +08:00
dongchengjie
23499497a3 chore: editor resizing debounce 2024-07-08 23:18:06 +08:00
dongchengjie
3b78d609b7 chore: remove dialog overflow-scroll 2024-07-08 22:07:43 +08:00
dongchengjie
65529c3356 fix: editor fails to resize after toggling on macos 2024-07-08 21:40:56 +08:00
MystiPanda
899849d4dc fix dev.yml 2024-07-08 16:17:23 +08:00
MystiPanda
52393206e6 ci: add dev workflow 2024-07-08 13:20:36 +08:00
Akioe Yu
2cd1fa6601 fix: dnd box z-index (#1353)
* fix: dnd box z-index

* fix: dnd boxes
2024-07-08 10:49:57 +08:00
MystiPanda
e371bbedc0 perf: optimized response speed 2024-07-08 00:29:49 +08:00
dongchengjie
9dde385073 chore: group types locale 2024-07-07 21:56:20 +08:00
❤是纱雾酱哟~
afa3d39cb3 ci: Integrate "Winget Releaser" (#1326) 2024-07-07 20:42:17 +08:00
MystiPanda
fe41817f25 perf: change port too slow 2024-07-07 18:22:02 +08:00
MystiPanda
a865465514 feat: get network interface 2024-07-07 18:13:10 +08:00
dongchengjie
9278e74e9e fix: usage percent style 2024-07-07 18:08:02 +08:00
MystiPanda
689a1f739f fix: trojan uri parser 2024-07-07 11:47:49 +08:00
MystiPanda
19dee57b7e fix: tray icon size
#1341
2024-07-07 11:18:22 +08:00
MystiPanda
8690b91632 chore: disable browser autocomplete 2024-07-07 11:16:59 +08:00
MystiPanda
fa31cab11b Release 1.7.3 2024-07-06 18:48:25 +08:00
MystiPanda
46ee783f99 fix: bypass reg error
#1335
2024-07-06 18:23:23 +08:00
MystiPanda
199bba5da4 fix: limite cipher types 2024-07-06 10:49:42 +08:00
MystiPanda
16e8791472 fix: rule parser 2024-07-06 10:35:29 +08:00
MystiPanda
1728442d62 Revert "chore: add base64 decode step"
This reverts commit 4727d613c0d9279a6d9a447522cc1f455fc3e1ac.
2024-07-06 10:12:51 +08:00
MystiPanda
22f7f059ce chore: decode base64 2024-07-06 10:12:40 +08:00
MystiPanda
97629c1fc3 fix: some error 2024-07-06 09:54:16 +08:00
MystiPanda
4727d613c0 chore: add base64 decode step 2024-07-06 00:57:38 +08:00
MystiPanda
9ed138ea2b fix: style 2024-07-06 00:45:21 +08:00
MystiPanda
2cbd998941 fix: type 2024-07-06 00:23:52 +08:00
MystiPanda
806d70c243 chore: Improve URI parser 2024-07-05 22:44:05 +08:00
MystiPanda
74a1c7d489 feat: finish rpoxies editor 2024-07-05 19:49:32 +08:00
MystiPanda
f6ed5dc126 fix: groups config type error
feat(unfinished): add proxy editor
2024-07-05 00:38:50 +08:00
MystiPanda
e25185b9b8 feat: add profile name param for script 2024-07-04 23:11:54 +08:00
MystiPanda
5e6d8873b9 build: remove unused resource file 2024-07-04 22:34:48 +08:00
MystiPanda
951b48c337 chore: disable unnecessary ports by default 2024-07-04 18:58:34 +08:00
MystiPanda
7f209b76bf feat: support cache for groups editor 2024-07-04 18:53:39 +08:00
MystiPanda
890bfbe02d chore: unified style 2024-07-04 18:50:21 +08:00
MystiPanda
70f8c28ca6 fix: useseq error 2024-07-04 14:30:34 +08:00
MystiPanda
47bacdaed0 fix: locale typo 2024-07-04 14:18:21 +08:00
MystiPanda
bcd8eb2a09 feat: support visual edit for proxy group 2024-07-04 14:13:19 +08:00
MystiPanda
e4855d0143 fix: rules editor get groups error 2024-07-04 11:03:51 +08:00
MystiPanda
94f0ff1ed1 feat: support get merged rule-set name 2024-07-03 22:13:24 +08:00
dongchengjie
b5f0243a89 fix: remove rule condition where is not required 2024-07-03 17:03:12 +08:00
MystiPanda
17e59b8783 Release 1.7.2 2024-07-03 13:26:06 +08:00
MystiPanda
ffdf308b40 fix: Insertion order
#1300
2024-07-03 13:13:54 +08:00
MystiPanda
cdadc80945 fix: dialog styles 2024-07-03 13:13:29 +08:00
MystiPanda
294e1f5b10 fix: panic 2024-07-03 12:49:29 +08:00
MystiPanda
1c5eab6055 feat: support edit rules file 2024-07-03 12:37:08 +08:00
MystiPanda
f74f06e403 fix: edit groups error 2024-07-03 10:58:57 +08:00
MystiPanda
f04ee0baf2 Release 1.7.1 2024-07-03 09:52:19 +08:00
MystiPanda
689273fc24 fix: rules drag error 2024-07-03 09:45:14 +08:00
MystiPanda
6f4c59a15c fix: compatibility 2024-07-03 08:15:13 +08:00
dongchengjie
dc87097dfe fix: search-box takes no effect in rule-editor #1288 2024-07-03 03:08:01 +08:00
dongchengjie
24f4ab7597 fix: sub usage percent fails to display when number is too small #1290 2024-07-03 02:59:32 +08:00
MystiPanda
ad94f0a292 Release 1.7.0 2024-07-03 00:32:14 +08:00
MystiPanda
695613a063 perf: optimize performance of the rule editor 2024-07-02 23:55:29 +08:00
dongchengjie
6f1828eabc feat: editor add tool-tar buttons 2024-07-02 23:24:44 +08:00
dongchengjie
bf158b3bf0 refactor: editor-viewer using react-monaco-editor 2024-07-02 21:02:29 +08:00
dongchengjie
13618e6a0a chore: bump schema to 1.18.5-alpha7 2024-07-02 19:38:44 +08:00
MystiPanda
c424e9dec8 chore: disable autocomplete 2024-07-02 19:02:05 +08:00
MystiPanda
a2e9523707 fix: default value of global extend config 2024-07-02 18:46:23 +08:00
dongchengjie
606817ae06 chore: add rule list search-box 2024-07-02 17:32:49 +08:00
dongchengjie
7124d326fc chore: rule types locale 2024-07-02 17:04:22 +08:00
MystiPanda
f9f4653e33 chore: check the validity of the rule content 2024-07-02 13:11:54 +08:00
MystiPanda
bf8eebe537 chore: Adjust the chain processing execution order and default value 2024-07-02 12:40:28 +08:00
MystiPanda
bd9eef6502 build: update depends 2024-07-02 12:21:17 +08:00
MystiPanda
e343b1790e chore: update locale 2024-07-01 23:30:14 +08:00
MystiPanda
d81ef1d67c feat: allow set bypass without using default value 2024-07-01 22:53:32 +08:00
Sukka
6e374bcd4e ci: speed up cargo install by enabling cache (webview2) (#1283) 2024-07-01 14:23:21 +08:00
MystiPanda
fb4648d2af feat: global merge and script 2024-07-01 08:25:03 +08:00
MystiPanda
a63fc25f14 feat: drag to reorder rules 2024-06-30 23:16:21 +08:00
MystiPanda
5e20e9ae1c chore: select rule-set name 2024-06-30 22:46:11 +08:00
MystiPanda
7d5d604ea6 feat: display rules list 2024-06-30 22:34:26 +08:00
MystiPanda
a722581868 fix: editor init error 2024-06-30 18:05:38 +08:00
Sukka
28f3044bdd ci: speed up cargo install by enabling cache (#1279) 2024-06-30 17:21:55 +08:00
MystiPanda
497804434d feat: rules editor 2024-06-30 17:17:04 +08:00
MystiPanda
d2d6ee806d chore: locale 2024-06-30 13:03:36 +08:00
MystiPanda
2e106265f9 feat(unfinished): rules editor 2024-06-30 12:46:31 +08:00
MystiPanda
28fb0b433b refactor: pure merge 2024-06-30 07:58:44 +08:00
MystiPanda
171bd6b327 fix: delete logic 2024-06-30 07:37:52 +08:00
MystiPanda
198e215d54 chore: update locale 2024-06-30 07:18:10 +08:00
MystiPanda
4d424e70bc feat: support seq editor 2024-06-30 00:22:05 +08:00
MystiPanda
3efef52398 refactor: Associate Profile with Merge/Script. 2024-06-29 23:07:44 +08:00
MystiPanda
b85929772e refactor: use async instead of block_on 2024-06-29 19:02:37 +08:00
dongchengjie
041522f94e fix: do not reactive when changed profile is not current 2024-06-29 11:22:11 +08:00
dongchengjie
80d3c9e96f feat: reactive after save when profile content changes 2024-06-29 09:21:50 +08:00
dongchengjie
6ee5e560cc fix: #1261 2024-06-29 05:02:06 +08:00
wonfen
2be9eb4bae chore: add TG link & descriptions 2024-06-29 04:55:43 +08:00
dongchengjie
0a8935686a chore: add descriptions for Miscellaneous 2024-06-28 15:54:27 +08:00
MystiPanda
0109d9148b fix: unset dns when exit 2024-06-28 12:18:18 +08:00
MystiPanda
212518c682 feat: set dns by service 2024-06-28 11:45:44 +08:00
dongchengjie
59b4f1ebab chore: remove duplicate locales 2024-06-26 19:11:39 +08:00
dongchengjie
ee9462c221 fix: await compatibility in #c648dc6 2024-06-26 18:29:50 +08:00
dongchengjie
c648dc6c99 feat: support manual memory cleanup when running in debug mode 2024-06-26 17:44:42 +08:00
dongchengjie
4f1b8094a3 chore: cleanup 2024-06-26 08:24:43 +08:00
dongchengjie
c89ccf7185 refactor: extract tooltip icon as component 2024-06-26 08:10:18 +08:00
dongchengjie
753395965a chore: tooltips and locales 2024-06-26 05:33:06 +08:00
dongchengjie
04be747d52 chore: fix connection table bg 2024-06-23 20:40:09 +08:00
dongchengjie
f828ed3edf fix: update_interval won't save when creating local profile while updating does & number input locales 2024-06-23 06:47:51 +08:00
MystiPanda
b98d9c2932 feat: handle break change update 2024-06-22 21:05:46 +08:00
dongchengjie
aba2ce8390 chore: hotkeys display delimiter 2024-06-21 18:24:58 +08:00
MystiPanda
8bd8e149cf chore: hide delay for preset outbound 2024-06-21 00:16:41 +08:00
MystiPanda
e7c359a2e7 revert: just kill 2024-06-20 14:27:13 +08:00
MystiPanda
d64d25380a feat: custom dmg background 2024-06-20 13:50:28 +08:00
dongchengjie
6cba6166fb chore: fix table header bg in connections 2024-06-20 02:01:51 +08:00
dongchengjie
e66f5fe253 chore: disable shortcuts #1239 2024-06-20 01:39:51 +08:00
MystiPanda
1d4388d444 fix: runtime error 2024-06-19 11:10:40 +08:00
MystiPanda
28ab08a7ca refactor: change core binary name 2024-06-19 10:04:28 +08:00
MystiPanda
6fa0f92ceb refactor: kill core by process name 2024-06-18 12:36:58 +08:00
MystiPanda
3083ab74a6 fix: reg error 2024-06-17 16:38:30 +08:00
Sukka
b6ea73af83 fix(#1226): missing conn unsub (#1228) 2024-06-17 13:36:35 +08:00
Sukka
1fa3ffb1ff refactor(connections): use swr subscription (#1226) 2024-06-17 13:14:36 +08:00
dongchengjie
af89630095 fix: resizing reset when data changes 2024-06-17 13:03:47 +08:00
Sukka
18f0177fce refactor(log): use swr subscription (#1220) 2024-06-17 10:48:37 +08:00
MystiPanda
d89eecacba chore: update depends 2024-06-17 10:46:18 +08:00
Sukka
a9149fb92e feat: add a wrapper around sockette w/ error retry (#1219)
* feat: add a wrapper around sockette w/ error retry

* chore: use import path alias

* perf: reduce retry
2024-06-16 18:25:33 +08:00
MystiPanda
9a04208a11 Revert "feat: disable running with admin permission and check service mode"
This reverts commit 481e473b604da37e8b7cbe2c929dc12ba5b6c120.
2024-06-16 12:06:23 +08:00
Sukka
6cdf199531 ci(alpha): avoid race by cancel non-latest concurrent runs (#1213) 2024-06-16 01:10:30 +08:00
Sukka
44dc7fe24a fix: hide save button in readonly editor view (#1208) 2024-06-15 19:24:26 +08:00
Sukka
455892b414 fix(#1203): correct types (#1207) 2024-06-15 19:23:58 +08:00
Sukka
4f5227782a perf: replace Array#map Array#filter chain w/ Array#reduce (#1203) 2024-06-15 12:22:33 +08:00
MystiPanda
481e473b60 feat: disable running with admin permission and check service mode 2024-06-14 23:15:49 +08:00
MystiPanda
2c2a1f638b build: downgrade auto-launch 2024-06-14 21:26:30 +08:00
MystiPanda
973e269f46 fix: fix bypass check regex 2024-06-14 21:23:58 +08:00
Sukka
9f76e0e056 chore: use swr subscription for layout traffic / memory (#1202)
* chore: update swr to 2

* refactor: use swr subscription for memory & traffic

* refactor: introduce `sockette`
2024-06-14 18:23:29 +08:00
dongchengjie
3a5f1b41a4 chore: update metacubexd #1200 2024-06-14 17:09:12 +08:00
dongchengjie
e2d8369daf feat: local profile name autofill #1191 2024-06-13 16:29:25 +08:00
MystiPanda
a20d4959bf refactor: remove grant logic 2024-06-13 16:07:56 +08:00
MystiPanda
7b887e4cdd fix: check service 2024-06-13 12:58:47 +08:00
MystiPanda
bc9cbd2993 ci: fix release 2024-06-12 12:25:12 +08:00
MystiPanda
9baa0e247f chore: update publisher 2024-06-12 11:51:44 +08:00
MystiPanda
2df8d2bc69 Release 1.6.6 2024-06-12 10:33:20 +08:00
MystiPanda
b8165fb06e fix: start param error 2024-06-12 10:19:23 +08:00
MystiPanda
c698b24e01 chore: update & fmt & clippy 2024-06-12 10:00:22 +08:00
dongchengjie
e70249cb2e chore: missing locale 2024-06-11 16:19:25 +08:00
dongchengjie
8a9bfe8281 chore: locale "Invalid Bypass Format" 2024-06-09 20:02:20 +08:00
MystiPanda
8c2a4e627e fix: start param error 2024-06-09 20:00:17 +08:00
MystiPanda
bf35c92c14 feat: check bypass format 2024-06-09 13:37:47 +08:00
MystiPanda
019293a034 feat: keep default bypass 2024-06-09 12:45:57 +08:00
dongchengjie
46dc40149e chore: missing locale 2024-06-09 11:16:13 +08:00
dongchengjie
444643eb6f chore: locale reorganization 2024-06-09 09:12:14 +08:00
Eric Huang
2913b911e3 feat(settings page): add loading state (#1157)
* feat(settings page): add loading state

* fix: type
2024-06-09 06:26:07 +08:00
wonfen
ca323371a7 chore: update ci with apple notarization 2024-06-09 02:01:33 +08:00
MystiPanda
a06cb39777 ci: fix error 2024-06-08 20:58:57 +08:00
MystiPanda
b0ec8767a2 ci: update 2024-06-08 20:45:28 +08:00
MystiPanda
353fb49a87 feat: add download button on updater dialog
#1129
2024-06-08 20:35:23 +08:00
MystiPanda
e453b40e0b fix: run app as normal user 2024-06-08 20:20:47 +08:00
MystiPanda
0c6f8ce77d build: update depends 2024-06-08 20:02:25 +08:00
MystiPanda
c0219662bb build: remove appimage 2024-06-08 19:36:58 +08:00
MystiPanda
aae71d375c ci: codesign 2024-06-08 18:52:34 +08:00
dongchengjie
b6228e4c59 chore: disbale Meta+Q on macOS 2024-06-08 13:12:24 +08:00
Sukka
3a9a1439d9 refactor: simplify useConnectionSetting (#1141) 2024-06-07 17:23:53 +08:00
wonfen
7cf256dc7c fix: apple sign error again 2024-06-07 17:19:44 +08:00
MystiPanda
c3c26998bf fix: sign error 2024-06-07 16:39:06 +08:00
MystiPanda
02e860480b fix: sign error 2024-06-07 16:37:58 +08:00
Eric Huang
7737b8b596 feat: make SettingItem clickable (#1138)
* feat: make `SettingItem` clickable

* clean up
2024-06-07 15:51:51 +08:00
Sukka
2725322fd5 refactor: replace recoil (#1137) 2024-06-07 12:27:37 +08:00
wonfen
6c6ccda6b3 Style: modify proxy pages 2024-06-07 10:32:27 +08:00
MystiPanda
d71269e223 fix: sign error 2024-06-06 16:06:58 +08:00
wonfen
36266d2b10 chore: apple developer sign test 2024-06-06 15:21:53 +08:00
dongchengjie
acae62de87 chore: test_delay trace 2024-06-05 19:17:43 +08:00
dongchengjie
9fe4197cae chore: theme colors to uppercase 2024-06-05 14:20:24 +08:00
dongchengjie
7fa1a8d54a build: no legacy chunks 2024-06-05 09:04:08 +08:00
Sukka
2333271c20 fix(#1126): add Object.hasOwn polyfill (#1127) 2024-06-05 00:12:06 +08:00
dongchengjie
5b83149567 fix: csp missing asset: 2024-06-04 15:26:39 +08:00
MystiPanda
250a35baab build: update depends 2024-06-04 12:16:42 +08:00
MystiPanda
d60ba95532 Release 1.6.5 2024-06-04 12:11:38 +08:00
MystiPanda
c901472198 fix: package error 2024-06-04 10:47:17 +08:00
MystiPanda
2a5b70fb13 feat: support rpm package 2024-06-04 10:16:25 +08:00
MystiPanda
dc6db6e4b3 build: use latest tauri 2024-06-04 10:09:04 +08:00
Zhenfu Shi
8df6f32314 chore: Enable Ad Hoc signing on macOS (#1117)
Closes #1116
2024-06-03 09:01:33 +08:00
wonfen
a2d8c894fe Revert "chore: change default test url"
This reverts commit a7cf968d04fe9abede0dc4478dc7c03edc224389.
2024-06-03 08:25:06 +08:00
dongchengjie
a1996768f1 chore: use icons files instead of hard coding 2024-06-03 07:21:40 +08:00
dongchengjie
205587cb9e fix: group selected be overwritten when saving test 2024-06-03 06:38:20 +08:00
dongchengjie
224b2ef952 chore: improve UI in connections page 2024-06-02 21:52:31 +08:00
aixiao0621
90a83dc753 fix: a display error on the connections page 2024-06-02 17:26:23 +08:00
MystiPanda
a7cf968d04 chore: change default test url 2024-06-01 23:14:47 +08:00
MystiPanda
80ff72bae1 build: update depends 2024-06-01 23:01:27 +08:00
dongchengjie
5320fc8111 refactor: polyfills review 2024-05-30 20:27:12 +08:00
dongchengjie
8753531e82 fix: research when search box mode changes 2024-05-30 10:45:24 +08:00
dongchengjie
03a845f2b3 chore: Content-Security-Policy 2024-05-29 10:20:56 +08:00
dongchengjie
25b05f127d ci: fix heap oom 2024-05-29 09:49:11 +08:00
dongchengjie
073beb0135 build: polyfills 2024-05-29 09:39:26 +08:00
dongchengjie
ef7659691b build: import babel 2024-05-28 09:59:41 +08:00
dongchengjie
e69c0c079e fix: try to fix #1084 2024-05-27 23:19:44 +08:00
dongchengjie
dc31269a06 typo: inconsistent style in layout-viewer 2024-05-27 16:37:48 +08:00
MystiPanda
df9eccabea Release 1.6.4 2024-05-26 22:07:53 +08:00
MystiPanda
7788f5ae4c Revert "refactor: use axios tauri adapter"
This reverts commit 8092e5c3a8d030649b00554353e21a761052fd3e.
2024-05-26 21:18:02 +08:00
dongchengjie
ff5456c178 chore: %mixed-port% hint for PAC script 2024-05-26 19:53:42 +08:00
MystiPanda
40ed702437 Release 1.6.3 2024-05-26 19:43:26 +08:00
MystiPanda
65924e9a5d chore: add content type 2024-05-26 19:26:57 +08:00
MystiPanda
a88d149dad fix: auto proxy changed by guard 2024-05-26 19:07:14 +08:00
MystiPanda
b9ec94d835 feat: Support PAC Mode 2024-05-26 17:59:39 +08:00
dongchengjie
fc1675575a chore: emoji display support in editor 2024-05-25 18:02:32 +08:00
farzadhallaji
2f7229720f Update fa.json (#1060) 2024-05-24 19:39:40 +08:00
MystiPanda
0187fc7b22 ci: use rust 1.77.0 2024-05-23 11:47:41 +08:00
dongchengjie
540e1a9650 fix: no snippets and warnings in runtime config editor 2024-05-21 06:18:09 +08:00
dongchengjie
a371cd1d79 chore: update locale in connection 2024-05-20 13:42:45 +08:00
dongchengjie
16b11fee31 chore: text overflow word-wrap & cleanup 2024-05-20 12:38:41 +08:00
MystiPanda
1bc46f22b4 build: use compatible 2024-05-19 19:30:53 +08:00
MystiPanda
554c8fe163 fix: installer package error 2024-05-19 19:13:21 +08:00
MystiPanda
8f6bf6e002 build: use latest core 2024-05-19 13:00:27 +08:00
dongchengjie
d5dd8e9346 chore: schema 1.18.5-alpha 2024-05-19 12:37:18 +08:00
dongchengjie
4a67e1021a chore: fix select component bg in connection 2024-05-18 18:42:51 +08:00
dongchengjie
c97061770a chore: fix search-box bg 2024-05-18 18:20:05 +08:00
MystiPanda
55b331511e chore: support more asset scope 2024-05-18 15:47:45 +08:00
oomeow
4b9b5e861f perf: memoize the proxy col items (#1029) 2024-05-18 15:14:22 +08:00
oomeow
b8599a0642 fix: clipboard doesn't work and set_shadow method is not supported in Linux (#1030) 2024-05-18 15:14:00 +08:00
dongchengjie
ae6530585a feat: editor import PAC definition 2024-05-18 14:59:17 +08:00
dongchengjie
39aa1fa2a4 chore: hint for canceling fixed #840 2024-05-17 20:44:18 +08:00
dongchengjie
4f740acabd chore: use search-box in logs and connections 2024-05-17 19:44:42 +08:00
dongchengjie
2cc9b91895 chore: component base-search-box 2024-05-17 19:13:33 +08:00
dongchengjie
4eedc39e97 chore: fix editor dialog scroll overflow 2024-05-17 04:03:49 +08:00
dongchengjie
b99e8d7f46 fix: missing locale 2024-05-15 23:27:31 +08:00
dongchengjie
a8a27aeadd chore: current bypass wrap 2024-05-15 22:59:53 +08:00
MystiPanda
21176d2fd3 feat: support monochrome tray icon 2024-05-15 14:50:10 +08:00
dongchengjie
224c65438f chore: logo svg 2024-05-14 16:53:39 +08:00
dongchengjie
f1c21b642f feat: doc reference link on settings header 2024-05-14 14:40:47 +08:00
dongchengjie
030d1f374a chore: use coding fonts in editor 2024-05-14 14:20:50 +08:00
dongchengjie
0b29fa2288 fix: switch missing break 2024-05-14 01:24:23 +08:00
dongchengjie
b721f148f0 chore: @ts-ignore schema check 2024-05-14 01:21:28 +08:00
dongchengjie
63434a2f87 chore: revert schema to beta4 2024-05-14 00:59:05 +08:00
dongchengjie
0d6f0e66be chore: palette locale 2024-05-13 23:12:29 +08:00
dongchengjie
fb3f1365c5 typo: unused import 2024-05-13 22:59:24 +08:00
dongchengjie
8de7d5d377 feat: css injection editor 2024-05-13 22:58:25 +08:00
Remember
952d7494ac Update 172.16.0.0/12 on Windows (#1013) 2024-05-13 18:28:41 +08:00
MystiPanda
9aeba20086 fix: use default bypass when empty 2024-05-13 18:27:19 +08:00
MystiPanda
9150c9c40e chore: Adapt Mac icon 2024-05-13 13:11:05 +08:00
MystiPanda
3e75897154 docs: Update README 2024-05-12 22:17:05 +08:00
MystiPanda
b0aa4402c2 chore: Change HomePage Logo 2024-05-12 21:17:21 +08:00
MystiPanda
41f80bcafd chore: Try a new icon 2024-05-12 19:31:06 +08:00
dongchengjie
c67359c49d chore: ru locale 2024-05-12 16:18:49 +08:00
RikudouPatrickstar
2f7c3cf21e chore: update notification message and zh translation (#1011) 2024-05-12 14:40:18 +08:00
MystiPanda
9731c8a750 perf: Disable Tun mode before shutting down 2024-05-12 11:06:44 +08:00
MystiPanda
8092e5c3a8 refactor: use axios tauri adapter 2024-05-11 21:54:56 +08:00
MystiPanda
f7ab8cc471 feat: Allow disable unused ports 2024-05-09 09:22:32 +08:00
MystiPanda
b593f62c4f chore: Update locale 2024-05-08 23:45:55 +08:00
MystiPanda
6905b7a410 perf: Limit drawing frame rate 2024-05-08 22:33:13 +08:00
MystiPanda
402f27b2a3 fix: bundle error 2024-05-08 14:52:38 +08:00
MystiPanda
c9c46d05d0 build: fix bundle error 2024-05-08 14:20:30 +08:00
MystiPanda
6591575d22 fix: depend error 2024-05-08 13:24:34 +08:00
MystiPanda
6064119779 Release 1.6.2 2024-05-08 13:14:00 +08:00
MystiPanda
00cd9b581d feat: support upgrade for release core 2024-05-08 12:48:27 +08:00
MystiPanda
a3d7b72485 docs: Update FAQ URL 2024-05-08 11:44:57 +08:00
MystiPanda
88c73be2f4 ci: update ci 2024-05-08 11:12:06 +08:00
MystiPanda
39a9181cdd refactor: remove prepend and append for provider 2024-05-08 00:22:14 +08:00
MystiPanda
0e5c6f56a0 feat: deep merge
#983
2024-05-08 00:13:32 +08:00
MystiPanda
5147a070a1 build: Add portable for fixed webview2 2024-05-06 19:13:08 +08:00
MystiPanda
b11be1838a ci: debug ci 2024-05-06 16:16:47 +08:00
MystiPanda
be99768a32 ci: fix ci script 2024-05-06 15:30:00 +08:00
MystiPanda
fe439a0cb6 ci: update release body 2024-05-06 15:15:07 +08:00
MystiPanda
87f49ec879 docs: update url 2024-05-06 15:05:56 +08:00
MystiPanda
20f2730125 build: try support fixed webview2 runtime 2024-05-06 14:18:12 +08:00
MystiPanda
bc5b34db6b ci: try resupport x86 2024-05-06 13:04:29 +08:00
dongchengjie
bcf9df3744 fix: non-ascii character secret causes controller link error
https://github.com/clash-verge-rev/clash-verge-rev/issues/973#issuecomment-2094839700
2024-05-05 23:04:33 +08:00
MystiPanda
4d3674ee0a fix: enhance when change tun config 2024-05-04 16:12:10 +08:00
dongchengjie
28567e4629 chore: profile template typo 2024-05-04 14:24:11 +08:00
MystiPanda
1180a4fb0b fix: try to set env 2024-05-03 18:00:55 +08:00
dongchengjie
71928d2c9f fix: #974 2024-05-03 12:50:04 +08:00
MystiPanda
3853072a2e chore: update locale 2024-05-02 23:44:09 +08:00
dongchengjie
0cf630ef23 chore: add service mode locale 2024-05-02 22:16:05 +08:00
MystiPanda
202015fe34 feat: Support drag and drop local files 2024-05-02 20:41:43 +08:00
MystiPanda
ae43e5cae4 fix: change script execution path 2024-05-01 11:39:09 +08:00
MystiPanda
67b67bae6a Release 1.6.1 2024-04-30 23:43:51 +08:00
MystiPanda
dbb8fe15cf fix: MacOS tray click 2024-04-30 23:21:25 +08:00
lxk955
56efa10f64 feat: Support for using regular expressions on the log page #707 (#959) 2024-04-30 22:59:42 +08:00
MystiPanda
5ff776f90d fix: service install path 2024-04-30 20:20:39 +08:00
MystiPanda
a25b072bf6 build: Restore core version 2024-04-30 09:42:25 +08:00
dongchengjie
c95951c0e4 fixup: wrong version 2024-04-30 02:13:24 +08:00
dongchengjie
4c8193b801 chore: update pnpm-lock.yaml (#952)
* chore: update schema version

* Update pnpm-lock.yaml
2024-04-30 01:44:05 +08:00
dongchengjie
598a544ff8 chore: update schema version (#950) 2024-04-29 22:46:29 +08:00
MystiPanda
465ef3fa9a chore: Config issue template 2024-04-29 15:43:43 +08:00
MystiPanda
2f876d93e3 docs: Update FAQ URL 2024-04-28 20:04:03 +08:00
MystiPanda
84e8c44e4f build: cargo update 2024-04-28 19:59:46 +08:00
MystiPanda
855d794bdb fix: Open the link with browser 2024-04-28 18:46:49 +08:00
dongchengjie
a3333f8fe1 fixup: can't edit file (#943) 2024-04-28 17:00:33 +08:00
dongchengjie
2e64d62ca4 chore: disable WebView keyboard shortcuts (#942) 2024-04-28 16:19:13 +08:00
lxk955
26a3dbcbe1 feat: support display current profile when the mouse is placed on the tray icon #782 (#938) 2024-04-27 20:54:02 +08:00
PlayerNeo
fa2e86df29 fix: disable left click menu on macOS (#930) 2024-04-27 20:52:29 +08:00
LiuTianYu
b4f0ece78f fix: #730 icon not change when the window is opened maximized (#924)
Co-authored-by: tyliu9 <tyliu9@toycloud.com>
2024-04-24 22:36:15 +08:00
wonfen
630b319a37 Release 1.6.0 2024-04-23 14:25:09 +08:00
MystiPanda
4a37e49798 feat: support ico format for tray icon (#911) 2024-04-22 20:43:15 +08:00
dongchengjie
cfbe98a39a fix: #907 (#908) 2024-04-22 01:40:25 +08:00
dongchengjie
91f097d514 chore: update locale (#904)
* chore: missing locale

* chore: External Controller locale
2024-04-21 14:10:34 +08:00
dongchengjie
c545521cd9 chore: Proxy Bypass placeholder (#901) 2024-04-20 19:27:17 +08:00
dongchengjie
11e0f49ada fix: minor glitches (#900)
* feat: show actual proxy name instead of proxy group when hovering on a group outbound

* fix: open empty edit form and save will cause `UID not found`

* chore: tauri.conf.json json schema

* chore: missing locales
2024-04-20 18:02:15 +08:00
MystiPanda
19ce53128b fix: startup script blocking 2024-04-20 17:52:54 +08:00
MystiPanda
deccff623a fix: default value for tun 2024-04-20 17:42:36 +08:00
MystiPanda
b2a210ec0d build: add depends 2024-04-19 15:55:46 +08:00
MystiPanda
bdb5169a6f chore: update lock file 2024-04-19 15:38:32 +08:00
dongchengjie
0865b702a3 fix: 使用npm安装meta-json-schema (#895)
* feat: allow manual selection of url-test group

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

* fixup: group delay test use defined url

* feat: connections sorted by start by default

* feat: Connection details show the full path of the process

* fix: editor no hints and add yaml support

* feat: quick suggestions

* chore: use monaco-editor

* chore: update schema url

* chore: change default merge config content

* fix: load schema via npm

* feat: runtime config viewer style auto adjust

* feat: adjust fixed proxy style

* fix: headState "showType" won't toggle hover text

* chore: switch version

* chore: Update pnpm lockfile
2024-04-19 13:54:16 +08:00
MystiPanda
d13b8fd486 refactor: change default value of mtu 2024-04-18 10:27:09 +08:00
dongchengjie
494911805e chore: 修改Merge配置文件默认内容 (#889) 2024-04-17 22:56:54 +08:00
MystiPanda
cba3a2be24 chore: Update pnpm lockfile 2024-04-17 22:16:50 +08:00
dongchengjie
0686781359 feat: Clash配置、Merge配置提供JSON Schema语法支持、[连接]界面调整 (#887) 2024-04-17 21:19:37 +08:00
MystiPanda
e5b82dca4d fix: SymbolicLink
#750
2024-04-13 15:46:11 +08:00
MystiPanda
2144a42a22 fix: service install path 2024-04-13 15:12:41 +08:00
汐殇
07a989e004 fix: Allow core files in specific directories to be upgraded normally (#857) 2024-04-12 01:16:57 +08:00
dongchengjie
4f7e8116cb feat: url-test支持手动选择、节点组fixed节点使用角标展示 (#840)
* feat: allow manual selection of url-test group

* feat: fixed proxy indicator

* fix: try to fix traffic websocket no longer updating

* fixup: group delay test use defined url
2024-04-09 13:15:45 +08:00
MystiPanda
c0f650d7dc fix: Update the home while updating profile 2024-04-04 15:07:55 +08:00
MystiPanda
d4a0136504 fix: Use System Browser 2024-04-04 14:56:10 +08:00
Damian Johnson
5f25e027c4 style: adjust confirm dialog & web ui settings dialog (#821) 2024-04-03 23:19:00 +08:00
Damian Johnson
9610dcce20 fix: service mode install script download not found (#822) 2024-04-03 23:18:10 +08:00
dongchengjie
3ee3e7c17b feat: support URL Schema 'profile-web-page-url' (#816) 2024-04-01 19:28:28 +08:00
HZ is not Chatty
98536250bd fixup! feat: Service Mode for Linux (#804) (#815)
* fixup! feat: Service Mode for Linux (#804)

* fixup! feat: Service Mode for Linux (#804)

* Partially revert "fixup! feat: Service Mode for Linux (#804)"

This reverts commit e6a5a2b4961dba4e891b1b62d6f35db4ca9ee5ce.
2024-04-01 19:25:58 +08:00
MystiPanda
e95808e6be feat: Try support service mode for MacOS 2024-03-31 23:16:47 +08:00
MystiPanda
9fc819a410 fix: script error 2024-03-31 16:37:33 +08:00
HZ is not Chatty
b0f1ce1fa0 feat: Service Mode for Linux (#804) 2024-03-31 16:16:23 +08:00
wonfen
503579a638 Sytle: fix mac logo padding 2024-03-30 16:31:17 +08:00
Damian Johnson
3ad216751a fix: icon not change when toggle window maxinized (#799) 2024-03-30 01:16:40 +08:00
cismous
ca8e3179bb Style filter input (#724)
* refactor: reduce duplicate code

* style: add a white background to the light color theme to avoid the gray text being too light
2024-03-30 01:14:03 +08:00
Damian Johnson
fd84e56c00 fix: missing proxy group delay check animations (#788)
* fix: missing proxy group delay check animation

* chore: cleanup

* chore: adjust content style
2024-03-29 08:20:31 +08:00
wonfen
8b67fb7290 Release 1.5.11 2024-03-28 12:06:41 +08:00
MystiPanda
2d1fdb319d feat: Support Persian
#715
2024-03-21 20:51:33 +08:00
MystiPanda
c200e18434 fix: Duplicate icon display error
#719
2024-03-21 20:29:16 +08:00
MystiPanda
b3ffcd020f fix: The update button cannot be clicked
#716
2024-03-21 19:56:30 +08:00
MystiPanda
9258a3dcd4 fix: Check if the install directory is empty when uninstall 2024-03-21 15:24:12 +08:00
MystiPanda
57f5478731 chore: fix typo 2024-03-21 14:09:59 +08:00
MystiPanda
973d75ebdd Release 1.5.10 2024-03-21 13:47:39 +08:00
MystiPanda
33519b27c8 chore: Change a little 2024-03-21 12:55:22 +08:00
MystiPanda
7da7ff4a69 build: Update depends 2024-03-21 11:53:25 +08:00
MystiPanda
dbd2f697f9 fix: a little 2024-03-21 11:43:16 +08:00
MystiPanda
f435762b88 feat: Confirm before deletion
#703
2024-03-21 11:39:01 +08:00
MystiPanda
ae46332e42 feat: Support config redir port and tproxy port 2024-03-21 10:54:56 +08:00
MystiPanda
d003883de9 chore: limit port config
#699
2024-03-20 21:23:10 +08:00
MystiPanda
4e438a44f1 fix: Limit icon width
#697
2024-03-20 20:38:45 +08:00
MystiPanda
02e19b3d44 fix: service logs are not cleared
#695
2024-03-20 20:31:00 +08:00
MystiPanda
ccc19512e7 docs: Improve the issue template 2024-03-20 18:06:02 +08:00
MystiPanda
c34539e389 feat: Optimize Linux tray menu 2024-03-18 11:11:18 +08:00
MystiPanda
f8aeacb949 Revert "fix: remove activation policy" 2024-03-18 09:53:48 +08:00
MystiPanda
9940190679 Release 1.5.9 2024-03-17 11:25:25 +08:00
MystiPanda
e2498b3e91 fix: drag error
#643
2024-03-16 20:37:39 +08:00
MystiPanda
82246fd9c7 fix: drag error 2024-03-16 17:06:59 +08:00
MystiPanda
11538552eb fix: Try to fix touch drag
#456
2024-03-16 10:54:19 +08:00
MystiPanda
4ce28f54de build: Update to Tauri 1.6.1 2024-03-16 00:03:34 +08:00
MystiPanda
e887ed74a3 ci: update alpha script 2024-03-15 21:52:56 +08:00
MystiPanda
28c086e97c ci: remove 2024-03-15 21:42:55 +08:00
MystiPanda
11465e89a3 feat: Try to support more architecture 2024-03-15 21:14:05 +08:00
MystiPanda
b2197187c1 refactor: Try to migrate to boa_engine
#634
2024-03-15 19:58:22 +08:00
MystiPanda
2b26a10745 fix: Avoid empty user-agent 2024-03-15 18:27:24 +08:00
MystiPanda
daf726ebbf feat: Try to cache remote images
#603
2024-03-15 16:43:39 +08:00
screw-hand
88dd886687 fix: mac setting theme font-famil not working (#632) 2024-03-15 15:17:05 +08:00
MystiPanda
609da457f7 fix: remove activation policy
#592
2024-03-15 12:44:25 +08:00
xkww3n
363e28ff69 fix: Set REJECT-DROP policy the same text color as REJECT (#622) 2024-03-14 18:54:26 +08:00
MystiPanda
bdd6bf9020 Release 1.5.8 2024-03-13 14:17:06 +08:00
wonfen
5c3dab3466 Sytle: A little tweak 2024-03-13 14:09:08 +08:00
MystiPanda
9b2c8fa25d feat: Add border-radius for window on linux 2024-03-13 13:58:29 +08:00
Amnesiash
df2f102d9e update profile ui (#594) 2024-03-13 10:53:39 +08:00
MystiPanda
95ebb0e6d2 fix: Try to fix #577 again 2024-03-11 22:52:29 +08:00
MystiPanda
e5d03652a9 fix: script 2024-03-11 22:10:20 +08:00
MystiPanda
56b53e2dd8 fix: typo 2024-03-11 22:05:56 +08:00
MystiPanda
cf0b7b213f fix: Try to fix #577 2024-03-11 22:03:45 +08:00
MystiPanda
d214c8e01b feat: Allow open devtools 2024-03-11 20:19:21 +08:00
MystiPanda
fba0f362a5 docs: Update Readme 2024-03-11 18:38:58 +08:00
MystiPanda
ec05d0857c Release 1.5.7 2024-03-11 17:09:01 +08:00
MystiPanda
54b744b7de fix: display error 2024-03-11 16:18:39 +08:00
MystiPanda
1a76780fff chore: Change IP 2024-03-11 16:05:09 +08:00
MystiPanda
58f5c44533 fix: a little 2024-03-11 15:02:41 +08:00
MystiPanda
d085da4dbf fix: Change DNS for MacOS Tun Mode
#568
2024-03-11 14:55:00 +08:00
MystiPanda
c4a5c356f7 fix: default value 2024-03-11 13:17:45 +08:00
MystiPanda
18fdc5c6a2 fix: Try to fix touch drag
#456
2024-03-11 12:36:20 +08:00
MystiPanda
35dabaab9c feat: Allow to control whether auto check update 2024-03-11 12:17:46 +08:00
MystiPanda
9bf31b10bb style: fix a little 2024-03-10 23:56:04 +08:00
MystiPanda
7186575cb1 style: fix styles 2024-03-10 23:47:12 +08:00
MystiPanda
2ecae40130 fix styles 2024-03-10 22:13:25 +08:00
MystiPanda
778ed62a90 chore: proxy group header height 2024-03-10 21:56:49 +08:00
MystiPanda
5e863a87dc chore: adjust proxy group font style 2024-03-10 21:52:40 +08:00
MystiPanda
7ce8597c25 chore: Adjust Profile Style 2024-03-10 21:37:52 +08:00
MystiPanda
1992237ce5 chore: Add Default value 2024-03-10 21:15:22 +08:00
MystiPanda
8f247b0f73 style: Adjust icon color 2024-03-10 20:48:13 +08:00
Amnesiash
e2159d80af style: Adjust colorful icon (#558)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-10 20:37:00 +08:00
MystiPanda
057023531e chore: i18n 2024-03-10 20:33:27 +08:00
MystiPanda
dfed65bf9f chore: Update Icon 2024-03-10 20:14:51 +08:00
MystiPanda
739161849a feat: Add option to control menu icon 2024-03-10 19:54:47 +08:00
MystiPanda
c65b280020 chore: Adjust styles 2024-03-10 13:24:38 +08:00
MystiPanda
d3bcf25ef0 Revert "chore: Adjust secondary text style (#545)"
This reverts commit f6bd3340e74a8ac4f30d3c8c2997a9935db6803a.
2024-03-10 13:08:09 +08:00
Amnesiash
f6bd3340e7 chore: Adjust secondary text style (#545) 2024-03-10 12:54:34 +08:00
MystiPanda
6a7c09bfe3 style: Adjust delay fontSize
#544
2024-03-10 12:51:53 +08:00
MystiPanda
8b9f294a5d docs: update preview 2024-03-10 12:32:08 +08:00
MystiPanda
4a282d9629 chore: Change Default Font 2024-03-10 12:23:11 +08:00
MystiPanda
909b88864f chore: delete debug output 2024-03-10 11:47:55 +08:00
MystiPanda
1346a7992c Release 1.5.6 2024-03-10 11:43:37 +08:00
MystiPanda
bba607e987 feat: Add default webui
#530
2024-03-10 11:41:22 +08:00
MystiPanda
f9cc490c35 Adjust styles 2024-03-10 11:21:17 +08:00
MystiPanda
a5aec2d9fa Adjust styles 2024-03-10 11:12:54 +08:00
wonfen
c0df368dc6 Sytle: UI improvement & Update Readme 2024-03-10 07:00:24 +08:00
MystiPanda
aa77433523 fix: fix a little 2024-03-10 00:41:18 +08:00
black23eep
44ad99f693 Update layout-traffic.tsx (#528) 2024-03-10 00:35:26 +08:00
MystiPanda
b7f7a82ea9 chore: update 2024-03-10 00:34:22 +08:00
MystiPanda
c69978c9fd fix: fontSize and some styles 2024-03-10 00:22:22 +08:00
Amnesiash
6174aa6ee1 fix settings.svg (#526) 2024-03-10 00:04:06 +08:00
MystiPanda
74b8d2e908 chore: center 2024-03-09 23:38:03 +08:00
Charles
63a515944f tweak(ui): menu icon use svg component (#524) 2024-03-09 23:13:08 +08:00
MystiPanda
5b5db7b860 fix: img path error 2024-03-09 22:43:53 +08:00
MystiPanda
b3bbacf2ef Release 1.5.5 2024-03-09 22:02:36 +08:00
Amnesiash
3a0429d049 refactor: Upgrade to the new UI (#521)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-09 21:37:21 +08:00
wonfen
ab539081fa UI: change paste icon, delete default profile name 2024-03-09 02:34:33 +08:00
Pylogmon
f0d88d4e73 feat: Merge Providers (#508) 2024-03-08 11:37:52 +08:00
MystiPanda
772cbd6ffd feat: Add animation for provider update 2024-03-02 13:52:48 +08:00
MystiPanda
17b9dbe9d7 chore: fix style 2024-02-28 18:25:50 +08:00
MystiPanda
75e5d42d8b feat: Try to support touch drag 2024-02-27 23:41:52 +08:00
MystiPanda
031c15fd7d chore: Remove unnecessary hotkey 2024-02-27 11:18:52 +08:00
MystiPanda
de1924cefc fix: Image display failed on Linux 2024-02-26 13:31:51 +08:00
Cyenoch
66db0a4751 Feat: Provide a switch for allowing invalid certificates (#450) 2024-02-25 16:07:06 +08:00
MystiPanda
c309410965 chore: change style 2024-02-24 15:39:29 +08:00
MystiPanda
7f461b99e2 Release 1.5.4 2024-02-24 14:00:01 +08:00
MystiPanda
7bfe0eeae9 feat: Support Open/Close Dashboard Hotkey
#439
2024-02-24 13:39:30 +08:00
MystiPanda
8619bd5be3 feat: Show current proxy for group node
#444
2024-02-24 13:09:53 +08:00
MystiPanda
56011d37d4 feat: allow disable group icon 2024-02-24 12:38:17 +08:00
MystiPanda
c2852c8a82 refactor: Optimize implementation of Custom tray icon 2024-02-24 11:25:22 +08:00
MystiPanda
6f546a424e feat: Support Custom Tray Icon 2024-02-24 00:52:21 +08:00
MystiPanda
447f7530af Release 1.5.3 2024-02-22 00:23:56 +08:00
MystiPanda
6136f1206b feat: add reset button 2024-02-22 00:19:45 +08:00
MystiPanda
36a3c5b501 fix: Do not set autolaunch at init
#423 #424
2024-02-21 23:50:50 +08:00
MystiPanda
dcd6c1f522 chore: rm -m arg 2024-02-21 23:38:01 +08:00
MystiPanda
2b074bcdcb chore: change default value of dns hijack 2024-02-21 16:40:47 +08:00
MystiPanda
b20ec7f0eb chore: change default value of strict route 2024-02-21 11:32:51 +08:00
MystiPanda
58cf69a2fe chore: fix placeholder 2024-02-21 11:13:28 +08:00
MystiPanda
096c148228 chore: Add Translation 2024-02-21 11:06:32 +08:00
MystiPanda
a68005d4ab feat: Disable system stack when service mode is turned off 2024-02-21 10:52:03 +08:00
MystiPanda
bf3a281987 fix: Config data display error
#417
2024-02-21 10:02:28 +08:00
MystiPanda
2965a6827d Release 1.5.2 2024-02-21 00:04:11 +08:00
MystiPanda
9f43a73c36 chore: Auto Update config.yml 2024-02-20 23:54:02 +08:00
MystiPanda
7551b45da2 feat: Support Tun Config (#416) 2024-02-20 23:27:03 +08:00
MystiPanda
bca3685eda chore: Update patch file 2024-02-20 18:39:36 +08:00
MystiPanda
6d20175800 fix: Allow program run with administrator to start at startup 2024-02-20 18:35:39 +08:00
MystiPanda
a62dd4c020 chore: add tooltip for tun mode 2024-02-19 18:10:10 +08:00
MystiPanda
d1d9620a61 feat: Support custom delay timeout (#397) 2024-02-18 11:11:22 +08:00
MystiPanda
5106d77c77 build: Try restart windows service after install (#395) 2024-02-18 10:19:54 +08:00
MystiPanda
69cf237d7a fix: Merge profile unexpect behavior 2024-02-17 15:58:52 +08:00
MystiPanda
7d9d1c82b6 ci: Update Release Note 2024-02-15 20:05:00 +08:00
MystiPanda
228ff5edf4 ci: change tag 2024-02-15 19:45:37 +08:00
MystiPanda
2baac618a8 chore: update ci script 2024-02-15 19:14:35 +08:00
MystiPanda
e8f499a938 chore: Change CI Name 2024-02-15 18:45:13 +08:00
MystiPanda
96ca20d0b4 ci: Support alpha package 2024-02-15 18:43:20 +08:00
MystiPanda
54acdc86e7 chore: remove 32bit package 2024-02-15 18:22:15 +08:00
MystiPanda
0815e02895 chore: default show proxy detials 2024-02-15 18:17:21 +08:00
wonfen
30cbb72b57 doc: remove 32bit package 2024-02-12 02:25:18 +08:00
MystiPanda
0b91397709 fix: build error 2024-02-11 20:51:00 +08:00
MystiPanda
89934c17ca Release v1.5.1 2024-02-11 20:32:25 +08:00
MystiPanda
e4a38e62eb style: clear script 2024-02-11 20:03:14 +08:00
MystiPanda
5e1e09d7bf fix: Custom GLOBAL group display error 2024-02-11 18:27:27 +08:00
MystiPanda
51ce3a1e42 feat: Show proxies count for provider 2024-02-10 13:13:27 +08:00
MystiPanda
54b46dfad9 feat: Save window maximize state 2024-02-10 12:51:30 +08:00
MystiPanda
787917ac66 style: change config name
#350
2024-02-08 10:23:54 +08:00
MystiPanda
619b49bdc4 fix: Script profile invalid
#347
2024-02-06 08:55:44 +08:00
MystiPanda
4b3a73d440 Release 1.5.0 2024-02-06 00:41:12 +08:00
MystiPanda
54f9c59d6e refactor: Remove clash field filter 2024-02-05 17:28:36 +08:00
wonfen
1d123996f6 chore: enable clash field TLS fingerprint 2024-02-04 13:07:25 +08:00
MystiPanda
c4768f6138 fix: Try to fix traffic parse error
#337
2024-02-04 10:24:37 +08:00
wonfen
5f3551ff34 chore: update changelog 2024-02-03 16:14:26 +08:00
MystiPanda
d3985c2e3b fix: Copy Env Type Select Error 2024-02-02 17:57:56 +08:00
MystiPanda
3b1843f3a3 chore: typo 2024-02-02 17:47:28 +08:00
MystiPanda
2c36796362 chore: Update Changelog 2024-02-02 17:34:15 +08:00
MystiPanda
655ccba89b fix: prevent_exit 2024-02-02 17:26:31 +08:00
MystiPanda
150d72f0f8 Release 1.4.11 2024-02-02 16:56:39 +08:00
MystiPanda
6a316b34a2 fix: exit_app event 2024-02-02 16:32:19 +08:00
MystiPanda
8e6b600609 chore: fix styles 2024-02-02 16:15:23 +08:00
MystiPanda
5630a4dd67 chore: Remove prevent_close 2024-02-02 15:57:03 +08:00
MystiPanda
38ee8aedc1 fix: Fix Nisi Error 2024-02-02 15:53:28 +08:00
ycjcl868
d594615532 chore: unused clash core (#284) 2024-01-23 11:22:15 +08:00
MystiPanda
2739fa60be chore: fix style 2024-01-21 13:49:07 +08:00
MystiPanda
327301782d revert: some style 2024-01-21 13:49:07 +08:00
wonfen
8781c5db8d Sytle: UI improvement 2024-01-20 16:05:01 +08:00
Lai Zn
31c34ea158 fix: Solve the confliction issues on auto updater failure (#273) 2024-01-20 13:03:48 +08:00
MystiPanda
ef9bbaca19 revert: Support both registry and api for windows sysproxy 2024-01-20 12:16:46 +08:00
MystiPanda
02d89072bc build: Update lock file 2024-01-18 22:34:14 +08:00
MystiPanda
4e8bd640a7 Release v1.4.10 2024-01-18 22:26:23 +08:00
MystiPanda
8c71a00600 feat: Add exit button on setting page 2024-01-18 22:19:14 +08:00
MystiPanda
3d36c70d53 build: Update Depends 2024-01-18 16:26:38 +08:00
MystiPanda
c72479c4d6 feat: Support Custom Start Page 2024-01-18 15:37:02 +08:00
Lai Zn
4bb88d8e44 feat: Use url path name as fallback subscription name (#255) 2024-01-18 14:36:37 +08:00
MystiPanda
0ee0958539 feat: Show SubInfo for Proxy Provider
#211
2024-01-18 14:26:57 +08:00
Lai Zn
b6dd6f3a94 fix: make port change set to system proxy immediately (#256) 2024-01-18 09:37:46 +08:00
MystiPanda
d11c322e1f fix: use nanoid to compatible with old devices 2024-01-18 01:16:39 +08:00
MystiPanda
cd92b34ef1 feat: Optimizing Provider Support 2024-01-18 01:02:30 +08:00
Kiri
b6481cfcda feat: Add support for loong64 (#246) 2024-01-17 22:30:51 +08:00
Lai Zn
db7eb92638 docs: Add guidelines for windows development (#250) 2024-01-17 19:02:39 +08:00
MystiPanda
f2d0477550 fix: Do not use proxy when test on tun mode 2024-01-17 18:08:09 +08:00
MystiPanda
776e207f09 Release v1.4.9 2024-01-17 15:43:15 +08:00
MystiPanda
733e8a0043 feat: Support Startup Script 2024-01-17 15:06:16 +08:00
MystiPanda
4fa19006ad feat: Support proxy group icon 2024-01-17 13:32:56 +08:00
MystiPanda
73a597e3e5 fix: Fix connection table sort error
#108
2024-01-17 12:54:54 +08:00
MystiPanda
d776f1765d chore: Update I18n 2024-01-17 11:30:19 +08:00
MystiPanda
741b6f6f9a fix: csp error 2024-01-17 11:08:14 +08:00
MystiPanda
b6f4695bcd feat: Add Test Page 2024-01-17 11:02:17 +08:00
MystiPanda
b7d3b807d2 fix: fix column width 2024-01-16 19:02:38 +08:00
MystiPanda
0cc386bc28 Release v1.4.8 2024-01-16 15:36:04 +08:00
MystiPanda
cb155707cd feat: Support both registry and api for windows sysproxy 2024-01-16 15:11:53 +08:00
MystiPanda
9b6b250cbd fix: Can not use specify update time when create profile 2024-01-16 10:29:04 +08:00
MystiPanda
5b7e29b8ad refactor: rm theme blur 2024-01-15 17:13:55 +08:00
MystiPanda
b6d748b414 Revert Use Tauri Http Api 2024-01-15 10:18:04 +08:00
MystiPanda
8fc4b338c2 Revert Use Tauri Websocket 2024-01-15 10:17:00 +08:00
MystiPanda
6d3ea19ac5 fix: Fix connections sort issue and add total traffic info 2024-01-15 00:46:19 +08:00
MystiPanda
d01ef48bf0 refactor: Use Tauri Http API 2024-01-14 19:35:03 +08:00
MystiPanda
71103bb7b9 refactor: Use Tauri WebSocket 2024-01-14 17:30:18 +08:00
MystiPanda
1c9bc00acc fix patch error 2024-01-11 14:47:25 +08:00
MystiPanda
bed128e8cf Release v1.4.7 2024-01-11 13:19:05 +08:00
MystiPanda
b5c3f18f24 feat: Disable updater for portable 2024-01-11 12:44:30 +08:00
MystiPanda
cbccdf5d93 feat: Support hide group
#214
2024-01-11 12:34:05 +08:00
Morris Li
a46f3a31e1 Fix expired Tauri domain (#222) 2024-01-11 10:17:56 +08:00
MystiPanda
a808f7b04e build: Use old sysproxy 2024-01-10 19:24:14 +08:00
MystiPanda
3a883b9e41 refactor: cargo clippy 2024-01-10 17:36:35 +08:00
MystiPanda
17d8691300 refactor: Optimizing the implementation of Linux URL Scheme registration 2024-01-10 16:34:35 +08:00
MystiPanda
523ce1dbdd fix: resolve scheme error 2024-01-10 15:37:40 +08:00
MystiPanda
4a3a9bf62c fix: linux build error 2024-01-10 15:22:08 +08:00
MystiPanda
3b30177959 feat: Support URL Scheme for MacOS 2024-01-10 14:04:06 +08:00
MystiPanda
7e66f89260 feat: Support URL Scheme for Linux 2024-01-10 13:03:34 +08:00
MystiPanda
1a93ba634f build: fix macos build script 2024-01-09 22:49:11 +08:00
MystiPanda
d93f823fc6 fix patch error 2024-01-09 22:25:25 +08:00
MystiPanda
f2198bf938 Release v1.4.6 2024-01-09 22:17:29 +08:00
MystiPanda
965f10698b feat: Support URL Scheme for Windows
#165
2024-01-09 21:57:06 +08:00
MystiPanda
b71367cd2a feat: Optimize control button style 2024-01-09 16:04:56 +08:00
MystiPanda
abe18ac825 feat: Add pin button 2024-01-09 15:13:59 +08:00
MystiPanda
bb193d3768 fix: Fix some compile error 2024-01-09 14:52:43 +08:00
wonfen
7a7f5cd4a8 Style: UI improvement & 1.4.6 ready 2024-01-09 13:57:53 +08:00
wonfen
ac9f49f8c9 chore: UI adjustment 2024-01-08 19:32:21 +08:00
MystiPanda
8f53859e00 chore: Use Latest and compatible core 2023-12-23 12:09:19 +08:00
MystiPanda
bfb7ff88d9 fix: Cargo clippy 2023-12-21 16:49:21 +08:00
MystiPanda
c36425fd3a fix: Get filename error
#165
2023-12-19 19:52:13 +08:00
MystiPanda
981f9d0b01 fix: portable flag 2023-12-15 21:39:34 +08:00
MystiPanda
fa89fe3e87 chore: update service url 2023-12-15 20:38:17 +08:00
MystiPanda
d132357c20 fix: user-agent version error 2023-12-15 15:18:01 +08:00
MystiPanda
a719237556 fix: Subinfo parse error 2023-12-15 11:35:10 +08:00
MystiPanda
8955ca5216 Release 1.4.5 2023-12-14 17:40:48 +08:00
MystiPanda
f665762cc8 fix: Window control button icon issue
#136
2023-12-14 14:59:55 +08:00
MystiPanda
d16fc2b68b chore: Remove unsafe function 2023-12-14 13:56:51 +08:00
MystiPanda
e1df32c32d fix: icon size 2023-12-14 13:20:01 +08:00
MystiPanda
943c6f77dc feat: Update MacOS tray icon 2023-12-14 13:11:46 +08:00
MystiPanda
4964382966 chore: Optimize service path 2023-12-14 13:03:52 +08:00
MystiPanda
021c6fdbe2 chore: Optimize upgrade process 2023-12-14 11:24:26 +08:00
MystiPanda
711f9805c9 chore: default enable clash field filter 2023-12-14 10:49:58 +08:00
MystiPanda
0aaae3afd6 chore: Delete unnecessary drag area 2023-12-13 11:09:19 +08:00
MystiPanda
eadd1042fb chore: Change service path 2023-12-13 10:59:07 +08:00
MystiPanda
16fa2c9f5e fix: Save wrong window size 2023-12-12 16:42:19 +08:00
MystiPanda
d4ab1df870 Revert "fix: Change PID file path"
This reverts commit c5855119d8c97d2eee7c66c9ec115c6d3999e36a.
2023-12-12 15:59:00 +08:00
MystiPanda
4a5aa1bcc1 fix: Can't switch env type 2023-12-11 10:51:59 +08:00
MystiPanda
01a9cda99a build: fix update issue 2023-12-10 22:24:46 +08:00
MystiPanda
dcdf606ff6 build: fix patch 2023-12-10 21:40:57 +08:00
MystiPanda
ea021de5eb v1.4.4 2023-12-10 21:36:22 +08:00
MystiPanda
5cc3526f8f feat: Support windows aarch64 (#112) 2023-12-10 20:45:27 +08:00
MystiPanda
3060fc2af4 feat: Patch for windows aarch64 2023-12-10 19:50:01 +08:00
MystiPanda
e4395dfeb4 build: Update Depends 2023-12-10 17:14:48 +08:00
MystiPanda
f65dadf1d7 chore: Hide script mode for alpha core 2023-12-10 16:31:55 +08:00
MystiPanda
f048762fd9 Support upgrade alpha core 2023-12-10 15:57:10 +08:00
MystiPanda
bb985f826e feat: Support update geodata
#37
2023-12-10 15:22:04 +08:00
zclkkk
bb239cba2a chore: Allow user to choose where to install (#110) 2023-12-10 09:50:43 +08:00
MystiPanda
e7e66e580a chore: Remove script mode 2023-12-09 17:04:03 +08:00
MystiPanda
689d689d3b docs: Update ISSUE_TEMPLATE 2023-12-09 12:00:01 +08:00
MystiPanda
125c3d3e0d feat: Support different tray icon for macos 2023-12-09 11:22:02 +08:00
MystiPanda
ffaa06560e feat: Support different tray icon for linux 2023-12-09 11:03:19 +08:00
Pylogmon
f9b716201f feat: Optimize copy environment variable logic (#106) 2023-12-08 22:16:42 +08:00
MystiPanda
8b14a5f0d8 perf: Improves window creation speed 2023-12-08 13:10:35 +08:00
MystiPanda
c5855119d8 fix: Change PID file path
#99
2023-12-08 11:59:40 +08:00
MystiPanda
a036597f5f docs: Update README 2023-12-07 16:49:59 +08:00
MystiPanda
b8688e2e66 feat: Add AppImage for x86 linux 2023-12-07 16:38:39 +08:00
MystiPanda
d8b2e08717 ci: fix build script 2023-12-07 16:17:34 +08:00
MystiPanda
7da78d3312 chore: Fix build error 2023-12-07 16:02:29 +08:00
MystiPanda
2292b107dc ci: Fix Linux CI Script 2023-12-07 15:57:53 +08:00
MystiPanda
1fcc74c658 ci: Fix Linux Build Script 2023-12-07 15:34:49 +08:00
MystiPanda
73be027951 chore: change default port to 7897 2023-12-07 14:52:14 +08:00
MystiPanda
132d91c2ab refactor: fix depends 2023-12-07 14:44:44 +08:00
MystiPanda
f08ce82c3f refactor: Remove unnecessary changes 2023-12-07 13:43:53 +08:00
MystiPanda
8de2712652 Revert "chore: change default port to 7897"
This reverts commit 55835785c095094c841db75934ca1b685c625c1b.
2023-12-07 13:32:49 +08:00
wonfen
55835785c0 chore: change default port to 7897 2023-12-06 03:37:16 +08:00
MystiPanda
cb711b7758 docs: Update README 2023-12-05 17:02:54 +08:00
MystiPanda
19c75293bf docs: UPDATELOG 2023-12-05 16:47:42 +08:00
MystiPanda
33219f00d5 Release 1.4.3 2023-12-05 16:28:50 +08:00
MystiPanda
4aaedbb0e6 chore: fix appid 2023-12-05 15:41:13 +08:00
MystiPanda
ee1f14d4eb refactor: Change app_home to standard locations of app_data 2023-12-05 15:39:44 +08:00
MystiPanda
feac8085c9 fix: Stop core before install update 2023-12-05 15:01:15 +08:00
MystiPanda
0a5ec11b1b docs: Update README 2023-12-05 13:33:23 +08:00
MystiPanda
03937174e5 refactor: Remove page animation 2023-12-05 13:30:55 +08:00
MystiPanda
e00529c05f refactor: Change Tun Icon Color 2023-12-05 13:23:54 +08:00
MystiPanda
5dbf8e1d2b build: Dependency downgrade 2023-12-05 12:55:20 +08:00
Pylogmon
bebf672186 refactor: Change Portable Config Path (#66) 2023-12-05 12:52:26 +08:00
Pylogmon
6d4a8f2a5a refactor: Hide Clash Field Option when Disable Filter (#63) 2023-12-05 08:34:57 +08:00
MystiPanda
14db0ae663 Update README.md 2023-12-04 14:51:46 +08:00
MystiPanda
88d1c1d140 ci: fix updater 2023-12-04 13:51:46 +08:00
MystiPanda
9106055be1 chore: Use Nsis 2023-12-04 13:45:28 +08:00
Pylogmon
2cf52f15ab fix: Open File (#56) 2023-12-04 12:15:12 +08:00
Pylogmon
b7e9d61c72 fix: Get Profile Filename (#54) 2023-12-04 12:15:01 +08:00
MystiPanda
0bc22db296 chore: change linux meta core to compatible 2023-12-04 11:38:54 +08:00
wonfen
6a943acc70 chore: change windows meta core to compatible 2023-12-04 06:00:31 +08:00
WhizPanda
7c97416e7d fix: alpha core can't display memory 2023-12-03 18:55:38 +08:00
WhizPanda
dd75504a66 style: fix icon size 2023-12-03 18:28:41 +08:00
WhizPanda
04c4ab2289 docs: Update README.md 2023-12-03 16:42:49 +08:00
Pylogmon
310a2e2511 style: improve drag icon style (#51) 2023-12-03 16:27:13 +08:00
WhizPanda
f6314431f0 ci: Fix Portable Script 2023-12-03 15:36:39 +08:00
wonfen
89100d0ca0 docs: update readme 2023-12-03 15:25:33 +08:00
WhizPanda
2b646636c1 feat: Support Both Stable and Alpha Version (#47) 2023-12-03 14:26:03 +08:00
wonfen
b10c1d5006 release: 1.4.2, tweak UI, fix emoji on mac 2023-12-03 14:01:53 +08:00
WhizPanda
225b829c1a chore: Adjust style 2023-12-02 23:20:04 +08:00
WhizPanda
5f4c7076ab chore: Replace Repo Name 2023-12-02 23:04:04 +08:00
WhizPanda
61c9b304d7 build: Update Depends 2023-12-02 22:36:04 +08:00
WhizPanda
789d7000cf style: Improve Style 2023-12-02 16:23:53 +08:00
WhizPanda
d23ef2bd59 feat: Support New Clash Field
#46
2023-12-02 15:18:54 +08:00
WhizPanda
8a77f832a3 build: Update Cargo Depends 2023-12-01 15:52:49 +08:00
WhizPanda
5a6d318cfb build: Update Depends 2023-12-01 14:49:32 +08:00
WhizPanda
e9f14de05d feat: support random mixed port 2023-12-01 12:56:18 +08:00
WhizPanda
2d8da45bda fix: Fix Updater Script 2023-12-01 11:19:49 +08:00
WhizPanda
83de33f5b8 Remove unnecessary conditions 2023-12-01 11:11:20 +08:00
Pylogmon
e63844f786 feat: Add Windows x86 and Linux armv7 Support (#44)
* feat: Add Windows x86 and Linux armv7 Support

* ci: Remove Linux armv7 Support
2023-12-01 11:03:18 +08:00
Pylogmon
0a805c16fd feat: Use Latest Meta Core mihomo (#41) 2023-12-01 02:44:18 +08:00
Pylogmon
653c7d4430 feat: Support cross-compiling to aarch64 (#40)
#19
2023-11-30 22:46:09 +08:00
Pylogmon
306c3bea21 feat: Support Disable Tray Click Event (#38)
#21
2023-11-30 22:45:02 +08:00
Pylogmon
389ce60bc9 feat: Set different tray icon on tun mode (#33)
好看!
2023-11-30 01:29:11 +08:00
Pylogmon
2d453a1a6c feat: Add Download Progress for Updater (#34) 2023-11-30 01:28:46 +08:00
Pylogmon
0775560ad2 feat: Support Drag to Reorder the Profile (#29)
* feat: Support Drag to Reorder the Profile

* style: Remove unnecessary styles
2023-11-29 08:54:02 +08:00
wonfen
2680c1e8b3 Merge pull request #27 from Pylogmon/emoji-font
feat: Embed Emoji fonts
2023-11-28 10:41:35 +08:00
Pylogmon
50ba2e3ad4 feat: Embed Emoji fonts 2023-11-28 10:36:32 +08:00
wonfen
c16875d0de fix: Adjust font order 2023-11-28 10:03:01 +08:00
wonfen
a4205cd0c2 chore: update preview gif 2023-11-28 08:49:22 +08:00
wonfen
2fb3e373c6 Merge branch 'main' of github.com:wonfen/clash-verge-rev 2023-11-28 07:53:11 +08:00
wonfen
ac1fa7209c update clashmeta core, Imporve UI, merge PR, reset icons, fix CI 2023-11-28 07:49:44 +08:00
wonfen
fd820d6af8 Merge pull request #24 from Pylogmon/tray
fix: Tray Icon Tooltip is Empty
2023-11-28 01:46:41 +08:00
wonfen
b88486601b Merge pull request #21 from Pylogmon/tray-event
feat: Config Tray Click Event
2023-11-28 01:46:16 +08:00
Pylogmon
92e712a508 chore: fix style 2023-11-27 20:10:31 +08:00
Pylogmon
64a9079ce4 chore: Add Translation 2023-11-27 20:04:47 +08:00
Pylogmon
f18d0ab923 fix: Tray Icon Tooltip is Empty 2023-11-27 19:55:42 +08:00
Pylogmon
def49e6d20 chore: Remove Debug Info 2023-11-26 18:49:48 +08:00
Pylogmon
f142db3d49 feat: Config Tray Click Event 2023-11-26 18:35:21 +08:00
wonfen
e7b04a89e2 fix: portable CI again 2023-11-23 15:01:30 +08:00
wonfen
1cb59f46e3 chore: change preview gif and fix portable CI 2023-11-23 14:34:11 +08:00
wonfen
a604746e0b fix: CI portable 2023-11-23 13:42:16 +08:00
wonfen
aba706a293 Merge pull request #3 from Kuingsmile/main
fix: Downgrade runas to fix service install bug
2023-11-23 11:11:06 +08:00
wonfen
c1b9113347 chore: update clash.meta core to 2023.11.23, change win icons. 2023-11-23 11:10:32 +08:00
Kuingsmile
5e7db2807d fix: Downgrade runas to fix service install bug 2023-11-22 17:17:12 -08:00
wonfen
203f830a30 Merge pull request #2 from Kuingsmile/main
feat: Update DialogContent width in EditorViewer
2023-11-23 07:46:12 +08:00
Kuingsmile
3dbe9193e2 feat: Update DialogContent width in EditorViewer
component
2023-11-22 06:49:47 -08:00
wonfen
717cf29595 Merge pull request #1 from Kuingsmile/main
feat: add UWP loopback tools
2023-11-22 16:23:38 +08:00
Kuingsmile
72300fec5e feat: add UWP loopback tools 2023-11-22 00:15:41 -08:00
wonfen
ec50b1d67a chore: UI adjustment, add translation, fix CI 2023-11-22 14:52:14 +08:00
wonfen
408a4420c9 chore: update readme & fix updater issue 2023-11-22 07:31:38 +08:00
wonfen
568218204a chore: remove old updater CI 2023-11-22 07:06:08 +08:00
wonfen
bd39e98c66 Merge remote-tracking branch 'nyanpasu/main' 2023-11-22 06:48:30 +08:00
wonfen
01be65a624 chore: delete clash core, update CI, change profile name, change URL test link 2023-11-22 02:56:47 +08:00
keiko233
a59d8a6a17 chore: fix typos 2023-11-16 11:31:06 +08:00
keiko233
70b71aa4f9 chore: add updater workflow 2023-11-16 10:59:27 +08:00
keiko233
aaf7991139 chore: remove un supported platform
* I don't have the relevant equipment to test with
* May support in future
2023-11-16 10:56:03 +08:00
keiko233
265d579fd9 chore: no need for second build 2023-11-16 10:41:28 +08:00
keiko233
f6dd91e47c chore: fix missing assets type 2023-11-16 10:37:26 +08:00
keiko233
cdae552ab0 chore: remove un supported platform 2023-11-16 10:30:25 +08:00
keiko233
730e887454 chore: fix typos 2023-11-16 10:30:01 +08:00
keiko233
5a3852d82c docs: add preview gif 2023-11-15 15:17:51 +08:00
keiko233
5b35580d2d chore: clean up workflows 2023-11-15 14:37:10 +08:00
keiko233
4b0705fe36 Bump Version 1.4.0 2023-11-15 14:06:29 +08:00
keiko233
d219b4d12a chore: add release build 2023-11-15 13:52:56 +08:00
keiko233
6e38331328 fix: rust lint 2023-11-15 13:49:06 +08:00
keiko233
c4bc9aea22 chore: test: use pnpm 2023-11-13 14:49:28 +08:00
keiko233
6c692d9308 feat: minor tweaks 2023-11-12 00:10:23 +08:00
keiko233
7ce8bd8988 feat: Nyanpasu Misc 2023-11-12 00:09:32 +08:00
keiko233
b4ead4076a chore: switch updater endpoints 2023-11-12 00:08:23 +08:00
keiko233
052893bbf8 fix: valid with unified-delay & tcp-concurrent 2023-11-11 22:34:30 +08:00
keiko233
a319fe8632 chore: delete current release assets 2023-11-11 22:27:28 +08:00
keiko233
8a84e68c13 feat: add baseContentIn animation 2023-11-11 19:34:03 +08:00
keiko233
0ca7defe83 feat: add route transition 2023-11-11 18:32:11 +08:00
keiko233
b704706ee9 feat: Material You! 2023-11-11 17:12:57 +08:00
keiko233
23fb634847 fix: touchpad scrolling causes blank area to appear 2023-11-11 15:03:41 +08:00
keiko233
0bd37eb8f9 chore: drop upload-artifact & use prerelease 2023-11-11 15:00:54 +08:00
keiko233
b86294e9cc chore: fix: typos
* ↑ baka
2023-11-10 11:50:01 +08:00
keiko233
f11f968f99 fix: typos 2023-11-10 11:35:38 +08:00
keiko233
ad81642954 fix: download clash core from backup repo 2023-11-10 11:26:05 +08:00
keiko233
ea42103ae7 chore: add dev workflow 2023-11-10 10:38:41 +08:00
keiko233
82435e37be feat: default disable ipv6 2023-11-10 09:10:15 +08:00
keiko233
b61d29b22a feat: default enable unified-delay & tcp-concurrent with use meta core 2023-11-10 09:08:17 +08:00
keiko233
04392a6a4c refactor: copy_clash_env 2023-11-09 10:52:52 +08:00
keiko233
6e9798d596 feat: support copy CMD & PowerShell proxy env 2023-11-09 09:58:17 +08:00
keiko233
bef553c9ae fix: use meta Country.mmdb 2023-11-09 09:35:41 +08:00
keiko233
7a76efa7a0 feat: default use meta core
* :)
2023-11-09 09:34:03 +08:00
keiko233
c5f64374ed feat: update Clash Default bypass addrs
*Enabling TUN will cause the local address to go through the proxy
2023-11-09 09:33:38 +08:00
GyDi
b767caa704 chore: fix check 2023-11-03 16:00:34 +08:00
GyDi
63974f97ab chore: fix check script 2023-11-03 15:52:36 +08:00
MZhao
37764a7caa feat: new windows tray icons (#899) 2023-11-03 15:01:46 +08:00
Aromia
fb586f0043 chore: update download links in README.md (#896) 2023-11-03 11:21:20 +08:00
Goooler
9990f4d8cf chore: bump github actions (#868)
https://github.com/actions/checkout/releases/tag/v4.0.0
https://github.com/actions/setup-node/releases/tag/v4.0.0
2023-11-02 20:14:05 +08:00
neovali
80f73cc5d0 chore: add fedora linux install description (#874) 2023-11-02 20:13:24 +08:00
GyDi
8775f67416 feat: support auto clean log files 2023-11-02 20:12:46 +08:00
GyDi
2ce302f6f8 fix: latency url empty 2023-11-01 23:22:30 +08:00
GyDi
69b9944b8e feat: increase the concurrency of latency test 2023-11-01 20:52:38 +08:00
GyDi
37f35e8b2d fix: change default port 2023-10-31 14:56:19 +08:00
GyDi
8ad9531fa6 fix: csp 2023-10-31 12:00:57 +08:00
GyDi
5ec37c08bf v1.3.8 2023-10-31 01:29:20 +08:00
GyDi
84cb008421 chore: update log 2023-10-31 01:28:57 +08:00
GyDi
a744cca35a fix: add default valid key 2023-10-30 17:10:51 +08:00
GyDi
1950cf5f99 fix: page null exception, close #821 2023-10-30 00:53:24 +08:00
GyDi
fbf230cd01 feat: adjust the delay display interval and color, close #836 2023-10-29 23:01:05 +08:00
keiko233
05abf1e419 chore: update dependencies 2023-10-22 00:44:01 +08:00
keiko233
f0c13980ed chore: tsconfig: skip Lib Check 2023-10-21 23:53:57 +08:00
keiko233
d136207d2f feat: theme: change color 2023-10-21 17:30:50 +08:00
keiko233
b331ee5d93 chore: update README.md 2023-10-21 17:02:13 +08:00
keiko233
73cd6e981a feat: profiles: import btn with loading state 2023-10-21 16:50:46 +08:00
keiko233
6826be73c7 feat: profile-viewer: handleOk with loading state 2023-10-21 16:47:39 +08:00
keiko233
0a61a607c9 feat: base-dialog: okBtn use LoadingButton 2023-10-21 16:47:10 +08:00
keiko233
7444e1566b chore: import MUI Lab 2023-10-21 16:43:30 +08:00
keiko233
e401661cfc chore: Update dependencies 2023-10-21 16:43:16 +08:00
keiko233
89d32f7109 feat: Nyanpasu Misc 2023-10-20 11:52:40 +08:00
keiko233
5b4c88f933 feat: Theme support modify --background-color 2023-10-20 11:12:49 +08:00
keiko233
966bce973a feat: settings use Grid layout 2023-10-20 10:05:03 +08:00
keiko233
69cbcae7ed chore: Use your own update endpoints and public keys 2023-10-19 09:37:31 +08:00
keiko233
e26cf282d6 chore: readme remove ad 2023-10-18 13:38:58 +08:00
GyDi
6ec7d55cbc chore: fix rust lint 2023-10-18 13:38:03 +08:00
GyDi
9888f2a322 chore: fix rust lint 2023-10-11 14:55:20 +08:00
GyDi
15d29b2c79 chore: fix alpha ci 2023-10-11 14:31:44 +08:00
GyDi
7e768e0a17 fix: try fix csp 2023-10-11 14:21:56 +08:00
GyDi
affa41b5a9 chore: update readme 2023-10-11 00:04:56 +08:00
keiko233
c8cf179ed9 feat: add Connections Info to ConnectionsPage
*Add Upload Traffic, Download Traffic and Active Connections to ConnectionsPage.
*IConnections uploadTotal and downloadTotal data missing not displayed, add it to ConnectionsPage interface here.
2023-10-10 17:05:31 +08:00
Majokeiko
9ef7310fc2 feat: ClashFieldViewer BaseDialog maxHeight usage percentage (#813)
*The overall interface will be more intuitive when the content is longer.
2023-10-10 14:29:27 +08:00
GyDi
cdd7d0f4c5 chore: update readme 2023-10-10 10:25:57 +08:00
GyDi
813d706dac chore: update readme 2023-10-10 10:25:01 +08:00
GyDi
223d4133c5 chore: update clash meta 2023-10-08 21:45:12 +08:00
GyDi
bb62ebc23e chore: update readme 2023-09-11 10:24:44 +08:00
GyDi
a4c2adf69a chore: fix updater 2023-09-11 10:16:54 +08:00
GyDi
0baff0a1e1 chore: fix updater 2023-09-11 10:12:40 +08:00
GyDi
51766ad72c chore: fix clash map, close #736 2023-09-10 19:06:02 +08:00
GyDi
bf00b42941 v1.3.7 2023-09-10 19:02:10 +08:00
GyDi
56482acfc2 chore: update log 2023-09-10 19:01:59 +08:00
GyDi
8898b1a608 chore: update auto launch 2023-09-10 18:45:17 +08:00
GyDi
525bc37649 fix: i18n 2023-09-10 15:03:29 +08:00
GyDi
9d29450f47 feat: add Open Dashboard to the hotkey, close #723 2023-09-10 14:46:03 +08:00
GyDi
26c98bdace feat: add check for updates button, close #766 2023-09-10 14:30:31 +08:00
GyDi
19ccd35f3f fix: fix page undefined exception, close #770 2023-09-10 13:45:18 +08:00
GyDi
efa7529c5c feat: add paste and clear icon 2023-09-09 16:52:00 +08:00
Majokeiko
334c11ccd1 feat: Subscription URL TextField use multiline (#761)
*Subscription link that are too long can make reading difficult, so use multiline TextField.
2023-09-07 16:14:42 +08:00
GyDi
c318a5dd79 chore: update mmdb 2023-08-28 15:06:13 +08:00
GyDi
25d1a8957d fix: set min window size, close #734 2023-08-28 15:00:27 +08:00
GyDi
9f4853b9d6 chore: change ubuntu ci version 2023-08-28 14:44:09 +08:00
GyDi
ac58f50b0a chore: update clash 2023-08-28 14:22:50 +08:00
GyDi
36f22b660e chore: update release link 2023-08-14 11:11:15 +08:00
GyDi
398046eca6 chore: update clash meta 2023-08-14 11:09:19 +08:00
GyDi
a6f2ce4bdd fix: rm debug code 2023-08-12 19:16:20 +08:00
GyDi
950a8ea8df v1.3.6 2023-08-12 16:12:03 +08:00
GyDi
2b6b979f88 chore: update log 2023-08-12 16:11:50 +08:00
GyDi
4b80e5ff47 fix: use sudo when pkexec not found 2023-08-12 15:58:37 +08:00
GyDi
a14d18c6b6 chore: update ci 2023-08-12 15:41:26 +08:00
GyDi
fe88c7ce97 feat: show loading when change profile 2023-08-05 22:07:30 +08:00
GyDi
c54b00a701 fix: remove div 2023-08-05 21:43:58 +08:00
GyDi
4a4b8c2e12 fix: list key 2023-08-05 21:43:05 +08:00
GyDi
f1770711cb feat: support proxy provider update 2023-08-05 21:38:44 +08:00
GyDi
94757f0f0c feat: add repo link 2023-08-05 19:54:59 +08:00
GyDi
15cf9be90d feat: support clash meta memory usage display 2023-08-05 19:40:23 +08:00
GyDi
cb1955c217 fix: websocket disconnect when window focus 2023-08-05 17:21:15 +08:00
GyDi
2ce944034d feat: supports show connection detail 2023-08-05 16:52:14 +08:00
GyDi
53a207e859 chore: update geo data to meta, close #707 2023-08-05 13:42:37 +08:00
GyDi
dbdd2411c3 chore: fix faq 2023-08-05 13:41:30 +08:00
GyDi
4ac3fbd726 chore: add faq and download link 2023-08-05 13:38:19 +08:00
GyDi
8f8f62555c chore: add promotion 2023-08-05 12:57:48 +08:00
whitemirror33
73b980c6a9 feat: update connection table with wider process column and click to show full detail (#696) 2023-08-04 14:36:28 +08:00
GyDi
d2cce3cc40 feat: more trace logs 2023-08-04 14:15:15 +08:00
Andrei Shevchuk
4139360788 feat: Add Russian Language (#697)
* Add Russian Language

* Add Russian support

* Minor update

* Update Russian translation
2023-08-03 11:07:58 +08:00
GyDi
ca7bb50212 fix: try fix undefined error 2023-07-28 09:26:06 +08:00
GyDi
48ebf626b8 chore: alpha ci 2023-07-26 17:07:20 +08:00
GyDi
564eb802b1 feat: center window when out of monitor 2023-07-24 20:55:26 +08:00
GyDi
c968949f49 chore: fix test ci 2023-07-24 09:59:13 +08:00
GyDi
74268ecce6 chore: fix test ci 2023-07-24 09:47:05 +08:00
GyDi
93f9db3af4 chore: test ci 2023-07-24 09:28:54 +08:00
GyDi
4cc0a9d4a9 chore: test ci 2023-07-24 09:13:19 +08:00
GyDi
a04dfba16b v1.3.5 2023-07-23 13:31:51 +08:00
GyDi
7f83b58b92 chore: update log 2023-07-23 13:31:34 +08:00
GyDi
ca2e6353ae fix: blurry tray icon in Windows 2023-07-23 13:25:54 +08:00
GyDi
6bf7795529 chore: update clash core 2023-07-23 13:11:35 +08:00
GyDi
401f4828b6 chore: fix check script 2023-07-23 13:11:17 +08:00
GyDi
7a0ce1efe7 chore: update issue templates 2023-07-22 23:04:13 +08:00
GyDi
cdc2550e34 v1.3.4 2023-07-22 20:38:08 +08:00
GyDi
2d760241f3 chore: update log 2023-07-22 20:37:22 +08:00
GyDi
3748e420a0 fix: enable context menu in editable element 2023-07-22 17:21:04 +08:00
GyDi
031a253101 feat: support copy environment variable 2023-07-22 15:35:32 +08:00
GyDi
cfce6d548b fix: save window size and pos in Windows 2023-07-22 13:13:16 +08:00
GyDi
038e93ea6a feat: save window size and position 2023-07-22 10:58:16 +08:00
GyDi
8028e145f1 feat: app log level add silent 2023-07-22 09:25:54 +08:00
GyDi
ddfb9fd0cc feat: overwrite resource file according to file modified 2023-07-22 09:18:54 +08:00
GyDi
a36ed6ab1e feat: support app log level settings 2023-07-22 08:53:37 +08:00
Kimiblock Moe
0c7fe0664e feat: Use polkit to elevate permission instaed of sudo (#678) 2023-07-21 23:05:33 +08:00
GyDi
7ae2f1980b chore: update check script 2023-07-11 13:25:55 +08:00
GyDi
0c89583b1b fix: optimize traffic graph high CPU usage when hidden 2023-07-10 23:44:09 +08:00
GyDi
f5ec43276a fix: remove fallback group select status, close #659 2023-07-10 23:16:19 +08:00
GyDi
e55e46011c chore: update clash 2023-07-10 13:36:17 +08:00
GyDi
77c0304faf feat: add unified-delay field 2023-06-30 13:58:51 +08:00
GyDi
ea476e26ae v1.3.3 2023-06-30 09:38:33 +08:00
GyDi
2eb47aef62 chore: update log 2023-06-30 09:35:49 +08:00
GyDi
3e4084d99c fix: error boundary with key 2023-06-30 09:17:59 +08:00
GyDi
955ac92043 chore: update clash meta 2023-06-30 09:02:26 +08:00
GyDi
5a65a07a39 fix: connections is null 2023-06-30 09:02:17 +08:00
GyDi
554d73c6ee fix: font family not works in some interfaces, close #639 2023-06-29 14:21:14 +08:00
GyDi
7bc7bc7c49 fix: encodeURIComponent secret 2023-06-29 14:15:57 +08:00
GyDi
7b8d47cdeb chore: update clash & clash meta 2023-06-08 13:52:40 +08:00
GyDi
63a8509f1f feat: add error boundary to the app root 2023-06-08 13:50:45 +08:00
GyDi
4e69454f72 fix: encode controller secret, close #601 2023-06-08 13:48:58 +08:00
GyDi
ec3e237093 fix: linux not change icon 2023-05-28 18:14:11 +08:00
GyDi
edd224a185 fix: try fix blank error 2023-05-28 17:35:00 +08:00
GyDi
b5142b8ef5 fix: close all connections when change mode 2023-05-28 17:07:39 +08:00
GyDi
ddfdf1d49d fix: macos not change icon 2023-05-28 16:46:17 +08:00
GyDi
e7675c29da chore: update deps 2023-05-28 16:45:43 +08:00
GyDi
0bf3fef431 chore: update clash 2023-05-28 16:44:07 +08:00
w568w
8cb0e81e89 feat: show tray icon variants in different status (#537) 2023-05-28 10:55:39 +08:00
GyDi
d468cb051d fix: error message null 2023-05-19 10:53:11 +08:00
GyDi
0b24be7532 chore: update gh proxy 2023-05-19 09:44:31 +08:00
GyDi
6316a0ede9 v1.3.2 2023-05-19 00:57:22 +08:00
GyDi
3c9d0e02f6 chore: update log 2023-05-19 00:57:10 +08:00
GyDi
02d4360526 fix: profile data undefined error, close #566 2023-05-18 20:02:46 +08:00
GyDi
aedf80e382 chore: update clash core 2023-05-05 20:45:52 +08:00
GyDi
51b492e1e2 chore: update deps 2023-05-05 20:27:46 +08:00
yettera765
d283f236db fix: import url error (#543)
use rustls instead of depending user's system tls
2023-05-05 12:08:08 +08:00
GyDi
371e937a0e v1.3.1 2023-04-11 10:04:55 +08:00
GyDi
5778c25477 chore: update log 2023-04-11 10:04:28 +08:00
Tatius Titus
d2cd3ec879 chore: Upgrade to React 18 (#495)
* chore: Upgrade to React 18

* runfix: Add children type to FC components

* chore: Remove @types/react
2023-04-07 12:59:44 +08:00
Mr-Spade
ba0a59629b fix: linux DEFAULT_BYPASS (#503) 2023-04-07 12:47:13 +08:00
GyDi
03fea7558d chore: update deps 2023-04-02 11:20:18 +08:00
GyDi
4901673c4e fix: open file with vscode 2023-04-02 11:19:48 +08:00
GyDi
a4d84bd2e8 chore: update deps 2023-04-02 10:09:14 +08:00
GyDi
0289218c92 chore: update clash meta 2023-04-02 09:43:32 +08:00
Tatius Titus
c3d32e59e7 fix: Do not render div as a descendant of p (#494) 2023-04-02 09:37:09 +08:00
Srinivas Gowda
8cab08351b chore: update clash core version (#476) 2023-03-27 10:59:27 +08:00
GyDi
5de0e58d5d fix: use replace instead 2023-03-17 08:37:45 +08:00
GyDi
438106f42e fix: escape path space 2023-03-17 08:36:19 +08:00
John Smith
6b21f38047 fix: escape the space in path (#451) 2023-03-17 08:20:35 +08:00
GyDi
1b07a5f3c0 fix: add target os linux 2023-03-16 23:57:34 +08:00
GyDi
a39ec5a061 chore: fix ci 2023-03-16 23:54:14 +08:00
GyDi
015df9e6dd fix: appimage path unwrap panic 2023-03-16 23:45:48 +08:00
GyDi
bf95284b72 v1.3.0 2023-03-16 21:33:51 +08:00
GyDi
07c04f0c2f chore: update log 2023-03-16 21:33:14 +08:00
GyDi
b9976ee68e feat: auto restart core after grand permission 2023-03-16 21:32:39 +08:00
GyDi
c8d9951ae4 feat: add restart core button 2023-03-16 20:56:37 +08:00
GyDi
97339512e1 fix: remove esc key listener in macOS 2023-03-16 20:42:45 +08:00
GyDi
95682a7a0b feat: support update all profiles 2023-03-16 20:32:41 +08:00
GyDi
87038c4c75 chore: rm file 2023-03-16 20:31:48 +08:00
GyDi
1e5b5193fe fix: adjust style 2023-03-16 19:32:59 +08:00
hybo
9cc47678a2 chore: evaluate error context lazily (#447) 2023-03-16 17:06:52 +08:00
GyDi
198109cd43 fix: adjust swr option 2023-03-16 17:03:12 +08:00
GyDi
16490541e4 fix: infinite retry when websocket error 2023-03-16 17:03:00 +08:00
GyDi
6d5ca25e03 fix: type error 2023-03-16 13:51:46 +08:00
GyDi
0ca44bd859 chore: update deps 2023-03-16 13:42:27 +08:00
GyDi
124b5b0549 chore: fix ci 2023-03-16 13:18:05 +08:00
GyDi
fed62fae3c chore: fix ci 2023-03-16 13:16:30 +08:00
GyDi
9e2812d55c feat: support to grant permission to clash core 2023-03-16 11:16:54 +08:00
GyDi
fdcbc3904a fix: do not parse log except the clash core 2023-03-16 10:34:28 +08:00
GyDi
cc0114cd90 feat: support clash fields filter in ui 2023-03-15 08:43:24 +08:00
GyDi
f97753c1d0 fix: field sort for filter 2023-03-15 08:43:03 +08:00
boatrainlsz
fcdf545a3c chore: update README (#435) 2023-03-07 22:55:38 +08:00
GyDi
bf28f887eb chore: update deps 2023-03-07 00:10:49 +08:00
GyDi
5f1f04e486 chore: update clash 2023-03-06 23:35:30 +08:00
GyDi
2242174749 fix: add meta fields 2023-02-18 00:46:03 +08:00
GyDi
e8af077fe2 chore: update clash 2023-02-18 00:09:40 +08:00
GyDi
055252f746 feat: open dir on the tray 2023-02-17 00:15:21 +08:00
GyDi
8dc84d7c17 fix: runtime config user select 2023-02-17 00:00:23 +08:00
GyDi
f140c75fb9 feat: support to disable clash fields filter 2023-02-16 23:52:55 +08:00
GyDi
c63dd46bac fix: app_handle as_ref 2023-02-15 17:25:35 +08:00
GyDi
a23b0ba945 fix: use crate 2023-02-11 23:31:55 +08:00
GyDi
7e768aa6e9 fix: appimage auto launch, close #403 2023-02-11 23:19:08 +08:00
inRm3D
18765508c0 chore: add openssl depend (#395) 2023-02-02 10:54:15 +08:00
GyDi
efb3464ee2 v1.2.3 2023-02-01 22:11:34 +08:00
GyDi
f3319c5f75 chore: update log 2023-02-01 22:11:06 +08:00
GyDi
384623fbb7 chore: aarch script 2023-02-01 21:41:15 +08:00
GyDi
3bcccdf3dd chore: update clash 2023-01-30 20:50:08 +08:00
GyDi
d9b913fa69 chore: meta ci 2023-01-30 20:40:57 +08:00
GyDi
7b9a430e8a chore: fix ci 2023-01-22 13:31:34 +08:00
GyDi
4f3ab0dc69 chore: fix ci 2023-01-19 18:02:08 +08:00
GyDi
427caaf9aa chore: alpha ci 2023-01-18 23:39:27 +08:00
inRm3D
85eefdebbb chore: try fix missing libssl3 (#378)
* Update ci.yml

* use package from source
2023-01-18 23:36:18 +08:00
GyDi
b31b70302b fix: compatible with UTF8 BOM, close #283 2023-01-17 19:51:02 +08:00
GyDi
c830ea676f feat: adjust macOS window style 2023-01-16 22:57:53 +08:00
GyDi
9249059cb7 fix: use selected proxy after profile changed 2023-01-15 21:33:03 +08:00
GyDi
9fee228d1a fix: error log 2023-01-15 19:11:51 +08:00
GyDi
20718c6ec1 chore: ci 2023-01-14 22:22:18 +08:00
GyDi
b8754f3f44 v1.2.2 2023-01-14 22:01:28 +08:00
GyDi
6f598ae59e chore: update log 2023-01-14 21:56:27 +08:00
GyDi
ca81fcaf37 fix: adjust fields order 2023-01-14 14:57:55 +08:00
GyDi
75867da94f fix: add meta fields 2023-01-14 14:51:29 +08:00
GyDi
53c11701de chore: ci 2023-01-14 12:20:17 +08:00
GyDi
4741793bb6 chore: update clash meta 2023-01-14 12:16:09 +08:00
GyDi
ab161a42ee fix: add os platform value 2023-01-14 12:07:31 +08:00
GyDi
4642b79b5b feat: recover core after panic, close #353 2023-01-14 11:45:47 +08:00
GyDi
1cb43fe110 chore: allow unused 2023-01-13 23:11:48 +08:00
GyDi
0123135237 fix: reconnect traffic websocket 2023-01-13 23:02:45 +08:00
GyDi
1d77f91acc chore: update deps 2023-01-13 21:29:21 +08:00
GyDi
9b89333b1b feat: use decorations in Linux, close #354 2023-01-12 00:42:33 +08:00
GyDi
829361d767 fix: parse bytes precision, close #334 2023-01-11 14:05:49 +08:00
GyDi
d3fca26499 fix: trigger new profile dialog, close #356 2023-01-11 13:45:16 +08:00
GyDi
ff3460e100 fix: parse log cause panic 2023-01-11 13:30:14 +08:00
GyDi
1a209a54c6 chore: update clash meta 2023-01-09 21:55:03 +08:00
GyDi
3bf0ac087f v1.2.1 2022-12-23 22:44:27 +08:00
GyDi
96cf6215ce chore: update log 2022-12-23 22:44:00 +08:00
GyDi
71ce6120c3 fix: avoid setting login item repeatedly, close #326 2022-12-23 22:39:27 +08:00
GyDi
21e82d0daa fix: adjust code 2022-12-15 21:54:48 +08:00
GyDi
8c20bb003b fix: adjust delay check concurrency 2022-12-15 12:23:57 +08:00
GyDi
90913d2486 chore: adjust code 2022-12-15 12:22:20 +08:00
GyDi
ef0de04a0f fix: change default column to auto 2022-12-14 16:56:33 +08:00
GyDi
8879a0ce8a fix: change default app version 2022-12-14 16:15:53 +08:00
GyDi
18db46800e fix: adjust rule ui 2022-12-14 15:59:41 +08:00
GyDi
955c5b5605 fix: adjust log ui 2022-12-14 15:49:05 +08:00
GyDi
c3b2c88213 fix: keep delay data 2022-12-14 15:29:02 +08:00
GyDi
7412bb35ad fix: use list item button 2022-12-14 15:16:49 +08:00
GyDi
2a6fbc5c5d fix: proxy item style 2022-12-14 15:15:44 +08:00
GyDi
90c9b87f8d feat: auto proxy layout column 2022-12-14 15:07:51 +08:00
GyDi
ffe2557e84 chore: fix ci 2022-12-13 18:13:28 +08:00
GyDi
a77798e5b4 chore: fix ci 2022-12-13 18:11:43 +08:00
GyDi
c1fa7bfce6 chore: fix ci 2022-12-13 18:02:23 +08:00
GyDi
bf50da1e6b chore: alpha ci 2022-12-13 17:44:46 +08:00
GyDi
2cd4aac5ce chore: alpha ci 2022-12-13 17:41:53 +08:00
GyDi
208b96c092 feat: support to change proxy layout column 2022-12-13 17:34:39 +08:00
GyDi
36a53f8134 feat: support to open core dir 2022-12-13 00:44:24 +08:00
GyDi
e8d8a8737e chore: update deps 2022-12-12 00:00:45 +08:00
MoeShin
41b9f90a0b fix: Virtuoso no work in legacy browsers (#318) 2022-12-08 10:47:42 +08:00
GyDi
5e39493e89 chore: update deps 2022-12-04 20:54:43 +08:00
GyDi
31f3c60401 fix: adjust ui 2022-12-04 00:45:39 +08:00
GyDi
519c74e4dd feat: profile page ui 2022-11-28 22:29:58 +08:00
GyDi
4419770b15 chore: deps 2022-11-28 22:27:23 +08:00
GyDi
b7fa86c848 chore: update clash version 2022-11-26 13:25:52 +08:00
GyDi
4d620c3db9 fix: refresh websocket 2022-11-25 22:22:57 +08:00
GyDi
319c8cb54c fix: adjust ui 2022-11-24 18:24:34 +08:00
GyDi
868b6f141c feat: save some fields in the runtime config, close #292 2022-11-24 17:52:25 +08:00
GyDi
589fdd4cfe chore: alpha ci 2022-11-24 11:14:03 +08:00
GyDi
3f36dd9a14 fix: parse bytes base 1024 2022-11-24 11:11:31 +08:00
GyDi
0a6568bbab fix: add clash fields 2022-11-24 10:46:21 +08:00
GyDi
4e26d56746 chore: ci 2022-11-24 10:41:03 +08:00
GyDi
d4e363fbd7 chore: ci 2022-11-24 10:35:22 +08:00
GyDi
b721b822eb fix: direct mode hide proxies 2022-11-24 10:35:09 +08:00
GyDi
58d6985080 feat: add meta feature 2022-11-24 10:26:41 +08:00
GyDi
ee2abd415e fix: profile can not edit 2022-11-24 10:26:25 +08:00
GyDi
09c9321159 chore: ci 2022-11-23 23:46:32 +08:00
GyDi
0900a7cf80 v1.2.0 2022-11-23 23:15:39 +08:00
GyDi
d3ec7f65fb chore: ci 2022-11-23 23:15:20 +08:00
GyDi
b24a94d6ce chore: update log 2022-11-23 23:13:44 +08:00
GyDi
65769c7766 feat: display proxy group type 2022-11-23 18:27:57 +08:00
GyDi
cbeef9fe06 chore: rm dead code 2022-11-23 17:45:49 +08:00
GyDi
b199209d0d fix: parse logger time 2022-11-23 17:45:43 +08:00
GyDi
cb48545600 fix: adjust service mode ui 2022-11-23 17:45:22 +08:00
GyDi
5e626e2cc5 feat: add use clash hook 2022-11-23 17:44:40 +08:00
GyDi
2709d1ff6e fix: adjust style 2022-11-23 17:42:01 +08:00
GyDi
de3ca6e237 fix: check hotkey and optimize hotkey input, close #287 2022-11-23 17:30:19 +08:00
GyDi
c2253e868c fix: mutex dead lock 2022-11-23 16:04:25 +08:00
GyDi
3a77c6f7eb fix: adjust item ui 2022-11-23 15:29:42 +08:00
GyDi
8e3ff7670b fix: regenerate config before change core 2022-11-23 09:57:21 +08:00
GyDi
7870147c16 fix: close connections when profile change 2022-11-23 00:20:57 +08:00
GyDi
64a371e5d8 fix: lint 2022-11-22 23:02:18 +08:00
GyDi
f837736a20 chore: fix check script 2022-11-22 23:01:04 +08:00
GyDi
dec503da81 fix: windows service mode 2022-11-22 22:01:34 +08:00
GyDi
1328299fda fix: init config file 2022-11-22 22:01:19 +08:00
GyDi
05190a52f1 chore: fix mmdb url 2022-11-22 21:15:45 +08:00
GyDi
46cc3105c4 fix: service mode error and fallback to sidecar 2022-11-22 20:47:21 +08:00
GyDi
8ba5853cdd fix: service mode viewer ui 2022-11-22 20:45:17 +08:00
GyDi
e5fbcc4a8c chore: update check script 2022-11-22 20:44:44 +08:00
GyDi
e559194e8e fix: create theme error, close #294 2022-11-22 16:01:33 +08:00
GyDi
c0d2994b8e feat: guard the mixed-port and external-controller 2022-11-22 15:45:17 +08:00
GyDi
89009aa1f8 feat: adjust builtin script and support meta guard script 2022-11-22 12:00:48 +08:00
MoeShin
ee8b580a98 fix: matchMedia().addEventListener #258 (#296) 2022-11-22 09:25:39 +08:00
GyDi
f4656fa493 chore: alpha release add portable 2022-11-22 00:16:30 +08:00
GyDi
adf9405fd7 chore: alpha ci 2022-11-21 23:19:25 +08:00
GyDi
f46db7ce1a fix: check config 2022-11-21 23:11:56 +08:00
GyDi
f00d726347 fix: show global when no rule groups 2022-11-21 23:06:32 +08:00
GyDi
e8d8063ebc fix: service viewer ref 2022-11-21 23:02:48 +08:00
GyDi
b4bc4f5ddc fix: service ref error 2022-11-21 22:33:06 +08:00
GyDi
0d3ffe210f feat: disable script mode when use clash meta 2022-11-21 22:28:57 +08:00
GyDi
8eb88a161a feat: check config when change core 2022-11-21 22:27:55 +08:00
GyDi
346c964419 fix: group proxies render list is null 2022-11-21 22:10:24 +08:00
GyDi
bc8be2460f feat: support builtin script for enhanced mode 2022-11-21 21:05:00 +08:00
GyDi
a33f24d19c chore: update ci 2022-11-20 23:17:05 +08:00
GyDi
8f839fbf8e chore: update ci node version 2022-11-20 23:14:43 +08:00
GyDi
10dd9101cd fix: pretty bytes 2022-11-20 23:08:30 +08:00
GyDi
aa5d3af8a1 feat: adjust profiles page ui 2022-11-20 22:37:34 +08:00
GyDi
dace993c21 refactor: adjust base components export 2022-11-20 22:03:55 +08:00
GyDi
32b72f0ef6 refactor: adjust setting dialog component 2022-11-20 21:48:39 +08:00
GyDi
3dbc54c8ae fix: use verge hook 2022-11-20 20:12:58 +08:00
GyDi
9dd3b8fd68 feat: optimize proxy page ui 2022-11-20 19:46:16 +08:00
GyDi
5fb1afc681 chore: adjust type 2022-11-19 17:22:29 +08:00
GyDi
a4c985a219 fix: adjust notice 2022-11-19 01:24:07 +08:00
GyDi
a1f819a458 feat: add error boundary 2022-11-19 01:22:19 +08:00
GyDi
166d7ba1cf chore: rm polyfill 2022-11-19 01:22:00 +08:00
GyDi
a089accd23 feat: adjust clash log 2022-11-18 22:08:06 +08:00
GyDi
38546e557f fix: windows issue 2022-11-18 20:15:34 +08:00
GyDi
080dca7ee0 fix: change dev log level 2022-11-18 18:37:29 +08:00
GyDi
a89e433f3d fix: patch clash config 2022-11-18 18:37:17 +08:00
GyDi
f30ef61b06 fix: cmds params 2022-11-18 18:26:55 +08:00
GyDi
03a36c1100 Merge branch 'refactor' 2022-11-18 18:21:49 +08:00
GyDi
546eb6e73e chore: rm code 2022-11-18 18:19:26 +08:00
GyDi
bedd3abf8a refactor: done 2022-11-18 18:18:41 +08:00
GyDi
ce2d4498e1 refactor: adjust all path methods and reduce unwrap 2022-11-18 10:26:39 +08:00
GyDi
a786023160 fix: adjust singleton detect 2022-11-18 08:24:27 +08:00
GyDi
98cf8aa445 fix: change template 2022-11-18 08:04:26 +08:00
GyDi
d733d9fd4c refactor: rm code 2022-11-17 23:21:13 +08:00
GyDi
58e9cb8b93 refactor: fix 2022-11-17 22:53:41 +08:00
GyDi
bb669acf95 refactor: rm dead code 2022-11-17 22:52:22 +08:00
GyDi
4f3751b7ce refactor: for windows 2022-11-17 20:19:40 +08:00
GyDi
84c12dee80 refactor: wip 2022-11-17 17:07:13 +08:00
GyDi
f5f865a139 refactor: wip 2022-11-16 01:26:41 +08:00
GyDi
902aed671a refactor: wip 2022-11-15 01:33:50 +08:00
GyDi
1423fe7e16 refactor: rm update item block_on 2022-11-14 23:07:51 +08:00
GyDi
58ed2e6f33 refactor: fix 2022-11-14 22:50:47 +08:00
GyDi
a90939f46a fix: copy resource file 2022-11-14 21:11:42 +08:00
GyDi
db7456b9c5 chore: tmpl add clash core 2022-11-14 21:10:29 +08:00
GyDi
a964b30c34 feat: add draft 2022-11-14 19:31:22 +08:00
GyDi
d566629d51 refactor: fix 2022-11-14 01:45:58 +08:00
GyDi
837422fbb8 refactor: wip 2022-11-14 01:26:33 +08:00
GyDi
afc37c71a6 fix: MediaQueryList addEventListener polyfill 2022-11-13 10:27:26 +08:00
GyDi
e8b014ea6d chore: fix ci 2022-11-12 17:07:47 +08:00
GyDi
b124512020 chore: alpha ci 2022-11-12 17:06:26 +08:00
GyDi
158f352328 chore: lock version 2022-11-12 17:06:18 +08:00
GyDi
f1895e32fb chore: fix ci 2022-11-12 16:54:57 +08:00
GyDi
6c3be01093 chore: alpha ci 2022-11-12 16:47:27 +08:00
GyDi
ebe548438c fix: change default tun dns-hijack 2022-11-12 11:48:02 +08:00
GyDi
a45c61f19e chore: format rust code 2022-11-12 11:37:23 +08:00
GyDi
b07a4b95aa fix: something 2022-11-11 22:45:32 +08:00
GyDi
777b4e13e6 chore: update deps 2022-11-11 21:28:34 +08:00
GyDi
92cfc9324d feat: change default latency test url 2022-11-11 01:24:18 +08:00
GyDi
97dfa18b9b fix: provider proxy sort by delay 2022-11-11 01:21:23 +08:00
GyDi
515c07ea2b chore: rm dead code 2022-11-10 22:58:46 +08:00
GyDi
4d17e45f86 chore: clash meta compatible and geosite.dat 2022-11-10 22:58:34 +08:00
GyDi
32095daf90 feat: auto close connection when proxy changed 2022-11-10 01:27:05 +08:00
GyDi
aa23ed892b fix: profile item menu ui dense 2022-11-09 15:53:42 +08:00
GyDi
3f079c8501 fix: disable auto scroll to proxy 2022-11-09 11:44:09 +08:00
GyDi
0aaf4bfde8 feat: support to change external controller 2022-11-06 23:23:26 +08:00
GyDi
b967cfc775 feat: add sub-rules 2022-11-05 15:50:06 +08:00
GyDi
2ca0483bf4 feat(macOS): support cmd+w and cmd+q 2022-11-04 00:51:46 +08:00
GyDi
03cedc7b35 chore: update clash meta 2022-11-04 00:36:32 +08:00
GyDi
35049b5830 chore: fix ci 2022-11-03 01:34:23 +08:00
GyDi
b707dde4da chore: compatible ci 2022-11-03 01:32:16 +08:00
GyDi
1ec8339b13 chore: update ci 2022-11-03 01:15:50 +08:00
GyDi
8bbe04a174 chore: test ci 2022-11-03 00:51:49 +08:00
GyDi
0b6a173ba0 chore: test ci 2022-11-03 00:48:27 +08:00
GyDi
8b41dfe94f chore: add x 2022-11-02 00:57:27 +08:00
GyDi
a18b1ac99b v1.1.2 2022-11-02 00:54:51 +08:00
GyDi
e007bcb640 fix: check remote profile 2022-11-02 00:51:25 +08:00
GyDi
1090e7ceae chore: update log 2022-11-02 00:44:45 +08:00
GyDi
933035dd7e feat: add version on tray 2022-11-01 23:29:59 +08:00
GyDi
aaaac78170 feat: add animation 2022-10-29 23:36:10 +08:00
GyDi
b5d2392def fix: remove smoother 2022-10-29 20:05:55 +08:00
angrylid
b88a736735 feat: add animation to ProfileNew component (#252)
* chore: add .vscode to .gitignore

* feat: add animation to ProfileNew component
2022-10-29 20:02:51 +08:00
GyDi
f238da416b fix: icon button color 2022-10-29 19:22:15 +08:00
GyDi
10c7e75491 feat: check remote profile field 2022-10-28 13:00:52 +08:00
GyDi
60af0735f4 fix: init system proxy correctly 2022-10-28 01:33:21 +08:00
GyDi
273cbf333e fix: open file 2022-10-28 01:26:45 +08:00
GyDi
189b17ad8f fix: reset proxy 2022-10-28 01:06:54 +08:00
GyDi
1eecf26429 fix: init config error 2022-10-28 01:02:47 +08:00
GyDi
2f9a3fa942 feat: system tray support zh language 2022-10-28 00:40:29 +08:00
GyDi
f166fb132e fix: adjust reset proxy 2022-10-27 21:14:14 +08:00
GyDi
0adb69139e fix: adjust code 2022-10-27 21:13:27 +08:00
GyDi
ab309e4b90 fix: add https proxy 2022-10-26 17:11:54 +08:00
GyDi
532c6d4fa5 fix: auto scroll into view when sorted proxies changed 2022-10-26 01:24:06 +08:00
GyDi
14f627d0d3 feat: display delay check result timely 2022-10-26 01:11:02 +08:00
GyDi
947c38c124 fix: refresh proxies interval, close #235 2022-10-26 01:08:34 +08:00
GyDi
f991e49203 fix: style 2022-10-25 23:54:21 +08:00
GyDi
4fbb6ed4ff chore: update deps 2022-10-25 23:54:10 +08:00
GyDi
2fa49303de fix: fetch profile with system proxy, close #249 2022-10-25 23:45:24 +08:00
LooSheng
f9527e9d2d fix: The profile is replaced when the request fails. (#246) 2022-10-23 17:22:26 +08:00
GyDi
80c63cb9d6 fix: default dns config 2022-10-22 22:26:40 +08:00
GyDi
a2455eeade fix: kill clash when exit in service mode, close #241 2022-10-22 13:55:06 +08:00
GyDi
5d7ad4b3bd chore: update mmdb 2022-10-22 13:30:08 +08:00
GyDi
f46fa61cf3 feat: update profile with system proxy/clash proxy 2022-10-18 23:19:21 +08:00
GyDi
da24a9637b chore: adjust code 2022-10-17 23:26:25 +08:00
GyDi
2cdc11c2ad fix: icon button color inherit 2022-10-16 14:40:45 +08:00
GyDi
191cd3f25c fix: app version to string 2022-10-16 14:38:57 +08:00
GyDi
f158bce8bd fix: break loop when core terminated 2022-10-14 14:46:15 +08:00
GyDi
94eebb2dd6 feat: change global mode ui, close #226 2022-10-13 11:54:52 +08:00
GyDi
375b146690 feat: default user agent same with app version 2022-10-12 11:28:47 +08:00
GyDi
8c13214ff2 v1.1.1 2022-10-11 23:03:12 +08:00
GyDi
681dfadb57 chore: update log 2022-10-11 23:02:49 +08:00
GyDi
b8b5d1cb09 fix: api error handle 2022-10-11 22:51:18 +08:00
GyDi
e187dd86ed fix: clash meta not load geoip, close #212 2022-10-11 22:24:54 +08:00
GyDi
c380d6988c fix: sort proxy during loading, close #221 2022-10-11 22:06:55 +08:00
GyDi
444acc8ef5 fix: not create windows when enable slient start 2022-10-11 21:50:00 +08:00
GyDi
068f08aa45 fix: root background color 2022-10-11 20:49:03 +08:00
GyDi
bdc101f69c fix: create window correctly 2022-10-11 00:57:34 +08:00
GyDi
0ece530b9e fix: set_activation_policy 2022-10-10 22:15:21 +08:00
GyDi
5853a86091 chore: rm aarch64 ci 2022-09-28 18:50:33 +08:00
GyDi
20ba87bc88 chore: test ci 2022-09-28 17:26:32 +08:00
GyDi
be166ec158 chore: test ci 2022-09-28 17:12:27 +08:00
GyDi
02147891c2 chore: fix ci 2022-09-28 16:04:05 +08:00
GyDi
c08cd588eb chore: fix test ci 2022-09-28 15:19:50 +08:00
Particle_G
882bb564b1 chore: add support for windows arm64, close #216 (#209) 2022-09-28 15:13:24 +08:00
GyDi
18edf06a20 fix: disable spell check 2022-09-28 14:15:22 +08:00
苏业钦
68b52b6130 chore: add support linux arm64 (#215) 2022-09-28 10:40:41 +08:00
GyDi
3503f3eb4b chore: clash meta linux use compatible version 2022-09-27 00:16:22 +08:00
GyDi
a75706f329 feat: optimize config feedback 2022-09-26 20:46:29 +08:00
GyDi
29fe9d973c fix: adjust init launch on dev 2022-09-26 01:35:19 +08:00
GyDi
87d3320fa1 v1.1.0 2022-09-26 01:27:56 +08:00
GyDi
0b96dbc04c chore: update log 2022-09-26 01:27:32 +08:00
GyDi
55ed58c4a0 fix: ignore disable auto launch error 2022-09-26 01:09:05 +08:00
GyDi
8503e8c9ad fix(macos): set auto launch path to application 2022-09-25 23:36:55 +08:00
GyDi
fcdf783c40 fix: i18n 2022-09-25 01:41:46 +08:00
GyDi
008b92acb2 feat: show connections with table layout 2022-09-25 01:35:21 +08:00
GyDi
baf8ef8516 fix: style 2022-09-24 20:55:40 +08:00
GyDi
0cf89467e3 fix: save enable log on localstorage 2022-09-24 19:03:14 +08:00
Priestch
37d8a563c0 fix: typo in api.ts (#207) 2022-09-24 18:48:11 +08:00
GyDi
f3de93d73a chore: use ubuntu latest 2022-09-24 16:08:39 +08:00
GyDi
4de23bd160 feat: show loading on proxy group delay check 2022-09-24 15:31:09 +08:00
GyDi
29397ca04f fix: refresh clash ui await patch 2022-09-24 14:01:28 +08:00
Shun Li
6038a36532 feat: add chains[0] and process to connections display (#205)
* add chains[0] display

* add metadata.process to connections
2022-09-24 13:06:32 +08:00
GyDi
186a922d06 refactor(hotkey): use tauri global shortcut 2022-09-23 15:31:01 +08:00
GyDi
be1f9e66e3 feat: adjust connection page ui 2022-09-23 00:08:52 +08:00
GyDi
9ac015e550 Merge pull request #197 from FoundTheWOUT/main
Support ordering connection
2022-09-22 22:55:46 +08:00
GyDi
f7a9ac0ba2 feat: yaml merge key 2022-09-21 22:15:24 +08:00
GyDi
39a8ddcfec feat: toggle log ws 2022-09-20 22:15:28 +08:00
GyDi
3a9e28ba48 chore: update deps 2022-09-20 21:26:38 +08:00
GyDi
b6a479355e feat: add rule page 2022-09-18 23:19:02 +08:00
GyDi
a791c964e8 feat: hotkey viewer 2022-09-18 15:52:53 +08:00
GyDi
9686a7f9bf feat: refresh ui when hotkey clicked 2022-09-18 15:50:03 +08:00
FoundTheWOUT
6b65b89350 Support ordering connection 2022-09-15 15:52:12 +08:00
GyDi
09a1915ec6 feat: support hotkey (wip) 2022-09-14 01:19:02 +08:00
GyDi
3424c421a4 fix: remove dead code 2022-09-12 13:42:21 +08:00
GyDi
77b89b6841 fix: style 2022-09-12 13:42:13 +08:00
GyDi
09807cfcad fix: handle is none 2022-09-12 00:45:19 +08:00
GyDi
6ca81df40c fix: unused 2022-09-12 00:02:25 +08:00
GyDi
2d00ddad2b refactor: optimize 2022-09-11 20:58:55 +08:00
GyDi
04f4934adb fix: style 2022-09-11 18:38:51 +08:00
GyDi
0593007fd0 fix: windows logo size 2022-09-11 17:50:58 +08:00
GyDi
c07cbf9dbb feat: hide window on macos 2022-09-09 16:42:35 +08:00
GyDi
ddd60aa1ff fix: do not kill sidecar during updating 2022-09-09 16:33:04 +08:00
GyDi
b5ecdbbc48 fix: delay update config 2022-09-09 16:30:27 +08:00
GyDi
0cca613b04 fix: reduce logo size 2022-09-09 16:19:13 +08:00
GyDi
b254ad177a feat: system proxy setting 2022-09-07 01:51:43 +08:00
GyDi
4fd9213d2c fix: window center 2022-09-06 22:18:06 +08:00
GyDi
982d1e219c chore: update resource 2022-09-06 15:32:22 +08:00
GyDi
3923b8631d fix: log level warn value 2022-09-06 14:42:58 +08:00
GyDi
72dcdf4483 feat: change default singleton port and support to change the port 2022-09-06 00:45:01 +08:00
GyDi
5f3a71ed5f feat: log info 2022-09-05 20:30:39 +08:00
GyDi
000d6ebcd0 feat: kill clash by pid 2022-09-05 16:30:29 +08:00
GyDi
7f0a04e3e3 feat: change clash port in dialog 2022-09-05 02:12:25 +08:00
GyDi
005c6bb9f1 feat: add proxy item check loading 2022-09-04 23:53:48 +08:00
GyDi
59c856fc0e feat: compatible with proxy providers health check 2022-09-04 22:55:54 +08:00
GyDi
5bf26619ee v1.0.6 2022-09-03 00:40:50 +08:00
GyDi
676c1d560b chore: update log 2022-09-03 00:39:39 +08:00
GyDi
5afaa7c079 chore: update deps 2022-09-02 23:06:00 +08:00
GyDi
b0b49b72b8 chore: update deps 2022-09-02 01:35:06 +08:00
GyDi
e7600458a9 chore: use ubuntu 18.04 2022-09-02 01:27:06 +08:00
GyDi
e0fa308984 feat: add empty ui 2022-09-02 01:09:38 +08:00
GyDi
6ce6fed10d feat: complete i18n 2022-09-02 01:05:45 +08:00
GyDi
b23ca2e138 fix: increase delay checker concurrency 2022-09-02 00:41:43 +08:00
GyDi
2798dc3d42 fix: external controller allow lan 2022-09-02 00:24:19 +08:00
GyDi
0586d4d7a9 chore: update clash meta version 2022-09-02 00:11:52 +08:00
GyDi
d2b3a52424 fix: remove useless optimizations 2022-08-31 21:44:23 +08:00
GyDi
ffb97242d9 chore: test ci 2022-08-29 17:26:09 +08:00
GyDi
13ab400d6d chore: update clash 2022-08-29 11:11:55 +08:00
GyDi
db6d47b3f3 chore: add test ci os version 2022-08-29 11:02:14 +08:00
GyDi
393943bb9f Merge branch 'main' of github.com:zzzgydi/clash-verge 2022-08-24 22:41:36 +08:00
GyDi
4c72ac14e6 chore: update auto launch 2022-08-24 22:41:12 +08:00
GyDi
7156871412 fix: reduce unsafe unwrap 2022-08-23 15:19:04 +08:00
FoundTheWOUT
20665a49ed fix: timer restore at app launch 2022-08-23 10:35:36 +08:00
GyDi
ad1dfc3137 fix: adjust log text 2022-08-19 17:11:59 +08:00
GyDi
8de67411ac fix: only script profile can display console 2022-08-17 01:45:37 +08:00
GyDi
2e75c9951b v1.0.5 2022-08-16 23:56:29 +08:00
GyDi
77f66f44a4 chore: update log 2022-08-16 23:56:04 +08:00
GyDi
3278a397f4 fix: fill button title attr 2022-08-16 23:46:33 +08:00
GyDi
d1c2abbaf3 feat: windows portable version do not check update 2022-08-16 01:53:40 +08:00
GyDi
8a443013c0 fix: do not reset system proxy when consistent 2022-08-16 01:27:32 +08:00
GyDi
d3735c4763 fix: adjust web ui item style 2022-08-16 01:08:54 +08:00
GyDi
f9887434d1 fix: clash field state error 2022-08-16 00:48:06 +08:00
GyDi
12afa005d9 feat: adjust clash info parsing logs 2022-08-15 20:21:43 +08:00
GyDi
7c0129b911 fix: badge color error 2022-08-15 20:15:44 +08:00
GyDi
332f92d08e fix: web ui port value error 2022-08-15 20:14:33 +08:00
GyDi
281ac16dca fix: delay show window 2022-08-15 01:37:26 +08:00
GyDi
e8a2b9446d feat: adjust runtime config 2022-08-15 01:30:37 +08:00
GyDi
4e33f3e722 feat: support restart app on tray 2022-08-15 01:22:39 +08:00
GyDi
e46e60153d fix: adjust dialog action button variant 2022-08-15 00:55:35 +08:00
GyDi
d3559bb1b7 feat: optimize profile page 2022-08-14 23:10:19 +08:00
GyDi
c8a046996b fix: script code error 2022-08-12 23:41:25 +08:00
GyDi
6c85b8717f fix: script exception handle 2022-08-12 11:14:34 +08:00
GyDi
142a62e371 feat: refactor 2022-08-12 03:20:55 +08:00
GyDi
ff6abf08b7 feat: adjust tun mode config 2022-08-11 03:26:08 +08:00
GyDi
2fd921cd60 feat: reimplement enhanced mode 2022-08-11 02:55:10 +08:00
GyDi
1d7bd57357 feat: use rquickjs crate 2022-08-10 13:05:48 +08:00
GyDi
1a4d76b4c3 feat: reimplement enhanced mode 2022-08-09 21:15:55 +08:00
GyDi
853d869086 chore: format 2022-08-09 14:33:47 +08:00
GyDi
c99d669b06 fix: change fields 2022-08-09 14:31:59 +08:00
FoundTheWOUT
19d3921637 fix: silent start (#150) 2022-08-09 14:14:06 +08:00
GyDi
4ee716457f fix: save profile when update 2022-08-08 23:17:22 +08:00
GyDi
5fb19bb187 fix: list compare wrong 2022-08-08 23:16:28 +08:00
GyDi
8b83c5349d fix: button color 2022-08-08 23:15:05 +08:00
GyDi
ac91942452 chore: rm code 2022-08-08 22:30:13 +08:00
GyDi
5ef23ddc1d fix: limit theme mode value 2022-08-08 22:28:44 +08:00
GyDi
2ad6c66a8d feat: finish clash field control 2022-08-08 22:14:03 +08:00
GyDi
630f877e95 feat: clash field viewer wip 2022-08-08 01:51:30 +08:00
GyDi
7dd5ce1356 fix: add valid clash field 2022-08-07 20:52:50 +08:00
GyDi
54b18d15b6 feat: support web ui 2022-08-06 21:56:54 +08:00
GyDi
2b0880af80 feat: adjust setting page style 2022-08-06 03:48:03 +08:00
GyDi
786ea17f95 refactor: ts path alias 2022-08-06 02:35:11 +08:00
GyDi
c699bae99c fix: icon style 2022-08-06 01:44:33 +08:00
GyDi
588e8e019c fix: reduce unwrap 2022-07-30 23:32:52 +08:00
GyDi
1e7a86b7c0 feat: runtime config viewer 2022-07-25 01:20:13 +08:00
GyDi
2bb18b18b7 chore: fix updater script 2022-07-18 12:55:59 +08:00
GyDi
da77c549a7 v1.0.4 2022-07-17 17:56:07 +08:00
GyDi
fc707c157a chore: update log 2022-07-17 17:52:41 +08:00
GyDi
5be6f550be feat: improve log rule 2022-07-17 17:39:44 +08:00
GyDi
66abf27edd fix: import mod 2022-07-17 16:51:54 +08:00
GyDi
3d1b6d7de7 feat: theme mode support follows system 2022-07-17 16:02:17 +08:00
GyDi
115e604627 chore: update deps 2022-07-17 14:54:09 +08:00
GyDi
f9aeec8eb5 refactor: mode manage on tray 2022-07-13 02:26:54 +08:00
GyDi
acf4d15a8d chore: rm code 2022-07-13 01:07:29 +08:00
GyDi
e5e08d1762 fix: add tray separator 2022-07-13 01:05:53 +08:00
GyDi
178c7817fa Merge pull request #128 from Limsanity/main
feat: support switch proxy mode
2022-07-13 01:02:14 +08:00
limsanity
a93a428a89 style: resolve formatting problem 2022-07-13 00:54:47 +08:00
limsanity
d126a361e1 feat(system tray): support switch rule/global/direct/script mode in system tray 2022-07-13 00:43:27 +08:00
GyDi
9e42ec0c06 chore: update deps 2022-07-11 00:41:21 +08:00
GyDi
1f05654faa chore: update clash version 2022-07-08 01:42:14 +08:00
GyDi
3ddef6440a chore: update aarch rule 2022-07-07 02:54:20 +08:00
GyDi
036b47acbc chore: aarch upload script 2022-07-07 02:53:53 +08:00
GyDi
42e585e7a2 fix: instantiate core after init app, close #122 2022-07-06 15:14:33 +08:00
GyDi
93d066bef7 chore: update clash version 2022-07-06 00:54:01 +08:00
GyDi
580c6fca00 chore: update deps 2022-07-06 00:49:09 +08:00
GyDi
03300da93a fix: rm macOS transition props 2022-07-05 01:24:23 +08:00
GyDi
2a1fc38799 chore: update deps 2022-07-05 00:52:22 +08:00
GyDi
646afbfa36 chore: change tray icon 2022-06-22 01:26:25 +08:00
GyDi
45a1435ce7 v1.0.3 2022-06-20 02:09:12 +08:00
GyDi
f4d7d32fd7 chore: update log 2022-06-20 02:06:46 +08:00
GyDi
1b00f93091 chore: update clash version 2022-06-20 01:47:15 +08:00
GyDi
9c9e5a54a7 fix: improve external-controller parse and log 2022-06-20 01:36:56 +08:00
GyDi
4cf8c9c477 chore: update macos icon 2022-06-18 18:41:53 +08:00
GyDi
a78e1d7f2b fix: show windows on click 2022-06-18 17:28:37 +08:00
GyDi
00c1d6c42c chore: new logo for mac 2022-06-18 17:28:05 +08:00
GyDi
5d92f09449 chore: update alpha ci 2022-06-17 01:18:19 +08:00
GyDi
ac7d820e6f chore: update tauri 2022-06-17 01:16:46 +08:00
GyDi
7d752443ca fix: adjust update profile notice error 2022-06-15 02:42:04 +08:00
GyDi
3311b5a6e3 fix: style issue on mac 2022-06-15 02:41:37 +08:00
GyDi
3801443c9c chore: update version 2022-06-15 01:15:05 +08:00
GyDi
9b2a2eb229 feat: improve yaml file error log 2022-06-14 01:40:02 +08:00
GyDi
d165f66815 chore: update deps 2022-06-14 01:23:58 +08:00
GyDi
6715d52b81 Merge pull request #104 from FoundTheWOUT/main
fix: check script run on all OS
2022-06-12 22:25:05 +08:00
FoundTheWOUT
ae160556d0 fix: check script run on all OS 2022-06-05 23:55:41 +08:00
GyDi
c040e1f9b7 feat: save proxy page state 2022-06-04 18:55:39 +08:00
GyDi
db14a8ac1a chore: portable script 2022-06-02 00:56:05 +08:00
GyDi
9beafd03ee chore: updater script 2022-06-01 10:06:08 +08:00
GyDi
4f081c5e14 v1.0.2 2022-06-01 01:26:05 +08:00
GyDi
ff4e9e42e5 chore: update log 2022-06-01 01:25:39 +08:00
GyDi
d5e3e6f256 fix: macOS disable transparent 2022-06-01 01:04:46 +08:00
GyDi
cc9660c0e7 chore: rm some files 2022-06-01 00:49:52 +08:00
GyDi
90d92707c8 chore: rust cache 2022-06-01 00:47:04 +08:00
GyDi
fe77bc7c37 chore: adjust ci and script 2022-06-01 00:43:57 +08:00
GyDi
54a7d8291b chore: enable meta by default 2022-06-01 00:31:12 +08:00
GyDi
7bb924ce5b chore: rm file 2022-05-30 00:58:48 +08:00
GyDi
9f2e25309e chore: adjust code 2022-05-30 00:57:31 +08:00
GyDi
b32b39579e chore: update deps 2022-05-29 23:35:22 +08:00
GyDi
00513e66bc fix: window transparent and can not get hwnd 2022-05-25 19:06:06 +08:00
GyDi
8850867b82 fix: create main window 2022-05-25 16:45:18 +08:00
GyDi
d6dfbbf8dd chore: update deps 2022-05-25 16:12:48 +08:00
GyDi
6c4797bce4 chore: update clash core 2022-05-25 16:08:58 +08:00
ctaoist
2c97eb4115 feat: light mode wip (#96)
* 关闭窗口释放UI资源

* windows 还有左键点击事件

* 兼容enhance profile

* bug 修复
2022-05-25 16:06:39 +08:00
GyDi
54b0828c20 chore: update clash version 2022-05-18 14:14:37 +08:00
GyDi
0d241a4993 chore: alpha ci 2022-05-18 14:07:41 +08:00
GyDi
802b0d1a75 chore: error text 2022-05-18 09:58:20 +08:00
GyDi
e76a7716bc chore: ci 2022-05-17 12:35:42 +08:00
GyDi
a9cc0a61d9 chore: fix alpha ci 2022-05-17 12:34:30 +08:00
GyDi
fe50e47fc6 chore: alpha ci 2022-05-17 11:41:45 +08:00
GyDi
0006b286ca chore: meta 2022-05-17 02:46:15 +08:00
GyDi
17f99da45e chore: fix ci 2022-05-17 02:21:58 +08:00
GyDi
f7ce03a819 chore: test ci 2022-05-17 02:17:02 +08:00
GyDi
c073501ac0 chore: alpha 2022-05-17 02:11:29 +08:00
GyDi
d4a4747c08 feat: clash meta core supports 2022-05-17 01:59:49 +08:00
GyDi
e7b19a5f66 fix: adjust notice 2022-05-17 00:51:37 +08:00
GyDi
0070ec09a2 fix: label text 2022-05-16 20:26:13 +08:00
GyDi
b3ec62a3db feat: script mode 2022-05-16 20:18:56 +08:00
GyDi
34ca4ad3b0 chore: test ci 2022-05-16 17:55:14 +08:00
GyDi
e0909731a4 chore: update deps 2022-05-16 17:54:21 +08:00
GyDi
d8d1cb2eda feat: clash meta core support (wip) 2022-05-16 01:52:50 +08:00
GyDi
2a24fbadae fix: icon path 2022-05-13 12:28:36 +08:00
GyDi
85e1c4bd47 fix: icon issue 2022-05-13 02:46:06 +08:00
GyDi
09fcb0739d fix: notice ui blocking 2022-05-13 02:29:43 +08:00
GyDi
0882c2b5c7 fix: service mode error 2022-05-13 02:11:03 +08:00
GyDi
bb7763d72b chore: fix linux build ci 2022-05-11 12:39:41 +08:00
GyDi
b919d7364a chore: test ci 2022-05-11 12:20:20 +08:00
GyDi
942a37290c chore: test ci 2022-05-11 12:12:55 +08:00
GyDi
ef24c6cbbd chore: test ci fix deps 2022-05-11 11:30:19 +08:00
GyDi
cf05571f1c chore: test ci 2022-05-11 11:22:09 +08:00
GyDi
0fa038b4f2 chore: test ci 2022-05-11 10:51:28 +08:00
GyDi
10a33a2978 chore: continue on error 2022-05-11 01:26:11 +08:00
GyDi
60a88381cf chore: test linux 2022-05-11 00:52:20 +08:00
GyDi
48d83c7d44 chore: test ci 2022-05-11 00:31:19 +08:00
GyDi
b967b7ed6b v1.0.1 2022-05-10 21:40:33 +08:00
GyDi
b60ecb6171 chore: update log 2022-05-10 21:40:07 +08:00
GyDi
e8b80216e8 fix: win11 drag lag 2022-05-09 14:41:26 +08:00
GyDi
69a5472a16 chore: change default height 2022-05-09 14:01:46 +08:00
GyDi
bd6eb606ca fix: rm unwrap 2022-05-09 14:01:14 +08:00
GyDi
e7f1e83a43 feat: reduce gpu usage when hidden 2022-05-07 14:43:52 +08:00
GyDi
357fe3b793 feat: interval update from now field 2022-05-07 14:43:08 +08:00
GyDi
ad6c06409e feat: adjust theme 2022-05-06 14:04:39 +08:00
GyDi
2a7feba808 fix: edit profile info 2022-05-06 12:46:27 +08:00
GyDi
45b7e28db2 feat: supports more remote headers close #81 2022-05-06 10:52:59 +08:00
GyDi
daa9479352 feat: check the remote profile 2022-05-06 01:26:24 +08:00
GyDi
0f688bd71a fix: change window default size 2022-05-06 01:15:15 +08:00
GyDi
ffe0843bd6 chore: update deps 2022-05-06 01:14:13 +08:00
GyDi
0ee4039853 chore: change default user agent 2022-05-06 00:42:20 +08:00
GyDi
a3a6e8cdf6 chore: merge
feat: remove outdated config
2022-04-28 16:28:18 +08:00
tianyoulan
30f60f87f4 feat: fix typo 2022-04-28 15:35:17 +08:00
tianyoulan
0d91657557 feat: remove trailing comma 2022-04-28 15:05:10 +08:00
tianyoulan
ec9b80a64e feat: remove outdated config 2022-04-28 15:02:37 +08:00
GyDi
91909f8886 fix: change service installer and uninstaller 2022-04-27 15:46:44 +08:00
GyDi
cc335dae38 v1.0.0 2022-04-26 00:50:48 +08:00
GyDi
0d6ea03ac8 chore: update log 2022-04-26 00:49:07 +08:00
GyDi
e1370b75e8 fix: adjust connection scroll 2022-04-26 00:37:28 +08:00
GyDi
c2d0ccea3c fix: adjust something 2022-04-25 20:00:11 +08:00
GyDi
9319851118 fix: adjust debounce wait time 2022-04-25 19:39:21 +08:00
GyDi
d45ad76fbc feat: windows service mode ui 2022-04-25 16:12:04 +08:00
GyDi
d8b9c3b832 feat: add some commands 2022-04-24 21:03:47 +08:00
GyDi
9f2534af3d feat: windows service mode 2022-04-24 21:00:17 +08:00
GyDi
2c2978b93a chore: update check script 2022-04-24 15:35:30 +08:00
GyDi
c9b0f22a2e wip: windows service mode 2022-04-23 17:26:32 +08:00
GyDi
2959cd140b test: windows service 2022-04-21 21:28:44 +08:00
GyDi
7834cfe5f0 chore: adjust guard log 2022-04-21 19:51:20 +08:00
GyDi
2b9c5d0cff fix: adjust dns config 2022-04-21 19:50:22 +08:00
GyDi
cb3b51dcde feat: add update interval 2022-04-21 14:26:41 +08:00
GyDi
0c23fe96be chore: adjust 2022-04-21 14:24:35 +08:00
GyDi
9d0bd73ee6 chore: update clash version 2022-04-20 21:02:41 +08:00
GyDi
15f096f764 feat: refactor and supports cron tasks 2022-04-20 20:39:27 +08:00
GyDi
9dd41be608 feat: supports cron update profiles 2022-04-20 20:37:16 +08:00
GyDi
17f8703c71 refactor: verge 2022-04-20 11:17:54 +08:00
GyDi
5edfd7b6f7 refactor: wip 2022-04-20 01:44:47 +08:00
GyDi
4371772ec7 refactor: mutex 2022-04-19 14:38:59 +08:00
GyDi
8e4e08a828 fix: traffic graph adapt to different fps 2022-04-19 13:55:26 +08:00
GyDi
401e6b5a57 refactor: wip 2022-04-19 01:41:20 +08:00
GyDi
01dfe83fcd chore: check script proxy agent supports 2022-04-17 00:37:21 +08:00
GyDi
177c5700ff feat: optimize traffic graph quadratic curve 2022-04-16 22:32:44 +08:00
GyDi
5d401a4fbb feat: optimize the animation of the traffic graph 2022-04-16 17:28:30 +08:00
GyDi
c60ab97103 fix: optimize clash launch 2022-04-15 01:29:25 +08:00
GyDi
f3d7a90ac7 fix: reset after exit 2022-04-14 01:29:33 +08:00
GyDi
7b114f961a fix: adjust code 2022-04-14 01:29:02 +08:00
GyDi
8caaf13c84 v0.0.29 2022-04-13 01:30:08 +08:00
GyDi
0218da1ee6 chore: update log 2022-04-13 01:29:48 +08:00
GyDi
f4786755f1 chore: update dep 2022-04-13 01:21:49 +08:00
GyDi
b9b1fcb4e5 chore: rename green to portable 2022-04-13 01:17:40 +08:00
GyDi
5bfda557bc feat: system tray add tun mode 2022-04-13 01:09:51 +08:00
GyDi
6e53df403e fix: adjust log 2022-04-13 00:49:30 +08:00
GyDi
4a7246ec6e feat: supports change config dir 2022-04-12 23:09:32 +08:00
GyDi
f53a021c08 chore: update clash version 2022-04-12 23:04:19 +08:00
GyDi
8fa95d06e1 feat: add default user agent 2022-04-12 01:05:51 +08:00
GyDi
b34bd9ffd2 chore: rm console 2022-04-11 02:26:09 +08:00
GyDi
fd1a7e34a8 feat: connections page supports filter 2022-04-11 02:25:34 +08:00
GyDi
973653a690 feat: log page supports filter 2022-04-11 01:46:33 +08:00
GyDi
11e4dfe940 fix: check button hover style 2022-04-10 03:33:17 +08:00
GyDi
e7804d39b3 feat: optimize delay checker concurrency strategy 2022-04-10 02:58:48 +08:00
GyDi
6cfbde43ab feat: support sort proxy node and custom test url 2022-04-10 02:09:36 +08:00
GyDi
30421bf247 chore: update deps 2022-04-10 01:31:05 +08:00
GyDi
ab1b6b45a2 refactor: proxy head 2022-04-08 01:35:18 +08:00
GyDi
59366c04dd v0.0.28 2022-04-07 01:28:30 +08:00
GyDi
fac437c1ef chore: update log 2022-04-07 01:27:48 +08:00
GyDi
797f0d92b3 feat: handle remote clash config fields 2022-04-07 01:20:44 +08:00
GyDi
75f3fa5144 fix: icon button color inherit 2022-04-06 22:52:00 +08:00
GyDi
6ea1b5c7c3 fix: remove the lonely zero 2022-04-06 22:48:10 +08:00
GyDi
6a81cc0e83 chore: readme 2022-04-06 01:52:20 +08:00
GyDi
d3e2c7c51a v0.0.27 2022-04-06 01:21:33 +08:00
GyDi
1c930a2eb9 chore: update log 2022-04-06 01:19:13 +08:00
GyDi
fb1ea5b37c refactor: update profile menu 2022-04-06 01:13:06 +08:00
GyDi
e82f344a2c chore: add updater script 2022-04-05 23:24:52 +08:00
GyDi
10ba205f97 chore: updater json supports arch field 2022-04-05 23:19:54 +08:00
GyDi
fbc8b97c25 chore: add ci 2022-04-05 20:21:53 +08:00
GyDi
c6a31b21e3 refactor: enhanced mode ui component 2022-04-03 01:41:48 +08:00
GyDi
4aa8c4b79d feat: add text color 2022-04-02 17:18:38 +08:00
GyDi
04179ae833 fix: i18n add value 2022-04-02 13:49:48 +08:00
GyDi
205ecde505 fix: proxy page first render 2022-04-02 01:23:31 +08:00
GyDi
7cb467c960 feat: control final tun config 2022-04-01 23:55:44 +08:00
GyDi
cbe7c69154 chore: add debug feature 2022-04-01 23:43:35 +08:00
GyDi
c93cbb614d feat: support css injection 2022-04-01 02:08:42 +08:00
GyDi
42c570a6e2 fix: console warning 2022-04-01 01:16:23 +08:00
GyDi
aed47c1178 feat: support theme setting 2022-03-31 23:38:00 +08:00
GyDi
a1ac4784c8 feat: add text color 2022-03-31 23:34:36 +08:00
GyDi
d8b751d56b feat: add theme setting 2022-03-31 23:11:50 +08:00
GyDi
213eb2ae88 refactor: ui theme 2022-03-30 12:36:39 +08:00
GyDi
17b7265a9a chore: add api 2022-03-30 01:30:22 +08:00
GyDi
3afdf9571e fix: icon button title 2022-03-30 01:29:25 +08:00
GyDi
bb8a59d1db chore: update tauri 2022-03-29 23:05:32 +08:00
GyDi
d26830d81e fix: macOS transition flickers close #47 2022-03-29 01:39:54 +08:00
GyDi
1f2c703ddc chore: update deps 2022-03-29 01:33:14 +08:00
GyDi
dd51a6e4d1 fix: csp image data 2022-03-28 23:17:11 +08:00
GyDi
caa7597097 chore: readme 2022-03-28 01:33:58 +08:00
GyDi
409996cb29 v0.0.26 2022-03-28 01:30:54 +08:00
GyDi
a15b37d177 chore: update log 2022-03-28 01:18:18 +08:00
GyDi
a436ef2126 fix: close dialog after save 2022-03-28 01:09:34 +08:00
GyDi
4c53e6e89d refactor: optimize enhance mode strategy 2022-03-28 01:07:14 +08:00
GyDi
083eb98949 fix: change to deep copy 2022-03-28 00:56:48 +08:00
GyDi
418951415c feat: enhanced mode supports more fields 2022-03-27 20:36:13 +08:00
GyDi
bac0d0a175 chore: profile release 2022-03-27 01:38:40 +08:00
GyDi
7bde7ebc30 feat: supports edit profile file 2022-03-27 00:58:17 +08:00
GyDi
7b7e6555c1 feat: supports silent start 2022-03-26 18:56:16 +08:00
GyDi
ea8565d0ca feat: use crate open 2022-03-23 23:00:14 +08:00
GyDi
1ba2902618 fix: window style close #45 2022-03-23 21:43:13 +08:00
GyDi
e91e8d565a feat: enhance connections display order 2022-03-23 14:02:08 +08:00
GyDi
206e613ace chore: adjust style 2022-03-23 13:58:57 +08:00
GyDi
9a88dcf33e fix: manage global proxy correctly 2022-03-23 12:49:34 +08:00
GyDi
9fc9e8972f fix: tauri csp 2022-03-23 01:47:35 +08:00
GyDi
4fe6703dd4 feat: save global selected 2022-03-22 12:38:59 +08:00
GyDi
d613cbb68f v0.0.25 2022-03-22 01:51:06 +08:00
GyDi
be2247b36f chore: update log 2022-03-22 01:50:24 +08:00
GyDi
66d1f49116 chore: update clash core 2022-03-22 01:50:15 +08:00
GyDi
37d35bbbdb fix: windows style 2022-03-22 01:36:06 +08:00
GyDi
321d4ccc8e fix: update state 2022-03-22 01:27:22 +08:00
inRm3D
6e4338d13c chore: readme (#43) 2022-03-21 21:55:45 +08:00
GyDi
677d15d8a7 chore: tauri feature 2022-03-21 11:40:52 +08:00
GyDi
a7e978dc06 fix: profile item loading state 2022-03-21 11:40:27 +08:00
GyDi
67f5218a73 chore: tauri allowList 2022-03-20 13:16:50 +08:00
GyDi
c2d1329b8b chore: update clash version 2022-03-20 12:37:55 +08:00
GyDi
0530ec0651 v0.0.24 2022-03-20 01:50:39 +08:00
GyDi
8a6e18badc chore: update log 2022-03-20 01:50:15 +08:00
GyDi
1a144a7070 feat: system tray supports system proxy setting 2022-03-20 01:36:43 +08:00
GyDi
e115432e11 feat: prevent context menu on Windows close #22 2022-03-19 19:28:53 +08:00
GyDi
145f6e2d53 feat: create local profile with selected file 2022-03-19 19:21:55 +08:00
GyDi
9c62311b56 feat: reduce the impact of the enhanced mode 2022-03-19 16:02:47 +08:00
GyDi
a150d91c48 fix: adjust windows style 2022-03-19 15:35:59 +08:00
GyDi
4730a4e352 feat: parse update log 2022-03-19 15:31:58 +08:00
GyDi
67c57be830 chore: update log supports 2022-03-19 14:04:58 +08:00
GyDi
d98e72ca25 chore: updater proxy 2022-03-19 11:14:12 +08:00
GyDi
d81fa1e610 chore: ci support linux 2022-03-19 10:50:44 +08:00
GyDi
8c12de8b73 chore: readme 2022-03-18 19:02:53 +08:00
GyDi
9b5218f85b feat: fill i18n 2022-03-18 14:59:23 +08:00
GyDi
a6cf5c7667 feat: dayjs i18n 2022-03-18 14:45:24 +08:00
GyDi
4fca73ffbd feat: connections page simply support 2022-03-18 14:43:22 +08:00
GyDi
46a9023782 feat: add wintun.dll by default 2022-03-18 11:49:00 +08:00
GyDi
5fed443476 fix: change mixed port error 2022-03-18 11:39:02 +08:00
GyDi
0fab0f207c chore: add dev feature 2022-03-18 10:59:35 +08:00
GyDi
ee67358ee6 chore: rename temp dir 2022-03-18 10:20:24 +08:00
GyDi
cefd44304b chore: resolve wintun.dll 2022-03-17 19:29:30 +08:00
GyDi
2a88cdc76d fix: auto launch path 2022-03-16 18:44:25 +08:00
GyDi
4c39d37ad5 fix: tun mode config 2022-03-15 14:23:57 +08:00
GyDi
9b9865f717 fix: adjsut open cmd error 2022-03-15 12:51:39 +08:00
GyDi
3f0456322d chore: fix ci env 2022-03-15 00:00:20 +08:00
GyDi
16ac005fd6 v0.0.23 2022-03-14 01:34:26 +08:00
GyDi
003fcf446a feat: event emit when clash config update 2022-03-13 01:37:41 +08:00
GyDi
39703120a4 chore: fix ci 2022-03-13 00:59:42 +08:00
GyDi
656c0efc0d chore: update tauri action 2022-03-13 00:58:49 +08:00
GyDi
63f7c0ed69 chore: green version bundle 2022-03-13 00:52:31 +08:00
GyDi
5103463524 chore: rm then 2022-03-12 23:58:20 +08:00
GyDi
97254a1e3a feat: i18n supports 2022-03-12 23:07:45 +08:00
GyDi
9f171a01e8 fix: parse external-controller 2022-03-12 21:35:20 +08:00
GyDi
6c9b6007a3 fix: config file case close #18 2022-03-12 21:03:17 +08:00
GyDi
f4a7c4bd69 chore: merge 2022-03-12 18:45:07 +08:00
ttyS3
5abad18e51 chore: show open app and log dir failed message 2022-03-12 18:04:56 +08:00
GyDi
57f70a6d35 chore: adjust error log 2022-03-12 15:57:16 +08:00
GyDi
cb78f3a707 feat: change open command on linux
refactor(logging): refine open dir failed log message, add Linux support
2022-03-12 15:40:23 +08:00
ttyS3
b331db74ee feat: add Linux open dir support
refactor(logging): refine open dir failed log message
2022-03-12 03:18:25 +08:00
GyDi
8fb9ad3fe7 fix: patch item option 2022-03-11 19:56:56 +08:00
GyDi
d4b5c55169 fix: user agent not works 2022-03-11 19:50:51 +08:00
GyDi
a41438d779 v0.0.22 2022-03-10 02:27:00 +08:00
GyDi
0895d8a813 chore: rm dead code 2022-03-10 02:25:35 +08:00
GyDi
89310d7b7c fix: external-controller 2022-03-10 02:19:06 +08:00
GyDi
ca0e936f7c feat: support more options for remote profile 2022-03-10 02:03:55 +08:00
GyDi
afe767e28a chore: reduce icon size on macOS 2022-03-10 01:04:53 +08:00
GyDi
1481b011d9 chore: adjust icon 2022-03-10 00:04:59 +08:00
GyDi
5f1b968b60 chore: add linux link 2022-03-09 20:14:15 +08:00
GyDi
5a729043ce feat: linux system proxy 2022-03-09 19:48:32 +08:00
GyDi
2de0dfcef7 chore: update deps 2022-03-09 02:36:40 +08:00
GyDi
7cc83ed080 fix: change proxy bypass on mac 2022-03-09 02:36:03 +08:00
GyDi
31ec03f8e5 fix: kill sidecars after install still in test 2022-03-09 02:09:51 +08:00
GyDi
0d095c4cf1 fix: log some error 2022-03-09 02:00:56 +08:00
GyDi
dbde09d008 chore: rename productName 2022-03-09 01:53:34 +08:00
GyDi
43d33e21e6 chore: update tauri version 2022-03-09 01:50:37 +08:00
GyDi
c8cf43a255 fix: apply_blur parameter 2022-03-08 10:44:41 +08:00
GyDi
b2d05672b1 fix: limit enhanced profile range 2022-03-08 01:45:46 +08:00
GyDi
4123c789e6 chore: update deps 2022-03-08 01:18:20 +08:00
GyDi
c65f3c7b68 chore: add default bypass 2022-03-08 00:39:09 +08:00
GyDi
41e3642e02 fix: profile updated field 2022-03-07 01:41:42 +08:00
GyDi
eae7602f80 fix: profile field check 2022-03-07 01:30:32 +08:00
GyDi
fd12c73cf9 fix: create dir panic 2022-03-07 01:06:21 +08:00
GyDi
2ef9d70224 v0.0.21 2022-03-06 18:36:02 +08:00
GyDi
3861531fb7 chore: default release body 2022-03-06 18:34:38 +08:00
GyDi
aeff41ff41 chore: update deps 2022-03-06 18:04:44 +08:00
GyDi
b5c8283575 fix: only error when selected 2022-03-06 17:06:45 +08:00
GyDi
b31cdce073 feat: enhance profile status 2022-03-06 17:02:29 +08:00
GyDi
6040aae12a feat: menu item refresh enhanced mode 2022-03-06 15:46:16 +08:00
GyDi
6ba4afc5bb fix: enhanced profile consistency 2022-03-06 15:40:16 +08:00
GyDi
4707e2b02a feat: profile enhanced mode 2022-03-06 14:59:25 +08:00
GyDi
08dd73fd72 feat: profile enhanced ui 2022-03-05 22:54:39 +08:00
GyDi
83bf67b8ca feat: profile item adjust 2022-03-05 19:04:20 +08:00
GyDi
b758ead371 fix: simply compatible with proxy providers 2022-03-05 16:18:44 +08:00
GyDi
357a0db03e fix: component warning 2022-03-05 15:48:39 +08:00
GyDi
6a3219a5e8 chore: update copyright 2022-03-05 01:51:29 +08:00
GyDi
b7e7e82f85 fix: when updater failed 2022-03-04 02:10:27 +08:00
GyDi
e4c66c8b2b fix: log file 2022-03-04 02:08:26 +08:00
GyDi
4d4f8b5ee8 chore: update deps 2022-03-03 20:37:06 +08:00
GyDi
5ab75f7eb9 chore: enhance wip 2022-03-03 01:58:05 +08:00
GyDi
8ff22bc737 fix: result 2022-03-03 01:56:47 +08:00
GyDi
1d65287fbb feat: enhanced profile (wip) 2022-03-03 01:52:02 +08:00
GyDi
dc24993bf6 v0.0.20 2022-03-02 01:59:53 +08:00
GyDi
ef606d1365 feat: edit profile item 2022-03-02 01:58:16 +08:00
GyDi
e26ecec545 fix: cover profile extra 2022-03-02 01:45:00 +08:00
GyDi
28e8aa9111 chore: adjust log field 2022-03-02 01:13:31 +08:00
GyDi
a8ff67e3b8 chore: rm file 2022-03-02 00:58:07 +08:00
GyDi
c311b92ae2 feat: use nanoid 2022-03-02 00:56:05 +08:00
GyDi
b04e22cd2c feat: compatible profile config 2022-03-01 11:05:33 +08:00
GyDi
4903c70686 refactor: profile config 2022-03-01 08:58:47 +08:00
GyDi
04aa963b9a v0.0.19 2022-02-28 01:59:53 +08:00
GyDi
7fdd2e81cf wip: refactor profile 2022-02-28 01:59:13 +08:00
GyDi
38778c8cac refactor: use anyhow to handle error 2022-02-28 01:34:25 +08:00
GyDi
565851b113 fix: display menu only on macos 2022-02-27 01:47:56 +08:00
GyDi
cd216b0591 fix: proxy global showType 2022-02-27 01:33:22 +08:00
GyDi
031e2acc63 feat: native menu supports 2022-02-27 01:29:57 +08:00
GyDi
b3afa6b0a4 chore: update deps 2022-02-27 01:16:43 +08:00
GyDi
40fc2b78d3 feat: filter proxy and display type 2022-02-27 00:58:14 +08:00
GyDi
24e6c1ea36 feat: use lock fn 2022-02-26 17:39:36 +08:00
GyDi
275b4e2944 feat: refactor proxy page 2022-02-26 17:39:08 +08:00
GyDi
061c93cb7d feat: proxy group auto scroll to current 2022-02-26 01:21:39 +08:00
GyDi
3b91117724 v0.0.18 2022-02-25 02:13:41 +08:00
GyDi
eb4fac28c4 chore: ci 2022-02-25 02:12:55 +08:00
GyDi
acb5fc6393 feat: clash tun mode supports 2022-02-25 02:09:39 +08:00
GyDi
961ed728a1 feat: use enhanced guard-state 2022-02-25 01:22:33 +08:00
GyDi
678fcfc3a3 feat: guard state supports debounce guard 2022-02-25 01:21:13 +08:00
GyDi
79d60e6b4a feat: adjust clash version display 2022-02-24 23:04:18 +08:00
GyDi
858b2ff6da feat: hide command window 2022-02-24 22:46:12 +08:00
GyDi
ba3355c74c fix: use full clash config 2022-02-23 23:23:07 +08:00
GyDi
6e3028cf86 feat: enhance log data 2022-02-23 02:00:45 +08:00
GyDi
a28598630d chore: update readme 2022-02-22 21:54:33 +08:00
GyDi
36810b2d42 v0.0.17 2022-02-22 02:08:31 +08:00
GyDi
de9923f2e1 fix: reconnect websocket when restart clash 2022-02-22 02:05:22 +08:00
GyDi
716b1ac528 chore: enhance publish ci 2022-02-22 01:56:06 +08:00
GyDi
77663d64a0 fix: wrong exe path 2022-02-21 22:33:37 +08:00
GyDi
49540c7151 chore: use tauri cli 2022-02-21 22:31:00 +08:00
GyDi
03710bc6ba v0.0.16 2022-02-21 01:33:11 +08:00
GyDi
489ae6c9b6 feat: change window style 2022-02-20 23:46:13 +08:00
GyDi
3b54eeb1c5 feat: fill verge template 2022-02-20 20:12:55 +08:00
GyDi
a9bd753e38 feat: enable customize guard duration 2022-02-20 20:11:39 +08:00
GyDi
bf15580081 feat: system proxy guard 2022-02-20 18:53:21 +08:00
GyDi
5b5a299b55 feat: enable show or hide traffic graph 2022-02-19 18:14:40 +08:00
GyDi
051b529c11 fix: patch verge config 2022-02-19 17:32:23 +08:00
GyDi
fa5fb815fb feat: traffic line graph 2022-02-19 17:02:24 +08:00
GyDi
a4c0a61698 chore: update deps 2022-02-19 00:34:09 +08:00
GyDi
80d8a8190d feat: adjust profile item ui 2022-02-19 00:23:47 +08:00
GyDi
e69a62c41a feat: adjust fetch profile url 2022-02-19 00:09:36 +08:00
GyDi
46183d4d43 feat: inline config file template 2022-02-18 23:57:13 +08:00
GyDi
4f2d3cc250 chore: update check script 2022-02-18 23:49:39 +08:00
GyDi
79daf543b0 fix: fetch profile panic 2022-02-17 13:44:26 +08:00
GyDi
5b83fb496c feat: kill sidecars when update app 2022-02-17 02:10:25 +08:00
GyDi
3dfe7c27cd feat: delete file 2022-02-17 01:58:12 +08:00
GyDi
e50538c634 feat: lock some async functions 2022-02-17 01:42:25 +08:00
GyDi
92b6ecc4dd chore: tauri updater env 2022-02-16 11:02:00 +08:00
GyDi
588ade0ab2 chore: add pubkey 2022-02-16 10:59:31 +08:00
GyDi
4375aefb49 v0.0.15 2022-02-16 03:22:25 +08:00
GyDi
c5cba7775a feat: support open dir 2022-02-16 03:21:34 +08:00
GyDi
7cf8ec6d61 fix: spawn command 2022-02-16 02:43:52 +08:00
GyDi
46cbd3ae7b feat: change allow list 2022-02-16 02:42:56 +08:00
GyDi
678cf8a341 feat: support check delay 2022-02-16 02:22:01 +08:00
GyDi
3a0f1f6197 fix: import error 2022-02-15 01:36:19 +08:00
GyDi
902ff23a95 feat: scroll to proxy item 2022-02-15 01:34:10 +08:00
GyDi
7e2e9aa836 chore: update deps 2022-02-15 00:33:58 +08:00
GyDi
491ef4559f fix: not open file when new profile 2022-02-15 00:21:34 +08:00
GyDi
b7bad0f585 feat: edit system proxy bypass 2022-02-14 01:26:24 +08:00
GyDi
ce248bc169 feat: disable user select 2022-02-13 19:52:35 +08:00
GyDi
d599be7e7c fix: reset value correctly 2022-02-13 19:40:31 +08:00
GyDi
5b1fc0e6c0 feat: new profile able to edit name and desc 2022-02-13 19:33:24 +08:00
GyDi
aeece6c201 chore: adjust files 2022-02-13 19:27:24 +08:00
GyDi
d7a1b974cd feat: update tauri version 2022-02-13 18:45:03 +08:00
GyDi
0db1872bd2 fix: something 2022-02-12 21:12:39 +08:00
GyDi
4333bc7004 fix: menu without fragment 2022-02-10 01:42:52 +08:00
GyDi
f6caac749e feat: display clash core version 2022-02-10 01:35:55 +08:00
GyDi
723303e1b6 feat: adjust profile item menu 2022-02-10 01:14:57 +08:00
GyDi
0daf5bae96 v0.0.14 2022-02-09 02:11:01 +08:00
GyDi
6d59fe2a34 feat: profile item ui 2022-02-09 02:08:27 +08:00
GyDi
3ed0f005ee fix: proxy list error 2022-02-09 02:02:29 +08:00
GyDi
66b89ee817 feat: support new profile 2022-02-07 17:26:05 +08:00
GyDi
c596c875a6 feat: support open command for viewing 2022-02-07 16:45:20 +08:00
GyDi
08c1a29383 chore: add item template yaml 2022-02-02 20:56:06 +08:00
GyDi
ab078d1ea0 fix: something 2022-01-31 23:34:58 +08:00
GyDi
b028b09041 fix: macos auto launch fail 2022-01-31 23:32:41 +08:00
GyDi
44c3eb7b35 chore: update deps and app name 2022-01-31 23:27:11 +08:00
GyDi
f54052c320 chore: cargo update 2022-01-28 22:02:17 +08:00
GyDi
d23a055e3b chore: update clash version 2022-01-28 22:00:15 +08:00
GyDi
8f76f3184f v0.0.13 2022-01-25 02:11:00 +08:00
GyDi
619c4853fd feat: global proxies use virtual list 2022-01-25 02:08:10 +08:00
GyDi
58fb6ade7c feat: enable change proxy mode 2022-01-25 01:51:44 +08:00
GyDi
f2c0626c68 feat: update styles 2022-01-25 01:48:26 +08:00
GyDi
3956d2bd63 feat: manage clash mode 2022-01-24 23:13:13 +08:00
GyDi
1d851631d6 chore: update readme 2022-01-22 23:57:39 +08:00
GyDi
517e38e33f v0.0.12 2022-01-21 03:09:21 +08:00
GyDi
a2644a4ebf chore: update ci 2022-01-21 03:08:40 +08:00
GyDi
f7bf6a8080 fix: type error 2022-01-21 03:08:20 +08:00
GyDi
681c1d9e9a v0.0.12 2022-01-21 02:59:33 +08:00
GyDi
9ab2e69f4f refactor: rename profiles & command state 2022-01-21 02:57:15 +08:00
GyDi
9af2ab57d1 feat: change system porxy when changed port 2022-01-21 02:50:13 +08:00
GyDi
f14e4dc0be feat: enable change mixed port 2022-01-21 02:32:23 +08:00
GyDi
39bd9641f4 feat: manage clash config 2022-01-21 02:31:44 +08:00
GyDi
177c283011 chore: add ahooks 2022-01-21 02:29:45 +08:00
GyDi
62ff1421e7 fix: restart clash should update something 2022-01-21 00:29:33 +08:00
GyDi
2577cbe1ac feat: enable update clash info 2022-01-20 23:41:08 +08:00
GyDi
7542f065a1 feat: rename edit as view 2022-01-19 23:58:34 +08:00
GyDi
aa2abb92ef chore: adjust ci 2022-01-19 23:55:05 +08:00
GyDi
efc00c1610 fix: script error... 2022-01-19 00:49:23 +08:00
GyDi
7585893cf0 fix: tag error 2022-01-19 00:46:43 +08:00
GyDi
4fd9019c25 fix: script error 2022-01-19 00:42:37 +08:00
GyDi
f4990aaa95 feat: test auto gen update.json ci 2022-01-19 00:37:59 +08:00
GyDi
834fa77385 v0.0.11 2022-01-17 02:53:07 +08:00
GyDi
3acd20c04d fix: remove cargo test 2022-01-17 02:50:19 +08:00
GyDi
878859d7cc v0.0.11 2022-01-17 02:44:08 +08:00
GyDi
7038776ec5 feat: adjust setting typography 2022-01-17 02:42:52 +08:00
GyDi
a74001ef6a feat: enable force select profile 2022-01-17 02:28:23 +08:00
GyDi
0bad1794c4 feat: support edit profile item 2022-01-17 02:16:17 +08:00
GyDi
ceec72b869 fix: reduce proxy item height 2022-01-17 02:15:54 +08:00
GyDi
5c1ed0ec06 feat: adjust control ui 2022-01-17 02:15:06 +08:00
GyDi
ce00fc502b feat: update profile supports noproxy 2022-01-16 22:57:42 +08:00
GyDi
ab82d2af4f refactor: something 2022-01-16 18:30:25 +08:00
GyDi
2eb3e1aef6 fix: put profile request with no proxy 2022-01-16 18:20:01 +08:00
GyDi
32cb0388cf refactor: notice caller 2022-01-16 17:56:43 +08:00
GyDi
30f9039a2c chore: change ci 2022-01-16 16:03:53 +08:00
GyDi
8bad63f72d fix: ci strategy 2022-01-16 14:30:49 +08:00
GyDi
aec4d89550 chore: enhance ci 2022-01-16 14:27:41 +08:00
GyDi
e8639a37af feat: rename page 2022-01-16 03:25:50 +08:00
GyDi
a92ab2cbe3 refactor: setting page 2022-01-16 03:22:37 +08:00
GyDi
fb0bb6e7f3 feat: refactor and adjust ui 2022-01-16 03:11:07 +08:00
GyDi
8dbf870122 feat: rm some commands 2022-01-16 03:09:36 +08:00
GyDi
50dab48f25 feat: change type 2022-01-15 21:58:13 +08:00
GyDi
e0c6354ff3 feat: supports auto launch on macos and windows 2022-01-15 21:55:05 +08:00
GyDi
5b03d251fd chore: add auto-launch 2022-01-15 21:00:19 +08:00
GyDi
9c4b69d1ae v0.0.10 2022-01-13 02:53:29 +08:00
GyDi
feba220fff feat: adjust proxy page 2022-01-13 02:51:30 +08:00
GyDi
f9efa3bf8f feat: press esc hide the window 2022-01-13 02:11:50 +08:00
GyDi
6ebf333982 fix: version update error 2022-01-12 22:19:44 +08:00
GyDi
2c7d07d606 v0.0.9 2022-01-12 02:55:07 +08:00
GyDi
b05185e7cc feat: show system proxy info 2022-01-12 02:54:50 +08:00
GyDi
148547de95 feat: support blur window 2022-01-12 02:27:29 +08:00
GyDi
fb6a97789e chore: add macos startup todo 2022-01-11 23:14:43 +08:00
GyDi
271f34c7e3 chore: cargo update 2022-01-11 23:13:02 +08:00
GyDi
9309f877d1 v0.0.8 2022-01-11 02:27:04 +08:00
GyDi
88db5fb38e chore: update ci 2022-01-11 02:26:50 +08:00
GyDi
4ee0a1b8c5 feat: windows support startup 2022-01-11 02:24:43 +08:00
GyDi
7617d734e2 feat: window self startup 2022-01-11 02:21:51 +08:00
GyDi
a15ae9badb fix: text 2022-01-10 22:57:21 +08:00
GyDi
97b6fed909 chore: ignore update json 2022-01-10 22:08:57 +08:00
GyDi
ab653afae9 fix: update profile after restart clash 2022-01-10 22:08:18 +08:00
GyDi
df158a741a fix: get proxies multiple times 2022-01-10 21:25:41 +08:00
GyDi
fb1371ebfa docs: fix img width 2022-01-10 02:52:12 +08:00
GyDi
82d5494235 docs: update 2022-01-10 02:49:42 +08:00
GyDi
6b754723d9 v0.0.7 2022-01-10 02:17:35 +08:00
GyDi
e003113132 feat: use tauri updater 2022-01-10 02:15:38 +08:00
GyDi
dda492ff87 feat: support update checker 2022-01-10 02:05:35 +08:00
GyDi
2459699d27 v0.0.6 2022-01-09 21:36:43 +08:00
GyDi
989acec027 chore: ci add macos 2022-01-09 21:34:14 +08:00
GyDi
701ab8b24f feat: support macos proxy config 2022-01-09 21:19:35 +08:00
GyDi
ed6bfa5fa2 feat: custom window decorations 2022-01-08 22:23:48 +08:00
GyDi
2d2899b68a feat: profiles add menu and delete button 2022-01-08 16:52:18 +08:00
GyDi
2d8b80bfa9 fix: delete profile item command 2022-01-08 14:21:12 +08:00
GyDi
c1cc560458 chore: temp 2022-01-08 02:54:37 +08:00
GyDi
95fb562533 v0.0.5 2022-01-08 02:12:56 +08:00
GyDi
17853ac237 chore: enhance ci 2022-01-08 02:12:12 +08:00
GyDi
e064a8bbdb chore: rename script 2022-01-08 01:56:28 +08:00
GyDi
8b9e3f1b59 chore: update publish script 2022-01-08 01:51:24 +08:00
GyDi
1d38739016 refactor: rename 2022-01-08 01:27:25 +08:00
GyDi
3bb46eaa6f refactor: impl structs methods 2022-01-07 23:29:20 +08:00
GyDi
3b1561a99b chore: update clash version 2022-01-06 23:40:57 +08:00
GyDi
a61e5c7310 fix: initialize profiles state 2022-01-05 23:30:18 +08:00
GyDi
ef8d1ebc0e chore: fixed tauri rev 2022-01-05 23:27:26 +08:00
GyDi
348b3d3738 feat: delay put profiles and retry 2022-01-05 02:01:32 +08:00
GyDi
3550aa10ba refactor: impl as struct methods 2022-01-05 02:00:59 +08:00
GyDi
c29eadd9ee feat: Window Send and Sync 2022-01-04 21:29:04 +08:00
GyDi
aecefaee8d chore: update tauri 2022-01-04 21:25:00 +08:00
GyDi
3bab5ec3a7 feat: support restart sidecar tray event 2021-12-31 18:24:50 +08:00
GyDi
3c908963ae feat: prevent click same 2021-12-31 18:21:35 +08:00
GyDi
cba176cdc9 fix: item header bgcolor 2021-12-31 18:19:53 +08:00
GyDi
cfdc854409 feat: scroller stable 2021-12-31 18:19:17 +08:00
GyDi
1d87f78088 feat: compatible with macos(wip) 2021-12-29 18:49:38 +08:00
GyDi
63b01376d6 chore: change tauri to git repo 2021-12-29 18:47:29 +08:00
GyDi
010e518adb fix: null type error 2021-12-29 01:01:22 +08:00
GyDi
6570784b46 chore: dev support macos 2021-12-29 01:01:09 +08:00
GyDi
63b31f41a7 v0.0.4 2021-12-28 01:48:25 +08:00
GyDi
96eca59afc feat: record selected proxy 2021-12-28 01:47:43 +08:00
GyDi
25978bde8e feat: display version 2021-12-28 01:35:58 +08:00
GyDi
7691d2b5db chore: save lock file 2021-12-28 00:14:15 +08:00
GyDi
edb108322e chore: post version script 2021-12-27 23:07:56 +08:00
GyDi
af3936b68e v0.0.3 2021-12-27 23:06:46 +08:00
GyDi
e76c25f9d3 v0.0.3 2021-12-27 23:06:45 +08:00
GyDi
124f455be2 chore: update version 2021-12-27 10:21:49 +08:00
GyDi
f679eefe0a chore: update version 2021-12-27 10:21:10 +08:00
99 changed files with 6000 additions and 3836 deletions

View File

@ -12,14 +12,16 @@ body:
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide/term.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue否则请在已有的issue下进行讨论
3. 请 **务必** 给issue填写一个简洁明了的标题以便他人快速检索
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
5. 请 **务必** 按照模板规范详细描述问题否则issue将会被关闭
4. 请 **务必** 查看 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本更新日志
5. 请 **务必** 尝试 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本,确定问题是否仍然存在
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本否则issue将会被直接关闭
## Before submitting the issue, please make sure of the following checklist:
1. Please make sure you have read the [Clash Verge Rev official documentation](https://clash-verge-rev.github.io/guide/term.html) and [FAQ](https://clash-verge-rev.github.io/faq/windows.html)
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
4. Please be sure to check out [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version update log
5. Please be sure to try the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version to ensure that the problem still exists
6. Please describe the problem in detail according to the template specification and try to update the Alpha version, otherwise the issue will be closed
- type: textarea
id: description
@ -57,7 +59,7 @@ body:
required: true
- type: textarea
attributes:
label: 日志 / Log
description: 请提供完整或相关部分的Debug日志请在“软件左侧菜单”->“设置”->“日志等级”调整到debugVerge错误请把“杂项设置”->“app日志等级”调整到debug/trace并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
label: 日志(勿上传日志文件,请粘贴日志内容) / Log (Do not upload the log file, paste the log content directly)
description: 请提供完整或相关部分的Debug日志请在“软件左侧菜单”->“设置”->“日志等级”调整到debugVerge错误请把“杂项设置”->“app日志等级”调整到debug并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to debug, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
validations:
required: true

View File

@ -3,8 +3,8 @@ name: Alpha Build
on:
workflow_dispatch:
schedule:
# UTC+8 00:00 (UTC 16:00 previous day) and UTC+8 12:00 (UTC 04:00)
- cron: "0 16,4 * * *"
# UTC+8 0,6,12,18
- cron: "0 16,22,4,10 * * *"
permissions: write-all
env:
CARGO_INCREMENTAL: 0
@ -25,7 +25,7 @@ jobs:
with:
fetch-depth: 2
- name: Check if version changed
- name: Check if version changed or src changed
id: check
run: |
# For manual workflow_dispatch, always run
@ -50,326 +50,80 @@ jobs:
if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then
echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION"
echo "should_run=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check if src or src-tauri directories changed
CURRENT_SRC_HASH=$(git rev-parse HEAD:src)
PREVIOUS_SRC_HASH=$(git rev-parse HEAD~1:src 2>/dev/null || echo "")
CURRENT_TAURI_HASH=$(git rev-parse HEAD:src-tauri 2>/dev/null || echo "")
PREVIOUS_TAURI_HASH=$(git rev-parse HEAD~1:src-tauri 2>/dev/null || echo "")
echo "Current src hash: $CURRENT_SRC_HASH"
echo "Previous src hash: $PREVIOUS_SRC_HASH"
echo "Current tauri hash: $CURRENT_TAURI_HASH"
echo "Previous tauri hash: $PREVIOUS_TAURI_HASH"
if [ "$CURRENT_SRC_HASH" != "$PREVIOUS_SRC_HASH" ] || [ "$CURRENT_TAURI_HASH" != "$PREVIOUS_TAURI_HASH" ]; then
echo "Source directories changed"
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "Version unchanged: $CURRENT_VERSION"
echo "Version and source directories unchanged"
echo "should_run=false" >> $GITHUB_OUTPUT
fi
delete_old_release:
delete_old_assets:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Release
uses: dev-drprasad/delete-tag-and-release@v1.1
- name: Delete Old Alpha Release Assets
uses: actions/github-script@v7
with:
tag_name: alpha
delete_release: true
repo: ${{ github.repository }}
github_token: ${{ secrets.GITHUB_TOKEN }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const releaseTag = 'alpha';
alpha:
needs: delete_old_release
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
try {
// Get the release by tag name
const { data: release } = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: releaseTag
});
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
console.log(`Found release with ID: ${release.id}`);
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
// Delete each asset
if (release.assets && release.assets.length > 0) {
console.log(`Deleting ${release.assets.length} assets`);
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
for (const asset of release.assets) {
console.log(`Deleting asset: ${asset.name} (${asset.id})`);
await github.rest.repos.deleteReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: asset.id
});
}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: alpha
releaseName: "Clash Verge Rev Alpha"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
alpha-for-linux-arm:
needs: delete_old_release
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
cat > /tmp/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt update
sudo apt install -y \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: "Install aarch64 tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
needs: delete_old_release
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
console.log('All assets deleted successfully');
} else {
console.log('No assets found to delete');
}
} catch (error) {
if (error.status === 404) {
console.log('Release not found, nothing to delete');
} else {
console.error('Error:', error);
throw error;
}
}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: [delete_old_release, alpha, alpha-for-linux-arm, alpha-for-fixed-webview2]
needs: delete_old_assets
steps:
- name: Checkout repository
uses: actions/checkout@v4
@ -380,7 +134,8 @@ jobs:
# Check if UPDATELOG.md exists
if [ -f "UPDATELOG.md" ]; then
# Extract the section starting with ## and containing -alpha until the next ## or end of file
ALPHA_LOGS=$(awk '/^## .*-alpha/{flag=1; print; next} /^## /{flag=0} flag' UPDATELOG.md)
# ALPHA_LOGS=$(awk '/^## .*-alpha/{flag=1; print; next} /^## /{flag=0} flag' UPDATELOG.md)
ALPHA_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$ALPHA_LOGS" ]; then
echo "Found alpha update logs"
@ -452,3 +207,307 @@ jobs:
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
alpha:
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: alpha
releaseName: "Clash Verge Rev Alpha"
releaseBody: "More new features are now supported."
releaseDraft: false
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
alpha-for-linux-arm:
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: "Setup for linux"
run: |-
sudo ls -lR /etc/apt/
cat > /tmp/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt update
sudo apt install -y \
libxslt1.1:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: "Install aarch64 tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: "Install armv7 tools"
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: "--max_old_space_size=4096"
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
cache-all-crates: true
cache-on-failure: true
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: "22"
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/109.0.1518.78/Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: "--max_old_space_size=4096"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -50,7 +50,7 @@ jobs:
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
@ -153,6 +153,7 @@ jobs:
sudo apt update
sudo apt install -y \
libxslt1.1:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \

View File

@ -3,12 +3,12 @@
#pnpm pretty-quick --staged
# 运行 clippy fmt
#cargo fmt --manifest-path ./src-tauri/Cargo.toml
cargo fmt --manifest-path ./src-tauri/Cargo.toml
# if [ $? -ne 0 ]; then
# echo "rustfmt failed to format the code. Please fix the issues and try again."
# exit 1
# fi
if [ $? -ne 0 ]; then
echo "rustfmt failed to format the code. Please fix the issues and try again."
exit 1
fi
#git add .

View File

@ -1,13 +1,13 @@
#!/bin/bash
# 运行 clippy
#cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
# cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
# 如果 clippy 失败,阻止 push
#if [ $? -ne 0 ]; then
# if [ $? -ne 0 ]; then
# echo "Clippy found issues in sub_crate. Please fix them before pushing."
# exit 1
#fi
# fi
# 允许 push
exit 0

View File

@ -1,17 +1,105 @@
## v2.2.1-alpha
**发行代号:拓**
## v2.2.3
#### 修复
1. **系统**
- 修复 MacOS 无法使用快捷键粘贴/选择/复制订阅地址。
#### 已知问题
- 仅在Ubuntu 22.04/24.04Fedora 41 **Gnome桌面环境** 做过简单测试不保证其他其他Linux发行版可用将在未来做进一步适配和调优
- MacOS 自定义图标与速率显示推荐图标尺寸为 256x256。其他尺寸可能会导致不正常图标和速率间隙
- MacOS 下 墙贴主要为浅色Tray 图标深色时图标闪烁;彩色 Tray 速率颜色淡
- Linux 下 Clash Verge Rev 内存占用显著高于 Windows / MacOS
### 2.2.3 相对于 2.2.2
#### 修复了:
- 首页“当前代理”因为重复刷新导致的CPU占用过高的问题
- “开机自启”和“DNS覆写”开关跳动问题
- 自定义托盘图标未能应用更改
- MacOS 自定义托盘图标显示速率时图标和文本间隙过大
- MacOS 托盘速率显示不全
- Linux 在系统服务模式下无法拉起 Mihomo 内核
- 使用异步操作,避免获取系统信息和切换代理模式可能带来的崩溃
- 相同节点名称可能导致的页面渲染出错
- URL Schemes被截断的问题
- 首页流量统计卡更好的时间戳范围
- 静默启动无法触发自动轻量化计时器
## v2.2.0
#### 新增了:
- Mihomo(Meta)内核升级至 1.19.4
- Clash Verge Rev 从现在开始不再强依赖系统服务和管理权限
- 支持根据用户偏好选择Sidecar(用户空间)模式或安装服务
- 增加载入初始配置文件的错误提示,防止切换到错误的订阅配置
- 检测是否以管理员模式运行软件,如果是提示无法使用开机自启
- 代理组显示节点数量
- 统一运行模式检测支持管理员模式下开启TUN模式
- 托盘切换代理模式会根据设置自动断开之前连接
- 如订阅获取失败回退使用Clash内核代理再次尝试
#### 移除了:
- 实时保存窗口位置和大小。这个功能可能会导致窗口异常大小和位置,还需观察。
#### 优化了:
- 重构了后端内核管理逻辑,更轻量化和有效的管理内核,提高了性能和稳定性
- 前端统一刷新应用数据,优化数据获取和刷新逻辑
- 优化首页流量图表代码,调整图表文字边距
- MacOS 托盘速率更好的显示样式和更新逻辑
- 首页仅在有流量图表时显示流量图表区域
- 更新DNS默认覆写配置
- 移除测试目录,简化资源初始化逻辑
## v2.2.2
**发行代号:拓**
感谢 Tunglies 对 Verge 后端重构,性能优化做出的重大贡献!
代号释义: 本次发布在功能上的大幅扩展。新首页设计为用户带来全新交互体验DNS 覆写功能增强网络控制能力解锁测试页面助力内容访问自由度提升轻量模式提供灵活使用选择。此外macOS 应用菜单集成、sidecar 模式、诊断信息导出等新特性进一步丰富了软件的适用场景。这些新增功能显著拓宽了 Clash Verge 的功能边界,为用户提供了更强大的工具和可能性。
#### 已知问题
- 仅在Ubuntu 22.04/24.04Fedora 41 **Gnome桌面环境** 做过简单测试不保证其他其他Linux发行版可用将在未来做进一步适配和调优
### 2.2.2 相对于 2.2.1(已下架不再提供)
#### 修复了:
- 弹黑框的问题(原因是服务崩溃触发重装机制)
- MacOS进入轻量模式以后隐藏Dock图标
- 增加轻量模式缺失的tray翻译
- Linux下的窗口边框被削掉的问题
#### 新增了:
- 加强服务检测和重装逻辑
- 增强内核与服务保活机制
- 增加服务模式下的僵尸进程清理机制
- 新增当服务模式多次尝试失败后自动回退至用户空间模式
### 2.2.1 相对于 2.2.0(已下架不再提供)
#### 修复了:
1. **首页**
- 修复 Direct 模式首页无法渲染
- 修复 首页启用轻量模式导致 ClashVergeRev 从托盘退出
- 修复 系统代理标识判断不准的问题
- 修复 系统代理地址错误的问题
- 代理模式“多余的切换动画”
2. **系统**
- 修复 MacOS 无法使用快捷键粘贴/选择/复制订阅地址。
- 修复 代理端口设置同步问题。
- 修复 Linux 无法与 Mihomo 核心 和 ClashVergeRev 服务通信
3. **界面**
- 修复 连接详情卡没有跟随主题色
4. **轻量模式**
- 修复 MacOS 轻量模式下 Dock 栏图标无法隐藏。
#### 新增了:
1. **首页**
- 首页文本过长自动截断
2. **轻量模式**
- 新增托盘进入轻量模式支持
- 新增进入轻量模式快捷键支持
3. **系统**
- 在 ClashVergeRev 对 Mihomo 进行操作时,总是尝试确保两者运行
- 服务器模式下启动mihomo内核的时候查找并停止其他已经存在的内核进程防止内核假死等问题带来的通信失败
4. **托盘**
- 新增 MacOS 启用托盘速率显示时,可选隐藏托盘图标显示
---
## 2.2.0(已下架不再提供)
#### 新增功能
1. **首页**
- 新增首页功能,默认启动页面改为首页。
@ -21,8 +109,8 @@
- 限制首页配置文件卡片URL长度。
2. **DNS 设置与覆写**
- 默认启用 DNS 设置。
- 新增 DNS 覆写功能。
- 默认启用 DNS 覆写。
3. **解锁测试**
- 新增解锁测试页面。
@ -32,10 +120,11 @@
- 添加自动轻量模式定时器。
5. **系统支持**
- Mihomo(meta)内核升级 1.19.3
- macOS 支持 CMD+W 关闭窗口。
- 新增 macOS 应用菜单。
- 添加管理员权限提示。
- 新增 sidecar 模式。
- 添加 macOS 安装服务时候的管理员权限提示。
- 新增 sidecar(用户空间启动内核) 模式。
6. **其他**
- 增强延迟测试日志和错误处理。
@ -47,28 +136,29 @@
- 修复 Windows 热键崩溃。
- 修复 macOS 无框标题。
- 修复 macOS 静默启动崩溃。
- 修复 macOS tray图标错位到左上角的问题。
- 修复 Windows/Linux 运行时崩溃。
- 修复 Netflix 检测错误
- 修复服务模式检测失败
- 修复 Win10 阴影和边框问题
- 修复 升级或重装后开机自启状态检测和同步问题
2. **性能**
- 优化小数值速度更新。
- 增加请求超时至 60 秒。
- 修复代理节点选择同步。
- 优化修改verge配置性能。
3. **构建**
2. **构建**
- 修复构建失败问题。
#### 优化
1. **性能**
- 重构后端,巨幅性能优化。
- 优化首页组件性能。
- 优化流量图表资源使用。
- 提升代理组列表滚动性能。
- 加快应用退出速度。
- 加快进入轻量模式速度。
- 优化小数值速度更新。
- 增加请求超时至 60 秒。
- 修复代理节点选择同步。
- 优化修改verge配置性能。
2. **重构**
- 重构后端,巨幅性能优化。
- 优化定时器管理。
- 重构 MihomoManager 处理流量。
- 优化 WebSocket 连接。
@ -87,14 +177,14 @@
感谢 Tychristine 对社区群组管理做出的重大贡献!
##### 2.1.2相对2.1.1(已下架不提供)更新了:
##### 2.1.2相对2.1.1(已下架不提供)更新了:
- 无法更新和签名验证失败的问题(该死的CDN缓存)
- 设置菜单区分Verge基本设置和高级设置
- 增加v2 Updater的更多功能和权限
- 退出Verge后Tun代理状态仍保留的问题
##### 2.1.1相对2.1.0(已下架不提供)更新了:
##### 2.1.1相对2.1.0(已下架不提供)更新了:
- 检测所需的Clash Verge Service版本杀毒软件误报可能与此有关因为检测和安装新版本Service需管理员权限
- MacOS下支持彩色托盘图标和更好速率显示感谢Tunglies

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.2.1-alpha",
"version": "2.2.3",
"license": "GPL-3.0-only",
"scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
@ -16,9 +16,11 @@
"updater-fixed-webview2": "node scripts/updater-fixed-webview2.mjs",
"portable": "node scripts/portable.mjs",
"portable-fixed-webview2": "node scripts/portable-fixed-webview2.mjs",
"fix-alpha-version": "node scripts/alpha_version.mjs",
"fix-alpha-version": "node scripts/fix-alpha_version.mjs",
"release-version": "node scripts/release_version.mjs",
"release-alpha-version": "node scripts/release-alpha_version.mjs",
"prepare": "husky",
"clean": "cd ./src-tauri && cargo clean && cd -"
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",

View File

@ -0,0 +1,96 @@
import fs from "fs/promises";
import path from "path";
/**
* 更新 package.json 文件中的版本号
*/
async function updatePackageVersion() {
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
let result = packageJson.version;
if (!result.includes("alpha")) {
result = `${result}-alpha`;
}
console.log("[INFO]: Current package.json version is: ", result);
packageJson.version = result;
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(`[INFO]: package.json version updated to: ${result}`);
} catch (error) {
console.error("Error updating package.json version:", error);
}
}
/**
* 更新 Cargo.toml 文件中的版本号
*/
async function updateCargoVersion() {
const _dirname = process.cwd();
const cargoTomlPath = path.join(_dirname, "src-tauri", "Cargo.toml");
try {
const data = await fs.readFile(cargoTomlPath, "utf8");
const lines = data.split("\n");
const updatedLines = lines.map((line) => {
if (line.startsWith("version =")) {
const versionMatch = line.match(/version\s*=\s*"([^"]+)"/);
if (versionMatch && !versionMatch[1].includes("alpha")) {
const newVersion = `${versionMatch[1]}-alpha`;
return line.replace(versionMatch[1], newVersion);
}
}
return line;
});
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
} catch (error) {
console.error("Error updating Cargo.toml version:", error);
}
}
/**
* 更新 tauri.conf.json 文件中的版本号
*/
async function updateTauriConfigVersion() {
const _dirname = process.cwd();
const tauriConfigPath = path.join(_dirname, "src-tauri", "tauri.conf.json");
try {
const data = await fs.readFile(tauriConfigPath, "utf8");
const tauriConfig = JSON.parse(data);
let version = tauriConfig.version;
if (!version.includes("alpha")) {
version = `${version}-alpha`;
}
console.log("[INFO]: Current tauri.conf.json version is: ", version);
tauriConfig.version = version;
await fs.writeFile(
tauriConfigPath,
JSON.stringify(tauriConfig, null, 2),
"utf8",
);
console.log(`[INFO]: tauri.conf.json version updated to: ${version}`);
} catch (error) {
console.error("Error updating tauri.conf.json version:", error);
}
}
/**
* 主函数依次更新所有文件的版本号
*/
async function main() {
await updatePackageVersion();
await updateCargoVersion();
await updateTauriConfigVersion();
}
main().catch(console.error);

197
scripts/release_version.mjs Normal file
View File

@ -0,0 +1,197 @@
import fs from "fs/promises";
import path from "path";
import { program } from "commander";
/**
* 验证版本号格式
* @param {string} version
* @returns {boolean}
*/
function isValidVersion(version) {
return /^v?\d+\.\d+\.\d+(-(alpha|beta|rc)(\.\d+)?)?$/i.test(version);
}
/**
* 标准化版本号确保v前缀可选
* @param {string} version
* @returns {string}
*/
function normalizeVersion(version) {
return version.startsWith("v") ? version : `v${version}`;
}
/**
* 更新 package.json 文件中的版本号
* @param {string} newVersion 新版本号
*/
async function updatePackageVersion(newVersion) {
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
console.log(
"[INFO]: Current package.json version is: ",
packageJson.version,
);
packageJson.version = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
await fs.writeFile(
packageJsonPath,
JSON.stringify(packageJson, null, 2),
"utf8",
);
console.log(
`[INFO]: package.json version updated to: ${packageJson.version}`,
);
return packageJson.version;
} catch (error) {
console.error("Error updating package.json version:", error);
throw error;
}
}
/**
* 更新 Cargo.toml 文件中的版本号
* @param {string} newVersion 新版本号
*/
async function updateCargoVersion(newVersion) {
const _dirname = process.cwd();
const cargoTomlPath = path.join(_dirname, "src-tauri", "Cargo.toml");
try {
const data = await fs.readFile(cargoTomlPath, "utf8");
const lines = data.split("\n");
const versionWithoutV = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
const updatedLines = lines.map((line) => {
if (line.trim().startsWith("version =")) {
return line.replace(
/version\s*=\s*"[^"]+"/,
`version = "${versionWithoutV}"`,
);
}
return line;
});
await fs.writeFile(cargoTomlPath, updatedLines.join("\n"), "utf8");
console.log(`[INFO]: Cargo.toml version updated to: ${versionWithoutV}`);
} catch (error) {
console.error("Error updating Cargo.toml version:", error);
throw error;
}
}
/**
* 更新 tauri.conf.json 文件中的版本号
* @param {string} newVersion 新版本号
*/
async function updateTauriConfigVersion(newVersion) {
const _dirname = process.cwd();
const tauriConfigPath = path.join(_dirname, "src-tauri", "tauri.conf.json");
try {
const data = await fs.readFile(tauriConfigPath, "utf8");
const tauriConfig = JSON.parse(data);
const versionWithoutV = newVersion.startsWith("v")
? newVersion.slice(1)
: newVersion;
console.log(
"[INFO]: Current tauri.conf.json version is: ",
tauriConfig.version,
);
tauriConfig.version = versionWithoutV;
await fs.writeFile(
tauriConfigPath,
JSON.stringify(tauriConfig, null, 2),
"utf8",
);
console.log(
`[INFO]: tauri.conf.json version updated to: ${versionWithoutV}`,
);
} catch (error) {
console.error("Error updating tauri.conf.json version:", error);
throw error;
}
}
/**
* 获取当前版本号从package.json
*/
async function getCurrentVersion() {
const _dirname = process.cwd();
const packageJsonPath = path.join(_dirname, "package.json");
try {
const data = await fs.readFile(packageJsonPath, "utf8");
const packageJson = JSON.parse(data);
return packageJson.version;
} catch (error) {
console.error("Error getting current version:", error);
throw error;
}
}
/**
* 主函数更新所有文件的版本号
* @param {string} versionArg 版本参数可以是标签或完整版本号
*/
async function main(versionArg) {
if (!versionArg) {
console.error("Error: Version argument is required");
process.exit(1);
}
try {
let newVersion;
const validTags = ["alpha", "beta", "rc"];
// 判断参数是标签还是完整版本号
if (validTags.includes(versionArg.toLowerCase())) {
// 标签模式:在当前版本基础上添加标签
const currentVersion = await getCurrentVersion();
const baseVersion = currentVersion.replace(
/-(alpha|beta|rc)(\.\d+)?$/i,
"",
);
newVersion = `${baseVersion}-${versionArg.toLowerCase()}`;
} else {
// 完整版本号模式
if (!isValidVersion(versionArg)) {
console.error(
"Error: Invalid version format. Expected format: vX.X.X or vX.X.X-tag (e.g. v2.2.3 or v2.2.3-alpha)",
);
process.exit(1);
}
newVersion = normalizeVersion(versionArg);
}
console.log(`[INFO]: Updating versions to: ${newVersion}`);
await updatePackageVersion(newVersion);
await updateCargoVersion(newVersion);
await updateTauriConfigVersion(newVersion);
console.log("[SUCCESS]: All version updates completed successfully!");
} catch (error) {
console.error("[ERROR]: Failed to update versions:", error);
process.exit(1);
}
}
// Example:
// pnpm release-version 2.2.3-alpha
// 设置命令行界面
program
.name("pnpm release-version")
.description(
"Update project version numbers. Can add tag (alpha/beta/rc) or set full version (e.g. v2.2.3 or v2.2.3-alpha)",
)
.argument(
"<version>",
"version tag (alpha/beta/rc) or full version (e.g. v2.2.3 or v2.2.3-alpha)",
)
.action(main)
.parse(process.argv);

View File

@ -43,3 +43,42 @@ export async function resolveUpdateLog(tag) {
return map[tag].join("\n").trim();
}
export async function resolveUpdateLogDefault() {
const cwd = process.cwd();
const file = path.join(cwd, UPDATE_LOG);
if (!fs.existsSync(file)) {
throw new Error("could not found UPDATELOG.md");
}
const data = await fsp.readFile(file, "utf-8");
const reTitle = /^## v[\d\.]+/;
const reEnd = /^---/;
let isCapturing = false;
let content = [];
let firstTag = "";
for (const line of data.split("\n")) {
if (reTitle.test(line) && !isCapturing) {
isCapturing = true;
firstTag = line.slice(3).trim();
continue;
}
if (isCapturing) {
if (reEnd.test(line)) {
break;
}
content.push(line);
}
}
if (!firstTag) {
throw new Error("could not found any version tag in UPDATELOG.md");
}
return content.join("\n").trim();
}

View File

@ -1,6 +1,6 @@
import fetch from "node-fetch";
import { getOctokit, context } from "@actions/github";
import { resolveUpdateLog } from "./updatelog.mjs";
import { resolveUpdateLog, resolveUpdateLogDefault } from "./updatelog.mjs";
// Add stable update JSON filenames
const UPDATE_TAG_NAME = "updater";
@ -8,8 +8,8 @@ const UPDATE_JSON_FILE = "update.json";
const UPDATE_JSON_PROXY = "update-proxy.json";
// Add alpha update JSON filenames
const ALPHA_TAG_NAME = "updater-alpha";
const ALPHA_UPDATE_JSON_FILE = "update-alpha.json";
const ALPHA_UPDATE_JSON_PROXY = "update-alpha-proxy.json";
const ALPHA_UPDATE_JSON_FILE = "update.json";
const ALPHA_UPDATE_JSON_PROXY = "update-proxy.json";
/// generate update.json
/// upload to update tag's release asset
@ -78,224 +78,235 @@ async function resolveUpdater() {
async function processRelease(github, options, tag, isAlpha) {
if (!tag) return;
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name).catch(
() => "No changelog available",
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
linux: { signature: "", url: "" }, // compatible with older formats
darwin: { signature: "", url: "" }, // compatible with older formats
"darwin-aarch64": { signature: "", url: "" },
"darwin-intel": { signature: "", url: "" },
"darwin-x86_64": { signature: "", url: "" },
"linux-x86_64": { signature: "", url: "" },
"linux-x86": { signature: "", url: "" },
"linux-i686": { signature: "", url: "" },
"linux-aarch64": { signature: "", url: "" },
"linux-armv7": { signature: "", url: "" },
"windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
},
};
const promises = release.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// Process all the platform URL and signature data
// win64 url
if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith("x64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith("x86-setup.exe")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x86-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url
if (name.endsWith("arm64-setup.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
// darwin url (intel)
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
updateData.platforms.darwin.url = browser_download_url;
updateData.platforms["darwin-intel"].url = browser_download_url;
updateData.platforms["darwin-x86_64"].url = browser_download_url;
}
// darwin signature (intel)
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.darwin.signature = sig;
updateData.platforms["darwin-intel"].signature = sig;
updateData.platforms["darwin-x86_64"].signature = sig;
}
// darwin url (aarch)
if (name.endsWith("aarch64.app.tar.gz")) {
updateData.platforms["darwin-aarch64"].url = browser_download_url;
// 使linux可以检查更新
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
}
// darwin signature (aarch)
if (name.endsWith("aarch64.app.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["darwin-aarch64"].signature = sig;
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].signature = sig;
updateData.platforms["linux-armv7"].signature = sig;
}
});
await Promise.allSettled(promises);
console.log(updateData);
// maybe should test the signature as well
// delete the null field
Object.entries(updateData.platforms).forEach(([key, value]) => {
if (!value.url) {
console.log(`[Error]: failed to parse release for "${key}"`);
delete updateData.platforms[key];
}
});
// Generate a proxy update file for accelerated GitHub resources
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url =
"https://download.clashverge.dev/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// Get the appropriate updater release based on isAlpha flag
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME;
console.log(
`Processing ${isAlpha ? "alpha" : "stable"} release:`,
releaseTag,
);
try {
let updateRelease;
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
const updateData = {
name: tag.name,
notes: await resolveUpdateLog(tag.name).catch(() =>
resolveUpdateLogDefault().catch(() => "No changelog available"),
),
pub_date: new Date().toISOString(),
platforms: {
win64: { signature: "", url: "" }, // compatible with older formats
linux: { signature: "", url: "" }, // compatible with older formats
darwin: { signature: "", url: "" }, // compatible with older formats
"darwin-aarch64": { signature: "", url: "" },
"darwin-intel": { signature: "", url: "" },
"darwin-x86_64": { signature: "", url: "" },
"linux-x86_64": { signature: "", url: "" },
"linux-x86": { signature: "", url: "" },
"linux-i686": { signature: "", url: "" },
"linux-aarch64": { signature: "", url: "" },
"linux-armv7": { signature: "", url: "" },
"windows-x86_64": { signature: "", url: "" },
"windows-aarch64": { signature: "", url: "" },
"windows-x86": { signature: "", url: "" },
"windows-i686": { signature: "", url: "" },
},
};
const promises = release.assets.map(async (asset) => {
const { name, browser_download_url } = asset;
// Process all the platform URL and signature data
// win64 url
if (name.endsWith("x64-setup.exe")) {
updateData.platforms.win64.url = browser_download_url;
updateData.platforms["windows-x86_64"].url = browser_download_url;
}
// win64 signature
if (name.endsWith("x64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.win64.signature = sig;
updateData.platforms["windows-x86_64"].signature = sig;
}
// win32 url
if (name.endsWith("x86-setup.exe")) {
updateData.platforms["windows-x86"].url = browser_download_url;
updateData.platforms["windows-i686"].url = browser_download_url;
}
// win32 signature
if (name.endsWith("x86-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-x86"].signature = sig;
updateData.platforms["windows-i686"].signature = sig;
}
// win arm url
if (name.endsWith("arm64-setup.exe")) {
updateData.platforms["windows-aarch64"].url = browser_download_url;
}
// win arm signature
if (name.endsWith("arm64-setup.exe.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["windows-aarch64"].signature = sig;
}
// darwin url (intel)
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
updateData.platforms.darwin.url = browser_download_url;
updateData.platforms["darwin-intel"].url = browser_download_url;
updateData.platforms["darwin-x86_64"].url = browser_download_url;
}
// darwin signature (intel)
if (name.endsWith(".app.tar.gz.sig") && !name.includes("aarch")) {
const sig = await getSignature(browser_download_url);
updateData.platforms.darwin.signature = sig;
updateData.platforms["darwin-intel"].signature = sig;
updateData.platforms["darwin-x86_64"].signature = sig;
}
// darwin url (aarch)
if (name.endsWith("aarch64.app.tar.gz")) {
updateData.platforms["darwin-aarch64"].url = browser_download_url;
// 使linux可以检查更新
updateData.platforms.linux.url = browser_download_url;
updateData.platforms["linux-x86_64"].url = browser_download_url;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].url = browser_download_url;
updateData.platforms["linux-armv7"].url = browser_download_url;
}
// darwin signature (aarch)
if (name.endsWith("aarch64.app.tar.gz.sig")) {
const sig = await getSignature(browser_download_url);
updateData.platforms["darwin-aarch64"].signature = sig;
updateData.platforms.linux.signature = sig;
updateData.platforms["linux-x86_64"].signature = sig;
updateData.platforms["linux-x86"].url = browser_download_url;
updateData.platforms["linux-i686"].url = browser_download_url;
updateData.platforms["linux-aarch64"].signature = sig;
updateData.platforms["linux-armv7"].signature = sig;
}
});
await Promise.allSettled(promises);
console.log(updateData);
// maybe should test the signature as well
// delete the null field
Object.entries(updateData.platforms).forEach(([key, value]) => {
if (!value.url) {
console.log(`[Error]: failed to parse release for "${key}"`);
delete updateData.platforms[key];
}
});
// Generate a proxy update file for accelerated GitHub resources
const updateDataNew = JSON.parse(JSON.stringify(updateData));
Object.entries(updateDataNew.platforms).forEach(([key, value]) => {
if (value.url) {
updateDataNew.platforms[key].url =
"https://download.clashverge.dev/" + value.url;
} else {
console.log(`[Error]: updateDataNew.platforms.${key} is null`);
}
});
// Get the appropriate updater release based on isAlpha flag
const releaseTag = isAlpha ? ALPHA_TAG_NAME : UPDATE_TAG_NAME;
console.log(
`Processing ${isAlpha ? "alpha" : "stable"} release:`,
releaseTag,
);
try {
// Try to get the existing release
const response = await github.rest.repos.getReleaseByTag({
let updateRelease;
try {
// Try to get the existing release
const response = await github.rest.repos.getReleaseByTag({
...options,
tag: releaseTag,
});
updateRelease = response.data;
console.log(
`Found existing ${releaseTag} release with ID: ${updateRelease.id}`,
);
} catch (error) {
// If release doesn't exist, create it
if (error.status === 404) {
console.log(
`Release with tag ${releaseTag} not found, creating new release...`,
);
const createResponse = await github.rest.repos.createRelease({
...options,
tag_name: releaseTag,
name: isAlpha
? "Auto-update Alpha Channel"
: "Auto-update Stable Channel",
body: `This release contains the update information for ${isAlpha ? "alpha" : "stable"} channel.`,
prerelease: isAlpha,
});
updateRelease = createResponse.data;
console.log(
`Created new ${releaseTag} release with ID: ${updateRelease.id}`,
);
} else {
// If it's another error, throw it
throw error;
}
}
// File names based on release type
const jsonFile = isAlpha ? ALPHA_UPDATE_JSON_FILE : UPDATE_JSON_FILE;
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
// Delete existing assets with these names
for (let asset of updateRelease.assets) {
if (asset.name === jsonFile) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === proxyFile) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
}
// Upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
tag: releaseTag,
release_id: updateRelease.id,
name: jsonFile,
data: JSON.stringify(updateData, null, 2),
});
updateRelease = response.data;
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: proxyFile,
data: JSON.stringify(updateDataNew, null, 2),
});
console.log(
`Found existing ${releaseTag} release with ID: ${updateRelease.id}`,
`Successfully uploaded ${isAlpha ? "alpha" : "stable"} update files to ${releaseTag}`,
);
} catch (error) {
// If release doesn't exist, create it
if (error.status === 404) {
console.log(
`Release with tag ${releaseTag} not found, creating new release...`,
);
const createResponse = await github.rest.repos.createRelease({
...options,
tag_name: releaseTag,
name: isAlpha
? "Auto-update Alpha Channel"
: "Auto-update Stable Channel",
body: `This release contains the update information for ${isAlpha ? "alpha" : "stable"} channel.`,
prerelease: isAlpha,
});
updateRelease = createResponse.data;
console.log(
`Created new ${releaseTag} release with ID: ${updateRelease.id}`,
);
} else {
// If it's another error, throw it
throw error;
}
console.error(
`Failed to process ${isAlpha ? "alpha" : "stable"} release:`,
error.message,
);
}
// File names based on release type
const jsonFile = isAlpha ? ALPHA_UPDATE_JSON_FILE : UPDATE_JSON_FILE;
const proxyFile = isAlpha ? ALPHA_UPDATE_JSON_PROXY : UPDATE_JSON_PROXY;
// Delete existing assets with these names
for (let asset of updateRelease.assets) {
if (asset.name === jsonFile) {
await github.rest.repos.deleteReleaseAsset({
...options,
asset_id: asset.id,
});
}
if (asset.name === proxyFile) {
await github.rest.repos
.deleteReleaseAsset({ ...options, asset_id: asset.id })
.catch(console.error); // do not break the pipeline
}
}
// Upload new assets
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: jsonFile,
data: JSON.stringify(updateData, null, 2),
});
await github.rest.repos.uploadReleaseAsset({
...options,
release_id: updateRelease.id,
name: proxyFile,
data: JSON.stringify(updateDataNew, null, 2),
});
console.log(
`Successfully uploaded ${isAlpha ? "alpha" : "stable"} update files to ${releaseTag}`,
);
} catch (error) {
console.error(
`Failed to process ${isAlpha ? "alpha" : "stable"} release:`,
error.message,
);
if (error.status === 404) {
console.log(`Release not found for tag: ${tag.name}, skipping...`);
} else {
console.error(
`Failed to get release for tag: ${tag.name}`,
error.message,
);
}
}
}

630
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "2.2.1-alpha"
version = "2.2.3"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@ -13,72 +13,72 @@ build = "build.rs"
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.0.6", features = [] }
tauri-build = { version = "2.1.0", features = [] }
[dependencies]
warp = "0.3"
anyhow = "1.0.97"
dirs = "6.0"
open = "5.1"
open = "5.3"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4.40"
sysinfo = "0.33.1"
sysinfo = "0.34"
boa_engine = "0.20.0"
serde_json = "1.0"
serde_yaml = "0.9"
once_cell = "1.20.3"
once_cell = "1.21.3"
port_scanner = "0.1.5"
delay_timer = "0.11.6"
parking_lot = "0.12"
percent-encoding = "2.3.1"
window-shadows = { version = "0.2.2" }
tokio = { version = "1.43", features = ["full"] }
tokio = { version = "1.44", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
regex = "1.10.5"
regex = "1.11.1"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
image = "0.25.5"
image = "0.25.6"
imageproc = "0.25.0"
rusttype = "0.9"
tauri = { version = "2.3.1", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
tauri = { version = "2.4.0", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.0", features = ["serde"] }
network-interface = { version = "2.0.1", features = ["serde"] }
tauri-plugin-shell = "2.2.0"
tauri-plugin-dialog = "2.2.0"
tauri-plugin-fs = "2.2.0"
tauri-plugin-notification = "2.2.1"
tauri-plugin-process = "2.2.0"
tauri-plugin-clipboard-manager = "2.2.1"
tauri-plugin-clipboard-manager = "2.2.2"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-devtools = "2.0.0-rc"
url = "2.5.4"
zip = "2.2.3"
reqwest_dav = "0.1.14"
tauri-plugin-devtools = "2.0.0"
zip = "2.5.0"
reqwest_dav = "0.1.15"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.3.1"
getrandom = "0.3.2"
tokio-tungstenite = "0.26.2"
futures = "0.3"
sys-locale = "0.3.1"
async-trait = "0.1.87"
sys-locale = "0.3.2"
async-trait = "0.1.88"
mihomo_api = { path = "src_crates/crate_mihomo_api" }
ab_glyph = "0.2.29"
tungstenite = "0.26.2"
libc = "0.2"
[target.'cfg(windows)'.dependencies]
runas = "=1.2.0"
deelevate = "0.2.0"
winreg = "0.55.0"
url = "2.5.4"
[target.'cfg(target_os = "linux")'.dependencies]
users = "0.11.0"
@ -86,9 +86,8 @@ users = "0.11.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-autostart = "2.2.0"
tauri-plugin-global-shortcut = "2.2.0"
tauri-plugin-updater = "2.5.1"
tauri-plugin-updater = "2.6.1"
tauri-plugin-window-state = "2.2.1"
#openssl
[features]
default = ["custom-protocol"]
@ -106,31 +105,33 @@ strip = true
incremental = true
[profile.fast-release]
inherits = "release" # 继承 release 的配置
panic = "abort" # 与 release 相同
codegen-units = 256 # 增加编译单元,提升编译速度
lto = false # 禁用 LTO提升编译速度
opt-level = 0 # 禁用优化,大幅提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
inherits = "release" # 继承 release 的配置
panic = "abort" # 与 release 相同
codegen-units = 256 # 增加编译单元,提升编译速度
lto = false # 禁用 LTO提升编译速度
opt-level = 0 # 禁用优化,大幅提升编译速度
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[profile.fast-dev]
inherits = "dev" # 继承 dev 的配置
codegen-units = 256 # 增加编译单元,提升编译速度
opt-level = 0 # 禁用优化,进一步提升编译速度
incremental = true # 启用增量编译
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
inherits = "dev" # 继承 dev 的配置
codegen-units = 256 # 增加编译单元,提升编译速度
opt-level = 0 # 禁用优化,进一步提升编译速度
incremental = true # 启用增量编译
debug = true # 保留调试信息
strip = false # 不剥离符号,保留调试信息
[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[dev-dependencies]
env_logger = "0.11.0"
mockito = "1.7.0"
tempfile = "3.17.1"
tempfile = "3.19.1"
[workspace]
members = [
"src_crates/crate_mihomo_api",]
members = ["src_crates/crate_mihomo_api"]
# [patch.crates-io]
# bitflags = { git = "https://github.com/bitflags/bitflags", rev = "2.9.0" }
# zerocopy = { git = "https://github.com/google/zerocopy", rev = "v0.8.24" }
# tungstenite = { git = "https://github.com/snapview/tungstenite-rs", rev = "v0.26.2" }

View File

@ -68,7 +68,6 @@
"shell:allow-spawn",
"shell:allow-stdin-write",
"dialog:allow-open",
"notification:default",
"global-shortcut:allow-is-registered",
"global-shortcut:allow-register",
"global-shortcut:allow-register-all",
@ -79,7 +78,6 @@
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"shell:default",
"dialog:default",
"notification:default"
"dialog:default"
]
}

View File

@ -1,5 +1,9 @@
use super::CmdResult;
use crate::{feat, utils::dirs, wrap_err};
use crate::{
feat, logging,
utils::{dirs, logging::Type},
wrap_err,
};
use tauri::Manager;
/// 打开应用程序所在目录
@ -194,7 +198,14 @@ pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
)
.unwrap_or_default();
}
logging!(
info,
Type::Cmd,
true,
"Copying icon file path: {:?} -> file dist: {:?}",
path,
dest_path
);
match fs::copy(file_path, &dest_path) {
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
Err(err) => Err(err.to_string()),

View File

@ -2,11 +2,9 @@ use chrono::Local;
use regex::Regex;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};
use tauri::command;
use tokio::sync::Mutex;
use tokio::task::JoinSet;
use tokio::{sync::Mutex, task::JoinSet};
// 定义解锁测试项目的结构
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -464,9 +462,11 @@ async fn check_netflix(client: &Client) -> UnlockItem {
let url2 = "https://www.netflix.com/title/70143836"; // Breaking Bad
// 创建简单的请求(不添加太多头部信息)
let result1 = client.get(url1)
let result1 = client
.get(url1)
.timeout(std::time::Duration::from_secs(30))
.send().await;
.send()
.await;
// 检查连接失败情况
if let Err(e) = &result1 {
@ -480,9 +480,11 @@ async fn check_netflix(client: &Client) -> UnlockItem {
}
// 如果第一个请求成功,尝试第二个请求
let result2 = client.get(url2)
let result2 = client
.get(url2)
.timeout(std::time::Duration::from_secs(30))
.send().await;
.send()
.await;
if let Err(e) = &result2 {
eprintln!("Netflix请求错误: {}", e);
@ -521,7 +523,8 @@ async fn check_netflix(client: &Client) -> UnlockItem {
// 成功解锁,尝试获取地区信息
// 使用Netflix测试内容获取区域
let test_url = "https://www.netflix.com/title/80018499";
match client.get(test_url)
match client
.get(test_url)
.timeout(std::time::Duration::from_secs(30))
.send()
.await
@ -561,7 +564,7 @@ async fn check_netflix(client: &Client) -> UnlockItem {
region: None,
check_time: Some(get_local_date_string()),
}
},
}
}
} else {
// 其他未知错误状态
@ -579,9 +582,11 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem {
// Fast.com API URL
let url = "https://api.fast.com/netflix/speedtest/v2?https=true&token=YXNkZmFzZGxmbnNkYWZoYXNkZmhrYWxm&urlCount=5";
let result = client.get(url)
let result = client
.get(url)
.timeout(std::time::Duration::from_secs(30))
.send().await;
.send()
.await;
match result {
Ok(response) => {
@ -602,7 +607,9 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem {
if let Some(targets) = data.get("targets").and_then(|t| t.as_array()) {
if !targets.is_empty() {
if let Some(location) = targets[0].get("location") {
if let Some(country) = location.get("country").and_then(|c| c.as_str()) {
if let Some(country) =
location.get("country").and_then(|c| c.as_str())
{
let emoji = country_code_to_emoji(country);
return UnlockItem {
name: "Netflix".to_string(),
@ -616,12 +623,12 @@ async fn check_netflix_cdn(client: &Client) -> UnlockItem {
}
// 如果无法解析区域信息
return UnlockItem {
UnlockItem {
name: "Netflix".to_string(),
status: "Unknown".to_string(),
region: None,
check_time: Some(get_local_date_string()),
};
}
}
Err(e) => {
eprintln!("解析Fast.com API响应失败: {}", e);

View File

@ -6,31 +6,33 @@ pub type CmdResult<T = ()> = Result<T, String>;
// Command modules
pub mod app;
pub mod clash;
pub mod lightweight;
pub mod media_unlock_checker;
pub mod network;
pub mod profile;
pub mod proxy;
pub mod runtime;
pub mod save_profile;
pub mod service;
pub mod system;
pub mod uwp;
pub mod validate;
pub mod verge;
pub mod webdav;
pub mod lighteweight;
// Re-export all command functions for backwards compatibility
pub use app::*;
pub use clash::*;
pub use lightweight::*;
pub use media_unlock_checker::*;
pub use network::*;
pub use profile::*;
pub use proxy::*;
pub use runtime::*;
pub use save_profile::*;
pub use service::*;
pub use system::*;
pub use uwp::*;
pub use validate::*;
pub use verge::*;
pub use webdav::*;
pub use lighteweight::*;

View File

@ -1,40 +1,25 @@
use super::CmdResult;
use crate::{
config::*,
core::*,
feat, log_err, ret_err,
utils::{dirs, help},
config::{Config, IProfiles, PrfItem, PrfOption},
core::{handle, tray::Tray, CoreManager},
feat, logging, ret_err,
utils::{dirs, help, logging::Type},
wrap_err,
};
/// 获取配置文件列表
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let _ = tray::Tray::global().update_menu();
let _ = Tray::global().update_menu();
Ok(Config::profiles().data().clone())
}
/// 增强配置文件
#[tauri::command]
pub async fn enhance_profiles() -> CmdResult {
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[enhance_profiles] 配置更新成功");
log_err!(tray::Tray::global().update_tooltip());
handle::Handle::refresh_clash();
Ok(())
}
Ok((false, error_msg)) => {
println!("[enhance_profiles] 配置验证失败: {}", error_msg);
handle::Handle::notice_message("config_validate::error", &error_msg);
Ok(())
}
Err(e) => {
println!("[enhance_profiles] 更新过程发生错误: {}", e);
handle::Handle::notice_message("config_validate::process_terminated", e.to_string());
Ok(())
}
}
wrap_err!(feat::enhance_profiles().await)?;
handle::Handle::refresh_clash();
Ok(())
}
/// 导入配置文件
@ -77,33 +62,113 @@ pub async fn delete_profile(index: String) -> CmdResult {
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件");
logging!(info, Type::Cmd, true, "开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone();
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
logging!(info, Type::Cmd, true, "当前配置: {:?}", current_profile);
// 如果要切换配置,先检查目标配置文件是否有语法错误
if let Some(new_profile) = profiles.current.as_ref() {
if current_profile.as_ref() != Some(new_profile) {
logging!(info, Type::Cmd, true, "正在切换到新配置: {}", new_profile);
// 获取目标配置文件路径
let profiles_config = Config::profiles();
let profiles_data = profiles_config.latest();
let config_file_result = match profiles_data.get_item(new_profile) {
Ok(item) => {
if let Some(file) = &item.file {
let path = dirs::app_profiles_dir().map(|dir| dir.join(file));
path.ok()
} else {
None
}
}
Err(e) => {
logging!(error, Type::Cmd, true, "获取目标配置信息失败: {}", e);
None
}
};
// 如果获取到文件路径检查YAML语法
if let Some(file_path) = config_file_result {
if !file_path.exists() {
logging!(
error,
Type::Cmd,
true,
"目标配置文件不存在: {}",
file_path.display()
);
handle::Handle::notice_message(
"config_validate::file_not_found",
format!("{}", file_path.display()),
);
return Ok(false);
}
match std::fs::read_to_string(&file_path) {
Ok(content) => match serde_yaml::from_str::<serde_yaml::Value>(&content) {
Ok(_) => {
logging!(info, Type::Cmd, true, "目标配置文件语法正确");
}
Err(err) => {
let error_msg = format!(" {}", err);
logging!(
error,
Type::Cmd,
true,
"目标配置文件存在YAML语法错误:{}",
error_msg
);
handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
&error_msg,
);
return Ok(false);
}
},
Err(err) => {
let error_msg = format!("无法读取目标配置文件: {}", err);
logging!(error, Type::Cmd, true, "{}", error_msg);
handle::Handle::notice_message(
"config_validate::file_read_error",
&error_msg,
);
return Ok(false);
}
}
}
}
}
// 更新profiles配置
println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
logging!(info, Type::Cmd, true, "正在更新配置草稿");
let _ = Config::profiles().draft().patch_config(profiles);
// 更新配置并进行验证
match CoreManager::global().update_config().await {
Ok((true, _)) => {
println!("[cmd配置patch] 配置更新成功");
logging!(info, Type::Cmd, true, "配置更新成功");
handle::Handle::refresh_clash();
let _ = tray::Tray::global().update_tooltip();
let _ = Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok((false, error_msg)) => {
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_msg);
Config::profiles().discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
logging!(
info,
Type::Cmd,
true,
"尝试恢复到之前的配置: {}",
prev_profile
);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
@ -112,7 +177,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
wrap_err!({ Config::profiles().draft().patch_config(restore_profiles) })?;
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
println!("[cmd配置patch] 成功恢复到之前的配置");
logging!(info, Type::Cmd, true, "成功恢复到之前的配置");
}
// 发送验证错误通知
@ -120,7 +185,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
Ok(false)
}
Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e);
logging!(warn, Type::Cmd, true, "更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
Ok(false)
@ -134,6 +199,8 @@ pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String,
) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
let profiles = IProfiles {
current: Some(profile_index),
items: None,

View File

@ -4,21 +4,21 @@ use crate::module::mihomo::MihomoManager;
#[tauri::command]
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
let proxies = mannager
mannager
.refresh_proxies()
.await
.map(|_| mannager.get_proxies())
.or_else(|_| Ok(mannager.get_proxies()));
proxies
.or_else(|_| Ok(mannager.get_proxies()))
}
#[tauri::command]
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
let mannager = MihomoManager::global();
let providers = mannager
mannager
.refresh_providers_proxies()
.await
.map(|_| mannager.get_providers_proxies())
.or_else(|_| Ok(mannager.get_providers_proxies()));
providers
.or_else(|_| Ok(mannager.get_providers_proxies()))
}

View File

@ -0,0 +1,40 @@
use super::CmdResult;
use crate::{
core::{service, CoreManager},
utils::i18n::t,
};
async fn execute_service_operation(
service_op: impl std::future::Future<Output = Result<(), impl ToString + std::fmt::Debug>>,
op_type: &str,
) -> CmdResult {
if service_op.await.is_err() {
let emsg = format!("{} {} failed", op_type, "Service");
return Err(t(emsg.as_str()));
}
if CoreManager::global().restart_core().await.is_err() {
let emsg = format!("{} {} failed", "Restart", "Core");
return Err(t(emsg.as_str()));
}
Ok(())
}
#[tauri::command]
pub async fn install_service() -> CmdResult {
execute_service_operation(service::install_service(), "Install").await
}
#[tauri::command]
pub async fn uninstall_service() -> CmdResult {
execute_service_operation(service::uninstall_service(), "Uninstall").await
}
#[tauri::command]
pub async fn reinstall_service() -> CmdResult {
execute_service_operation(service::reinstall_service(), "Reinstall").await
}
#[tauri::command]
pub async fn repair_service() -> CmdResult {
execute_service_operation(service::force_reinstall_service(), "Repair").await
}

View File

@ -1,8 +1,7 @@
use super::CmdResult;
use crate::{
core::{self, handle, service, CoreManager},
core::{handle, CoreManager},
module::sysinfo::PlatformSpecification,
wrap_err,
};
use once_cell::sync::Lazy;
use std::{
@ -24,7 +23,7 @@ static APP_START_TIME: Lazy<AtomicI64> = Lazy::new(|| {
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new();
let sysinfo = PlatformSpecification::new_async().await;
let info = format!("{:?}", sysinfo);
let app_handle = handle::Handle::global().app_handle().unwrap();
@ -37,7 +36,7 @@ pub async fn export_diagnostic_info() -> CmdResult<()> {
#[tauri::command]
pub async fn get_system_info() -> CmdResult<String> {
let sysinfo = PlatformSpecification::new();
let sysinfo = PlatformSpecification::new_async().await;
let info = format!("{:?}", sysinfo);
Ok(info)
}
@ -45,17 +44,7 @@ pub async fn get_system_info() -> CmdResult<String> {
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
match CoreManager::global().get_running_mode().await {
core::RunningMode::Service => Ok("service".to_string()),
core::RunningMode::Sidecar => Ok("sidecar".to_string()),
core::RunningMode::NotRunning => Ok("not_running".to_string()),
}
}
/// 安装/重装系统服务
#[tauri::command]
pub async fn install_service() -> CmdResult {
wrap_err!(service::reinstall_service().await)
Ok(CoreManager::global().get_running_mode().await.to_string())
}
/// 获取应用的运行时间(毫秒)
@ -69,3 +58,37 @@ pub fn get_app_uptime() -> CmdResult<i64> {
Ok(now - start_time)
}
/// 检查应用是否以管理员身份运行
#[tauri::command]
#[cfg(target_os = "windows")]
pub fn is_admin() -> CmdResult<bool> {
use deelevate::{PrivilegeLevel, Token};
let result = Token::with_current_process()
.and_then(|token| token.privilege_level())
.map(|level| level != PrivilegeLevel::NotPrivileged)
.unwrap_or(false);
Ok(result)
}
/// 非Windows平台检测是否以管理员身份运行
#[tauri::command]
#[cfg(not(target_os = "windows"))]
pub fn is_admin() -> CmdResult<bool> {
#[cfg(target_os = "macos")]
{
Ok(unsafe { libc::geteuid() } == 0)
}
#[cfg(target_os = "linux")]
{
Ok(unsafe { libc::geteuid() } == 0)
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
Ok(false)
}
}

View File

@ -2,8 +2,8 @@ use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{
config::PrfItem,
core::{handle, CoreManager},
enhance,
utils::{dirs, help},
enhance, logging,
utils::{dirs, help, logging::Type},
};
use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell;
@ -66,21 +66,27 @@ impl Config {
let script_item = PrfItem::from_script(Some("Script".to_string()))?;
Self::profiles().data().append_item(script_item.clone())?;
}
// 生成运行时配置
crate::log_err!(Self::generate().await);
if let Err(err) = Self::generate().await {
logging!(error, Type::Config, true, "生成运行时配置失败: {}", err);
} else {
logging!(info, Type::Config, true, "生成运行时配置成功");
}
// 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if config_result.is_ok() {
// 验证配置文件
println!("[首次启动] 开始验证配置");
logging!(info, Type::Config, true, "开始验证配置");
match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => {
if !is_valid {
println!(
logging!(
warn,
Type::Config,
true,
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
error_msg
);
@ -89,12 +95,12 @@ impl Config {
.await?;
Some(("config_validate::boot_error", error_msg))
} else {
println!("[首次启动] 配置验证成功");
logging!(info, Type::Config, true, "配置验证成功");
Some(("config_validate::success", String::new()))
}
}
Err(err) => {
println!("[首次启动] 验证进程执行失败: {}", err);
logging!(warn, Type::Config, true, "验证进程执行失败: {}", err);
CoreManager::global()
.use_default_config("config_validate::process_terminated", "")
.await?;
@ -102,7 +108,7 @@ impl Config {
}
}
} else {
println!("[首次启动] 生成配置文件失败,使用默认配置");
logging!(warn, Type::Config, true, "生成配置文件失败,使用默认配置");
CoreManager::global()
.use_default_config("config_validate::error", "")
.await?;

View File

@ -189,11 +189,16 @@ pub struct IVerge {
pub enable_tray_speed: Option<bool>,
pub enable_tray_icon: Option<bool>,
/// 自动进入轻量模式
pub enable_auto_light_weight_mode: Option<bool>,
/// 自动进入轻量模式的延迟(分钟)
pub auto_light_weight_minutes: Option<u64>,
/// 服务状态跟踪
pub service_state: Option<crate::core::service::ServiceState>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -295,11 +300,13 @@ impl IVerge {
webdav_username: None,
webdav_password: None,
enable_tray_speed: Some(true),
enable_tray_icon: Some(true),
enable_global_hotkey: Some(true),
enable_auto_light_weight_mode: Some(false),
auto_light_weight_minutes: Some(10),
enable_dns_settings: Some(true),
home_cards: None,
service_state: None,
..Self::default()
}
}
@ -381,10 +388,12 @@ impl IVerge {
patch!(webdav_username);
patch!(webdav_password);
patch!(enable_tray_speed);
patch!(enable_tray_icon);
patch!(enable_auto_light_weight_mode);
patch!(auto_light_weight_minutes);
patch!(enable_dns_settings);
patch!(home_cards);
patch!(service_state);
}
/// 在初始化前尝试拿到单例端口的值
@ -473,10 +482,12 @@ pub struct IVergeResponse {
pub webdav_username: Option<String>,
pub webdav_password: Option<String>,
pub enable_tray_speed: Option<bool>,
pub enable_tray_icon: Option<bool>,
pub enable_auto_light_weight_mode: Option<bool>,
pub auto_light_weight_minutes: Option<u64>,
pub enable_dns_settings: Option<bool>,
pub home_cards: Option<serde_json::Value>,
pub service_state: Option<crate::core::service::ServiceState>,
}
impl From<IVerge> for IVergeResponse {
@ -539,10 +550,12 @@ impl From<IVerge> for IVergeResponse {
webdav_username: verge.webdav_username,
webdav_password: verge.webdav_password,
enable_tray_speed: verge.enable_tray_speed,
enable_tray_icon: verge.enable_tray_icon,
enable_auto_light_weight_mode: verge.enable_auto_light_weight_mode,
auto_light_weight_minutes: verge.auto_light_weight_minutes,
enable_dns_settings: verge.enable_dns_settings,
home_cards: verge.home_cards,
service_state: verge.service_state,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,24 @@
use crate::log_err;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::sync::Arc;
use std::{sync::Arc, time::Duration};
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use tauri_plugin_shell::process::CommandChild;
use crate::{logging, logging_error, utils::logging::Type};
/// 存储启动期间的错误消息
#[derive(Debug, Clone)]
struct ErrorMessage {
status: String,
message: String,
}
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub is_exiting: Arc<RwLock<bool>>,
pub core_process: Arc<RwLock<Option<CommandChild>>>,
/// 存储启动过程中产生的错误消息队列
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
startup_completed: Arc<RwLock<bool>>,
}
impl Handle {
@ -19,7 +28,8 @@ impl Handle {
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
core_process: Arc::new(RwLock::new(None)),
startup_errors: Arc::new(RwLock::new(Vec::new())),
startup_completed: Arc::new(RwLock::new(false)),
})
}
@ -43,26 +53,115 @@ impl Handle {
pub fn refresh_clash() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-clash-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-clash-config", "yes")
);
}
}
pub fn refresh_verge() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-verge-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-verge-config", "yes")
);
}
}
#[allow(unused)]
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://refresh-profiles-config", "yes"));
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-profiles-config", "yes")
);
}
}
/// 通知前端显示消息,如果在启动过程中,则将消息存入启动错误队列
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
let handle = Self::global();
let status_str = status.into();
let msg_str = msg.into();
// 检查是否正在启动过程中
if !*handle.startup_completed.read() {
logging!(
info,
Type::Frontend,
true,
"启动过程中发现错误,加入消息队列: {} - {}",
status_str,
msg_str
);
// 将消息添加到启动错误队列
let mut errors = handle.startup_errors.write();
errors.push(ErrorMessage {
status: status_str,
message: msg_str,
});
return;
}
// 已经完成启动,直接发送消息
if let Some(window) = handle.get_window() {
logging_error!(
Type::Frontend,
true,
window.emit("verge://notice-message", (status_str, msg_str))
);
}
}
/// 标记启动已完成,并发送所有启动阶段累积的错误消息
pub fn mark_startup_completed(&self) {
{
let mut completed = self.startup_completed.write();
*completed = true;
}
self.send_startup_errors();
}
/// 发送启动时累积的所有错误消息
fn send_startup_errors(&self) {
let errors = {
let mut errors = self.startup_errors.write();
std::mem::take(&mut *errors)
};
if errors.is_empty() {
return;
}
logging!(
info,
Type::Frontend,
true,
"发送{}条启动时累积的错误消息",
errors.len()
);
// 等待2秒以确保前端已完全加载延迟发送错误通知
if let Some(window) = self.get_window() {
let window_clone = window.clone();
let errors_clone = errors.clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(Duration::from_secs(2)).await;
for error in errors_clone {
let _ =
window_clone.emit("verge://notice-message", (error.status, error.message));
// 每条消息之间间隔500ms避免消息堆积
tokio::time::sleep(Duration::from_millis(500)).await;
}
});
}
}
@ -71,21 +170,6 @@ impl Handle {
*is_exiting = true;
}
pub fn set_core_process(&self, process: CommandChild) {
let mut core_process = self.core_process.write();
*core_process = Some(process);
}
pub fn take_core_process(&self) -> Option<CommandChild> {
let mut core_process = self.core_process.write();
core_process.take()
}
/// 检查是否有运行中的核心进程
pub fn has_core_process(&self) -> bool {
self.core_process.read().is_some()
}
pub fn is_exiting(&self) -> bool {
*self.is_exiting.read()
}

View File

@ -1,4 +1,10 @@
use crate::{config::Config, core::handle, feat, log_err, utils::resolve};
use crate::{
config::Config,
core::handle,
feat, logging, logging_error,
module::lightweight::entry_lightweight_mode,
utils::{logging::Type, resolve},
};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
@ -23,22 +29,27 @@ impl Hotkey {
let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
println!(
"Initializing hotkeys, global hotkey enabled: {}",
logging!(
debug,
Type::Hotkey,
true,
"Initializing hotkeys with enable: {}",
enable_global_hotkey
);
log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
// 如果全局热键被禁用,则不注册热键
if !enable_global_hotkey {
println!("Global hotkey is disabled, skipping registration");
log::info!(target: "app", "Global hotkey is disabled, skipping registration");
return Ok(());
}
if let Some(hotkeys) = verge.latest().hotkeys.as_ref() {
println!("Found {} hotkeys to register", hotkeys.len());
log::info!(target: "app", "Found {} hotkeys to register", hotkeys.len());
logging!(
debug,
Type::Hotkey,
true,
"Has {} hotkeys need to register",
hotkeys.len()
);
for hotkey in hotkeys.iter() {
let mut iter = hotkey.split(',');
@ -47,28 +58,52 @@ impl Hotkey {
match (key, func) {
(Some(key), Some(func)) => {
println!("Registering hotkey: {} -> {}", key, func);
log::info!(target: "app", "Registering hotkey: {} -> {}", key, func);
logging!(
debug,
Type::Hotkey,
true,
"Registering hotkey: {} -> {}",
key,
func
);
if let Err(e) = self.register(key, func) {
println!("Failed to register hotkey {} -> {}: {:?}", key, func, e);
log::error!(target: "app", "Failed to register hotkey {} -> {}: {:?}", key, func, e);
logging!(
error,
Type::Hotkey,
true,
"Failed to register hotkey {} -> {}: {:?}",
key,
func,
e
);
} else {
println!("Successfully registered hotkey {} -> {}", key, func);
log::info!(target: "app", "Successfully registered hotkey {} -> {}", key, func);
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} -> {}",
key,
func
);
}
}
_ => {
let key = key.unwrap_or("None");
let func = func.unwrap_or("None");
println!("Invalid hotkey configuration: `{key}`:`{func}`");
log::error!(target: "app", "Invalid hotkey configuration: `{key}`:`{func}`");
logging!(
error,
Type::Hotkey,
true,
"Invalid hotkey configuration: `{}`:`{}`",
key,
func
);
}
}
}
self.current.lock().clone_from(hotkeys);
} else {
println!("No hotkeys configured");
log::info!(target: "app", "No hotkeys configured");
logging!(debug, Type::Hotkey, true, "No hotkeys configured");
}
Ok(())
@ -85,45 +120,60 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
println!(
logging!(
debug,
Type::Hotkey,
true,
"Attempting to register hotkey: {} for function: {}",
hotkey, func
hotkey,
func
);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
if manager.is_registered(hotkey) {
println!(
logging!(
debug,
Type::Hotkey,
true,
"Hotkey {} was already registered, unregistering first",
hotkey
);
log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey);
manager.unregister(hotkey)?;
}
let f = match func.trim() {
"open_or_close_dashboard" => {
println!("Registering open_or_close_dashboard function");
log::info!(target: "app", "Registering open_or_close_dashboard function");
logging!(
debug,
Type::Hotkey,
true,
"Registering open_or_close_dashboard function"
);
|| {
println!("=== Hotkey Dashboard Window Operation Start ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ===");
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation Start ==="
);
// 使用 spawn_blocking 来确保在正确的线程上执行
async_runtime::spawn_blocking(|| {
println!("Toggle dashboard window visibility");
log::info!(target: "app", "Toggle dashboard window visibility");
logging!(
debug,
Type::Hotkey,
true,
"Toggle dashboard window visibility"
);
// 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏它
if window.is_visible().unwrap_or(false) {
println!("Window is visible, hiding it");
log::info!(target: "app", "Window is visible, hiding it");
logging!(info, Type::Window, true, "Window is visible, hiding it");
let _ = window.hide();
} else {
// 如果窗口不可见,则显示它
println!("Window is hidden, showing it");
log::info!(target: "app", "Window is hidden, showing it");
logging!(info, Type::Window, true, "Window is hidden, showing it");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
@ -132,14 +182,22 @@ impl Hotkey {
}
} else {
// 如果窗口不存在,创建一个新窗口
println!("Window does not exist, creating a new one");
log::info!(target: "app", "Window does not exist, creating a new one");
resolve::create_window();
logging!(
info,
Type::Window,
true,
"Window does not exist, creating a new one"
);
resolve::create_window(true);
}
});
println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation End ==="
);
}
}
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
@ -147,13 +205,13 @@ impl Hotkey {
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(None),
"entry_lightweight_mode" => || entry_lightweight_mode(),
"quit" => || feat::quit(Some(0)),
#[cfg(target_os = "macos")]
"hide" => || feat::hide(),
_ => {
println!("Invalid function: {}", func);
log::error!(target: "app", "Invalid function: {}", func);
logging!(error, Type::Hotkey, true, "Invalid function: {}", func);
bail!("invalid function \"{func}\"");
}
};
@ -162,21 +220,18 @@ impl Hotkey {
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey, event| {
if event.state == ShortcutState::Pressed {
println!("Hotkey pressed: {:?}", hotkey);
log::info!(target: "app", "Hotkey pressed: {:?}", hotkey);
logging!(debug, Type::Hotkey, true, "Hotkey pressed: {:?}", hotkey);
if hotkey.key == Code::KeyQ && is_quit {
if let Some(window) = app_handle.get_webview_window("main") {
if window.is_focused().unwrap_or(false) {
println!("Executing quit function");
log::info!(target: "app", "Executing quit function");
logging!(debug, Type::Hotkey, true, "Executing quit function");
f();
}
}
} else {
// 直接执行函数,不做任何状态检查
println!("Executing function directly");
log::info!(target: "app", "Executing function directly");
logging!(debug, Type::Hotkey, true, "Executing function directly");
// 获取全局热键状态
let is_enable_global_hotkey = Config::verge()
@ -199,8 +254,14 @@ impl Hotkey {
}
});
println!("Successfully registered hotkey {} for {}", hotkey, func);
log::info!(target: "app", "Successfully registered hotkey {} for {}", hotkey, func);
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} for {}",
hotkey,
func
);
Ok(())
}
@ -208,7 +269,7 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
manager.unregister(hotkey)?;
log::debug!(target: "app", "unregister hotkey {hotkey}");
logging!(debug, Type::Hotkey, true, "Unregister hotkey {}", hotkey);
Ok(())
}
@ -224,7 +285,7 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
log_err!(self.register(key, func));
logging_error!(Type::Hotkey, true, self.register(key, func));
});
*current = new_hotkeys;
@ -281,7 +342,13 @@ impl Drop for Hotkey {
fn drop(&mut self) {
let app_handle = handle::Handle::global().app_handle().unwrap();
if let Err(e) = app_handle.global_shortcut().unregister_all() {
log::error!(target:"app", "Error unregistering all hotkeys: {:?}", e);
logging!(
error,
Type::Hotkey,
true,
"Error unregistering all hotkeys: {:?}",
e
);
}
}
}

View File

@ -1,13 +1,95 @@
use crate::{config::Config, utils::dirs};
use crate::{
config::Config,
logging,
utils::{dirs, logging::Type},
};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, env::current_exe, path::PathBuf, process::Command as StdCommand};
use std::{
collections::HashMap,
env::current_exe,
path::PathBuf,
process::Command as StdCommand,
time::{SystemTime, UNIX_EPOCH},
};
use tokio::time::Duration;
// Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211";
const REQUIRED_SERVICE_VERSION: &str = "1.0.2"; // 定义所需的服务版本号
const REQUIRED_SERVICE_VERSION: &str = "1.0.5"; // 定义所需的服务版本号
// 限制重装时间和次数的常量
const REINSTALL_COOLDOWN_SECS: u64 = 300; // 5分钟冷却期
const MAX_REINSTALLS_PER_DAY: u32 = 3; // 每24小时最多重装3次
const ONE_DAY_SECS: u64 = 86400; // 24小时的秒数
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct ServiceState {
pub last_install_time: u64, // 上次安装时间戳 (Unix 时间戳,秒)
pub install_count: u32, // 24小时内安装次数
pub last_check_time: u64, // 上次检查时间
pub last_error: Option<String>, // 上次错误信息
pub prefer_sidecar: bool, // 用户是否偏好sidecar模式如拒绝安装服务或安装失败
}
impl ServiceState {
// 获取当前的服务状态
pub fn get() -> Self {
if let Some(state) = Config::verge().latest().service_state.clone() {
return state;
}
Self::default()
}
// 保存服务状态
pub fn save(&self) -> Result<()> {
let config = Config::verge();
let mut latest = config.latest().clone();
latest.service_state = Some(self.clone());
*config.draft() = latest;
config.apply();
Config::verge().latest().save_file()
}
// 更新安装信息
pub fn record_install(&mut self) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
// 检查是否需要重置计数器24小时已过
if now - self.last_install_time > ONE_DAY_SECS {
self.install_count = 0;
}
self.last_install_time = now;
self.install_count += 1;
}
// 检查是否可以重新安装
pub fn can_reinstall(&self) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
// 如果在冷却期内,不允许重装
if now - self.last_install_time < REINSTALL_COOLDOWN_SECS {
return false;
}
// 如果24小时内安装次数过多也不允许
if now - self.last_install_time < ONE_DAY_SECS
&& self.install_count >= MAX_REINSTALLS_PER_DAY
{
return false;
}
true
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ResponseBody {
@ -38,34 +120,56 @@ pub struct VersionJsonResponse {
}
#[cfg(target_os = "windows")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
pub async fn uninstall_service() -> Result<()> {
logging!(info, Type::Service, true, "uninstall service");
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service.exe");
let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let _ = match level {
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
_ => StdCommand::new(uninstall_path)
.creation_flags(0x08000000)
.status()?,
};
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "windows")]
pub async fn install_service() -> Result<()> {
logging!(info, Type::Service, true, "install service");
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
use std::os::windows::process::CommandExt;
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service.exe");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
_ => StdCommand::new(install_path)
@ -83,24 +187,64 @@ pub async fn reinstall_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "windows")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
logging!(info, Type::Service, true, "reinstall service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
#[cfg(target_os = "linux")]
pub async fn uninstall_service() -> Result<()> {
logging!(info, Type::Service, true, "uninstall service");
use users::get_effective_uid;
let install_path = tauri::utils::platform::current_exe()?.with_file_name("install-service");
let uninstall_path = tauri::utils::platform::current_exe()?.with_file_name("uninstall-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().replace(" ", "\\ ");
let uninstall_shell: String = uninstall_path.to_string_lossy().replace(" ", "\\ ");
let elevator = crate::utils::help::linux_elevator();
@ -112,8 +256,38 @@ pub async fn reinstall_service() -> Result<()> {
.arg(uninstall_shell)
.status()?,
};
log::info!(target:"app", "status code:{}", status.code().unwrap());
logging!(
info,
Type::Service,
true,
"uninstall status code:{}",
status.code().unwrap()
);
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn install_service() -> Result<()> {
logging!(info, Type::Service, true, "install service");
use users::get_effective_uid;
let install_path = tauri::utils::platform::current_exe()?.with_file_name("install-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().replace(" ", "\\ ");
let elevator = crate::utils::help::linux_elevator();
let status = match get_effective_uid() {
0 => StdCommand::new(install_shell).status()?,
_ => StdCommand::new(elevator.clone())
@ -122,6 +296,132 @@ pub async fn reinstall_service() -> Result<()> {
.arg(install_shell)
.status()?,
};
logging!(
info,
Type::Service,
true,
"install status code:{}",
status.code().unwrap()
);
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn reinstall_service() -> Result<()> {
logging!(info, Type::Service, true, "reinstall service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
#[cfg(target_os = "macos")]
pub async fn uninstall_service() -> Result<()> {
use crate::utils::i18n::t;
logging!(info, Type::Service, true, "uninstall service");
let binary_path = dirs::service_path()?;
let uninstall_path = binary_path.with_file_name("uninstall-service");
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
let prompt = t("Service Administrator Prompt");
let command = format!(
r#"do shell script "sudo '{uninstall_shell}'" with administrator privileges with prompt "{prompt}""#
);
logging!(debug, Type::Service, true, "uninstall command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to uninstall service with status {}",
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
pub async fn install_service() -> Result<()> {
use crate::utils::i18n::t;
logging!(info, Type::Service, true, "install service");
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().into_owned();
let prompt = t("Service Administrator Prompt");
let command = format!(
r#"do shell script "sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""#
);
logging!(debug, Type::Service, true, "install command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
@ -135,54 +435,49 @@ pub async fn reinstall_service() -> Result<()> {
#[cfg(target_os = "macos")]
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
logging!(info, Type::Service, true, "reinstall service");
let binary_path = dirs::service_path()?;
let install_path = binary_path.with_file_name("install-service");
let uninstall_path = binary_path.with_file_name("uninstall-service");
// 获取当前服务状态
let mut service_state = ServiceState::get();
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
// 检查是否允许重装
if !service_state.can_reinstall() {
logging!(
warn,
Type::Service,
true,
"service reinstall rejected: cooldown period or max attempts reached"
);
bail!("Service reinstallation is rate limited. Please try again later.");
}
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().into_owned();
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
// 获取提示文本,如果 i18n 失败则使用硬编码默认值
let prompt = crate::utils::i18n::t("Service Administrator Prompt");
let prompt = if prompt == "Service Administrator Prompt" {
if Config::verge().latest().language.as_deref() == Some("zh")
|| Config::verge().latest().language.is_none()
{
"Clash Verge 需要使用管理员权限来重新安装系统服务"
} else {
"Clash Verge needs administrator privileges to reinstall the system service"
}
} else {
&prompt
};
let command = format!(
r#"do shell script "sudo '{uninstall_shell}' && sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""#
);
log::debug!(target: "app", "command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
.status()?;
if !status.success() {
bail!(
"failed to install service with status {}",
status.code().unwrap()
// 先卸载服务
if let Err(err) = uninstall_service().await {
logging!(
warn,
Type::Service,
true,
"failed to uninstall service: {}",
err
);
}
Ok(())
// 再安装服务
match install_service().await {
Ok(_) => {
// 记录安装信息并保存
service_state.record_install();
service_state.last_error = None;
service_state.save()?;
Ok(())
}
Err(err) => {
let error = format!("failed to install service: {}", err);
service_state.last_error = Some(error.clone());
service_state.save()?;
bail!(error)
}
}
}
/// check the windows service status
@ -226,19 +521,54 @@ pub async fn check_service_version() -> Result<String> {
/// check if service needs to be reinstalled
pub async fn check_service_needs_reinstall() -> bool {
// 获取当前服务状态
let service_state = ServiceState::get();
// 首先检查是否在冷却期或超过重装次数限制
if !service_state.can_reinstall() {
log::info!(target: "app", "service reinstall check: in cooldown period or max attempts reached");
return false;
}
// 然后才检查版本和可用性
match check_service_version().await {
Ok(version) => version != REQUIRED_SERVICE_VERSION,
Err(_) => true, // 如果无法获取版本或服务未运行,也需要重新安装
Ok(version) => {
// 打印更详细的日志,方便排查问题
log::info!(target: "app", "服务版本检测:当前={}, 要求={}", version, REQUIRED_SERVICE_VERSION);
let needs_reinstall = version != REQUIRED_SERVICE_VERSION;
if needs_reinstall {
log::warn!(target: "app", "发现服务版本不匹配,需要重装! 当前={}, 要求={}",
version, REQUIRED_SERVICE_VERSION);
// 打印版本字符串的原始字节,确认没有隐藏字符
log::debug!(target: "app", "当前版本字节: {:?}", version.as_bytes());
log::debug!(target: "app", "要求版本字节: {:?}", REQUIRED_SERVICE_VERSION.as_bytes());
} else {
log::info!(target: "app", "服务版本匹配,无需重装");
}
needs_reinstall
}
Err(err) => {
// 检查服务是否可用如果可用但版本检查失败可能只是版本API有问题
match is_service_running().await {
Ok(true) => {
log::info!(target: "app", "service is running but version check failed: {}", err);
false // 服务在运行,不需要重装
}
_ => {
log::info!(target: "app", "service is not running or unavailable");
true // 服务不可用,需要重装
}
}
}
}
}
/// start the clash by service
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
// 检查服务版本,如果不匹配则重新安装
if check_service_needs_reinstall().await {
log::info!(target: "app", "service version mismatch, reinstalling");
reinstall_service().await?;
}
/// 尝试使用现有服务启动核心,不进行重装
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
log::info!(target:"app", "attempting to start core with existing service");
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
@ -278,6 +608,106 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
Ok(())
}
/// start the clash by service
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
log::info!(target: "app", "正在尝试通过服务启动核心");
// 先检查服务版本,不受冷却期限制
let version_check = match check_service_version().await {
Ok(version) => {
log::info!(target: "app", "检测到服务版本: {}, 要求版本: {}",
version, REQUIRED_SERVICE_VERSION);
// 通过字节比较确保完全匹配
if version.as_bytes() != REQUIRED_SERVICE_VERSION.as_bytes() {
log::warn!(target: "app", "服务版本不匹配,需要重装");
false // 版本不匹配
} else {
log::info!(target: "app", "服务版本匹配");
true // 版本匹配
}
}
Err(err) => {
log::warn!(target: "app", "无法获取服务版本: {}", err);
false // 无法获取版本
}
};
// 先尝试直接启动服务,如果服务可用且版本匹配
if version_check {
if let Ok(true) = is_service_running().await {
// 服务正在运行且版本匹配,直接使用
log::info!(target: "app", "服务已在运行且版本匹配,尝试使用");
return start_with_existing_service(config_file).await;
}
}
// 强制执行版本检查,如果版本不匹配则重装
if !version_check {
log::info!(target: "app", "服务版本不匹配,尝试重装");
// 获取服务状态,检查是否可以重装
let service_state = ServiceState::get();
if !service_state.can_reinstall() {
log::warn!(target: "app", "由于限制无法重装服务");
// 尝试直接启动,即使版本不匹配
if let Ok(()) = start_with_existing_service(config_file).await {
log::info!(target: "app", "尽管版本不匹配,但成功启动了服务");
return Ok(());
} else {
bail!("服务版本不匹配且无法重装,启动失败");
}
}
// 尝试重装
log::info!(target: "app", "开始重装服务");
if let Err(err) = reinstall_service().await {
log::warn!(target: "app", "服务重装失败: {}", err);
// 尝试使用现有服务
log::info!(target: "app", "尝试使用现有服务");
return start_with_existing_service(config_file).await;
}
// 重装成功,尝试启动
log::info!(target: "app", "服务重装成功,尝试启动");
return start_with_existing_service(config_file).await;
}
// 检查服务状态
match check_service().await {
Ok(_) => {
// 服务可访问但可能没有运行核心,尝试直接启动
log::info!(target: "app", "服务可用但未运行核心,尝试启动");
if let Ok(()) = start_with_existing_service(config_file).await {
return Ok(());
}
}
Err(err) => {
log::warn!(target: "app", "服务检查失败: {}", err);
}
}
// 服务不可用或启动失败,检查是否需要重装
if check_service_needs_reinstall().await {
log::info!(target: "app", "服务需要重装");
// 尝试重装
if let Err(err) = reinstall_service().await {
log::warn!(target: "app", "服务重装失败: {}", err);
bail!("Failed to reinstall service: {}", err);
}
// 重装后再次尝试启动
log::info!(target: "app", "服务重装完成,尝试启动核心");
start_with_existing_service(config_file).await
} else {
// 不需要或不能重装,返回错误
log::warn!(target: "app", "服务不可用且无法重装");
bail!("Service is not available and cannot be reinstalled at this time")
}
}
/// stop the clash by service
pub(super) async fn stop_core_by_service() -> Result<()> {
let url = format!("{SERVICE_URL}/stop_clash");
@ -298,8 +728,41 @@ pub async fn is_service_running() -> Result<bool> {
// 检查服务状态码和消息
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(debug, Type::Service, "Service is running");
Ok(true)
} else {
logging!(debug, Type::Service, "Service is not running");
Ok(false)
}
}
pub async fn is_service_available() -> Result<()> {
let resp = check_service().await?;
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(debug, Type::Service, "Service is available");
}
Ok(())
}
/// 强制重装服务用于UI中的修复服务按钮
pub async fn force_reinstall_service() -> Result<()> {
log::info!(target: "app", "用户请求强制重装服务");
// 创建默认服务状态(重置所有限制)
let service_state = ServiceState::default();
service_state.save()?;
log::info!(target: "app", "已重置服务状态,开始执行重装");
// 执行重装
match reinstall_service().await {
Ok(()) => {
log::info!(target: "app", "服务重装成功");
Ok(())
}
Err(err) => {
log::error!(target: "app", "强制重装服务失败: {}", err);
bail!("强制重装服务失败: {}", err)
}
}
}

View File

@ -1,7 +1,8 @@
use crate::{
config::{Config, IVerge},
core::handle::Handle,
log_err,
logging_error,
utils::logging::Type,
};
use anyhow::Result;
use once_cell::sync::OnceCell;
@ -231,8 +232,8 @@ impl Sysopt {
} else {
log::info!(target: "app", "Auto launch enabled successfully");
}
log_err!(result)
},
logging_error!(Type::System, true, result);
}
false => {
let result = autostart_manager.disable();
if let Err(ref e) = result {
@ -240,8 +241,8 @@ impl Sysopt {
} else {
log::info!(target: "app", "Auto launch disabled successfully");
}
log_err!(result)
},
logging_error!(Type::System, true, result);
}
};
Ok(())
@ -256,7 +257,7 @@ impl Sysopt {
Ok(status) => {
log::info!(target: "app", "Auto launch status: {}", status);
Ok(status)
},
}
Err(e) => {
log::error!(target: "app", "Failed to get auto launch status: {}", e);
Err(anyhow::anyhow!("Failed to get auto launch status: {}", e))
@ -323,7 +324,7 @@ impl Sysopt {
enable: true,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
log_err!(autoproxy.set_auto_proxy());
logging_error!(Type::System, true, autoproxy.set_auto_proxy());
} else {
let sysproxy = Sysproxy {
enable: true,
@ -332,7 +333,7 @@ impl Sysopt {
bypass: get_bypass(),
};
log_err!(sysproxy.set_system_proxy());
logging_error!(Type::System, true, sysproxy.set_system_proxy());
}
}

View File

@ -1,4 +1,6 @@
use crate::{config::Config, core::CoreManager, feat};
use crate::{
config::Config, core::CoreManager, feat, logging, logging_error, utils::logging::Type,
};
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
@ -54,18 +56,18 @@ impl Timer {
)
.is_err()
{
log::debug!(target: "app", "Timer already initialized, skipping...");
logging!(debug, Type::Timer, "Timer already initialized, skipping...");
return Ok(());
}
log::info!(target: "app", "Initializing timer...");
logging!(info, Type::Timer, true, "Initializing timer...");
// Initialize timer tasks
if let Err(e) = self.refresh() {
// Reset initialization flag on error
self.initialized
.store(false, std::sync::atomic::Ordering::SeqCst);
log::error!(target: "app", "Failed to initialize timer: {}", e);
logging_error!(Type::Timer, false, "Failed to initialize timer: {}", e);
return Err(e);
}
@ -98,15 +100,15 @@ impl Timer {
for uid in profiles_to_update {
if let Some(task) = timer_map.get(&uid) {
log::info!(target: "app", "Advancing task for uid: {}", uid);
logging!(info, Type::Timer, "Advancing task for uid: {}", uid);
if let Err(e) = delay_timer.advance_task(task.task_id) {
log::warn!(target: "app", "Failed to advance task {}: {}", uid, e);
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
}
}
}
}
log::info!(target: "app", "Timer initialization completed");
logging!(info, Type::Timer, "Timer initialization completed");
Ok(())
}
@ -116,11 +118,16 @@ impl Timer {
let diff_map = self.gen_diff();
if diff_map.is_empty() {
log::debug!(target: "app", "No timer changes needed");
logging!(debug, Type::Timer, "No timer changes needed");
return Ok(());
}
log::info!(target: "app", "Refreshing {} timer tasks", diff_map.len());
logging!(
info,
Type::Timer,
"Refreshing {} timer tasks",
diff_map.len()
);
// Apply changes while holding locks
let mut timer_map = self.timer_map.write();
@ -131,9 +138,16 @@ impl Timer {
DiffFlag::Del(tid) => {
timer_map.remove(&uid);
if let Err(e) = delay_timer.remove_task(tid) {
log::warn!(target: "app", "Failed to remove task {} for uid {}: {}", tid, uid, e);
logging!(
warn,
Type::Timer,
"Failed to remove task {} for uid {}: {}",
tid,
uid,
e
);
} else {
log::debug!(target: "app", "Removed task {} for uid {}", tid, uid);
logging!(debug, Type::Timer, "Removed task {} for uid {}", tid, uid);
}
}
DiffFlag::Add(tid, interval) => {
@ -146,16 +160,23 @@ impl Timer {
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
log::error!(target: "app", "Failed to add task for uid {}: {}", uid, e);
logging_error!(Type::Timer, "Failed to add task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
log::debug!(target: "app", "Added task {} for uid {}", tid, uid);
logging!(debug, Type::Timer, "Added task {} for uid {}", tid, uid);
}
}
DiffFlag::Mod(tid, interval) => {
// Remove old task first
if let Err(e) = delay_timer.remove_task(tid) {
log::warn!(target: "app", "Failed to remove old task {} for uid {}: {}", tid, uid, e);
logging!(
warn,
Type::Timer,
"Failed to remove old task {} for uid {}: {}",
tid,
uid,
e
);
}
// Then add the new one
@ -168,10 +189,10 @@ impl Timer {
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
log::error!(target: "app", "Failed to update task for uid {}: {}", uid, e);
logging_error!(Type::Timer, "Failed to update task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
log::debug!(target: "app", "Updated task {} for uid {}", tid, uid);
logging!(debug, Type::Timer, "Updated task {} for uid {}", tid, uid);
}
}
}
@ -250,7 +271,14 @@ impl Timer {
tid: TaskID,
minutes: u64,
) -> Result<()> {
log::info!(target: "app", "Adding task: uid={}, id={}, interval={}min", uid, tid, minutes);
logging!(
info,
Type::Timer,
"Adding task: uid={}, id={}, interval={}min",
uid,
tid,
minutes
);
// Create a task with reasonable retries and backoff
let task = TaskBuilder::default()
@ -275,7 +303,7 @@ impl Timer {
/// Async task with better error handling and logging
async fn async_task(uid: String) {
let task_start = std::time::Instant::now();
log::info!(target: "app", "Running timer task for profile: {}", uid);
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
// Update profile
let profile_result = feat::update_profile(uid.clone(), None).await;
@ -286,23 +314,26 @@ impl Timer {
match CoreManager::global().update_config().await {
Ok(_) => {
let duration = task_start.elapsed().as_millis();
log::info!(
target: "app",
logging!(
info,
Type::Timer,
"Timer task completed successfully for uid: {} (took {}ms)",
uid, duration
uid,
duration
);
}
Err(e) => {
log::error!(
target: "app",
logging_error!(
Type::Timer,
"Failed to refresh config after profile update for uid {}: {}",
uid, e
uid,
e
);
}
}
}
Err(e) => {
log::error!(target: "app", "Failed to update profile uid {}: {}", uid, e);
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
}
}
}

View File

@ -6,9 +6,9 @@ use crate::{
cmd,
config::Config,
feat,
module::mihomo::Rate,
module::{lightweight::entry_lightweight_mode, mihomo::Rate},
resolve,
utils::{dirs, i18n::t, resolve::VERSION},
utils::{dirs::find_target_icons, i18n::t, logging::Type, resolve::VERSION},
};
use anyhow::Result;
@ -20,6 +20,7 @@ use parking_lot::Mutex;
use parking_lot::RwLock;
#[cfg(target_os = "macos")]
pub use speed_rate::{SpeedRate, Traffic};
use std::fs;
#[cfg(target_os = "macos")]
use std::sync::Arc;
use tauri::{
@ -31,16 +32,124 @@ use tauri::{
use tokio::sync::broadcast;
use super::handle;
#[derive(Clone)]
struct TrayState {}
#[cfg(target_os = "macos")]
pub struct Tray {
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
is_subscribed: Arc<RwLock<bool>>,
pub rate_cache: Arc<Mutex<Option<Rate>>>,
}
#[cfg(not(target_os = "macos"))]
pub struct Tray {}
impl TrayState {
pub fn get_common_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_common_tray_icon = verge.common_tray_icon.unwrap_or(false);
if is_common_tray_icon {
if let Some(common_icon_path) = find_target_icons("common").unwrap() {
let icon_data = fs::read(common_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon.ico").to_vec(),
)
}
}
pub fn get_sysproxy_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_sysproxy_tray_icon = verge.sysproxy_tray_icon.unwrap_or(false);
if is_sysproxy_tray_icon {
if let Some(sysproxy_icon_path) = find_target_icons("sysproxy").unwrap() {
let icon_data = fs::read(sysproxy_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
)
}
}
pub fn get_tun_tray_icon() -> (bool, Vec<u8>) {
let verge = Config::verge().latest().clone();
let is_tun_tray_icon = verge.tun_tray_icon.unwrap_or(false);
if is_tun_tray_icon {
if let Some(tun_icon_path) = find_target_icons("tun").unwrap() {
let icon_data = fs::read(tun_icon_path).unwrap();
return (true, icon_data);
}
}
#[cfg(target_os = "macos")]
{
let tray_icon_colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
if tray_icon_colorful == "monochrome" {
(
false,
include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
)
} else {
(
false,
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
)
}
}
#[cfg(not(target_os = "macos"))]
{
(
false,
include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
)
}
}
}
impl Tray {
pub fn global() -> &'static Tray {
static TRAY: OnceCell<Tray> = OnceCell::new();
@ -50,6 +159,7 @@ impl Tray {
speed_rate: Arc::new(Mutex::new(None)),
shutdown_tx: Arc::new(RwLock::new(None)),
is_subscribed: Arc::new(RwLock::new(false)),
rate_cache: Arc::new(Mutex::new(None)),
});
#[cfg(not(target_os = "macos"))]
@ -84,6 +194,7 @@ impl Tray {
tray.on_tray_icon_event(|_, event| {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
log::debug!(target: "app","tray event: {:?}", tray_event);
if let TrayIconEvent::Click {
button: MouseButton::Left,
@ -94,7 +205,7 @@ impl Tray {
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(),
"main_window" => resolve::create_window(true),
_ => {}
}
}
@ -148,114 +259,67 @@ impl Tray {
}
/// 更新托盘图标
#[allow(unused_variables)]
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let verge = Config::verge().latest().clone();
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let common_tray_icon = verge.common_tray_icon.as_ref().unwrap_or(&false);
let sysproxy_tray_icon = verge.sysproxy_tray_icon.as_ref().unwrap_or(&false);
let tun_tray_icon = verge.tun_tray_icon.as_ref().unwrap_or(&false);
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray = app_handle.tray_by_id("main").unwrap();
#[cfg(target_os = "macos")]
let tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let icon_bytes = if *system_proxy && !*tun_mode {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon-sys.ico").to_vec();
if *sysproxy_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("sysproxy.png");
let ico_path = icon_dir_path.join("sysproxy.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
} else if *tun_mode {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon-tun.ico").to_vec();
if *tun_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("tun.png");
let ico_path = icon_dir_path.join("tun.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
} else {
#[cfg(target_os = "macos")]
let mut icon = match tray_icon.as_str() {
"colorful" => include_bytes!("../../../icons/tray-icon.ico").to_vec(),
_ => include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
};
#[cfg(not(target_os = "macos"))]
let mut icon = include_bytes!("../../../icons/tray-icon.ico").to_vec();
if *common_tray_icon {
let icon_dir_path = dirs::app_home_dir()?.join("icons");
let png_path = icon_dir_path.join("common.png");
let ico_path = icon_dir_path.join("common.ico");
if ico_path.exists() {
icon = std::fs::read(ico_path).unwrap();
} else if png_path.exists() {
icon = std::fs::read(png_path).unwrap();
}
}
icon
let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) {
(true, true) => TrayState::get_tun_tray_icon(),
(true, false) => TrayState::get_sysproxy_tray_icon(),
(false, true) => TrayState::get_tun_tray_icon(),
(false, false) => TrayState::get_common_tray_icon(),
};
#[cfg(target_os = "macos")]
{
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
let is_colorful = tray_icon == "colorful";
let enable_tray_speed = verge.enable_tray_speed.unwrap_or(true);
let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true);
let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
let is_colorful = colorful == "colorful";
// 处理图标和速率
let final_icon_bytes = if enable_tray_speed {
let rate = rate.or_else(|| {
self.speed_rate
.lock()
.as_ref()
.and_then(|speed_rate| speed_rate.get_curent_rate())
});
if !enable_tray_speed {
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
let _ = tray.set_icon_as_template(!is_colorful);
return Ok(());
}
// 使用新的方法渲染图标和速率
SpeedRate::add_speed_text(icon_bytes, rate)?
let rate = if let Some(rate) = rate {
Some(rate)
} else {
icon_bytes
let guard = self.speed_rate.lock();
if let Some(rate) = guard.as_ref().unwrap().get_curent_rate() {
Some(rate)
} else {
Some(Rate::default())
}
};
// 设置系统托盘图标
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&final_icon_bytes)?));
// 只对单色图标使用 template 模式
let _ = tray.set_icon_as_template(!is_colorful);
let mut rate_guard = self.rate_cache.lock();
if *rate_guard != rate {
*rate_guard = rate;
let bytes = if enable_tray_icon {
Some(icon_bytes)
} else {
None
};
let rate = rate_guard.as_ref();
let rate_bytes = SpeedRate::add_speed_text(is_custom_icon, bytes, rate).unwrap();
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?));
let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful);
}
Ok(())
}
#[cfg(not(target_os = "macos"))]
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
Ok(())
{
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
Ok(())
}
}
/// 更新托盘提示
@ -490,6 +554,15 @@ fn create_tray_menu(
)
.unwrap();
let lighteweight_mode = &MenuItem::with_id(
app_handle,
"entry_lightweight_mode",
t("LightWeight Mode"),
true,
hotkeys.get("entry_lightweight_mode").map(|s| s.as_str()),
)
.unwrap();
let copy_env =
&MenuItem::with_id(app_handle, "copy_env", t("Copy Env"), true, None::<&str>).unwrap();
@ -582,6 +655,8 @@ fn create_tray_menu(
separator,
system_proxy,
tun_mode,
separator,
lighteweight_mode,
copy_env,
open_dir,
more,
@ -600,17 +675,17 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
println!("change mode to: {}", mode);
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(),
"open_window" => resolve::create_window(true),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"copy_env" => feat::copy_clash_env(),
"open_app_dir" => crate::log_err!(cmd::open_app_dir()),
"open_core_dir" => crate::log_err!(cmd::open_core_dir()),
"open_logs_dir" => crate::log_err!(cmd::open_logs_dir()),
"open_app_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_app_dir()),
"open_core_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_core_dir()),
"open_logs_dir" => crate::logging_error!(Type::Cmd, true, cmd::open_logs_dir()),
"restart_clash" => feat::restart_clash_core(),
"restart_app" => feat::restart_app(),
"entry_lightweight_mode" => entry_lightweight_mode(),
"quit" => {
println!("quit");
feat::quit(Some(0));
}
id if id.starts_with("profiles_") => {

View File

@ -15,7 +15,6 @@ use tungstenite::client::IntoClientRequest;
pub struct SpeedRate {
rate: Arc<Mutex<(Rate, Rate)>>,
last_update: Arc<Mutex<std::time::Instant>>,
// 移除 base_image不再缓存原始图像
}
impl SpeedRate {
@ -77,44 +76,71 @@ impl SpeedRate {
}
// 分离图标加载和速率渲染
pub fn add_speed_text(icon_bytes: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
pub fn add_speed_text(
is_custom_icon: bool,
icon_bytes: Option<Vec<u8>>,
rate: Option<&Rate>,
) -> Result<Vec<u8>> {
let rate = rate.unwrap_or(&Rate { up: 0, down: 0 });
// 加载原始图标
let icon_image = image::load_from_memory(&icon_bytes)?;
let (icon_width, icon_height) = (icon_image.width(), icon_image.height());
let (mut icon_width, mut icon_height) = (0, 256);
let icon_image = if let Some(bytes) = icon_bytes.clone() {
let icon_image = image::load_from_memory(&bytes)?;
icon_width = icon_image.width();
icon_height = icon_image.height();
icon_image
} else {
// 返回一个空的 RGBA 图像
image::DynamicImage::new_rgba8(0, 0)
};
// 判断是否为彩色图标
let is_colorful =
!crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
let total_width = match (is_custom_icon, icon_bytes.is_some()) {
(true, true) => 510,
(true, false) => 740,
(false, false) => 740,
(false, true) => icon_width + 740,
};
// 增加文本宽度和间距
let text_width = 580; // 文本区域宽度
let total_width = icon_width + text_width;
// println!(
// "icon_height: {}, icon_wight: {}, total_width: {}",
// icon_height, icon_width, total_width
// );
// 创建新的透明画布
let mut combined_image = RgbaImage::new(total_width, icon_height);
// 将原始图标绘制到新画布的左侧
for y in 0..icon_height {
for x in 0..icon_width {
let pixel = icon_image.get_pixel(x, y);
combined_image.put_pixel(x, y, pixel);
if icon_bytes.is_some() {
for y in 0..icon_height {
for x in 0..icon_width {
let pixel = icon_image.get_pixel(x, y);
combined_image.put_pixel(x, y, pixel);
}
}
}
let is_colorful = if let Some(bytes) = icon_bytes.clone() {
!crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false)
} else {
false
};
// 选择文本颜色
let (text_color, shadow_color) = if is_colorful {
// 彩色图标使用黑色文本和轻微白色阴影
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 160u8]),
Rgba([144u8, 144u8, 144u8, 255u8]),
// Rgba([255u8, 255u8, 255u8, 128u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
// (
// Rgba([160u8, 160u8, 160u8, 255u8]),
// // Rgba([255u8, 255u8, 255u8, 128u8]),
// Rgba([0u8, 0u8, 0u8, 255u8]),
// )
} else {
// 单色图标使用白色文本和轻微黑色阴影
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 120u8]),
Rgba([0u8, 0u8, 0u8, 128u8]),
)
};
// 减小字体大小以适应文本区域
@ -124,17 +150,30 @@ impl SpeedRate {
let scale = ab_glyph::PxScale::from(font_size);
// 使用更简洁的速率格式
let up_text = format_bytes_speed(rate.up);
let down_text = format_bytes_speed(rate.down);
let up_text = format!("{}", format_bytes_speed(rate.up));
let down_text = format!("{}", format_bytes_speed(rate.down));
// For test rate display
// let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024));
// 计算文本位置,确保垂直间距合适
// 修改文本位置为居右显示
let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
// 计算右对齐的文本位置
let up_text_x = total_width - up_text_width;
let down_text_x = total_width - down_text_width;
// let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32;
// let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32;
// let up_text_x = total_width - up_text_width;
// let down_text_x = total_width - down_text_width;
// 计算左对齐的文本位置
let (up_text_x, down_text_x) = {
if is_custom_icon || icon_bytes.is_some() {
let text_left_offset = 30;
let left_begin = icon_width + text_left_offset;
(left_begin, left_begin)
} else {
(icon_width, icon_width)
}
};
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
let text_height = font_size as i32;

View File

@ -0,0 +1 @@
pub mod service;

View File

@ -0,0 +1 @@

View File

@ -1,8 +1,8 @@
use crate::{
config::{Config, IVerge},
core::backup,
log_err,
utils::dirs::app_home_dir,
logging_error,
utils::{dirs::app_home_dir, logging::Type},
};
use anyhow::Result;
use reqwest_dav::list_cmd::ListFile;
@ -69,8 +69,9 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
// extract zip file
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
zip.extract(app_home_dir()?)?;
log_err!(
logging_error!(
Type::Backup,
true,
super::patch_verge(
IVerge {
webdav_url,

View File

@ -1,9 +1,9 @@
use crate::{
config::Config,
core::{handle, tray, CoreManager},
log_err,
logging_error,
module::mihomo::MihomoManager,
utils::resolve,
utils::{logging::Type, resolve},
};
use serde_yaml::{Mapping, Value};
use tauri::Manager;
@ -28,12 +28,31 @@ pub fn restart_clash_core() {
pub fn restart_app() {
tauri::async_runtime::spawn_blocking(|| {
tauri::async_runtime::block_on(async {
log_err!(CoreManager::global().stop_core().await);
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
resolve::resolve_reset_async().await;
let app_handle = handle::Handle::global().app_handle().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
tauri::process::restart(&app_handle.env());
});
resolve::resolve_reset();
let app_handle = handle::Handle::global().app_handle().unwrap();
std::thread::sleep(std::time::Duration::from_secs(1));
tauri::process::restart(&app_handle.env());
});
}
fn after_change_clash_mode() {
tauri::async_runtime::spawn(async {
match MihomoManager::global().get_connections().await {
Ok(connections) => {
if let Some(connections_array) = connections["connections"].as_array() {
for connection in connections_array {
if let Some(id) = connection["id"].as_str() {
let _ = MihomoManager::global().delete_connection(id).await;
}
}
}
}
Err(err) => {
log::error!(target: "app", "Failed to get connections: {}", err);
}
}
});
}
@ -47,7 +66,6 @@ pub fn change_clash_mode(mode: String) {
});
tauri::async_runtime::spawn(async move {
log::debug!(target: "app", "change clash mode to {mode}");
match MihomoManager::global().patch_configs(json_value).await {
Ok(_) => {
// 更新订阅
@ -55,8 +73,16 @@ pub fn change_clash_mode(mode: String) {
if Config::clash().data().save_config().is_ok() {
handle::Handle::refresh_clash();
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
logging_error!(Type::Tray, true, tray::Tray::global().update_menu());
logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None));
}
let is_auto_close_connection = Config::verge()
.data()
.auto_close_connection
.unwrap_or(false);
if is_auto_close_connection {
after_change_clash_mode();
}
}
Err(err) => println!("{err}"),

View File

@ -1,8 +1,9 @@
use crate::{
config::{Config, IVerge},
core::{handle, hotkey, sysopt, tray, CoreManager},
log_err,
logging_error,
module::lightweight,
utils::logging::Type,
};
use anyhow::Result;
use serde_yaml::Mapping;
@ -18,8 +19,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
CoreManager::global().restart_core().await?;
} else {
if patch.get("mode").is_some() {
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
logging_error!(Type::Tray, true, tray::Tray::global().update_menu());
logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None));
}
Config::runtime().latest().patch_config(patch);
CoreManager::global().update_config().await?;
@ -89,6 +90,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let http_enabled = patch.verge_http_enabled;
let http_port = patch.verge_port;
let enable_tray_speed = patch.enable_tray_speed;
let enable_tray_icon = patch.enable_tray_icon;
let enable_global_hotkey = patch.enable_global_hotkey;
let tray_event = patch.tray_event;
let home_cards = patch.home_cards.clone();
@ -145,6 +147,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|| tun_tray_icon.is_some()
|| tray_icon.is_some()
|| enable_tray_speed.is_some()
|| enable_tray_icon.is_some()
{
update_flags |= UpdateFlags::SystrayIcon as i32;
}
@ -164,6 +167,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
// Process updates based on flags
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
Config::generate().await?;
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {

View File

@ -82,3 +82,11 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
Ok(())
}
/// 增强配置
pub async fn enhance_profiles() -> Result<()> {
crate::core::CoreManager::global()
.update_config()
.await
.map(|_| ())
}

View File

@ -28,6 +28,19 @@ pub fn toggle_system_proxy() {
/// Toggle TUN mode on/off
pub fn toggle_tun_mode(not_save_file: Option<bool>) {
// tauri::async_runtime::spawn(async move {
// logging!(
// info,
// Type::Service,
// true,
// "Toggle TUN mode need install service"
// );
// if is_service_available().await.is_err() {
// logging_error!(Type::Service, true, install_service().await);
// }
// logging_error!(Type::Core, true, CoreManager::global().restart_core().await);
// });
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);

View File

@ -6,8 +6,6 @@ use crate::{
module::mihomo::MihomoManager,
utils::resolve,
};
use tauri::Manager;
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
/// Open or close the dashboard window
#[allow(dead_code)]
@ -47,40 +45,10 @@ pub fn open_or_close_dashboard() {
} else {
println!("No existing window found, creating new window");
log::info!(target: "app", "No existing window found, creating new window");
resolve::create_window();
resolve::create_window(true);
}
}
/// Setup window state monitor to save window position and size in real-time
pub fn setup_window_state_monitor(app_handle: &tauri::AppHandle) {
let window = app_handle.get_webview_window("main").unwrap();
let app_handle_clone = app_handle.clone();
// 监听窗口移动事件
let app_handle_move = app_handle_clone.clone();
window.on_window_event(move |event| {
match event {
// 窗口移动时保存状态
tauri::WindowEvent::Moved(_) => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 窗口调整大小时保存状态
tauri::WindowEvent::Resized(_) => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 其他可能改变窗口状态的事件
tauri::WindowEvent::ScaleFactorChanged { .. } => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
// 窗口关闭时保存
tauri::WindowEvent::CloseRequested { .. } => {
let _ = app_handle_move.save_window_state(StateFlags::all());
}
_ => {}
}
});
}
/// 优化的应用退出函数
pub fn quit(code: Option<i32>) {
log::debug!(target: "app", "启动退出流程");

View File

@ -2,6 +2,7 @@ mod cmd;
mod config;
mod core;
mod enhance;
mod error;
mod feat;
mod module;
mod utils;
@ -16,6 +17,7 @@ use tauri::AppHandle;
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use utils::logging::Type;
/// A global singleton handle to the application.
pub struct AppHandleManager {
@ -110,7 +112,6 @@ pub fn run() {
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init())
@ -120,13 +121,13 @@ pub fn run() {
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
log_err!(app.deep_link().register_all());
logging_error!(Type::System, true, app.deep_link().register_all());
}
app.deep_link().on_open_url(|event| {
tauri::async_runtime::spawn(async move {
if let Some(url) = event.urls().first() {
log_err!(resolve_scheme(url.to_string()).await);
logging_error!(Type::Setup, true, resolve_scheme(url.to_string()).await);
}
});
});
@ -151,9 +152,14 @@ pub fn run() {
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
cmd::install_service,
cmd::get_app_uptime,
cmd::get_auto_launch_status,
cmd::is_admin,
// service 管理
cmd::install_service,
cmd::uninstall_service,
cmd::reinstall_service,
cmd::repair_service,
// clash
cmd::get_clash_info,
cmd::patch_clash_config,
@ -276,13 +282,25 @@ pub fn run() {
tauri::WindowEvent::Focused(true) => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().register("CMD+Q", "quit"));
log_err!(hotkey::Hotkey::global().register("CMD+W", "hide"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+Q", "quit")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+W", "hide")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("Control+Q", "quit")
);
};
{
let is_enable_global_hotkey = Config::verge()
@ -290,19 +308,31 @@ pub fn run() {
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().init())
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().init())
}
}
}
tauri::WindowEvent::Focused(false) => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
log_err!(hotkey::Hotkey::global().unregister("CMD+W"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
};
{
let is_enable_global_hotkey = Config::verge()
@ -310,20 +340,32 @@ pub fn run() {
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().reset())
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().reset())
}
}
}
tauri::WindowEvent::Destroyed => {
#[cfg(target_os = "macos")]
{
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
log_err!(hotkey::Hotkey::global().unregister("CMD+W"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
}
#[cfg(not(target_os = "macos"))]
{
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
};
}
_ => {}

View File

@ -5,46 +5,51 @@ use tauri::{Listener, Manager};
use crate::{
config::Config,
core::{handle, timer::Timer},
log_err,
log_err, logging,
utils::logging::Type,
AppHandleManager,
};
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
pub fn enable_auto_light_weight_mode() {
println!("[lightweight_mode] 开启自动轻量模式");
log::info!(target: "app", "[lightweight_mode] 开启自动轻量模式");
Timer::global().init().unwrap();
logging!(info, Type::Lightweight, true, "开启自动轻量模式");
setup_window_close_listener();
setup_webview_focus_listener();
}
pub fn disable_auto_light_weight_mode() {
println!("[lightweight_mode] 关闭自动轻量模式");
log::info!(target: "app", "[lightweight_mode] 关闭自动轻量模式");
logging!(info, Type::Lightweight, true, "关闭自动轻量模式");
let _ = cancel_light_weight_timer();
cancel_window_close_listener();
}
pub fn entry_lightweight_mode() {
println!("尝试进入轻量模式。motherfucker");
if let Some(window) = handle::Handle::global().get_window() {
log_err!(window.close());
}
if let Some(window) = handle::Handle::global().get_window() {
if let Some(webview) = window.get_webview_window("main") {
log_err!(webview.destroy());
println!("[lightweight_mode] 轻量模式已开启");
log::info!(target: "app", "[lightweight_mode] 轻量模式已开启");
if window.is_visible().unwrap_or(false) {
let _ = window.hide();
}
if let Some(webview) = window.get_webview_window("main") {
let _ = webview.destroy();
}
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_accessory();
logging!(info, Type::Lightweight, true, "轻量模式已开启");
}
let _ = cancel_light_weight_timer();
}
fn setup_window_close_listener() -> u32 {
if let Some(window) = handle::Handle::global().get_window() {
let handler = window.listen("tauri://close-requested", move |_event| {
let _ = setup_light_weight_timer();
println!("[lightweight_mode] 监听到关闭请求,开始轻量模式计时");
log::info!(target: "app", "[lightweight_mode] 监听到关闭请求,开始轻量模式计时");
logging!(
info,
Type::Lightweight,
true,
"监听到关闭请求,开始轻量模式计时"
);
});
return handler;
}
@ -55,8 +60,12 @@ fn setup_webview_focus_listener() -> u32 {
if let Some(window) = handle::Handle::global().get_window() {
let handler = window.listen("tauri://focus", move |_event| {
log_err!(cancel_light_weight_timer());
println!("[lightweight_mode] 监听到窗口获得焦点,取消轻量模式计时");
log::info!(target: "app", "[lightweight_mode] 监听到窗口获得焦点,取消轻量模式计时");
logging!(
info,
Type::Lightweight,
true,
"监听到窗口获得焦点,取消轻量模式计时"
);
});
return handler;
}
@ -66,8 +75,7 @@ fn setup_webview_focus_listener() -> u32 {
fn cancel_window_close_listener() {
if let Some(window) = handle::Handle::global().get_window() {
window.unlisten(setup_window_close_listener());
println!("[lightweight_mode] 取消了窗口关闭监听");
log::info!(target: "app", "[lightweight_mode] 取消了窗口关闭监听");
logging!(info, Type::Lightweight, true, "取消了窗口关闭监听");
}
}
@ -84,7 +92,6 @@ fn setup_light_weight_timer() -> Result<()> {
let once_by_minutes = Config::verge()
.latest()
.auto_light_weight_minutes
.clone()
.unwrap_or(10);
let task = TaskBuilder::default()
@ -92,17 +99,14 @@ fn setup_light_weight_timer() -> Result<()> {
.set_maximum_parallel_runnable_num(1)
.set_frequency_once_by_minutes(once_by_minutes)
.spawn_async_routine(move || async move {
println!("[lightweight_mode] 计时器到期,开始进入轻量模式");
log::info!(target: "app",
"[lightweight_mode] 计时器到期,开始进入轻量模式"
);
logging!(info, Type::Timer, true, "计时器到期,开始进入轻量模式");
entry_lightweight_mode();
})
.context("failed to create light weight timer task")?;
.context("failed to create timer task")?;
delay_timer
.add_task(task)
.context("failed to add light weight timer task")?;
.context("failed to add timer task")?;
let timer_task = crate::core::timer::TimerTask {
task_id,
@ -112,12 +116,11 @@ fn setup_light_weight_timer() -> Result<()> {
timer_map.insert(LIGHT_WEIGHT_TASK_UID.to_string(), timer_task);
println!(
"[lightweight_mode] 轻量模式计时器已设置,{} 分钟后将自动进入轻量模式",
once_by_minutes
);
log::info!(target: "app",
"[lightweight_mode] 轻量模式计时器已设置,{} 分钟后将自动进入轻量模式",
logging!(
info,
Type::Timer,
true,
"计时器已设置,{} 分钟后将自动进入轻量模式",
once_by_minutes
);
@ -131,11 +134,9 @@ fn cancel_light_weight_timer() -> Result<()> {
if let Some(task) = timer_map.remove(LIGHT_WEIGHT_TASK_UID) {
delay_timer
.remove_task(task.task_id)
.context("failed to remove light weight timer task")?;
.context("failed to remove timer task")?;
logging!(info, Type::Timer, true, "计时器已取消");
}
println!("[lightweight_mode] 轻量模式计时器已取消");
log::info!(target: "app", "[lightweight_mode] 轻量模式计时器已取消");
Ok(())
}

View File

@ -1,3 +1,3 @@
pub mod lightweight;
pub mod mihomo;
pub mod sysinfo;
pub mod lightweight;

View File

@ -1,4 +1,7 @@
use crate::core::{handle, CoreManager};
use crate::{
cmd::system,
core::{handle, CoreManager},
};
use std::fmt::{self, Debug, Formatter};
use sysinfo::System;
@ -9,14 +12,15 @@ pub struct PlatformSpecification {
system_arch: String,
verge_version: String,
running_mode: String,
is_admin: bool,
}
impl Debug for PlatformSpecification {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode
"System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode, self.is_admin
)
}
}
@ -32,16 +36,10 @@ impl PlatformSpecification {
let config = handler.config();
let verge_version = config.version.clone().unwrap_or("Null".into());
// Get running mode asynchronously
let running_mode = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
match CoreManager::global().get_running_mode().await {
crate::core::RunningMode::Service => "Service".to_string(),
crate::core::RunningMode::Sidecar => "Sidecar".to_string(),
crate::core::RunningMode::NotRunning => "Not Running".to_string(),
}
})
});
// 使用默认值避免在同步上下文中执行异步操作
let running_mode = "NotRunning".to_string();
let is_admin = system::is_admin().unwrap_or_default();
Self {
system_name,
@ -50,6 +48,17 @@ impl PlatformSpecification {
system_arch,
verge_version,
running_mode,
is_admin,
}
}
// 异步方法来获取完整的系统信息
pub async fn new_async() -> Self {
let mut info = Self::new();
let running_mode = CoreManager::global().get_running_mode().await;
info.running_mode = running_mode.to_string();
info
}
}

View File

@ -77,6 +77,36 @@ pub fn app_profiles_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("profiles"))
}
/// icons dir
pub fn app_icons_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("icons"))
}
pub fn find_target_icons(target: &str) -> Result<Option<String>> {
let icons_dir = app_icons_dir()?;
let mut matching_files = Vec::new();
for entry in fs::read_dir(icons_dir)? {
let entry = entry?;
let path = entry.path();
if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
if file_name.starts_with(target)
&& (file_name.ends_with(".ico") || file_name.ends_with(".png"))
{
matching_files.push(path);
}
}
}
if matching_files.is_empty() {
Ok(None)
} else {
let first = path_to_str(matching_files.first().unwrap())?;
Ok(Some(first.to_string()))
}
}
/// logs dir
pub fn app_logs_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("logs"))

View File

@ -1,8 +1,8 @@
use crate::enhance::seq::SeqMap;
use crate::{enhance::seq::SeqMap, logging, utils::logging::Type};
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping, Value};
use serde_yaml::Mapping;
use std::{fs, path::PathBuf, str::FromStr};
/// read data from yaml as struct T
@ -22,19 +22,41 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
})
}
/// read mapping from yaml fix #165
/// read mapping from yaml
pub fn read_mapping(path: &PathBuf) -> Result<Mapping> {
let mut val: Value = read_yaml(path)?;
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
if !path.exists() {
bail!("file not found \"{}\"", path.display());
}
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
let yaml_str = fs::read_to_string(path)
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
// YAML语法检查
match serde_yaml::from_str::<serde_yaml::Value>(&yaml_str) {
Ok(mut val) => {
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
}
Err(err) => {
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
logging!(error, Type::Config, true, "{}", error_msg);
crate::core::handle::Handle::notice_message(
"config_validate::yaml_syntax_error",
&error_msg,
);
bail!("YAML syntax error: {}", err)
}
}
}
/// read mapping from yaml fix #165
@ -136,52 +158,6 @@ pub fn linux_elevator() -> String {
}
}
#[macro_export]
macro_rules! error {
($result: expr) => {
log::error!(target: "app", "{}", $result);
};
}
#[macro_export]
macro_rules! log_err {
($result: expr) => {
if let Err(err) = $result {
log::error!(target: "app", "{err}");
}
};
($result: expr, $err_str: expr) => {
if let Err(_) = $result {
log::error!(target: "app", "{}", $err_str);
}
};
}
#[macro_export]
macro_rules! trace_err {
($result: expr, $err_str: expr) => {
if let Err(err) = $result {
log::trace!(target: "app", "{}, err {}", $err_str, err);
}
}
}
/// wrap the anyhow error
/// transform the error to String
#[macro_export]
macro_rules! wrap_err {
($stat: expr) => {
match $stat {
Ok(a) => Ok(a),
Err(err) => {
log::error!(target: "app", "{}", err.to_string());
Err(format!("{}", err.to_string()))
}
}
};
}
/// return the string literal error
#[macro_export]
macro_rules! ret_err {
@ -213,15 +189,16 @@ macro_rules! t {
/// ```
#[cfg(target_os = "macos")]
pub fn format_bytes_speed(speed: u64) -> String {
if speed < 1024 {
format!("{}B/s", speed)
} else if speed < 1024 * 1024 {
format!("{:.1}KB/s", speed as f64 / 1024.0)
} else if speed < 1024 * 1024 * 1024 {
format!("{:.1}MB/s", speed as f64 / 1024.0 / 1024.0)
} else {
format!("{:.1}GB/s", speed as f64 / 1024.0 / 1024.0 / 1024.0)
const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"];
let mut size = speed as f64;
let mut unit_index = 0;
while size >= 1000.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
format!("{:.1}{}/s", size, UNITS[unit_index])
}
#[cfg(target_os = "macos")]

View File

@ -177,6 +177,7 @@ fn init_dns_config() -> Result<()> {
(
"default-nameserver".into(),
Value::Sequence(vec![
Value::String("system".into()),
Value::String("223.6.6.6".into()),
Value::String("8.8.8.8".into()),
]),
@ -189,14 +190,7 @@ fn init_dns_config() -> Result<()> {
Value::String("https://dns.alidns.com/dns-query".into()),
]),
),
(
"fallback".into(),
Value::Sequence(vec![
Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("https://dns.google/dns-query".into()),
Value::String("https://cloudflare-dns.com/dns-query".into()),
]),
),
("fallback".into(), Value::Sequence(vec![])),
(
"nameserver-policy".into(),
Value::Mapping(serde_yaml::Mapping::new()),
@ -206,6 +200,7 @@ fn init_dns_config() -> Result<()> {
Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("tls://223.5.5.5".into()),
]),
),
("direct-nameserver".into(), Value::Sequence(vec![])),
@ -300,15 +295,11 @@ pub fn init_config() -> Result<()> {
/// after tauri setup
pub fn init_resources() -> Result<()> {
let app_dir = dirs::app_home_dir()?;
let test_dir = app_dir.join("test");
let res_dir = dirs::app_resources_dir()?;
if !app_dir.exists() {
let _ = fs::create_dir_all(&app_dir);
}
if !test_dir.exists() {
let _ = fs::create_dir_all(&test_dir);
}
if !res_dir.exists() {
let _ = fs::create_dir_all(&res_dir);
}
@ -320,7 +311,6 @@ pub fn init_resources() -> Result<()> {
for file in file_list.iter() {
let src_path = res_dir.join(file);
let dest_path = app_dir.join(file);
let test_dest_path = test_dir.join(file);
log::debug!(target: "app", "src_path: {src_path:?}, dest_path: {dest_path:?}");
let handle_copy = |dest: &PathBuf| {
@ -332,9 +322,6 @@ pub fn init_resources() -> Result<()> {
};
};
if src_path.exists() && !test_dest_path.exists() {
handle_copy(&test_dest_path);
}
if src_path.exists() && !dest_path.exists() {
handle_copy(&dest_path);
continue;

View File

@ -0,0 +1,139 @@
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Type {
Cmd,
Core,
Config,
Setup,
System,
Service,
Hotkey,
Window,
Tray,
Timer,
Frontend,
Backup,
Lightweight,
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Type::Cmd => write!(f, "[Cmd]"),
Type::Core => write!(f, "[Core]"),
Type::Config => write!(f, "[Config]"),
Type::Setup => write!(f, "[Setup]"),
Type::System => write!(f, "[System]"),
Type::Service => write!(f, "[Service]"),
Type::Hotkey => write!(f, "[Hotkey]"),
Type::Window => write!(f, "[Window]"),
Type::Tray => write!(f, "[Tray]"),
Type::Timer => write!(f, "[Timer]"),
Type::Frontend => write!(f, "[Frontend]"),
Type::Backup => write!(f, "[Backup]"),
Type::Lightweight => write!(f, "[Lightweight]"),
}
}
}
#[macro_export]
macro_rules! error {
($result: expr) => {
log::error!(target: "app", "{}", $result);
};
}
#[macro_export]
macro_rules! log_err {
($result: expr) => {
if let Err(err) = $result {
log::error!(target: "app", "{err}");
}
};
($result: expr, $err_str: expr) => {
if let Err(_) = $result {
log::error!(target: "app", "{}", $err_str);
}
};
}
#[macro_export]
macro_rules! trace_err {
($result: expr, $err_str: expr) => {
if let Err(err) = $result {
log::trace!(target: "app", "{}, err {}", $err_str, err);
}
}
}
/// wrap the anyhow error
/// transform the error to String
#[macro_export]
macro_rules! wrap_err {
($stat: expr) => {
match $stat {
Ok(a) => Ok(a),
Err(err) => {
log::error!(target: "app", "{}", err.to_string());
Err(format!("{}", err.to_string()))
}
}
};
}
#[macro_export]
macro_rules! logging {
// 带 println 的版本(支持格式化参数)
($level:ident, $type:expr, true, $($arg:tt)*) => {
println!("{} {}", $type, format_args!($($arg)*));
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
// 带 println 的版本(使用 false 明确不打印)
($level:ident, $type:expr, false, $($arg:tt)*) => {
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
// 不带 print 参数的版本(默认不打印)
($level:ident, $type:expr, $($arg:tt)*) => {
log::$level!(target: "app", "{} {}", $type, format_args!($($arg)*));
};
}
#[macro_export]
macro_rules! logging_error {
// 1. 处理 Result<T, E>,带打印控制
($type:expr, $print:expr, $expr:expr) => {
match $expr {
Ok(_) => {},
Err(err) => {
if $print {
println!("[{}] Error: {}", $type, err);
}
log::error!(target: "app", "[{}] {}", $type, err);
}
}
};
// 2. 处理 Result<T, E>,默认不打印
($type:expr, $expr:expr) => {
if let Err(err) = $expr {
log::error!(target: "app", "[{}] {}", $type, err);
}
};
// 3. 处理格式化字符串,带打印控制
($type:expr, $print:expr, $fmt:literal $(, $arg:expr)*) => {
if $print {
println!("[{}] {}", $type, format_args!($fmt $(, $arg)*));
}
log::error!(target: "app", "[{}] {}", $type, format_args!($fmt $(, $arg)*));
};
// 4. 处理格式化字符串,不带 bool 时,默认 `false`
($type:expr, $fmt:literal $(, $arg:expr)*) => {
logging_error!($type, false, $fmt $(, $arg)*);
};
}

View File

@ -3,6 +3,7 @@ pub mod error;
pub mod help;
pub mod i18n;
pub mod init;
pub mod logging;
pub mod resolve;
pub mod server;
pub mod tmpl;

View File

@ -1,7 +1,12 @@
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem}, core::*, log_err, module::lightweight, utils::{error, init, server}, wrap_err
config::{Config, IVerge, PrfItem},
core::*,
logging, logging_error,
module::lightweight,
utils::{error, init, logging::Type, server},
wrap_err,
};
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
@ -10,10 +15,9 @@ use serde_yaml::Mapping;
use std::net::TcpListener;
use tauri::{App, Manager};
use url::Url;
use tauri::Url;
//#[cfg(not(target_os = "linux"))]
// use window_shadows::set_shadow;
use tauri_plugin_notification::NotificationExt;
pub static VERSION: OnceCell<String> = OnceCell::new();
@ -47,106 +51,98 @@ pub async fn resolve_setup(app: &mut App) {
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
log_err!(init::init_config());
log_err!(init::init_resources());
log_err!(init::init_scheme());
log_err!(init::startup_script().await);
logging_error!(Type::Config, true, init::init_config());
logging_error!(Type::Setup, true, init::init_resources());
logging_error!(Type::Setup, true, init::init_scheme());
logging_error!(Type::Setup, true, init::startup_script().await);
// 处理随机端口
log_err!(resolve_random_port_config());
logging_error!(Type::System, true, resolve_random_port_config());
// 启动核心
log::trace!(target:"app", "init config");
log_err!(Config::init_config().await);
logging!(trace, Type::Config, true, "Initial config");
logging_error!(Type::Config, true, Config::init_config().await);
if service::check_service().await.is_err() {
match service::reinstall_service().await {
Ok(_) => {
log::info!(target:"app", "install service susccess.");
#[cfg(not(target_os = "macos"))]
std::thread::sleep(std::time::Duration::from_millis(1000));
#[cfg(target_os = "macos")]
{
let mut service_runing = false;
for _ in 0..40 {
if service::check_service().await.is_ok() {
service_runing = true;
break;
} else {
log::warn!(target: "app", "service not runing, sleep 500ms and check again.");
std::thread::sleep(std::time::Duration::from_millis(500));
}
}
if !service_runing {
log::warn!(target: "app", "service not running, will fallback to user mode");
}
}
}
Err(e) => {
log::warn!(target: "app", "failed to install service: {e:?}, will fallback to user mode");
}
}
}
log::trace!(target: "app", "launch core");
log_err!(CoreManager::global().init().await);
logging!(trace, Type::Core, "Starting CoreManager");
logging_error!(Type::Core, true, CoreManager::global().init().await);
// setup a simple http server for singleton
log::trace!(target: "app", "launch embed server");
server::embed_server();
log::trace!(target: "app", "init system tray");
log_err!(tray::Tray::global().init());
log_err!(tray::Tray::global().create_systray(app));
log::trace!(target: "app", "Initial system tray");
logging_error!(Type::Tray, true, tray::Tray::global().init());
logging_error!(Type::Tray, true, tray::Tray::global().create_systray(app));
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().update_sysproxy().await
);
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().init_guard_sysproxy()
);
// 初始化热键
log::trace!(target: "app", "init hotkeys");
log_err!(hotkey::Hotkey::global().init());
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
create_window(!is_silent_start);
let silent_start = { Config::verge().data().enable_silent_start };
if !silent_start.unwrap_or(false) {
create_window();
}
log_err!(tray::Tray::global().update_part());
log_err!(timer::Timer::global().init());
logging_error!(Type::System, true, timer::Timer::global().init());
let enable_auto_light_weight_mode = { Config::verge().data().enable_auto_light_weight_mode };
if enable_auto_light_weight_mode.unwrap_or(false) {
lightweight::enable_auto_light_weight_mode();
}
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
// 初始化热键
logging!(trace, Type::System, true, "Initial hotkeys");
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
}
/// reset system proxy
pub fn resolve_reset() {
tauri::async_runtime::block_on(async move {
#[cfg(target_os = "macos")]
tray::Tray::global().unsubscribe_traffic();
/// reset system proxy (异步版)
pub async fn resolve_reset_async() {
#[cfg(target_os = "macos")]
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
#[cfg(target_os = "macos")]
tray::Tray::global().unsubscribe_traffic();
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
log_err!(CoreManager::global().stop_core().await);
#[cfg(target_os = "macos")]
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().reset_sysproxy().await
);
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
#[cfg(target_os = "macos")]
{
logging!(info, Type::System, true, "Restoring system DNS settings");
restore_public_dns().await;
});
}
}
/// create main window
pub fn create_window() {
println!("Starting to create window");
log::info!(target: "app", "Starting to create window");
pub fn create_window(is_showup: bool) {
logging!(info, Type::Window, true, "Creating window");
let app_handle = handle::Handle::global().app_handle().unwrap();
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_regular();
if let Some(window) = handle::Handle::global().get_window() {
println!("Found existing window, trying to show it");
log::info!(target: "app", "Found existing window, trying to show it");
logging!(
info,
Type::Window,
true,
"Found existing window, attempting to restore visibility"
);
if window.is_minimized().unwrap_or(false) {
println!("Window is minimized, unminimizing");
log::info!(target: "app", "Window is minimized, unminimizing");
logging!(
info,
Type::Window,
true,
"Window is minimized, restoring window state"
);
let _ = window.unminimize();
}
let _ = window.show();
@ -154,8 +150,7 @@ pub fn create_window() {
return;
}
println!("Creating new window");
log::info!(target: "app", "Creating new window");
logging!(info, Type::Window, true, "Creating new application window");
#[cfg(target_os = "windows")]
let window = tauri::WebviewWindowBuilder::new(
@ -201,17 +196,46 @@ pub fn create_window() {
match window {
Ok(window) => {
println!("Window created successfully, attempting to show");
log::info!(target: "app", "Window created successfully, attempting to show");
let _ = window.show();
let _ = window.set_focus();
logging!(info, Type::Window, true, "Window created successfully");
if is_showup {
println!("is showup");
let _ = window.show();
let _ = window.set_focus();
} else {
let _ = window.hide();
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_accessory();
}
// 设置窗口状态监控,实时保存窗口位置和大小
crate::feat::setup_window_state_monitor(&app_handle);
// crate::feat::setup_window_state_monitor(&app_handle);
// 标记前端UI已准备就绪向前端发送启动完成事件
let app_handle_clone = app_handle.clone();
tauri::async_runtime::spawn(async move {
use tauri::Emitter;
logging!(
info,
Type::Window,
true,
"标记前端UI已准备就绪开始处理启动错误队列"
);
handle::Handle::global().mark_startup_completed();
if let Some(window) = app_handle_clone.get_webview_window("main") {
let _ = window.emit("verge://startup-completed", ());
}
});
}
Err(e) => {
println!("Failed to create window: {:?}", e);
log::error!(target: "app", "Failed to create window: {:?}", e);
logging!(
error,
Type::Window,
true,
"Failed to create window: {:?}",
e
);
}
}
}
@ -219,8 +243,6 @@ pub fn create_window() {
pub async fn resolve_scheme(param: String) -> Result<()> {
log::info!(target:"app", "received deep link: {}", param);
let app_handle = handle::Handle::global().app_handle().unwrap();
let param_str = if param.starts_with("[") && param.len() > 4 {
param
.get(2..param.len() - 2)
@ -243,41 +265,32 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
.find(|(key, _)| key == "name")
.map(|(_, value)| value.into_owned());
let encode_url = link_parsed
.query_pairs()
.find(|(key, _)| key == "url")
.map(|(_, value)| value.into_owned());
// 通过直接获取查询部分并解析特定参数来避免 URL 转义问题
let url_param = if let Some(query) = link_parsed.query() {
let prefix = "url=";
if let Some(pos) = query.find(prefix) {
let raw_url = &query[pos + prefix.len()..];
Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string())
} else {
None
}
} else {
None
};
match encode_url {
match url_param {
Some(url) => {
let url = percent_decode_str(url.as_ref())
.decode_utf8_lossy()
.to_string();
log::info!(target:"app", "decoded subscription url: {}", url);
create_window();
create_window(false);
match PrfItem::from_url(url.as_ref(), name, None, None).await {
Ok(item) => {
let uid = item.uid.clone().unwrap();
let _ = wrap_err!(Config::profiles().data().append_item(item));
handle::Handle::notice_message("import_sub_url::ok", uid);
app_handle
.notification()
.builder()
.title("Clash Verge")
.body("Import profile success")
.show()
.unwrap();
}
Err(e) => {
handle::Handle::notice_message("import_sub_url::error", e.to_string());
app_handle
.notification()
.builder()
.title("Clash Verge")
.body(format!("Import profile failed: {e}"))
.show()
.unwrap();
}
}
}

View File

@ -3,7 +3,8 @@ extern crate warp;
use super::resolve;
use crate::{
config::{Config, IVerge, DEFAULT_PAC},
log_err,
logging_error,
utils::logging::Type,
};
use anyhow::{bail, Result};
use port_scanner::local_port_available;
@ -48,7 +49,7 @@ pub fn embed_server() {
tauri::async_runtime::spawn(async move {
let visible = warp::path!("commands" / "visible").map(move || {
resolve::create_window();
resolve::create_window(false);
"ok"
});
@ -69,7 +70,11 @@ pub fn embed_server() {
.unwrap_or_default()
});
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
log_err!(resolve::resolve_scheme(query.param).await);
logging_error!(
Type::Setup,
true,
resolve::resolve_scheme(query.param).await
);
Ok("ok")
}

View File

@ -6,9 +6,9 @@ edition = "2024"
debug = []
[dependencies]
reqwest = { version = "0.12.12", features = ["json"] }
serde = { version = "1.0.218", features = ["derive"] }
reqwest = { version = "0.12.15", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
[dev-dependencies]
tokio = { version = "1.43.0", features = ["rt", "macros"] }
tokio = { version = "1.44.1", features = ["rt", "macros"] }

View File

@ -98,6 +98,12 @@ impl MihomoManager {
}
impl MihomoManager {
pub async fn is_mihomo_running(&self) -> Result<(), String> {
let url = format!("{}/version", self.mihomo_server);
let _response = self.send_request(Method::GET, url, None).await?;
Ok(())
}
pub async fn put_configs_force(&self, clash_config_path: &str) -> Result<(), String> {
let url = format!("{}/configs?force=true", self.mihomo_server);
let payload = serde_json::json!({
@ -134,4 +140,23 @@ impl MihomoManager {
let response = self.send_request(Method::GET, url, None).await?;
Ok(response)
}
pub async fn get_connections(&self) -> Result<serde_json::Value, String> {
let url = format!("{}/connections", self.mihomo_server);
let response = self.send_request(Method::GET, url, None).await?;
Ok(response)
}
pub async fn delete_connection(&self, id: &str) -> Result<(), String> {
let url = format!("{}/connections/{}", self.mihomo_server, id);
let response = self.send_request(Method::DELETE, url, None).await?;
if response["code"] == 204 {
Ok(())
} else {
Err(response["message"]
.as_str()
.unwrap_or("unknown error")
.to_string())
}
}
}

View File

@ -1,5 +1,5 @@
{
"version": "2.2.1-alpha",
"version": "2.2.3",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
@ -11,9 +11,15 @@
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["resources", "resources/locales/*"],
"resources": [
"resources",
"resources/locales/*"
],
"publisher": "Clash Verge Rev",
"externalBin": ["sidecar/verge-mihomo", "sidecar/verge-mihomo-alpha"],
"externalBin": [
"sidecar/verge-mihomo",
"sidecar/verge-mihomo-alpha"
],
"copyright": "GNU General Public License v3.0",
"category": "DeveloperTool",
"shortDescription": "Clash Verge Rev",
@ -32,8 +38,10 @@
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK",
"endpoints": [
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json",
"https://download.clashverge.dev/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
"https://gh-proxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha-proxy.json",
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater-alpha/update-alpha.json"
],
"windows": {
@ -42,15 +50,25 @@
},
"deep-link": {
"desktop": {
"schemes": ["clash", "clash-verge"]
"schemes": [
"clash",
"clash-verge"
]
}
}
},
"app": {
"security": {
"capabilities": ["desktop-capability", "migrated"],
"capabilities": [
"desktop-capability",
"migrated"
],
"assetProtocol": {
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"],
"scope": [
"$APPDATA/**",
"$RESOURCE/../**",
"**"
],
"enable": true
},
"csp": null

13
src/App.tsx Normal file
View File

@ -0,0 +1,13 @@
import { AppDataProvider } from "./providers/app-data-provider";
import React from "react";
import Layout from "./pages/_layout";
function App() {
return (
<AppDataProvider>
<Layout />
</AppDataProvider>
);
}
export default App;

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useLockFn } from "ahooks";
import { Box, Button, Snackbar } from "@mui/material";
import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { deleteConnection } from "@/services/api";
import parseTraffic from "@/utils/parse-traffic";
import { t } from "i18next";
@ -14,6 +14,7 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
(props, ref) => {
const [open, setOpen] = useState(false);
const [detail, setDetail] = useState<IConnectionsItem>(null!);
const theme = useTheme();
useImperativeHandle(ref, () => ({
open: (detail: IConnectionsItem) => {
@ -35,6 +36,8 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
maxWidth: "520px",
maxHeight: "480px",
overflowY: "auto",
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
},
}}
message={
@ -54,6 +57,7 @@ interface InnerProps {
const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const { metadata, rulePayload } = data;
const theme = useTheme();
const chains = [...data.chains].reverse().join(" / ");
const rule = rulePayload ? `${data.rule}(${rulePayload})` : data.rule;
const host = metadata.host
@ -99,11 +103,11 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const onDelete = useLockFn(async () => deleteConnection(data.id));
return (
<Box sx={{ userSelect: "text" }}>
<Box sx={{ userSelect: "text", color: theme.palette.text.secondary }}>
{information.map((each) => (
<div key={each.label}>
<b>{each.label}</b>
<span style={{ wordBreak: "break-all" }}>: {each.value}</span>
<span style={{ wordBreak: "break-all", color: theme.palette.text.primary }}>: {each.value}</span>
</div>
))}

View File

@ -1,13 +1,10 @@
import { useTranslation } from "react-i18next";
import { Typography, Stack, Divider } from "@mui/material";
import { DeveloperBoardOutlined } from "@mui/icons-material";
import { useClashInfo } from "@/hooks/use-clash";
import { useClash } from "@/hooks/use-clash";
import { EnhancedCard } from "./enhanced-card";
import useSWR from "swr";
import { getRules } from "@/services/api";
import { getAppUptime } from "@/services/cmds";
import { useMemo } from "react";
import { useAppData } from "@/providers/app-data-provider";
// 将毫秒转换为时:分:秒格式的函数
const formatUptime = (uptimeMs: number) => {
@ -19,32 +16,15 @@ const formatUptime = (uptimeMs: number) => {
export const ClashInfoCard = () => {
const { t } = useTranslation();
const { clashInfo } = useClashInfo();
const { version: clashVersion } = useClash();
// 使用SWR获取应用运行时间降低更新频率
const { data: uptimeMs = 0 } = useSWR(
"appUptime",
getAppUptime,
{
refreshInterval: 1000,
revalidateOnFocus: false,
dedupingInterval: 1000,
},
);
const { clashConfig, sysproxy, rules, uptime } = useAppData();
// 使用useMemo缓存格式化后的uptime避免频繁计算
const uptime = useMemo(() => formatUptime(uptimeMs), [uptimeMs]);
// 获取规则数据,只在组件加载时获取一次
const { data: rules = [] } = useSWR("getRules", getRules, {
revalidateOnFocus: false,
errorRetryCount: 2,
});
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
// 使用备忘录组件内容,减少重新渲染
const cardContent = useMemo(() => {
if (!clashInfo) return null;
if (!clashConfig) return null;
return (
<Stack spacing={1.5}>
@ -62,7 +42,7 @@ export const ClashInfoCard = () => {
{t("System Proxy Address")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{clashInfo.server || "-"}
{sysproxy?.server || "-"}
</Typography>
</Stack>
<Divider />
@ -71,7 +51,7 @@ export const ClashInfoCard = () => {
{t("Mixed Port")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{clashInfo.mixed_port || "-"}
{clashConfig["mixed-port"] || "-"}
</Typography>
</Stack>
<Divider />
@ -80,7 +60,7 @@ export const ClashInfoCard = () => {
{t("Uptime")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{uptime}
{formattedUptime}
</Typography>
</Stack>
<Divider />
@ -94,7 +74,7 @@ export const ClashInfoCard = () => {
</Stack>
</Stack>
);
}, [clashInfo, clashVersion, t, uptime, rules.length]);
}, [clashConfig, clashVersion, t, formattedUptime, rules.length, sysproxy]);
return (
<EnhancedCard

View File

@ -1,8 +1,7 @@
import { useTranslation } from "react-i18next";
import { Box, Typography, Paper, Stack, Fade } from "@mui/material";
import { useLockFn } from "ahooks";
import useSWR from "swr";
import { closeAllConnections, getClashConfig } from "@/services/api";
import { closeAllConnections } from "@/services/api";
import { patchClashMode } from "@/services/cmds";
import { useVerge } from "@/hooks/use-verge";
import {
@ -10,31 +9,19 @@ import {
MultipleStopRounded,
DirectionsRounded,
} from "@mui/icons-material";
import { useState, useEffect, useMemo } from "react";
import { useMemo } from "react";
import { useAppData } from "@/providers/app-data-provider";
export const ClashModeCard = () => {
const { t } = useTranslation();
const { verge } = useVerge();
// 获取当前Clash配置
const { data: clashConfig, mutate: mutateClash } = useSWR(
"getClashConfig",
getClashConfig,
{ revalidateOnFocus: false }
);
const { clashConfig, refreshProxy } = useAppData();
// 支持的模式列表
const modeList = useMemo(() => ["rule", "global", "direct"] as const, []);
// 本地状态记录当前模式
const [localMode, setLocalMode] = useState<string>("rule");
// 当从API获取到当前模式时更新本地状态
useEffect(() => {
if (clashConfig?.mode) {
setLocalMode(clashConfig.mode.toLowerCase());
}
}, [clashConfig]);
// 直接使用API返回的模式不维护本地状态
const currentMode = clashConfig?.mode?.toLowerCase();
// 模式图标映射
const modeIcons = useMemo(() => ({
@ -45,22 +32,17 @@ export const ClashModeCard = () => {
// 切换模式的处理函数
const onChangeMode = useLockFn(async (mode: string) => {
if (mode === localMode) return;
setLocalMode(mode);
if (mode === currentMode) return;
if (verge?.auto_close_connection) {
closeAllConnections();
}
try {
await patchClashMode(mode);
mutateClash();
// 使用共享的刷新方法
refreshProxy();
} catch (error) {
console.error("Failed to change mode:", error);
if (clashConfig?.mode) {
setLocalMode(clashConfig.mode.toLowerCase());
}
}
});
@ -73,8 +55,8 @@ export const ClashModeCard = () => {
alignItems: "center",
justifyContent: "center",
gap: 1,
bgcolor: mode === localMode ? "primary.main" : "background.paper",
color: mode === localMode ? "primary.contrastText" : "text.primary",
bgcolor: mode === currentMode ? "primary.main" : "background.paper",
color: mode === currentMode ? "primary.contrastText" : "text.primary",
borderRadius: 1.5,
transition: "all 0.2s ease-in-out",
position: "relative",
@ -86,7 +68,7 @@ export const ClashModeCard = () => {
"&:active": {
transform: "translateY(1px)",
},
"&::after": mode === localMode
"&::after": mode === currentMode
? {
content: '""',
position: "absolute",
@ -132,7 +114,7 @@ export const ClashModeCard = () => {
{modeList.map((mode) => (
<Paper
key={mode}
elevation={mode === localMode ? 2 : 0}
elevation={mode === currentMode ? 2 : 0}
onClick={() => onChangeMode(mode)}
sx={buttonStyles(mode)}
>
@ -141,7 +123,7 @@ export const ClashModeCard = () => {
variant="body2"
sx={{
textTransform: "capitalize",
fontWeight: mode === localMode ? 600 : 400,
fontWeight: mode === currentMode ? 600 : 400,
}}
>
{t(mode)}
@ -161,15 +143,13 @@ export const ClashModeCard = () => {
overflow: "visible",
}}
>
<Fade in={true} timeout={200}>
<Typography
variant="caption"
component="div"
sx={descriptionStyles}
>
{t(`${localMode} Mode Description`)}
</Typography>
</Fade>
<Typography
variant="caption"
component="div"
sx={descriptionStyles}
>
{t(`${currentMode} Mode Description`)}
</Typography>
</Box>
</Box>
);

View File

@ -13,7 +13,7 @@ import {
SelectChangeEvent,
Tooltip,
} from "@mui/material";
import { useEffect, useState, useMemo, useCallback, useRef } from "react";
import { useEffect, useState, useMemo, useCallback } from "react";
import {
SignalWifi4Bar as SignalStrong,
SignalWifi3Bar as SignalGood,
@ -24,16 +24,11 @@ import {
ChevronRight,
} from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import { useCurrentProxy } from "@/hooks/use-current-proxy";
import { EnhancedCard } from "@/components/home/enhanced-card";
import {
getProxies,
updateProxy,
getConnections,
deleteConnection,
} from "@/services/api";
import { updateProxy, deleteConnection } from "@/services/api";
import delayManager from "@/services/delay";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-provider";
// 本地存储的键名
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
@ -53,11 +48,16 @@ function convertDelayColor(delayValue: number) {
const mainColor = colorStr.split(".")[0];
switch (mainColor) {
case "success": return "success";
case "warning": return "warning";
case "error": return "error";
case "primary": return "primary";
default: return "default";
case "success":
return "success";
case "warning":
return "warning";
case "error":
return "error";
case "primary":
return "primary";
default:
return "default";
}
}
@ -79,7 +79,7 @@ function getSignalIcon(delay: number) {
// 简单的防抖函数
function debounce(fn: Function, ms = 100) {
let timeoutId: ReturnType<typeof setTimeout>;
return function(this: any, ...args: any[]) {
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
@ -87,20 +87,16 @@ function debounce(fn: Function, ms = 100) {
export const CurrentProxyCard = () => {
const { t } = useTranslation();
const { currentProxy, primaryGroupName, mode, refreshProxy } = useCurrentProxy();
const navigate = useNavigate();
const theme = useTheme();
const { verge } = useVerge();
const { proxies, connections, clashConfig, refreshProxy } = useAppData();
// 判断模式
const mode = clashConfig?.mode?.toLowerCase() || "rule";
const isGlobalMode = mode === "global";
const isDirectMode = mode === "direct";
// 使用 useRef 存储最后一次刷新时间和是否正在刷新
const lastRefreshRef = useRef<number>(0);
const isRefreshingRef = useRef<boolean>(false);
const pendingRefreshRef = useRef<boolean>(false);
// 定义状态类型
type ProxyState = {
proxyData: {
@ -133,149 +129,275 @@ export const CurrentProxyCard = () => {
// 初始化选择的组
useEffect(() => {
if (!proxies) return;
// 提取primaryGroupName
const getPrimaryGroupName = () => {
if (!proxies?.groups?.length) return "";
// 查找主要的代理组(优先级:包含关键词 > 第一个非GLOBAL组
const primaryKeywords = [
"auto",
"select",
"proxy",
"节点选择",
"自动选择",
];
const primaryGroup =
proxies.groups.find((group: { name: string }) =>
primaryKeywords.some((keyword) =>
group.name.toLowerCase().includes(keyword.toLowerCase()),
),
) || proxies.groups.filter((g: { name: string }) => g.name !== "GLOBAL")[0];
return primaryGroup?.name || "";
};
const primaryGroupName = getPrimaryGroupName();
// 根据模式确定初始组
if (isGlobalMode) {
setState(prev => ({
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: "GLOBAL"
}
group: "GLOBAL",
},
}));
} else if (isDirectMode) {
setState(prev => ({
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: "DIRECT"
}
group: "DIRECT",
},
}));
} else {
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
setState(prev => ({
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: savedGroup || primaryGroupName || ""
}
group: savedGroup || primaryGroupName || "",
},
}));
}
}, [isGlobalMode, isDirectMode, primaryGroupName]);
}, [isGlobalMode, isDirectMode, proxies]);
// 带锁的代理数据获取函数,防止并发请求
const fetchProxyData = useCallback(async (force = false) => {
// 防止重复请求
if (isRefreshingRef.current) {
pendingRefreshRef.current = true;
return;
}
// 检查刷新间隔
const now = Date.now();
if (!force && now - lastRefreshRef.current < 1000) {
return;
}
isRefreshingRef.current = true;
lastRefreshRef.current = now;
try {
const data = await getProxies();
// 监听代理数据变化,更新状态
useEffect(() => {
if (!proxies) return;
// 使用函数式更新确保状态更新的原子性
setState((prev) => {
// 过滤和格式化组
const filteredGroups = data.groups
.filter(g => g.name !== "DIRECT" && g.name !== "REJECT")
.map(g => ({
const filteredGroups = proxies.groups
.filter((g: { name: string }) => g.name !== "DIRECT" && g.name !== "REJECT")
.map((g: { name: string; now: string; all: Array<{ name: string }> }) => ({
name: g.name,
now: g.now || "",
all: g.all.map(p => p.name),
all: g.all.map((p: { name: string }) => p.name),
}));
// 使用函数式更新确保状态更新的原子性
setState(prev => {
let newProxy = "";
let newDisplayProxy = null;
let newGroup = prev.selection.group;
let newProxy = "";
let newDisplayProxy = null;
let newGroup = prev.selection.group;
// 根据模式确定新代理
if (isDirectMode) {
newGroup = "DIRECT";
newProxy = "DIRECT";
newDisplayProxy = data.records?.DIRECT || null;
} else if (isGlobalMode && data.global) {
newGroup = "GLOBAL";
newProxy = data.global.now || "";
newDisplayProxy = data.records?.[newProxy] || null;
} else {
// 普通模式 - 检查当前选择的组是否存在
const currentGroup = filteredGroups.find(g => g.name === prev.selection.group);
// 根据模式确定新代理
if (isDirectMode) {
newGroup = "DIRECT";
newProxy = "DIRECT";
newDisplayProxy = proxies.records?.DIRECT || null;
} else if (isGlobalMode && proxies.global) {
newGroup = "GLOBAL";
newProxy = proxies.global.now || "";
newDisplayProxy = proxies.records?.[newProxy] || null;
} else {
// 普通模式 - 检查当前选择的组是否存在
const currentGroup = filteredGroups.find(
(g: { name: string }) => g.name === prev.selection.group,
);
// 如果当前组不存在或为空,自动选择第一个组
if (!currentGroup && filteredGroups.length > 0) {
newGroup = filteredGroups[0].name;
const firstGroup = filteredGroups[0];
newProxy = firstGroup.now;
newDisplayProxy = data.records?.[newProxy] || null;
// 如果当前组不存在或为空,自动选择第一个组
if (!currentGroup && filteredGroups.length > 0) {
newGroup = filteredGroups[0].name;
const firstGroup = filteredGroups[0];
newProxy = firstGroup.now;
newDisplayProxy = proxies.records?.[newProxy] || null;
// 保存到本地存储
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
if (newProxy) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
// 保存到本地存储
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
if (newProxy) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
} else if (currentGroup) {
// 使用当前组的代理
newProxy = currentGroup.now;
newDisplayProxy = data.records?.[newProxy] || null;
}
} else if (currentGroup) {
// 使用当前组的代理
newProxy = currentGroup.now;
newDisplayProxy = proxies.records?.[newProxy] || null;
}
}
// 返回新状态
// 返回新状态
return {
proxyData: {
groups: filteredGroups,
records: proxies.records || {},
globalProxy: proxies.global?.now || "",
directProxy: proxies.records?.DIRECT || null,
},
selection: {
group: newGroup,
proxy: newProxy,
},
displayProxy: newDisplayProxy,
};
});
}, [proxies, isGlobalMode, isDirectMode]);
// 使用防抖包装状态更新,避免快速连续更新,增加防抖时间
const debouncedSetState = useCallback(
debounce((updateFn: (prev: ProxyState) => ProxyState) => {
setState(updateFn);
}, 300),
[],
);
// 处理代理组变更
const handleGroupChange = useCallback(
(event: SelectChangeEvent) => {
if (isGlobalMode || isDirectMode) return;
const newGroup = event.target.value;
// 保存到本地存储
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
// 获取该组当前选中的代理
setState((prev) => {
const group = prev.proxyData.groups.find((g: { name: string }) => g.name === newGroup);
if (group) {
return {
...prev,
selection: {
group: newGroup,
proxy: group.now,
},
displayProxy: prev.proxyData.records[group.now] || null,
};
}
return {
proxyData: {
groups: filteredGroups,
records: data.records || {},
globalProxy: data.global?.now || "",
directProxy: data.records?.DIRECT || null,
},
...prev,
selection: {
...prev.selection,
group: newGroup,
proxy: newProxy
},
displayProxy: newDisplayProxy
};
});
} catch (error) {
console.error("获取代理信息失败", error);
} finally {
isRefreshingRef.current = false;
},
[isGlobalMode, isDirectMode],
);
// 处理待处理的刷新请求
if (pendingRefreshRef.current) {
pendingRefreshRef.current = false;
setTimeout(() => fetchProxyData(), 100);
// 处理代理节点变更
const handleProxyChange = useCallback(
async (event: SelectChangeEvent) => {
if (isDirectMode) return;
const newProxy = event.target.value;
const currentGroup = state.selection.group;
const previousProxy = state.selection.proxy;
// 立即更新UI优化体验
debouncedSetState((prev: ProxyState) => ({
...prev,
selection: {
...prev.selection,
proxy: newProxy,
},
displayProxy: prev.proxyData.records[newProxy] || null,
}));
// 非特殊模式下保存到本地存储
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
}
}, [isGlobalMode, isDirectMode]);
// 响应 currentProxy 变化
useEffect(() => {
if (currentProxy && (!state.displayProxy || currentProxy.name !== state.displayProxy.name)) {
fetchProxyData(true);
}
}, [currentProxy, fetchProxyData, state.displayProxy]);
try {
// 更新代理设置
await updateProxy(currentGroup, newProxy);
// 平滑的定期刷新,使用固定间隔
useEffect(() => {
fetchProxyData();
// 自动关闭连接设置
if (verge?.auto_close_connection && previousProxy) {
connections.data.forEach((conn: any) => {
if (conn.chains.includes(previousProxy)) {
deleteConnection(conn.id);
}
});
}
const intervalId = setInterval(() => {
fetchProxyData();
}, 3000); // 使用固定的3秒间隔平衡响应速度和性能
// 延长刷新延迟时间
setTimeout(() => {
refreshProxy();
}, 500);
} catch (error) {
console.error("更新代理失败", error);
}
},
[
isDirectMode,
isGlobalMode,
state.proxyData.records,
state.selection,
verge?.auto_close_connection,
refreshProxy,
debouncedSetState,
connections.data,
],
);
return () => clearInterval(intervalId);
}, [fetchProxyData]);
// 导航到代理页面
const goToProxies = useCallback(() => {
navigate("/");
}, [navigate]);
// 获取要显示的代理节点
const currentProxy = useMemo(() => {
// 从state中获取当前代理信息
return state.displayProxy;
}, [state.displayProxy]);
// 获取当前节点的延迟
const currentDelay = currentProxy
? delayManager.getDelayFix(currentProxy, state.selection.group)
: -1;
// 获取信号图标
const signalInfo = getSignalIcon(currentDelay);
// 自定义渲染选择框中的值
const renderProxyValue = useCallback(
(selected: string) => {
if (!selected || !state.proxyData.records[selected]) return selected;
const delayValue = delayManager.getDelayFix(
state.proxyData.records[selected],
state.selection.group,
);
return (
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography noWrap>{selected}</Typography>
<Chip
size="small"
label={delayManager.formatDelay(delayValue)}
color={convertDelayColor(delayValue)}
/>
</Box>
);
},
[state.proxyData.records, state.selection.group],
);
// 计算要显示的代理选项 - 使用 useMemo 优化
const proxyOptions = useMemo(() => {
@ -285,160 +407,37 @@ export const CurrentProxyCard = () => {
if (isGlobalMode && state.proxyData.records) {
// 全局模式下的选项
return Object.keys(state.proxyData.records)
.filter(name => name !== "DIRECT" && name !== "REJECT")
.map(name => ({ name }));
.filter((name) => name !== "DIRECT" && name !== "REJECT")
.map((name) => ({ name }));
}
// 普通模式
const group = state.proxyData.groups.find(g => g.name === state.selection.group);
const group = state.proxyData.groups.find(
(g: { name: string }) => g.name === state.selection.group,
);
if (group) {
return group.all.map(name => ({ name }));
return group.all.map((name) => ({ name }));
}
return [];
}, [isDirectMode, isGlobalMode, state.proxyData, state.selection.group]);
// 使用防抖包装状态更新,避免快速连续更新
const debouncedSetState = useCallback(
debounce((updateFn: (prev: ProxyState) => ProxyState) => {
setState(updateFn);
}, 50),
[]
);
// 处理代理组变更
const handleGroupChange = useCallback((event: SelectChangeEvent) => {
if (isGlobalMode || isDirectMode) return;
const newGroup = event.target.value;
// 保存到本地存储
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
// 获取该组当前选中的代理
setState(prev => {
const group = prev.proxyData.groups.find(g => g.name === newGroup);
if (group) {
return {
...prev,
selection: {
group: newGroup,
proxy: group.now
},
displayProxy: prev.proxyData.records[group.now] || null
};
}
return {
...prev,
selection: {
...prev.selection,
group: newGroup
}
};
});
}, [isGlobalMode, isDirectMode]);
// 处理代理节点变更
const handleProxyChange = useCallback(async (event: SelectChangeEvent) => {
if (isDirectMode) return;
const newProxy = event.target.value;
const currentGroup = state.selection.group;
const previousProxy = state.selection.proxy;
// 立即更新UI优化体验
debouncedSetState((prev: ProxyState) => ({
...prev,
selection: {
...prev.selection,
proxy: newProxy
},
displayProxy: prev.proxyData.records[newProxy] || null
}));
// 非特殊模式下保存到本地存储
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
try {
// 更新代理设置
await updateProxy(currentGroup, newProxy);
// 自动关闭连接设置
if (verge?.auto_close_connection && previousProxy) {
getConnections().then(({ connections }) => {
connections.forEach(conn => {
if (conn.chains.includes(previousProxy)) {
deleteConnection(conn.id);
}
});
});
}
// 刷新代理信息,使用较短的延迟
setTimeout(() => {
refreshProxy();
fetchProxyData(true);
}, 200);
} catch (error) {
console.error("更新代理失败", error);
}
}, [isDirectMode, isGlobalMode, state.proxyData.records, state.selection, verge?.auto_close_connection, refreshProxy, fetchProxyData, debouncedSetState]);
// 导航到代理页面
const goToProxies = useCallback(() => {
navigate("/");
}, [navigate]);
// 获取要显示的代理节点
const proxyToDisplay = state.displayProxy || currentProxy;
// 获取当前节点的延迟
const currentDelay = proxyToDisplay
? delayManager.getDelayFix(proxyToDisplay, state.selection.group)
: -1;
// 获取信号图标
const signalInfo = getSignalIcon(currentDelay);
// 自定义渲染选择框中的值
const renderProxyValue = useCallback((selected: string) => {
if (!selected || !state.proxyData.records[selected]) return selected;
const delayValue = delayManager.getDelayFix(
state.proxyData.records[selected],
state.selection.group
);
return (
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography noWrap>{selected}</Typography>
<Chip
size="small"
label={delayManager.formatDelay(delayValue)}
color={convertDelayColor(delayValue)}
/>
</Box>
);
}, [state.proxyData.records, state.selection.group]);
return (
<EnhancedCard
title={t("Current Node")}
icon={
<Tooltip
title={
proxyToDisplay
currentProxy
? `${signalInfo.text}: ${delayManager.formatDelay(currentDelay)}`
: "无代理节点"
}
>
<Box sx={{ color: signalInfo.color }}>
{proxyToDisplay ? signalInfo.icon : <SignalNone color="disabled" />}
{currentProxy ? signalInfo.icon : <SignalNone color="disabled" />}
</Box>
</Tooltip>
}
iconColor={proxyToDisplay ? "primary" : undefined}
iconColor={currentProxy ? "primary" : undefined}
action={
<Button
variant="outlined"
@ -451,7 +450,7 @@ export const CurrentProxyCard = () => {
</Button>
}
>
{proxyToDisplay ? (
{currentProxy ? (
<Box>
{/* 代理节点信息显示 */}
<Box
@ -468,30 +467,56 @@ export const CurrentProxyCard = () => {
>
<Box>
<Typography variant="body1" fontWeight="medium">
{proxyToDisplay.name}
{currentProxy.name}
</Typography>
<Box sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}>
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
{proxyToDisplay.type}
<Box
sx={{ display: "flex", alignItems: "center", flexWrap: "wrap" }}
>
<Typography
variant="caption"
color="text.secondary"
sx={{ mr: 1 }}
>
{currentProxy.type}
</Typography>
{isGlobalMode && (
<Chip size="small" label={t("Global Mode")} color="primary" sx={{ mr: 0.5 }} />
<Chip
size="small"
label={t("Global Mode")}
color="primary"
sx={{ mr: 0.5 }}
/>
)}
{isDirectMode && (
<Chip size="small" label={t("Direct Mode")} color="success" sx={{ mr: 0.5 }} />
<Chip
size="small"
label={t("Direct Mode")}
color="success"
sx={{ mr: 0.5 }}
/>
)}
{/* 节点特性 */}
{proxyToDisplay.udp && <Chip size="small" label="UDP" variant="outlined" />}
{proxyToDisplay.tfo && <Chip size="small" label="TFO" variant="outlined" />}
{proxyToDisplay.xudp && <Chip size="small" label="XUDP" variant="outlined" />}
{proxyToDisplay.mptcp && <Chip size="small" label="MPTCP" variant="outlined" />}
{proxyToDisplay.smux && <Chip size="small" label="SMUX" variant="outlined" />}
{currentProxy.udp && (
<Chip size="small" label="UDP" variant="outlined" />
)}
{currentProxy.tfo && (
<Chip size="small" label="TFO" variant="outlined" />
)}
{currentProxy.xudp && (
<Chip size="small" label="XUDP" variant="outlined" />
)}
{currentProxy.mptcp && (
<Chip size="small" label="MPTCP" variant="outlined" />
)}
{currentProxy.smux && (
<Chip size="small" label="SMUX" variant="outlined" />
)}
</Box>
</Box>
{/* 显示延迟 */}
{proxyToDisplay && !isDirectMode && (
{currentProxy && !isDirectMode && (
<Chip
size="small"
label={delayManager.formatDelay(currentDelay)}
@ -500,7 +525,12 @@ export const CurrentProxyCard = () => {
)}
</Box>
{/* 代理组选择器 */}
<FormControl fullWidth variant="outlined" size="small" sx={{ mb: 1.5 }}>
<FormControl
fullWidth
variant="outlined"
size="small"
sx={{ mb: 1.5 }}
>
<InputLabel id="proxy-group-select-label">{t("Group")}</InputLabel>
<Select
labelId="proxy-group-select-label"
@ -535,39 +565,41 @@ export const CurrentProxyCard = () => {
},
}}
>
{proxyOptions.map((proxy) => {
const delayValue = delayManager.getDelayFix(
state.proxyData.records[proxy.name],
state.selection.group
);
return (
<MenuItem
key={proxy.name}
value={proxy.name}
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
pr: 1,
}}
>
<Typography noWrap sx={{ flex: 1, mr: 1 }}>
{proxy.name}
</Typography>
<Chip
size="small"
label={delayManager.formatDelay(delayValue)}
color={convertDelayColor(delayValue)}
sx={{
minWidth: "60px",
height: "22px",
flexShrink: 0,
}}
/>
</MenuItem>
);
})}
{isDirectMode
? null
: proxyOptions.map((proxy, index) => {
const delayValue = delayManager.getDelayFix(
state.proxyData.records[proxy.name],
state.selection.group,
);
return (
<MenuItem
key={`${proxy.name}-${index}`}
value={proxy.name}
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
pr: 1,
}}
>
<Typography noWrap sx={{ flex: 1, mr: 1 }}>
{proxy.name}
</Typography>
<Chip
size="small"
label={delayManager.formatDelay(delayValue)}
color={convertDelayColor(delayValue)}
sx={{
minWidth: "60px",
height: "22px",
flexShrink: 0,
}}
/>
</MenuItem>
);
})}
</Select>
</FormControl>
</Box>

View File

@ -31,6 +31,16 @@ export const EnhancedCard = ({
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
// 统一的标题截断样式
const titleTruncateStyle = {
minWidth: 0,
maxWidth: "100%",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
display: "block"
};
return (
<Box
sx={{
@ -52,7 +62,13 @@ export const EnhancedCard = ({
borderColor: "divider",
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Box sx={{
display: "flex",
alignItems: "center",
minWidth: 0,
flex: 1,
overflow: "hidden"
}}>
<Box
sx={{
display: "flex",
@ -62,21 +78,32 @@ export const EnhancedCard = ({
width: 38,
height: 38,
mr: 1.5,
flexShrink: 0,
backgroundColor: alpha(theme.palette[iconColor].main, 0.12),
color: theme.palette[iconColor].main,
}}
>
{icon}
</Box>
{typeof title === "string" ? (
<Typography variant="h6" fontWeight="medium" fontSize={18}>
{title}
</Typography>
) : (
title
)}
<Box sx={{ minWidth: 0, flex: 1 }}>
{typeof title === "string" ? (
<Typography
variant="h6"
fontWeight="medium"
fontSize={18}
sx={titleTruncateStyle}
title={title}
>
{title}
</Typography>
) : (
<Box sx={titleTruncateStyle}>
{title}
</Box>
)}
</Box>
</Box>
{action}
{action && <Box sx={{ ml: 2, flexShrink: 0 }}>{action}</Box>}
</Box>
<Box
sx={{

View File

@ -39,69 +39,35 @@ export interface EnhancedTrafficGraphRef {
// 时间范围类型
type TimeRange = 1 | 5 | 10; // 分钟
// 创建一个明确的类型
// 数据点类型
type DataPoint = ITrafficItem & { name: string; timestamp: number };
// 控制帧率的工具函数
const FPS_LIMIT = 1; // 限制为1fps因为数据每秒才更新一次
const FRAME_MIN_TIME = 1000 / FPS_LIMIT; // 每帧最小时间间隔即1000ms
// 全局存储流量数据历史记录
declare global {
interface Window {
trafficHistoryData?: DataPoint[];
trafficHistoryStyle?: "line" | "area";
trafficHistoryTimeRange?: TimeRange;
}
}
// 初始化全局存储
if (typeof window !== "undefined" && !window.trafficHistoryData) {
window.trafficHistoryData = [];
window.trafficHistoryStyle = "area";
window.trafficHistoryTimeRange = 10;
}
/**
*
* Recharts 线
*/
export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
(props, ref) => {
const theme = useTheme();
const { t } = useTranslation();
// 从全局变量恢复状态
const [timeRange, setTimeRange] = useState<TimeRange>(
window.trafficHistoryTimeRange || 10
);
const [chartStyle, setChartStyle] = useState<"line" | "area">(
window.trafficHistoryStyle || "area"
);
// 使用useRef存储数据避免不必要的重渲染
const dataBufferRef = useRef<DataPoint[]>([]);
// 只为渲染目的的状态
// 基础状态
const [timeRange, setTimeRange] = useState<TimeRange>(10);
const [chartStyle, setChartStyle] = useState<"line" | "area">("area");
const [displayData, setDisplayData] = useState<DataPoint[]>([]);
// 帧率控制
const lastUpdateTimeRef = useRef<number>(0);
const pendingUpdateRef = useRef<boolean>(false);
const rafIdRef = useRef<number | null>(null);
// 数据缓冲区
const dataBufferRef = useRef<DataPoint[]>([]);
// 根据时间范围计算保留的数据点数量
const getMaxPointsByTimeRange = useCallback(
(minutes: TimeRange): number => {
// 使用更低的采样率来减少点的数量每2秒一个点而不是每秒一个点
return minutes * 30; // 每分钟30个点(每2秒1个点)
},
[],
(minutes: TimeRange): number => minutes * 60,
[]
);
// 最大数据点数量 - 基于选择的时间范围
// 最大数据点数量
const MAX_BUFFER_SIZE = useMemo(
() => getMaxPointsByTimeRange(10),
[getMaxPointsByTimeRange],
[getMaxPointsByTimeRange]
);
// 颜色配置
@ -113,149 +79,50 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
tooltip: theme.palette.background.paper,
text: theme.palette.text.primary,
}),
[theme],
[theme]
);
// 切换时间范围
const handleTimeRangeClick = useCallback(() => {
setTimeRange((prevRange) => {
// 在1、5、10分钟之间循环切换
const newRange = prevRange === 1 ? 5 : prevRange === 5 ? 10 : 1;
window.trafficHistoryTimeRange = newRange; // 保存到全局
return newRange;
return prevRange === 1 ? 5 : prevRange === 5 ? 10 : 1;
});
}, []);
// 初始化数据缓冲区
useEffect(() => {
let initialBuffer: DataPoint[] = [];
// 创建初始空数据
const now = Date.now();
const tenMinutesAgo = now - 10 * 60 * 1000;
// 如果全局有保存的数据,优先使用
if (window.trafficHistoryData && window.trafficHistoryData.length > 0) {
initialBuffer = [...window.trafficHistoryData];
const initialBuffer = Array.from(
{ length: MAX_BUFFER_SIZE },
(_, index) => {
const pointTime =
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
const date = new Date(pointTime);
// 确保数据长度符合要求
if (initialBuffer.length > MAX_BUFFER_SIZE) {
initialBuffer = initialBuffer.slice(-MAX_BUFFER_SIZE);
} else if (initialBuffer.length < MAX_BUFFER_SIZE) {
// 如果历史数据不足,则在前面补充空数据
const now = Date.now();
const oldestTimestamp = initialBuffer.length > 0
? initialBuffer[0].timestamp
: now - 10 * 60 * 1000;
const additionalPoints = MAX_BUFFER_SIZE - initialBuffer.length;
const timeInterval = initialBuffer.length > 0
? (initialBuffer[0].timestamp - (now - 10 * 60 * 1000)) / additionalPoints
: (10 * 60 * 1000) / MAX_BUFFER_SIZE;
const emptyPrefix: DataPoint[] = Array.from(
{ length: additionalPoints },
(_, index) => {
const pointTime = oldestTimestamp - (additionalPoints - index) * timeInterval;
const date = new Date(pointTime);
return {
up: 0,
down: 0,
timestamp: pointTime,
name: date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
}
);
initialBuffer = [...emptyPrefix, ...initialBuffer];
return {
up: 0,
down: 0,
timestamp: pointTime,
name: date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
}
} else {
// 没有历史数据时,创建空的初始缓冲区
const now = Date.now();
const tenMinutesAgo = now - 10 * 60 * 1000;
initialBuffer = Array.from(
{ length: MAX_BUFFER_SIZE },
(_, index) => {
const pointTime =
tenMinutesAgo + index * ((10 * 60 * 1000) / MAX_BUFFER_SIZE);
const date = new Date(pointTime);
return {
up: 0,
down: 0,
timestamp: pointTime,
name: date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
};
}
);
}
);
dataBufferRef.current = initialBuffer;
window.trafficHistoryData = initialBuffer; // 保存到全局
// 更新显示数据
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(initialBuffer.slice(-pointsToShow));
// 清理函数,取消任何未完成的动画帧
return () => {
if (rafIdRef.current !== null) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
};
}, [MAX_BUFFER_SIZE, getMaxPointsByTimeRange, timeRange]);
// 处理数据更新并控制帧率的函数
const updateDisplayData = useCallback(() => {
if (pendingUpdateRef.current) {
pendingUpdateRef.current = false;
// 根据当前时间范围计算需要显示的点数
const pointsToShow = getMaxPointsByTimeRange(timeRange);
// 从缓冲区中获取最新的数据点
const newDisplayData = dataBufferRef.current.slice(-pointsToShow);
setDisplayData(newDisplayData);
}
rafIdRef.current = null;
}, [timeRange, getMaxPointsByTimeRange]);
// 节流更新函数
const throttledUpdateData = useCallback(() => {
pendingUpdateRef.current = true;
const now = performance.now();
const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
if (rafIdRef.current === null) {
if (timeSinceLastUpdate >= FRAME_MIN_TIME) {
// 如果距离上次更新已经超过最小帧时间,立即更新
lastUpdateTimeRef.current = now;
rafIdRef.current = requestAnimationFrame(updateDisplayData);
} else {
// 否则,在适当的时间进行更新
const timeToWait = FRAME_MIN_TIME - timeSinceLastUpdate;
setTimeout(() => {
lastUpdateTimeRef.current = performance.now();
rafIdRef.current = requestAnimationFrame(updateDisplayData);
}, timeToWait);
}
}
}, [updateDisplayData]);
// 监听时间范围变化,更新显示数据
useEffect(() => {
throttledUpdateData();
}, [timeRange, throttledUpdateData]);
}, [MAX_BUFFER_SIZE, getMaxPointsByTimeRange]);
// 添加数据点方法
const appendData = useCallback((data: ITrafficItem) => {
@ -281,24 +148,24 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
timestamp: timestamp,
};
// 更新ref保持原数组大小
// 更新缓冲区,保持原数组大小
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
dataBufferRef.current = newBuffer;
// 保存到全局变量
window.trafficHistoryData = newBuffer;
// 更新显示数据
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(newBuffer.slice(-pointsToShow));
}, [timeRange, getMaxPointsByTimeRange]);
// 使用节流更新显示数据
throttledUpdateData();
}, [throttledUpdateData]);
// 监听时间范围变化,更新显示数据
useEffect(() => {
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(dataBufferRef.current.slice(-pointsToShow));
}, [timeRange, getMaxPointsByTimeRange]);
// 切换图表样式
const toggleStyle = useCallback(() => {
setChartStyle((prev) => {
const newStyle = prev === "line" ? "area" : "line";
window.trafficHistoryStyle = newStyle; // 保存到全局
return newStyle;
});
setChartStyle((prev) => prev === "line" ? "area" : "line");
}, []);
// 暴露方法给父组件
@ -308,13 +175,12 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
appendData,
toggleStyle,
}),
[appendData, toggleStyle],
[appendData, toggleStyle]
);
// 格式化工具提示内容
const formatTooltip = useCallback((value: number, name: string, props: any) => {
const [num, unit] = parseTraffic(value);
// 使用props.dataKey判断是上传还是下载
return [`${num} ${unit}/s`, props?.dataKey === "up" ? t("Upload") : t("Download")];
}, [t]);
@ -327,7 +193,6 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
// 格式化X轴标签
const formatXLabel = useCallback((value: string) => {
if (!value) return "";
// 只显示小时和分钟
const parts = value.split(":");
return `${parts[0]}:${parts[1]}`;
}, []);
@ -340,7 +205,7 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
// 共享图表配置
const chartConfig = useMemo(() => ({
data: displayData,
margin: { top: 10, right: 20, left: 0, bottom: 0 },
margin: { top: 20, right: 10, left: 0, bottom: -10 },
}), [displayData]);
// 共享的线条/区域配置
@ -352,7 +217,7 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
isAnimationActive: false, // 禁用动画以减少CPU使用
}), []);
// 曲线类型 - 使用线性曲线避免错位
// 曲线类型
const curveType = "monotone";
return (
@ -363,186 +228,138 @@ export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
position: "relative",
bgcolor: "action.hover",
borderRadius: 1,
padding: 1,
cursor: "pointer",
}}
onClick={toggleStyle}
>
<ResponsiveContainer width="100%" height="100%">
{chartStyle === "line" ? (
<LineChart {...chartConfig}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={colors.grid} opacity={0.3} />
<XAxis
dataKey="name"
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
interval="preserveStart"
tickFormatter={formatXLabel}
minTickGap={30}
/>
<YAxis
tickFormatter={formatYAxis}
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
width={40}
domain={[0, "auto"]}
/>
<Tooltip
formatter={formatTooltip}
labelFormatter={(label) => `${t("Time")}: ${label}`}
contentStyle={{
backgroundColor: colors.tooltip,
borderColor: colors.grid,
borderRadius: 4,
}}
itemStyle={{ color: colors.text }}
isAnimationActive={false}
/>
<Line
type={curveType}
{...commonLineProps}
dataKey="up"
name={t("Upload")}
stroke={colors.up}
/>
<Line
type={curveType}
{...commonLineProps}
dataKey="down"
name={t("Download")}
stroke={colors.down}
/>
{/* 根据chartStyle动态选择图表类型 */}
{(() => {
// 创建共享的图表组件
const commonChartComponents = (
<>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={colors.grid} opacity={0.3} />
<XAxis
dataKey="name"
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
interval="preserveStart"
tickFormatter={formatXLabel}
minTickGap={30}
/>
<YAxis
tickFormatter={formatYAxis}
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
width={44}
domain={[0, "auto"]}
padding={{ top: 8, bottom: 0 }}
/>
<Tooltip
formatter={formatTooltip}
labelFormatter={(label) => `${t("Time")}: ${label}`}
contentStyle={{
backgroundColor: colors.tooltip,
borderColor: colors.grid,
borderRadius: 4,
}}
itemStyle={{ color: colors.text }}
isAnimationActive={false}
/>
{/* 可点击的时间范围标签 */}
<text
x="1%"
y="6%"
textAnchor="start"
fill={theme.palette.text.secondary}
fontSize={11}
fontWeight="bold"
onClick={handleTimeRangeClick}
style={{ cursor: "pointer" }}
>
{getTimeRangeText()}
</text>
{/* 可点击的时间范围标签 */}
<text
x="1%"
y="11%"
textAnchor="start"
fill={theme.palette.text.secondary}
fontSize={11}
fontWeight="bold"
onClick={handleTimeRangeClick}
style={{ cursor: "pointer" }}
>
{getTimeRangeText()}
</text>
{/* 上传标签 - 右上角 */}
<text
x="98%"
y="7%"
textAnchor="end"
fill={colors.up}
fontSize={12}
fontWeight="bold"
>
{t("Upload")}
</text>
{/* 上传标签 - 右上角 */}
<text
x="99%"
y="11%"
textAnchor="end"
fill={colors.up}
fontSize={12}
fontWeight="bold"
onClick={toggleStyle}
style={{ cursor: "pointer" }}
>
{t("Upload")}
</text>
{/* 下载标签 - 右上角下方 */}
<text
x="98%"
y="16%"
textAnchor="end"
fill={colors.down}
fontSize={12}
fontWeight="bold"
>
{t("Download")}
</text>
</LineChart>
) : (
<AreaChart {...chartConfig}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={colors.grid} opacity={0.3} />
<XAxis
dataKey="name"
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
interval="preserveStart"
tickFormatter={formatXLabel}
minTickGap={30}
/>
<YAxis
tickFormatter={formatYAxis}
tick={{ fontSize: 10, fill: colors.text }}
tickLine={{ stroke: colors.grid }}
axisLine={{ stroke: colors.grid }}
width={40}
domain={[0, "auto"]}
padding={{ top: 10, bottom: 0 }}
/>
<Tooltip
formatter={formatTooltip}
labelFormatter={(label) => `${t("Time")}: ${label}`}
contentStyle={{
backgroundColor: colors.tooltip,
borderColor: colors.grid,
borderRadius: 4,
}}
itemStyle={{ color: colors.text }}
isAnimationActive={false}
/>
<Area
type={curveType}
{...commonLineProps}
dataKey="up"
name={t("Upload")}
stroke={colors.up}
fill={colors.up}
fillOpacity={0.2}
/>
<Area
type={curveType}
{...commonLineProps}
dataKey="down"
name={t("Download")}
stroke={colors.down}
fill={colors.down}
fillOpacity={0.3}
/>
{/* 下载标签 - 右上角下方 */}
<text
x="99%"
y="19%"
textAnchor="end"
fill={colors.down}
fontSize={12}
fontWeight="bold"
onClick={toggleStyle}
style={{ cursor: "pointer" }}
>
{t("Download")}
</text>
</>
);
{/* 可点击的时间范围标签 */}
<text
x="1%"
y="6%"
textAnchor="start"
fill={theme.palette.text.secondary}
fontSize={11}
fontWeight="bold"
onClick={handleTimeRangeClick}
style={{ cursor: "pointer" }}
>
{getTimeRangeText()}
</text>
{/* 上传标签 - 右上角 */}
<text
x="98%"
y="7%"
textAnchor="end"
fill={colors.up}
fontSize={12}
fontWeight="bold"
>
{t("Upload")}
</text>
{/* 下载标签 - 右上角下方 */}
<text
x="98%"
y="16%"
textAnchor="end"
fill={colors.down}
fontSize={12}
fontWeight="bold"
>
{t("Download")}
</text>
</AreaChart>
)}
// 根据chartStyle返回相应的图表类型
if (chartStyle === "line") {
return (
<LineChart {...chartConfig}>
{commonChartComponents}
<Line
type={curveType}
{...commonLineProps}
dataKey="up"
name={t("Upload")}
stroke={colors.up}
/>
<Line
type={curveType}
{...commonLineProps}
dataKey="down"
name={t("Download")}
stroke={colors.down}
/>
</LineChart>
);
} else {
return (
<AreaChart {...chartConfig}>
{commonChartComponents}
<Area
type={curveType}
{...commonLineProps}
dataKey="up"
name={t("Upload")}
stroke={colors.up}
fill={colors.up}
fillOpacity={0.2}
/>
<Area
type={curveType}
{...commonLineProps}
dataKey="down"
name={t("Download")}
stroke={colors.down}
fill={colors.down}
fillOpacity={0.3}
/>
</AreaChart>
);
}
})()}
</ResponsiveContainer>
</Box>
);

View File

@ -28,6 +28,7 @@ import { createAuthSockette } from "@/utils/websocket";
import parseTraffic from "@/utils/parse-traffic";
import { getConnections, isDebugEnabled, gc } from "@/services/api";
import { ReactNode } from "react";
import { useAppData } from "@/providers/app-data-provider";
interface MemoryUsage {
inuse: number;
@ -157,11 +158,13 @@ export const EnhancedTrafficStats = () => {
const pageVisible = useVisibility();
const [isDebug, setIsDebug] = useState(false);
// 使用AppDataProvider
const { connections, uptime } = useAppData();
// 使用单一状态对象减少状态更新次数
const [stats, setStats] = useState({
traffic: { up: 0, down: 0 },
memory: { inuse: 0, oslimit: undefined as number | undefined },
connections: { uploadTotal: 0, downloadTotal: 0, activeConnections: 0 },
});
// 创建一个标记来追踪最后更新时间,用于节流
@ -176,36 +179,6 @@ export const EnhancedTrafficStats = () => {
memory: null as ReturnType<typeof createAuthSockette> | null,
});
// 获取连接数据
const fetchConnections = useCallback(async () => {
if (!pageVisible) return;
try {
const connections = await getConnections();
if (connections) {
setStats(prev => ({
...prev,
connections: {
uploadTotal: connections.uploadTotal || 0,
downloadTotal: connections.downloadTotal || 0,
activeConnections: connections.connections ? connections.connections.length : 0,
}
}));
}
} catch (err) {
console.error("Failed to fetch connections:", err);
}
}, [pageVisible]);
// 定期更新连接数据
useEffect(() => {
if (!pageVisible) return;
fetchConnections();
const intervalId = setInterval(fetchConnections, CONNECTIONS_UPDATE_INTERVAL);
return () => clearInterval(intervalId);
}, [pageVisible, fetchConnections]);
// 检查是否支持调试
useEffect(() => {
isDebugEnabled().then((flag) => setIsDebug(flag));
@ -328,14 +301,14 @@ export const EnhancedTrafficStats = () => {
const [up, upUnit] = parseTraffic(stats.traffic.up);
const [down, downUnit] = parseTraffic(stats.traffic.down);
const [inuse, inuseUnit] = parseTraffic(stats.memory.inuse);
const [uploadTotal, uploadTotalUnit] = parseTraffic(stats.connections.uploadTotal);
const [downloadTotal, downloadTotalUnit] = parseTraffic(stats.connections.downloadTotal);
const [uploadTotal, uploadTotalUnit] = parseTraffic(connections.uploadTotal);
const [downloadTotal, downloadTotalUnit] = parseTraffic(connections.downloadTotal);
return {
up, upUnit, down, downUnit, inuse, inuseUnit,
uploadTotal, uploadTotalUnit, downloadTotal, downloadTotalUnit
};
}, [stats]);
}, [stats, connections.uploadTotal, connections.downloadTotal]);
// 渲染流量图表 - 使用useMemo缓存渲染结果
const trafficGraphComponent = useMemo(() => {
@ -398,7 +371,7 @@ export const EnhancedTrafficStats = () => {
{
icon: <LinkRounded fontSize="small" />,
title: t("Active Connections"),
value: stats.connections.activeConnections,
value: connections.count,
unit: "",
color: "success" as const,
},
@ -424,14 +397,16 @@ export const EnhancedTrafficStats = () => {
color: "error" as const,
onClick: isDebug ? handleGarbageCollection : undefined,
},
], [t, parsedData, stats.connections.activeConnections, isDebug, handleGarbageCollection]);
], [t, parsedData, connections.count, isDebug, handleGarbageCollection]);
return (
<Grid container spacing={1} columns={{ xs: 8, sm: 8, md: 12 }}>
<Grid size={12}>
{/* 流量图表区域 */}
{trafficGraphComponent}
</Grid>
{trafficGraph && (
<Grid size={12}>
{/* 流量图表区域 */}
{trafficGraphComponent}
</Grid>
)}
{/* 统计卡片区域 */}
{statCards.map((card, index) => (
<Grid key={index} size={4}>

View File

@ -27,6 +27,7 @@ import { openWebUrl, updateProfile } from "@/services/cmds";
import { useLockFn } from "ahooks";
import { Notice } from "@/components/base";
import { EnhancedCard } from "./enhanced-card";
import { useAppData } from "@/providers/app-data-provider";
// 定义旋转动画
const round = keyframes`
@ -35,19 +36,10 @@ const round = keyframes`
`;
// 辅助函数解析URL和过期时间
const parseUrl = (url?: string, maxLength: number = 25) => {
const parseUrl = (url?: string) => {
if (!url) return "-";
let parsedUrl = "";
if (url.startsWith("http")) {
parsedUrl = new URL(url).host;
} else {
parsedUrl = "local";
}
if (parsedUrl.length > maxLength) {
return parsedUrl.substring(0, maxLength - 3) + "...";
}
return parsedUrl;
if (url.startsWith("http")) return new URL(url).host;
return "local";
};
const parseExpire = (expire?: number) => {
@ -81,6 +73,14 @@ export interface HomeProfileCardProps {
onProfileUpdated?: () => void;
}
// 添加一个通用的截断样式
const truncateStyle = {
maxWidth: "calc(100% - 28px)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
};
// 提取独立组件减少主组件复杂度
const ProfileDetails = ({ current, onUpdateProfile, updating }: {
current: ProfileItem;
@ -109,8 +109,8 @@ const ProfileDetails = ({ current, onUpdateProfile, updating }: {
{current.url && (
<Stack direction="row" alignItems="center" spacing={1}>
<DnsOutlined fontSize="small" color="action" />
<Typography variant="body2" color="text.secondary">
{t("From")}:{" "}
<Typography variant="body2" color="text.secondary" noWrap sx={{ display: "flex", alignItems: "center" }}>
<span style={{ flexShrink: 0 }}>{t("From")}: </span>
{current.home ? (
<Link
component="button"
@ -118,22 +118,46 @@ const ProfileDetails = ({ current, onUpdateProfile, updating }: {
onClick={() => current.home && openWebUrl(current.home)}
sx={{
display: "inline-flex",
alignItems: "center"
alignItems: "center",
minWidth: 0,
maxWidth: "calc(100% - 40px)",
ml: 0.5
}}
title={parseUrl(current.url)}
>
{parseUrl(current.url)}
<Typography
component="span"
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
minWidth: 0,
flex: 1
}}
>
{parseUrl(current.url)}
</Typography>
<LaunchOutlined
fontSize="inherit"
sx={{ ml: 0.5, fontSize: "0.8rem", opacity: 0.7, flexShrink: 0 }}
/>
</Link>
) : (
<Box
<Typography
component="span"
fontWeight="medium"
sx={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
minWidth: 0,
flex: 1,
ml: 0.5
}}
title={parseUrl(current.url)}
>
{parseUrl(current.url)}
</Box>
</Typography>
)}
</Typography>
</Stack>
@ -247,6 +271,7 @@ const EmptyProfile = ({ onClick }: { onClick: () => void }) => {
export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { refreshAll } = useAppData();
// 更新当前订阅
const [updating, setUpdating] = useState(false);
@ -259,6 +284,9 @@ export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardPr
await updateProfile(current.uid);
Notice.success(t("Update subscription successfully"));
onProfileUpdated?.();
// 刷新首页数据
refreshAll();
} catch (err: any) {
Notice.error(err?.message || err.toString());
} finally {
@ -285,16 +313,30 @@ export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardPr
fontSize={18}
onClick={() => current.home && openWebUrl(current.home)}
sx={{
display: "inline-flex",
alignItems: "center",
color: "inherit",
textDecoration: "none",
display: "flex",
alignItems: "center",
minWidth: 0,
maxWidth: "100%",
"& > span": {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flex: 1
}
}}
title={current.name}
>
{current.name}
<span>{current.name}</span>
<LaunchOutlined
fontSize="inherit"
sx={{ ml: 0.5, fontSize: "0.8rem", opacity: 0.7 }}
sx={{
ml: 0.5,
fontSize: "0.8rem",
opacity: 0.7,
flexShrink: 0
}}
/>
</Link>
);

View File

@ -129,7 +129,7 @@ export const IpInfoCard = () => {
}
>
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<Skeleton variant="text" width="60%" height={32} />
<Skeleton variant="text" width="60%" height={30} />
<Skeleton variant="text" width="80%" height={24} />
<Skeleton variant="text" width="70%" height={24} />
<Skeleton variant="text" width="50%" height={24} />

View File

@ -18,12 +18,8 @@ import {
HelpOutlineRounded,
SvgIconComponent,
} from "@mui/icons-material";
import useSWR from "swr";
import {
getSystemProxy,
getAutotemProxy,
getRunningMode,
} from "@/services/cmds";
import { useVerge } from "@/hooks/use-verge";
import { useSystemState } from "@/hooks/use-system-state";
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
@ -36,71 +32,64 @@ interface TabButtonProps {
}
// 抽取Tab组件以减少重复代码
const TabButton: FC<TabButtonProps> = memo(({
isActive,
onClick,
icon: Icon,
label,
hasIndicator = false
}) => (
<Paper
elevation={isActive ? 2 : 0}
onClick={onClick}
sx={{
cursor: "pointer",
px: 2,
py: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 1,
bgcolor: isActive ? "primary.main" : "background.paper",
color: isActive ? "primary.contrastText" : "text.primary",
borderRadius: 1.5,
flex: 1,
maxWidth: 160,
transition: "all 0.2s ease-in-out",
position: "relative",
"&:hover": {
transform: "translateY(-1px)",
boxShadow: 1,
},
"&:after": isActive
? {
content: '""',
position: "absolute",
bottom: -9,
left: "50%",
width: 2,
height: 9,
bgcolor: "primary.main",
transform: "translateX(-50%)",
}
: {},
}}
>
<Icon fontSize="small" />
<Typography
variant="body2"
sx={{ fontWeight: isActive ? 600 : 400 }}
const TabButton: FC<TabButtonProps> = memo(
({ isActive, onClick, icon: Icon, label, hasIndicator = false }) => (
<Paper
elevation={isActive ? 2 : 0}
onClick={onClick}
sx={{
cursor: "pointer",
px: 2,
py: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 1,
bgcolor: isActive ? "primary.main" : "background.paper",
color: isActive ? "primary.contrastText" : "text.primary",
borderRadius: 1.5,
flex: 1,
maxWidth: 160,
transition: "all 0.2s ease-in-out",
position: "relative",
"&:hover": {
transform: "translateY(-1px)",
boxShadow: 1,
},
"&:after": isActive
? {
content: '""',
position: "absolute",
bottom: -9,
left: "50%",
width: 2,
height: 9,
bgcolor: "primary.main",
transform: "translateX(-50%)",
}
: {},
}}
>
{label}
</Typography>
{hasIndicator && (
<Box
sx={{
width: 8,
height: 8,
borderRadius: "50%",
bgcolor: isActive ? "#fff" : "success.main",
position: "absolute",
top: 8,
right: 8,
}}
/>
)}
</Paper>
));
<Icon fontSize="small" />
<Typography variant="body2" sx={{ fontWeight: isActive ? 600 : 400 }}>
{label}
</Typography>
{hasIndicator && (
<Box
sx={{
width: 8,
height: 8,
borderRadius: "50%",
bgcolor: isActive ? "#fff" : "success.main",
position: "absolute",
top: 8,
right: 8,
}}
/>
)}
</Paper>
),
);
interface TabDescriptionProps {
description: string;
@ -108,52 +97,57 @@ interface TabDescriptionProps {
}
// 抽取描述文本组件
const TabDescription: FC<TabDescriptionProps> = memo(({ description, tooltipTitle }) => (
<Fade in={true} timeout={200}>
<Typography
variant="caption"
component="div"
sx={{
width: "95%",
textAlign: "center",
color: "text.secondary",
p: 0.8,
borderRadius: 1,
borderColor: "primary.main",
borderWidth: 1,
borderStyle: "solid",
backgroundColor: "background.paper",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 0.5,
wordBreak: "break-word",
hyphens: "auto",
}}
>
{description}
<Tooltip title={tooltipTitle}>
<HelpOutlineRounded
sx={{ fontSize: 14, opacity: 0.7, flexShrink: 0 }}
/>
</Tooltip>
</Typography>
</Fade>
));
const TabDescription: FC<TabDescriptionProps> = memo(
({ description, tooltipTitle }) => (
<Fade in={true} timeout={200}>
<Typography
variant="caption"
component="div"
sx={{
width: "95%",
textAlign: "center",
color: "text.secondary",
p: 0.8,
borderRadius: 1,
borderColor: "primary.main",
borderWidth: 1,
borderStyle: "solid",
backgroundColor: "background.paper",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 0.5,
wordBreak: "break-word",
hyphens: "auto",
}}
>
{description}
<Tooltip title={tooltipTitle}>
<HelpOutlineRounded
sx={{ fontSize: 14, opacity: 0.7, flexShrink: 0 }}
/>
</Tooltip>
</Typography>
</Fade>
),
);
export const ProxyTunCard: FC = () => {
const { t } = useTranslation();
const theme = useTheme();
const [activeTab, setActiveTab] = useState<string>(() =>
localStorage.getItem(LOCAL_STORAGE_TAB_KEY) || "system"
const [activeTab, setActiveTab] = useState<string>(
() => localStorage.getItem(LOCAL_STORAGE_TAB_KEY) || "system",
);
// 获取代理状态信息
const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy);
const { data: runningMode } = useSWR("getRunningMode", getRunningMode);
const { verge } = useVerge();
const { isSidecarMode, isAdminMode } = useSystemState();
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "sidecar";
// 从verge配置中获取开关状态
const { enable_system_proxy, enable_tun_mode } = verge ?? {};
// 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用
const isTunAvailable = !isSidecarMode || isAdminMode;
// 处理错误
const handleError = (err: Error) => {
@ -170,20 +164,22 @@ export const ProxyTunCard: FC = () => {
const tabDescription = useMemo(() => {
if (activeTab === "system") {
return {
text: sysproxy?.enable
text: enable_system_proxy
? t("System Proxy Enabled")
: t("System Proxy Disabled"),
tooltip: t("System Proxy Info")
tooltip: t("System Proxy Info"),
};
} else {
return {
text: isSidecarMode
text: !isTunAvailable
? t("TUN Mode Service Required")
: t("TUN Mode Intercept Info"),
tooltip: t("Tun Mode Info")
: enable_tun_mode
? t("TUN Mode Enabled")
: t("TUN Mode Disabled"),
tooltip: t("TUN Mode Intercept Info"),
};
}
}, [activeTab, sysproxy?.enable, isSidecarMode, t]);
}, [activeTab, enable_system_proxy, enable_tun_mode, isTunAvailable, t]);
return (
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
@ -203,13 +199,14 @@ export const ProxyTunCard: FC = () => {
onClick={() => handleTabChange("system")}
icon={ComputerRounded}
label={t("System Proxy")}
hasIndicator={sysproxy?.enable}
hasIndicator={enable_system_proxy}
/>
<TabButton
isActive={activeTab === "tun"}
onClick={() => handleTabChange("tun")}
icon={TroubleshootRounded}
label={t("Tun Mode")}
hasIndicator={enable_tun_mode && isTunAvailable}
/>
</Stack>

View File

@ -1,21 +1,30 @@
import { useTranslation } from "react-i18next";
import { Typography, Stack, Divider, Chip, IconButton } from "@mui/material";
import { InfoOutlined, SettingsOutlined } from "@mui/icons-material";
import { Typography, Stack, Divider, Chip, IconButton, Tooltip } from "@mui/material";
import {
InfoOutlined,
SettingsOutlined,
WarningOutlined,
AdminPanelSettingsOutlined,
DnsOutlined,
ExtensionOutlined
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { EnhancedCard } from "./enhanced-card";
import useSWR from "swr";
import { getRunningMode, getSystemInfo, installService } from "@/services/cmds";
import { getSystemInfo, installService } from "@/services/cmds";
import { useNavigate } from "react-router-dom";
import { version as appVersion } from "@root/package.json";
import { useCallback, useEffect, useMemo, useState } from "react";
import { check as checkUpdate } from "@tauri-apps/plugin-updater";
import { useLockFn } from "ahooks";
import { Notice } from "@/components/base";
import { useSystemState } from "@/hooks/use-system-state";
export const SystemInfoCard = () => {
const { t } = useTranslation();
const { verge, patchVerge } = useVerge();
const navigate = useNavigate();
const { isAdminMode, isSidecarMode, mutateRunningMode } = useSystemState();
// 系统信息状态
const [systemState, setSystemState] = useState({
@ -23,16 +32,6 @@ export const SystemInfoCard = () => {
lastCheckUpdate: "-",
});
// 获取运行模式
const { data: runningMode = "sidecar", mutate: mutateRunningMode } = useSWR(
"getRunningMode",
getRunningMode,
{ suspense: false, revalidateOnFocus: false }
);
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "sidecar";
// 初始化系统信息
useEffect(() => {
// 获取系统信息
@ -42,7 +41,10 @@ export const SystemInfoCard = () => {
if (lines.length > 0) {
const sysName = lines[0].split(": ")[1] || "";
const sysVersion = lines[1].split(": ")[1] || "";
setSystemState(prev => ({ ...prev, osInfo: `${sysName} ${sysVersion}` }));
setSystemState((prev) => ({
...prev,
osInfo: `${sysName} ${sysVersion}`,
}));
}
})
.catch(console.error);
@ -53,9 +55,9 @@ export const SystemInfoCard = () => {
try {
const timestamp = parseInt(lastCheck, 10);
if (!isNaN(timestamp)) {
setSystemState(prev => ({
setSystemState((prev) => ({
...prev,
lastCheckUpdate: new Date(timestamp).toLocaleString()
lastCheckUpdate: new Date(timestamp).toLocaleString(),
}));
}
} catch (e) {
@ -65,9 +67,9 @@ export const SystemInfoCard = () => {
// 如果启用了自动检查更新但没有记录,设置当前时间并延迟检查
const now = Date.now();
localStorage.setItem("last_check_update", now.toString());
setSystemState(prev => ({
setSystemState((prev) => ({
...prev,
lastCheckUpdate: new Date(now).toLocaleString()
lastCheckUpdate: new Date(now).toLocaleString(),
}));
setTimeout(() => {
@ -84,9 +86,9 @@ export const SystemInfoCard = () => {
async () => {
const now = Date.now();
localStorage.setItem("last_check_update", now.toString());
setSystemState(prev => ({
setSystemState((prev) => ({
...prev,
lastCheckUpdate: new Date(now).toLocaleString()
lastCheckUpdate: new Date(now).toLocaleString(),
}));
return await checkUpdate();
},
@ -94,7 +96,7 @@ export const SystemInfoCard = () => {
revalidateOnFocus: false,
refreshInterval: 24 * 60 * 60 * 1000, // 每天检查一次
dedupingInterval: 60 * 60 * 1000, // 1小时内不重复检查
}
},
);
// 导航到设置页面
@ -104,13 +106,13 @@ export const SystemInfoCard = () => {
// 切换自启动状态
const toggleAutoLaunch = useCallback(async () => {
if (!verge) return;
if (!verge || isAdminMode) return;
try {
await patchVerge({ enable_auto_launch: !verge.enable_auto_launch });
} catch (err) {
console.error("切换开机自启动状态失败:", err);
}
}, [verge, patchVerge]);
}, [verge, patchVerge, isAdminMode]);
// 安装系统服务
const onInstallService = useLockFn(async () => {
@ -118,18 +120,20 @@ export const SystemInfoCard = () => {
Notice.info(t("Installing Service..."), 1000);
await installService();
Notice.success(t("Service Installed Successfully"), 2000);
await mutateRunningMode();
await mutateRunningMode();
} catch (err: any) {
Notice.error(err.message || err.toString(), 3000);
}
});
// 点击运行模式处理
// 点击运行模式处理,Sidecar或纯管理员模式允许安装服务
const handleRunningModeClick = useCallback(() => {
if (isSidecarMode) {
if (isSidecarMode || (isAdminMode && isSidecarMode)) {
onInstallService();
}
}, [isSidecarMode, onInstallService]);
}, [isSidecarMode, isAdminMode, onInstallService]);
// 检查更新
const onCheckUpdate = useLockFn(async () => {
@ -147,16 +151,82 @@ export const SystemInfoCard = () => {
});
// 是否启用自启动
const autoLaunchEnabled = useMemo(() => verge?.enable_auto_launch || false, [verge]);
const autoLaunchEnabled = useMemo(
() => verge?.enable_auto_launch || false,
[verge],
);
// 运行模式样式
const runningModeStyle = useMemo(() => ({
cursor: isSidecarMode ? "pointer" : "default",
textDecoration: isSidecarMode ? "underline" : "none",
"&:hover": {
opacity: isSidecarMode ? 0.7 : 1,
},
}), [isSidecarMode]);
const runningModeStyle = useMemo(
() => ({
// Sidecar或纯管理员模式允许安装服务
cursor: (isSidecarMode || (isAdminMode && isSidecarMode)) ? "pointer" : "default",
textDecoration: (isSidecarMode || (isAdminMode && isSidecarMode)) ? "underline" : "none",
display: "flex",
alignItems: "center",
gap: 0.5,
"&:hover": {
opacity: (isSidecarMode || (isAdminMode && isSidecarMode)) ? 0.7 : 1,
},
}),
[isSidecarMode, isAdminMode],
);
// 获取模式图标和文本
const getModeIcon = () => {
if (isAdminMode) {
// 判断是否为组合模式(管理员+服务)
if (!isSidecarMode) {
return (
<>
<AdminPanelSettingsOutlined
sx={{ color: "primary.main", fontSize: 16 }}
titleAccess={t("Administrator Mode")}
/>
<DnsOutlined
sx={{ color: "success.main", fontSize: 16, ml: 0.5 }}
titleAccess={t("Service Mode")}
/>
</>
);
}
return (
<AdminPanelSettingsOutlined
sx={{ color: "primary.main", fontSize: 16 }}
titleAccess={t("Administrator Mode")}
/>
);
} else if (isSidecarMode) {
return (
<ExtensionOutlined
sx={{ color: "info.main", fontSize: 16 }}
titleAccess={t("Sidecar Mode")}
/>
);
} else {
return (
<DnsOutlined
sx={{ color: "success.main", fontSize: 16 }}
titleAccess={t("Service Mode")}
/>
);
}
};
// 获取模式文本
const getModeText = () => {
if (isAdminMode) {
// 判断是否同时处于服务模式
if (!isSidecarMode) {
return t("Administrator + Service Mode");
}
return t("Administrator Mode");
} else if (isSidecarMode) {
return t("Sidecar Mode");
} else {
return t("Service Mode");
}
};
// 只有当verge存在时才渲染内容
if (!verge) return null;
@ -182,21 +252,29 @@ export const SystemInfoCard = () => {
</Typography>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="body2" color="text.secondary">
{t("Auto Launch")}
</Typography>
<Chip
size="small"
label={autoLaunchEnabled ? t("Enabled") : t("Disabled")}
color={autoLaunchEnabled ? "success" : "default"}
variant={autoLaunchEnabled ? "filled" : "outlined"}
onClick={toggleAutoLaunch}
sx={{ cursor: "pointer" }}
/>
<Stack direction="row" spacing={1} alignItems="center">
{isAdminMode && (
<Tooltip title={t("Administrator mode does not support auto launch")}>
<WarningOutlined sx={{ color: "warning.main", fontSize: 20 }} />
</Tooltip>
)}
<Chip
size="small"
label={autoLaunchEnabled ? t("Enabled") : t("Disabled")}
color={autoLaunchEnabled ? "success" : "default"}
variant={autoLaunchEnabled ? "filled" : "outlined"}
onClick={toggleAutoLaunch}
disabled={isAdminMode}
sx={{ cursor: isAdminMode ? "not-allowed" : "pointer" }}
/>
</Stack>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography variant="body2" color="text.secondary">
{t("Running Mode")}
</Typography>
@ -206,7 +284,8 @@ export const SystemInfoCard = () => {
onClick={handleRunningModeClick}
sx={runningModeStyle}
>
{isSidecarMode ? t("Sidecar Mode") : t("Service Mode")}
{getModeIcon()}
{getModeText()}
</Typography>
</Stack>
<Divider />

View File

@ -179,17 +179,21 @@ export const ProfileItem = (props: Props) => {
/// 0 不使用任何代理
/// 1 使用订阅好的代理
/// 2 至少使用一个代理,根据订阅,如果没订阅,默认使用系统代理
const onUpdate = useLockFn(async (type: 0 | 1 | 2) => {
const onUpdate = useLockFn(async (type: 0 | 1 | 2): Promise<void> => {
setAnchorEl(null);
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }));
const option: Partial<IProfileOption> = {};
// 存储原始设置以便回退后恢复
const originalOptions = {
with_proxy: itemData.option?.with_proxy,
self_proxy: itemData.option?.self_proxy
};
// 根据类型设置初始更新选项
const option: Partial<IProfileOption> = {};
if (type === 0) {
option.with_proxy = false;
option.self_proxy = false;
} else if (type === 1) {
// nothing
} else if (type === 2) {
if (itemData.option?.self_proxy) {
option.with_proxy = false;
@ -201,14 +205,31 @@ export const ProfileItem = (props: Props) => {
}
try {
// 尝试正常更新
await updateProfile(itemData.uid, option);
Notice.success(t("Update subscription successfully"));
mutate("getProfiles");
} catch (err: any) {
// 更新失败,尝试使用自身代理
const errmsg = err?.message || err.toString();
Notice.error(
errmsg.replace(/error sending request for url (\S+?): /, ""),
);
Notice.info(t("Update failed, retrying with Clash proxy..."));
try {
await updateProfile(itemData.uid, {
with_proxy: false,
self_proxy: true
});
Notice.success(t("Update with Clash proxy successfully"));
await updateProfile(itemData.uid, originalOptions);
mutate("getProfiles");
} catch (retryErr: any) {
const retryErrmsg = retryErr?.message || retryErr.toString();
Notice.error(
`${t("Update failed even with Clash proxy")}: ${retryErrmsg.replace(/error sending request for url (\S+?): /, "")}`,
);
}
} finally {
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }));
}

View File

@ -88,11 +88,13 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
formIns.handleSubmit(async (form) => {
setLoading(true);
try {
// 基本验证
if (!form.type) throw new Error("`Type` should not be null");
if (form.type === "remote" && !form.url) {
throw new Error("The URL should not be null");
}
// 处理表单数据
if (form.option?.update_interval) {
form.option.update_interval = +form.option.update_interval;
} else {
@ -101,25 +103,72 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
if (form.option?.user_agent === "") {
delete form.option.user_agent;
}
const name = form.name || `${form.type} file`;
const item = { ...form, name };
const isRemote = form.type === "remote";
// 创建
if (openType === "new") {
await createProfile(item, fileDataRef.current);
}
// 编辑
else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
// 保存原始代理设置以便回退成功后恢复
const originalOptions = {
with_proxy: form.option?.with_proxy,
self_proxy: form.option?.self_proxy
};
// 执行创建或更新操作,本地配置不需要回退机制
if (!isRemote) {
if (openType === "new") {
await createProfile(item, fileDataRef.current);
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
}
} else {
// 远程配置使用回退机制
try {
// 尝试正常操作
if (openType === "new") {
await createProfile(item, fileDataRef.current);
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, item);
}
} catch (err) {
// 首次创建/更新失败,尝试使用自身代理
Notice.info(t("Profile creation failed, retrying with Clash proxy..."));
// 使用自身代理的配置
const retryItem = {
...item,
option: {
...item.option,
with_proxy: false,
self_proxy: true
}
};
// 使用自身代理再次尝试
if (openType === "new") {
await createProfile(retryItem, fileDataRef.current);
} else {
if (!form.uid) throw new Error("UID not found");
await patchProfile(form.uid, retryItem);
// 编辑模式下恢复原始代理设置
await patchProfile(form.uid, { option: originalOptions });
}
Notice.success(t("Profile creation succeeded with Clash proxy"));
}
}
// 成功后的操作
setOpen(false);
setLoading(false);
setTimeout(() => formIns.reset(), 500);
fileDataRef.current = null;
props.onChange();
} catch (err: any) {
Notice.error(err.message || err.toString());
} finally {
setLoading(false);
}
})

View File

@ -1,195 +1,46 @@
import dayjs from "dayjs";
import useSWR, { mutate } from "swr";
import { useState } from "react";
import {
Button,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
IconButton,
List,
ListItem,
ListItemText,
styled,
Box,
alpha,
Typography,
Divider,
LinearProgress,
keyframes,
alpha,
styled,
useTheme
} from "@mui/material";
import { RefreshRounded } from "@mui/icons-material";
import { useTranslation } from "react-i18next";
import { getProxyProviders, proxyProviderUpdate } from "@/services/api";
import { BaseDialog } from "../base";
import { useLockFn } from "ahooks";
import { proxyProviderUpdate } from "@/services/api";
import { useAppData } from "@/providers/app-data-provider";
import { Notice } from "@/components/base";
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
import dayjs from "dayjs";
import parseTraffic from "@/utils/parse-traffic";
const round = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
export const ProviderButton = () => {
const { t } = useTranslation();
const { data } = useSWR("getProxyProviders", getProxyProviders);
const [open, setOpen] = useState(false);
const hasProvider = Object.keys(data || {}).length > 0;
const [updating, setUpdating] = useState(
Object.keys(data || {}).map(() => false),
);
const setUpdatingAt = (status: boolean, index: number) => {
setUpdating((prev) => {
const next = [...prev];
next[index] = status;
return next;
});
};
const handleUpdate = async (key: string, index: number) => {
setUpdatingAt(true, index);
proxyProviderUpdate(key)
.then(async () => {
setUpdatingAt(false, index);
await mutate("getProxies");
await mutate("getProxyProviders");
})
.catch(async () => {
setUpdatingAt(false, index);
await mutate("getProxies");
await mutate("getProxyProviders");
});
// 定义代理提供者类型
interface ProxyProviderItem {
name?: string;
proxies: any[];
updatedAt: number;
vehicleType: string;
subscriptionInfo?: {
Upload: number;
Download: number;
Total: number;
Expire: number;
};
}
if (!hasProvider) return null;
return (
<>
<Button
size="small"
variant="outlined"
sx={{ textTransform: "capitalize" }}
onClick={() => setOpen(true)}
>
{t("Proxy Provider")}
</Button>
<BaseDialog
open={open}
title={
<Box display="flex" justifyContent="space-between" gap={1}>
<Typography variant="h6">{t("Proxy Provider")}</Typography>
<Button
variant="contained"
size="small"
onClick={async () => {
Object.entries(data || {}).forEach(async ([key], index) => {
await handleUpdate(key, index);
});
}}
>
{t("Update All")}
</Button>
</Box>
}
contentSx={{ width: 400 }}
disableOk
cancelBtn={t("Close")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(data || {}).map(([key, item], index) => {
const time = dayjs(item.updatedAt);
const sub = item.subscriptionInfo;
const hasSubInfo = !!sub;
const upload = sub?.Upload || 0;
const download = sub?.Download || 0;
const total = sub?.Total || 0;
const expire = sub?.Expire || 0;
const progress = Math.min(
Math.round(((download + upload) * 100) / (total + 0.01)) + 1,
100,
);
return (
<>
<ListItem
sx={{
p: 0,
borderRadius: "10px",
border: "solid 2px var(--divider-color)",
mb: 1,
}}
key={key}
>
<ListItemText
sx={{ px: 1 }}
primary={
<>
<Typography
variant="h6"
component="span"
noWrap
title={key}
>
{key}
</Typography>
<TypeBox component="span" sx={{ marginLeft: "8px" }}>
{item.proxies.length}
</TypeBox>
</>
}
secondary={
<>
<StyledTypeBox component="span">
{item.vehicleType}
</StyledTypeBox>
<StyledTypeBox component="span">
{t("Update At")} {time.fromNow()}
</StyledTypeBox>
{hasSubInfo && (
<>
<Box sx={{ ...boxStyle, fontSize: 14 }}>
<span title="Used / Total">
{parseTraffic(upload + download)} /{" "}
{parseTraffic(total)}
</span>
<span title="Expire Time">
{parseExpire(expire)}
</span>
</Box>
<LinearProgress
variant="determinate"
value={progress}
style={{ opacity: total > 0 ? 1 : 0 }}
/>
</>
)}
</>
}
/>
<Divider orientation="vertical" flexItem />
<IconButton
size="small"
color="inherit"
title={`${t("Update")}${t("Proxy Provider")}`}
onClick={() => handleUpdate(key, index)}
sx={{
animation: updating[index]
? `1s linear infinite ${round}`
: "none",
}}
>
<RefreshRounded />
</IconButton>
</ListItem>
</>
);
})}
</List>
</BaseDialog>
</>
);
};
// 样式化组件 - 类型框
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
display: "inline-block",
border: "1px solid #ccc",
@ -202,28 +53,271 @@ const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
lineHeight: 1.25,
}));
const StyledTypeBox = styled(Box)<{ component?: React.ElementType }>(
({ theme }) => ({
display: "inline-block",
border: "1px solid #ccc",
borderColor: alpha(theme.palette.primary.main, 0.5),
color: alpha(theme.palette.primary.main, 0.8),
borderRadius: 4,
fontSize: 10,
marginRight: "4px",
padding: "0 2px",
lineHeight: 1.25,
}),
);
const boxStyle = {
height: 26,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
};
function parseExpire(expire?: number) {
// 解析过期时间
const parseExpire = (expire?: number) => {
if (!expire) return "-";
return dayjs(expire * 1000).format("YYYY-MM-DD");
}
};
export const ProviderButton = () => {
const { t } = useTranslation();
const theme = useTheme();
const [open, setOpen] = useState(false);
const { proxyProviders, refreshProxy, refreshProxyProviders } = useAppData();
const [updating, setUpdating] = useState<Record<string, boolean>>({});
// 检查是否有提供者
const hasProviders = Object.keys(proxyProviders || {}).length > 0;
// 更新单个代理提供者
const updateProvider = useLockFn(async (name: string) => {
try {
// 设置更新状态
setUpdating(prev => ({ ...prev, [name]: true }));
await proxyProviderUpdate(name);
// 刷新数据
await refreshProxy();
await refreshProxyProviders();
Notice.success(`${name} 更新成功`);
} catch (err: any) {
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
}
});
// 更新所有代理提供者
const updateAllProviders = useLockFn(async () => {
try {
// 获取所有provider的名称
const allProviders = Object.keys(proxyProviders || {});
if (allProviders.length === 0) {
Notice.info("没有可更新的代理提供者");
return;
}
// 设置所有provider为更新中状态
const newUpdating = allProviders.reduce((acc, key) => {
acc[key] = true;
return acc;
}, {} as Record<string, boolean>);
setUpdating(newUpdating);
// 改为串行逐个更新所有provider
for (const name of allProviders) {
try {
await proxyProviderUpdate(name);
// 每个更新完成后更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
} catch (err) {
console.error(`更新 ${name} 失败`, err);
// 继续执行下一个,不中断整体流程
}
}
// 刷新数据
await refreshProxy();
await refreshProxyProviders();
Notice.success("全部代理提供者更新成功");
} catch (err: any) {
Notice.error(`更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除所有更新状态
setUpdating({});
}
});
const handleClose = () => {
setOpen(false);
};
if (!hasProviders) return null;
return (
<>
<Button
variant="outlined"
size="small"
startIcon={<StorageOutlined />}
onClick={() => setOpen(true)}
sx={{ mr: 1 }}
>
{t("Proxy Provider")}
</Button>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">{t("Proxy Provider")}</Typography>
<Box>
<Button
variant="contained"
size="small"
onClick={updateAllProviders}
>
{t("Update All")}
</Button>
</Box>
</Box>
</DialogTitle>
<DialogContent>
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(proxyProviders || {}).map(([key, item]) => {
const provider = item as ProxyProviderItem;
const time = dayjs(provider.updatedAt);
const isUpdating = updating[key];
// 订阅信息
const sub = provider.subscriptionInfo;
const hasSubInfo = !!sub;
const upload = sub?.Upload || 0;
const download = sub?.Download || 0;
const total = sub?.Total || 0;
const expire = sub?.Expire || 0;
// 流量使用进度
const progress = total > 0
? Math.min(Math.round(((download + upload) * 100) / total) + 1, 100)
: 0;
return (
<ListItem
key={key}
sx={[
{
p: 0,
mb: "8px",
borderRadius: 2,
overflow: "hidden",
transition: "all 0.2s"
},
({ palette: { mode, primary } }) => {
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
const hoverColor = mode === "light"
? alpha(primary.main, 0.1)
: alpha(primary.main, 0.2);
return {
backgroundColor: bgcolor,
"&:hover": {
backgroundColor: hoverColor,
}
};
}
]}
>
<ListItemText
sx={{ px: 2, py: 1 }}
primary={
<Box sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}>
<Typography
variant="subtitle1"
component="div"
noWrap
title={key}
sx={{ display: "flex", alignItems: "center" }}
>
<span style={{ marginRight: "8px" }}>{key}</span>
<TypeBox component="span">
{provider.proxies.length}
</TypeBox>
<TypeBox component="span">
{provider.vehicleType}
</TypeBox>
</Typography>
<Typography variant="body2" color="text.secondary" noWrap>
<small>{t("Update At")}: </small>{time.fromNow()}
</Typography>
</Box>
}
secondary={
<>
{/* 订阅信息 */}
{hasSubInfo && (
<>
<Box sx={{
mb: 1,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}>
<span title={t("Used / Total") as string}>
{parseTraffic(upload + download)} / {parseTraffic(total)}
</span>
<span title={t("Expire Time") as string}>
{parseExpire(expire)}
</span>
</Box>
{/* 进度条 */}
<LinearProgress
variant="determinate"
value={progress}
sx={{
height: 6,
borderRadius: 3,
opacity: total > 0 ? 1 : 0,
}}
/>
</>
)}
</>
}
/>
<Divider orientation="vertical" flexItem />
<Box sx={{
width: 40,
display: "flex",
justifyContent: "center",
alignItems: "center"
}}>
<IconButton
size="small"
color="primary"
onClick={(e) => {
updateProvider(key);
}}
disabled={isUpdating}
sx={{
animation: isUpdating ? "spin 1s linear infinite" : "none",
"@keyframes spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" }
}
}}
title={t("Update Provider") as string}
>
<RefreshRounded />
</IconButton>
</Box>
</ListItem>
);
})}
</List>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} variant="outlined">
{t("Close")}
</Button>
</DialogActions>
</Dialog>
</>
);
};

View File

@ -5,6 +5,8 @@ import {
ListItemButton,
Typography,
styled,
Chip,
Tooltip,
} from "@mui/material";
import {
ExpandLessRounded,
@ -21,17 +23,19 @@ import { useThemeMode } from "@/services/states";
import { useEffect, useMemo, useState } from "react";
import { convertFileSrc } from "@tauri-apps/api/core";
import { downloadIconCache } from "@/services/cmds";
import { useTranslation } from "react-i18next";
interface RenderProps {
item: IRenderItem;
indent: boolean;
onLocation: (group: IProxyGroupItem) => void;
onLocation: (group: IRenderItem["group"]) => void;
onCheckAll: (groupName: string) => void;
onHeadState: (groupName: string, patch: Partial<HeadState>) => void;
onChangeProxy: (group: IProxyGroupItem, proxy: IProxyItem) => void;
onChangeProxy: (group: IRenderItem["group"], proxy: IRenderItem["proxy"] & { name: string }) => void;
}
export const ProxyRender = (props: RenderProps) => {
const { t } = useTranslation();
const { indent, item, onLocation, onCheckAll, onHeadState, onChangeProxy } =
props;
const { type, group, headState, proxy, proxyCol } = item;
@ -123,7 +127,20 @@ export const ProxyRender = (props: RenderProps) => {
},
}}
/>
{headState?.open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={t("Proxy Count")} arrow>
<Chip
size="small"
label={`${group.all.length}`}
sx={{
mr: 1,
backgroundColor: (theme) => alpha(theme.palette.primary.main, 0.1),
color: (theme) => theme.palette.primary.main,
}}
/>
</Tooltip>
{headState?.open ? <ExpandLessRounded /> : <ExpandMoreRounded />}
</Box>
</ListItemButton>
);
}

View File

@ -1,6 +1,4 @@
import useSWR from "swr";
import { useEffect, useMemo, useCallback } from "react";
import { getProxies } from "@/services/api";
import { useVerge } from "@/hooks/use-verge";
import { filterSort } from "./use-filter-sort";
import { useWindowWidth } from "./use-window-width";
@ -9,12 +7,52 @@ import {
DEFAULT_STATE,
type HeadState,
} from "./use-head-state";
import { useAppData } from "@/providers/app-data-provider";
// 定义代理项接口
interface IProxyItem {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
mptcp: boolean;
smux: boolean;
history: {
time: string;
delay: number;
}[];
provider?: string;
testUrl?: string;
[key: string]: any; // 添加索引签名以适应其他可能的属性
}
// 代理组类型
type ProxyGroup = {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
mptcp: boolean;
smux: boolean;
history: {
time: string;
delay: number;
}[];
now: string;
all: IProxyItem[];
hidden?: boolean;
icon?: string;
testUrl?: string;
provider?: string;
};
export interface IRenderItem {
// 组 | head | item | empty | item col
type: 0 | 1 | 2 | 3 | 4;
key: string;
group: IProxyGroupItem;
group: ProxyGroup;
proxy?: IProxyItem;
col?: number;
proxyCol?: IProxyItem[];
@ -51,16 +89,8 @@ const groupProxies = <T = any>(list: T[], size: number): T[][] => {
};
export const useRenderList = (mode: string) => {
const { data: proxiesData, mutate: mutateProxies } = useSWR(
"getProxies",
getProxies,
{
refreshInterval: 2000,
revalidateOnFocus: false,
revalidateOnReconnect: true,
},
);
// 使用全局数据提供者
const { proxies: proxiesData, refreshProxy } = useAppData();
const { verge } = useVerge();
const { width } = useWindowWidth();
const [headStates, setHeadState] = useHeadStateNew();
@ -80,9 +110,9 @@ export const useRenderList = (mode: string) => {
(mode === "rule" && !groups.length) ||
(mode === "global" && proxies.length < 2)
) {
setTimeout(() => mutateProxies(), 500);
setTimeout(() => refreshProxy(), 500);
}
}, [proxiesData, mode, mutateProxies]);
}, [proxiesData, mode, refreshProxy]);
// 处理渲染列表
const renderList: IRenderItem[] = useMemo(() => {
@ -94,7 +124,7 @@ export const useRenderList = (mode: string) => {
? proxiesData.groups
: [proxiesData.global!];
const retList = renderGroups.flatMap((group) => {
const retList = renderGroups.flatMap((group: ProxyGroup) => {
const headState = headStates[group.name] || DEFAULT_STATE;
const ret: IRenderItem[] = [
{
@ -131,9 +161,9 @@ export const useRenderList = (mode: string) => {
});
} else if (col > 1) {
return ret.concat(
groupProxies(proxies, col).map((proxyCol) => ({
groupProxies(proxies, col).map((proxyCol, colIndex) => ({
type: 4,
key: `col-${group.name}-${proxyCol[0].name}`,
key: `col-${group.name}-${proxyCol[0].name}-${colIndex}`,
group,
headState,
col,
@ -158,12 +188,12 @@ export const useRenderList = (mode: string) => {
});
if (!useRule) return retList.slice(1);
return retList.filter((item) => !item.group.hidden);
return retList.filter((item: IRenderItem) => !item.group.hidden);
}, [headStates, proxiesData, mode, col]);
return {
renderList,
onProxies: mutateProxies,
onProxies: refreshProxy,
onHeadState: setHeadState,
currentColumns: col,
};

View File

@ -1,170 +1,39 @@
import dayjs from "dayjs";
import useSWR, { mutate } from "swr";
import { useState } from "react";
import {
Button,
Box,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
IconButton,
List,
ListItem,
ListItemText,
Typography,
styled,
Box,
alpha,
Divider,
keyframes,
alpha,
styled,
useTheme
} from "@mui/material";
import { RefreshRounded } from "@mui/icons-material";
import { useTranslation } from "react-i18next";
import { getRuleProviders, ruleProviderUpdate } from "@/services/api";
import { BaseDialog } from "../base";
import { useLockFn } from "ahooks";
import { ruleProviderUpdate } from "@/services/api";
import { Notice } from "@/components/base";
import { StorageOutlined, RefreshRounded } from "@mui/icons-material";
import { useAppData } from "@/providers/app-data-provider";
import dayjs from "dayjs";
const round = keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
// 定义规则提供者类型
interface RuleProviderItem {
behavior: string;
ruleCount: number;
updatedAt: number;
vehicleType: string;
}
export const ProviderButton = () => {
const { t } = useTranslation();
const { data } = useSWR("getRuleProviders", getRuleProviders);
const [open, setOpen] = useState(false);
const hasProvider = Object.keys(data || {}).length > 0;
const [updating, setUpdating] = useState(
Object.keys(data || {}).map(() => false),
);
const setUpdatingAt = (status: boolean, index: number) => {
setUpdating((prev) => {
const next = [...prev];
next[index] = status;
return next;
});
};
const handleUpdate = async (key: string, index: number) => {
setUpdatingAt(true, index);
ruleProviderUpdate(key)
.then(async () => {
setUpdatingAt(false, index);
await mutate("getRules");
await mutate("getRuleProviders");
})
.catch(async () => {
setUpdatingAt(false, index);
await mutate("getRules");
await mutate("getRuleProviders");
});
};
if (!hasProvider) return null;
return (
<>
<Button
size="small"
variant="outlined"
sx={{ textTransform: "capitalize" }}
onClick={() => setOpen(true)}
>
{t("Rule Provider")}
</Button>
<BaseDialog
open={open}
title={
<Box display="flex" justifyContent="space-between" gap={1}>
<Typography variant="h6">{t("Rule Provider")}</Typography>
<Button
variant="contained"
size="small"
onClick={async () => {
Object.entries(data || {}).forEach(async ([key], index) => {
await handleUpdate(key, index);
});
}}
>
{t("Update All")}
</Button>
</Box>
}
contentSx={{ width: 400 }}
disableOk
cancelBtn={t("Close")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(data || {}).map(([key, item], index) => {
const time = dayjs(item.updatedAt);
return (
<>
<ListItem
sx={{
p: 0,
borderRadius: "10px",
border: "solid 2px var(--divider-color)",
mb: 1,
}}
key={key}
>
<ListItemText
sx={{ px: 1 }}
primary={
<>
<Typography
variant="h6"
component="span"
noWrap
title={key}
>
{key}
</Typography>
<TypeBox component="span" sx={{ marginLeft: "8px" }}>
{item.ruleCount}
</TypeBox>
</>
}
secondary={
<>
<StyledTypeBox component="span">
{item.vehicleType}
</StyledTypeBox>
<StyledTypeBox component="span">
{item.behavior}
</StyledTypeBox>
<StyledTypeBox component="span">
{t("Update At")} {time.fromNow()}
</StyledTypeBox>
</>
}
/>
<Divider orientation="vertical" flexItem />
<IconButton
size="small"
color="inherit"
title={`${t("Update")}${t("Rule Provider")}`}
onClick={() => handleUpdate(key, index)}
sx={{
animation: updating[index]
? `1s linear infinite ${round}`
: "none",
}}
>
<RefreshRounded />
</IconButton>
</ListItem>
</>
);
})}
</List>
</BaseDialog>
</>
);
};
const TypeBox = styled(Box, {
shouldForwardProp: (prop) => prop !== "component",
})<{ component?: React.ElementType }>(({ theme }) => ({
// 辅助组件 - 类型框
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
display: "inline-block",
border: "1px solid #ccc",
borderColor: alpha(theme.palette.secondary.main, 0.5),
@ -176,16 +45,222 @@ const TypeBox = styled(Box, {
lineHeight: 1.25,
}));
const StyledTypeBox = styled(Box, {
shouldForwardProp: (prop) => prop !== "component",
})<{ component?: React.ElementType }>(({ theme }) => ({
display: "inline-block",
border: "1px solid #ccc",
borderColor: alpha(theme.palette.primary.main, 0.5),
color: alpha(theme.palette.primary.main, 0.8),
borderRadius: 4,
fontSize: 10,
marginRight: "4px",
padding: "0 2px",
lineHeight: 1.25,
}));
export const ProviderButton = () => {
const { t } = useTranslation();
const theme = useTheme();
const [open, setOpen] = useState(false);
const { ruleProviders, refreshRules, refreshRuleProviders } = useAppData();
const [updating, setUpdating] = useState<Record<string, boolean>>({});
// 检查是否有提供者
const hasProviders = Object.keys(ruleProviders || {}).length > 0;
// 更新单个规则提供者
const updateProvider = useLockFn(async (name: string) => {
try {
// 设置更新状态
setUpdating(prev => ({ ...prev, [name]: true }));
await ruleProviderUpdate(name);
// 刷新数据
await refreshRules();
await refreshRuleProviders();
Notice.success(`${name} 更新成功`);
} catch (err: any) {
Notice.error(`${name} 更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
}
});
// 更新所有规则提供者
const updateAllProviders = useLockFn(async () => {
try {
// 获取所有provider的名称
const allProviders = Object.keys(ruleProviders || {});
if (allProviders.length === 0) {
Notice.info("没有可更新的规则提供者");
return;
}
// 设置所有provider为更新中状态
const newUpdating = allProviders.reduce((acc, key) => {
acc[key] = true;
return acc;
}, {} as Record<string, boolean>);
setUpdating(newUpdating);
// 改为串行逐个更新所有provider
for (const name of allProviders) {
try {
await ruleProviderUpdate(name);
// 每个更新完成后更新状态
setUpdating(prev => ({ ...prev, [name]: false }));
} catch (err) {
console.error(`更新 ${name} 失败`, err);
// 继续执行下一个,不中断整体流程
}
}
// 刷新数据
await refreshRules();
await refreshRuleProviders();
Notice.success("全部规则提供者更新成功");
} catch (err: any) {
Notice.error(`更新失败: ${err?.message || err.toString()}`);
} finally {
// 清除所有更新状态
setUpdating({});
}
});
const handleClose = () => {
setOpen(false);
};
if (!hasProviders) return null;
return (
<>
<Button
variant="outlined"
size="small"
startIcon={<StorageOutlined />}
onClick={() => setOpen(true)}
>
{t("Rule Provider")}
</Button>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
>
<DialogTitle>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="h6">{t("Rule Providers")}</Typography>
<Button
variant="contained"
size="small"
onClick={updateAllProviders}
>
{t("Update All")}
</Button>
</Box>
</DialogTitle>
<DialogContent>
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(ruleProviders || {}).map(([key, item]) => {
const provider = item as RuleProviderItem;
const time = dayjs(provider.updatedAt);
const isUpdating = updating[key];
return (
<ListItem
key={key}
sx={[
{
p: 0,
mb: "8px",
borderRadius: 2,
overflow: "hidden",
transition: "all 0.2s"
},
({ palette: { mode, primary } }) => {
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
const hoverColor = mode === "light"
? alpha(primary.main, 0.1)
: alpha(primary.main, 0.2);
return {
backgroundColor: bgcolor,
"&:hover": {
backgroundColor: hoverColor,
borderColor: alpha(primary.main, 0.3)
}
};
}
]}
>
<ListItemText
sx={{ px: 2, py: 1 }}
primary={
<Box sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}>
<Typography
variant="subtitle1"
component="div"
noWrap
title={key}
sx={{ display: "flex", alignItems: "center" }}
>
<span style={{ marginRight: "8px" }}>{key}</span>
<TypeBox component="span">
{provider.ruleCount}
</TypeBox>
</Typography>
<Typography variant="body2" color="text.secondary" noWrap>
<small>{t("Update At")}: </small>{time.fromNow()}
</Typography>
</Box>
}
secondary={
<Box sx={{ display: "flex" }}>
<TypeBox component="span">
{provider.vehicleType}
</TypeBox>
<TypeBox component="span">
{provider.behavior}
</TypeBox>
</Box>
}
/>
<Divider orientation="vertical" flexItem />
<Box sx={{
width: 40,
display: "flex",
justifyContent: "center",
alignItems: "center"
}}>
<IconButton
size="small"
color="primary"
onClick={() => updateProvider(key)}
disabled={isUpdating}
sx={{
animation: isUpdating ? "spin 1s linear infinite" : "none",
"@keyframes spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" }
}
}}
title={t("Update Provider") as string}
>
<RefreshRounded />
</IconButton>
</Box>
</ListItem>
);
})}
</List>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} variant="outlined">
{t("Close")}
</Button>
</DialogActions>
</Dialog>
</>
);
};

View File

@ -57,21 +57,18 @@ const DEFAULT_DNS_CONFIG = {
"*.msftncsi.com",
"www.msftconnecttest.com",
],
"default-nameserver": ["223.6.6.6", "8.8.8.8"],
"default-nameserver": ["system", "223.6.6.6", "8.8.8.8"],
nameserver: [
"8.8.8.8",
"https://doh.pub/dns-query",
"https://dns.alidns.com/dns-query",
],
fallback: [
"https://dns.alidns.com/dns-query",
"https://dns.google/dns-query",
"https://cloudflare-dns.com/dns-query",
],
fallback: [],
"nameserver-policy": {},
"proxy-server-nameserver": [
"https://doh.pub/dns-query",
"https://dns.alidns.com/dns-query",
"tls://223.5.5.5"
],
"direct-nameserver": [],
"direct-nameserver-follow-policy": false,
@ -98,12 +95,12 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fakeIpFilterMode: "blacklist" | "whitelist";
preferH3: boolean;
respectRules: boolean;
useHosts: boolean;
useSystemHosts: boolean;
fakeIpFilter: string;
nameserver: string;
fallback: string;
defaultNameserver: string;
useHosts: boolean;
useSystemHosts: boolean;
proxyServerNameserver: string;
directNameserver: string;
directNameserverFollowPolicy: boolean;
@ -120,12 +117,12 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fakeIpFilterMode: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"],
preferH3: DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules: DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"],
fakeIpFilter: DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
defaultNameserver: DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
nameserver: DEFAULT_DNS_CONFIG.nameserver.join(", "),
fallback: DEFAULT_DNS_CONFIG.fallback.join(", "),
useHosts: DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"],
proxyServerNameserver:
DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || "",
directNameserver: DEFAULT_DNS_CONFIG["direct-nameserver"]?.join(", ") || "",
@ -209,6 +206,9 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
preferH3: config["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules:
config["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: config["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
config["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"],
fakeIpFilter:
config["fake-ip-filter"]?.join(", ") ??
DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
@ -220,9 +220,6 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
defaultNameserver:
config["default-nameserver"]?.join(", ") ??
DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
useHosts: config["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
config["use-system-hosts"] ?? DEFAULT_DNS_CONFIG["use-system-hosts"],
proxyServerNameserver:
config["proxy-server-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""),
@ -259,12 +256,12 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fakeIpFilterMode: DEFAULT_DNS_CONFIG["fake-ip-filter-mode"],
preferH3: DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules: DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"],
fakeIpFilter: DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
defaultNameserver: DEFAULT_DNS_CONFIG["default-nameserver"].join(", "),
nameserver: DEFAULT_DNS_CONFIG.nameserver.join(", "),
fallback: DEFAULT_DNS_CONFIG.fallback.join(", "),
useHosts: DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts: DEFAULT_DNS_CONFIG["use-system-hosts"],
proxyServerNameserver:
DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || "",
directNameserver:
@ -330,6 +327,10 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
preferH3: dnsConfig["prefer-h3"] ?? DEFAULT_DNS_CONFIG["prefer-h3"],
respectRules:
dnsConfig["respect-rules"] ?? DEFAULT_DNS_CONFIG["respect-rules"],
useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
dnsConfig["use-system-hosts"] ??
DEFAULT_DNS_CONFIG["use-system-hosts"],
fakeIpFilter:
dnsConfig["fake-ip-filter"]?.join(", ") ??
DEFAULT_DNS_CONFIG["fake-ip-filter"].join(", "),
@ -342,10 +343,6 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
fallback:
dnsConfig.fallback?.join(", ") ??
DEFAULT_DNS_CONFIG.fallback.join(", "),
useHosts: dnsConfig["use-hosts"] ?? DEFAULT_DNS_CONFIG["use-hosts"],
useSystemHosts:
dnsConfig["use-system-hosts"] ??
DEFAULT_DNS_CONFIG["use-system-hosts"],
proxyServerNameserver:
dnsConfig["proxy-server-nameserver"]?.join(", ") ??
(DEFAULT_DNS_CONFIG["proxy-server-nameserver"]?.join(", ") || ""),
@ -458,12 +455,12 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
"fake-ip-filter-mode": values.fakeIpFilterMode,
"prefer-h3": values.preferH3,
"respect-rules": values.respectRules,
"use-hosts": values.useHosts,
"use-system-hosts": values.useSystemHosts,
"fake-ip-filter": parseList(values.fakeIpFilter),
"default-nameserver": parseList(values.defaultNameserver),
nameserver: parseList(values.nameserver),
fallback: parseList(values.fallback),
"use-hosts": values.useHosts,
"use-system-hosts": values.useSystemHosts,
"direct-nameserver-follow-policy": values.directNameserverFollowPolicy,
"fallback-filter": {
geoip: values.fallbackGeoip,
"geoip-code": values.fallbackGeoipCode,
@ -472,13 +469,16 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
},
};
// 只在有nameserverPolicy时添加
// 只在有值时添加其他可选字段
if (values.fallback) {
dnsConfig["fallback"] = parseList(values.fallback);
}
const policy = parseNameserverPolicy(values.nameserverPolicy);
if (Object.keys(policy).length > 0) {
dnsConfig["nameserver-policy"] = policy;
}
// 只在有值时添加其他可选字段
if (values.proxyServerNameserver) {
dnsConfig["proxy-server-nameserver"] = parseList(
values.proxyServerNameserver,
@ -489,9 +489,6 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
dnsConfig["direct-nameserver"] = parseList(values.directNameserver);
}
dnsConfig["direct-nameserver-follow-policy"] =
values.directNameserverFollowPolicy;
return dnsConfig;
};
@ -576,7 +573,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
open={open}
title={
<Box display="flex" justifyContent="space-between" alignItems="center">
{t("DNS Settings")}
{t("DNS Overwrite")}
<Box display="flex" alignItems="center" gap={1}>
<Button
variant="outlined"
@ -755,7 +752,7 @@ export const DnsViewer = forwardRef<DialogRef>((props, ref) => {
size="small"
value={values.defaultNameserver}
onChange={handleChange("defaultNameserver")}
placeholder="223.6.6.6, 8.8.8.8"
placeholder="system,223.6.6.6, 8.8.8.8"
/>
</Item>

View File

@ -20,6 +20,7 @@ const HOTKEY_FUNC = [
"clash_mode_direct",
"toggle_system_proxy",
"toggle_tun_mode",
"entry_lightweight_mode",
];
export const HotkeyViewer = forwardRef<DialogRef>((props, ref) => {

View File

@ -198,6 +198,26 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
</GuardState>
</Item>
)}
{OS === "macos" && (
<Item>
<ListItemText primary={t("Enable Tray Icon")} />
<GuardState
value={
verge?.enable_tray_icon === false &&
verge?.enable_tray_speed === false
? true
: (verge?.enable_tray_icon ?? true)
}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ enable_tray_icon: e })}
onGuard={(e) => patchVerge({ enable_tray_icon: e })}
>
<Switch edge="end" />
</GuardState>
</Item>
)}
<Item>
<ListItemText primary={t("Common Tray Icon")} />

View File

@ -66,9 +66,9 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
sx={{
cursor: "pointer",
color: "primary.main",
"&:hover": { textDecoration: "underline" }
"&:hover": { textDecoration: "underline" },
}}
onClick={() => entry_lightweight_mode()}
onClick={async () => await entry_lightweight_mode()}
>
{t("Enable")}
</Typography>
@ -115,17 +115,25 @@ export const LiteModeViewer = forwardRef<DialogRef>((props, ref) => {
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">{t("mins")}</InputAdornment>
)
}
<InputAdornment position="end">
{t("mins")}
</InputAdornment>
),
},
}}
/>
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: "italic" }}>
{t("When closing the window, LightWeight Mode will be automatically activated after _n minutes",
{ n: values.autoEnterLiteModeDelay })}
<Typography
variant="body2"
color="text.secondary"
sx={{ fontStyle: "italic" }}
>
{t(
"When closing the window, LightWeight Mode will be automatically activated after _n minutes",
{ n: values.autoEnterLiteModeDelay },
)}
</Typography>
</ListItem>
</>

View File

@ -5,7 +5,6 @@ import {
SettingsRounded,
ShuffleRounded,
LanRounded,
DnsRounded,
} from "@mui/icons-material";
import { DialogRef, Notice, Switch } from "@/components/base";
import { useClash } from "@/hooks/use-clash";
@ -49,7 +48,16 @@ const SettingClash = ({ onError }: Props) => {
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
// 独立跟踪DNS设置开关状态
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(false);
const [dnsSettingsEnabled, setDnsSettingsEnabled] = useState(() => {
// 尝试从localStorage获取之前保存的状态
const savedState = localStorage.getItem("dns_settings_enabled");
if (savedState !== null) {
return savedState === "true";
}
// 如果没有保存的状态则从verge配置中获取
return verge?.enable_dns_settings ?? false;
});
const { addListener } = useListen();
const webRef = useRef<DialogRef>(null);
@ -59,12 +67,6 @@ const SettingClash = ({ onError }: Props) => {
const networkRef = useRef<DialogRef>(null);
const dnsRef = useRef<DialogRef>(null);
// 初始化时从verge配置中加载DNS设置开关状态
useEffect(() => {
const dnsSettingsState = verge?.enable_dns_settings ?? false;
setDnsSettingsEnabled(dnsSettingsState);
}, [verge]);
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IConfigData>) => {
mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
@ -84,15 +86,21 @@ const SettingClash = ({ onError }: Props) => {
// 实现DNS设置开关处理函数
const handleDnsToggle = useLockFn(async (enable: boolean) => {
try {
// 立即更新UI状态
setDnsSettingsEnabled(enable);
// 保存到localStorage用于记住用户的选择
localStorage.setItem("dns_settings_enabled", String(enable));
// 更新verge配置
await patchVerge({ enable_dns_settings: enable });
await invoke("apply_dns_config", { apply: enable });
setTimeout(() => {
mutateClash();
}, 500); // 延迟500ms确保后端完成处理
} catch (err: any) {
Notice.error(err.message || err.toString());
// 如果出错,恢复原始状态
setDnsSettingsEnabled(!enable);
localStorage.setItem("dns_settings_enabled", String(!enable));
Notice.error(err.message || err.toString());
await patchVerge({ enable_dns_settings: !enable }).catch(() => {
// 忽略恢复状态时的错误
});
@ -143,7 +151,6 @@ const SettingClash = ({ onError }: Props) => {
/>
}
>
{/* 使用独立状态不再依赖dns?.enable */}
<Switch
edge="end"
checked={dnsSettingsEnabled}

View File

@ -18,12 +18,12 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import {
getSystemProxy,
getAutotemProxy,
getRunningMode,
installService,
getAutoLaunchStatus,
} from "@/services/cmds";
import { useLockFn } from "ahooks";
import { Box, Button, Tooltip } from "@mui/material";
import { Button, Tooltip } from "@mui/material";
import { useSystemState } from "@/hooks/use-system-state";
interface Props {
onError?: (err: Error) => void;
@ -36,23 +36,29 @@ const SettingSystem = ({ onError }: Props) => {
const { data: sysproxy } = useSWR("getSystemProxy", getSystemProxy);
const { data: autoproxy } = useSWR("getAutotemProxy", getAutotemProxy);
const { data: runningMode, mutate: mutateRunningMode } = useSWR(
"getRunningMode",
getRunningMode,
const { data: autoLaunchEnabled } = useSWR(
"getAutoLaunchStatus",
getAutoLaunchStatus,
{ revalidateOnFocus: false }
);
const { data: autoLaunchEnabled } = useSWR("getAutoLaunchStatus", getAutoLaunchStatus);
const { isAdminMode, isSidecarMode, mutateRunningMode } = useSystemState();
// 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用
const isTunAvailable = !isSidecarMode || isAdminMode;
// 当实际自启动状态与配置不同步时更新配置
useEffect(() => {
if (autoLaunchEnabled !== undefined && verge && verge.enable_auto_launch !== autoLaunchEnabled) {
if (
autoLaunchEnabled !== undefined &&
verge &&
verge.enable_auto_launch !== autoLaunchEnabled
) {
// 静默更新配置不触发UI刷新
mutateVerge({ ...verge, enable_auto_launch: autoLaunchEnabled }, false);
}
}, [autoLaunchEnabled]);
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "sidecar";
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
@ -103,12 +109,12 @@ const SettingSystem = ({ onError }: Props) => {
icon={SettingsRounded}
onClick={() => tunRef.current?.open()}
/>
{isSidecarMode && (
{isSidecarMode && !isAdminMode && (
<Tooltip title={t("TUN requires Service Mode")}>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip>
)}
{isSidecarMode && (
{isSidecarMode && !isAdminMode && (
<Tooltip title={t("Install Service")}>
<Button
variant="outlined"
@ -130,20 +136,20 @@ const SettingSystem = ({ onError }: Props) => {
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) return;
// 当在sidecar模式下且非管理员模式时禁用切换
if (isSidecarMode && !isAdminMode) return;
onChangeData({ enable_tun_mode: e });
}}
onGuard={(e) => {
// 当在sidecar模式下禁用切换
if (isSidecarMode) {
// 当在sidecar模式下且非管理员模式时禁用切换
if (isSidecarMode && !isAdminMode) {
Notice.error(t("TUN requires Service Mode"), 2000);
return Promise.reject(new Error(t("TUN requires Service Mode")));
}
return patchVerge({ enable_tun_mode: e });
}}
>
<Switch edge="end" disabled={isSidecarMode} />
<Switch edge="end" disabled={isSidecarMode && !isAdminMode} />
</GuardState>
</SettingItem>
<SettingItem
@ -184,14 +190,32 @@ const SettingSystem = ({ onError }: Props) => {
</GuardState>
</SettingItem>
<SettingItem label={t("Auto Launch")}>
<SettingItem
label={t("Auto Launch")}
extra={
isAdminMode && (
<Tooltip title={t("Administrator mode does not support auto launch")}>
<WarningRounded sx={{ color: "warning.main", mr: 1 }} />
</Tooltip>
)
}
>
<GuardState
value={enable_auto_launch ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ enable_auto_launch: e })}
onChange={(e) => {
// 在管理员模式下禁用更改
if (isAdminMode) return;
onChangeData({ enable_auto_launch: e });
}}
onGuard={async (e) => {
if (isAdminMode) {
Notice.error(t("Administrator mode does not support auto launch"), 2000);
return Promise.reject(new Error(t("Administrator mode does not support auto launch")));
}
try {
// 在应用更改之前先触发UI更新让用户立即看到反馈
onChangeData({ enable_auto_launch: e });
@ -206,7 +230,7 @@ const SettingSystem = ({ onError }: Props) => {
}
}}
>
<Switch edge="end" />
<Switch edge="end" disabled={isAdminMode} />
</GuardState>
</SettingItem>

View File

@ -52,7 +52,7 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
);
// 是否以sidecar模式运行
const isSidecarMode = runningMode === "sidecar";
const isSidecarMode = runningMode === "Sidecar";
const sysproxyRef = useRef<DialogRef>(null);
const tunRef = useRef<DialogRef>(null);
@ -120,17 +120,7 @@ const ProxyControlSwitches = ({ label, onError }: ProxySwitchProps) => {
}}
>
<Box sx={{ display: "flex", alignItems: "center" }}>
{proxy_auto_config ? (
autoproxy?.enable ? (
<PlayCircleOutlineRounded
sx={{ color: "success.main", mr: 1.5, fontSize: 28 }}
/>
) : (
<PauseCircleOutlineRounded
sx={{ color: "text.disabled", mr: 1.5, fontSize: 28 }}
/>
)
) : sysproxy?.enable ? (
{enable_system_proxy ? (
<PlayCircleOutlineRounded
sx={{ color: "success.main", mr: 1.5, fontSize: 28 }}
/>

View File

@ -1,32 +1,25 @@
import useSWR from "swr";
import { useMemo } from "react";
import { getProxies } from "@/services/api";
import { getClashConfig } from "@/services/api";
import { useAppData } from "@/providers/app-data-provider";
// 定义代理组类型
interface ProxyGroup {
name: string;
now: string;
}
// 获取当前代理节点信息的自定义Hook
export const useCurrentProxy = () => {
// 获取代理信息
const { data: proxiesData, mutate: mutateProxies } = useSWR(
"getProxies",
getProxies,
{
refreshInterval: 2000,
revalidateOnFocus: false,
revalidateOnReconnect: true,
},
);
// 获取当前Clash配置包含模式信息
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
// 从AppDataProvider获取数据
const { proxies, clashConfig, refreshProxy } = useAppData();
// 获取当前模式
const currentMode = clashConfig?.mode?.toLowerCase() || "rule";
// 获取当前代理节点信息
const currentProxyInfo = useMemo(() => {
if (!proxiesData) return { currentProxy: null, primaryGroupName: null };
if (!proxies) return { currentProxy: null, primaryGroupName: null };
const { global, groups, records } = proxiesData;
const { global, groups, records } = proxies;
// 默认信息
let primaryGroupName = "GLOBAL";
@ -43,11 +36,11 @@ export const useCurrentProxy = () => {
"自动选择",
];
const primaryGroup =
groups.find((group) =>
groups.find((group: ProxyGroup) =>
primaryKeywords.some((keyword) =>
group.name.toLowerCase().includes(keyword.toLowerCase()),
),
) || groups.filter((g) => g.name !== "GLOBAL")[0];
) || groups.filter((g: ProxyGroup) => g.name !== "GLOBAL")[0];
if (primaryGroup) {
primaryGroupName = primaryGroup.name;
@ -71,12 +64,12 @@ export const useCurrentProxy = () => {
};
return { currentProxy, primaryGroupName };
}, [proxiesData, currentMode]);
}, [proxies, currentMode]);
return {
currentProxy: currentProxyInfo.currentProxy,
primaryGroupName: currentProxyInfo.primaryGroupName,
mode: currentMode,
refreshProxy: mutateProxies,
refreshProxy,
};
};

View File

@ -0,0 +1,29 @@
import useSWR from "swr";
import { getRunningMode, isAdmin } from "@/services/cmds";
/**
* hook
*
*/
export function useSystemState() {
// 获取运行模式
const { data: runningMode = "Sidecar", mutate: mutateRunningMode } =
useSWR("getRunningMode", getRunningMode, {
suspense: false,
revalidateOnFocus: false
});
// 获取管理员状态
const { data: isAdminMode = false } =
useSWR("isAdmin", isAdmin, {
suspense: false,
revalidateOnFocus: false
});
return {
runningMode,
isAdminMode,
isSidecarMode: runningMode === "Sidecar",
mutateRunningMode
};
}

View File

@ -27,6 +27,7 @@
"Proxies": "Proxies",
"Proxy Groups": "Proxy Groups",
"Proxy Provider": "Proxy Provider",
"Proxy Count": "Proxy Count",
"Update All": "Update All",
"Update At": "Update At",
"rule": "rule",
@ -201,12 +202,16 @@
"Tun Mode": "Tun Mode",
"TUN requires Service Mode": "TUN mode requires install service",
"Install Service": "Install Service",
"Install Service failed": "Install Service failed",
"Restart Core failed": "Restart Core failed",
"Reset to Default": "Reset to Default",
"Tun Mode Info": "Tun (Virtual NIC) mode: Captures all system traffic, when enabled, there is no need to enable system proxy.",
"System Proxy Enabled": "System proxy is enabled, your applications will access the network through the proxy",
"System Proxy Disabled": "System proxy is disabled, it is recommended for most users to turn on this option",
"TUN Mode Enabled": "TUN mode is enabled, applications will access the network through the virtual network card",
"TUN Mode Disabled": "TUN mode is disabled, suitable for special applications",
"TUN Mode Service Required": "TUN mode requires service mode, please install the service first",
"TUN Mode Intercept Info": "TUN mode can take over all application traffic, suitable for special applications",
"TUN Mode Intercept Info": "TUN mode can take over all application traffic, suitable for special applications that do not follow the system proxy settings",
"rule Mode Description": "Routes traffic according to preset rules, provides flexible proxy strategies",
"global Mode Description": "All traffic goes through proxy servers, suitable for scenarios requiring global internet access",
"direct Mode Description": "All traffic doesn't go through proxy nodes, but is forwarded by Clash kernel to target servers, suitable for specific scenarios requiring kernel traffic distribution",
@ -250,6 +255,7 @@
"PAC Script Content": "PAC Script Content",
"PAC URL": "PAC URL: ",
"Auto Launch": "Auto Launch",
"Administrator mode does not support auto launch": "Administrator mode does not support auto launch",
"Silent Start": "Silent Start",
"Silent Start Info": "Start the program in background mode without displaying the panel",
"TG Channel": "Telegram Channel",
@ -351,6 +357,7 @@
"clash_mode_direct": "Direct Mode",
"toggle_system_proxy": "Enable/Disable System Proxy",
"toggle_tun_mode": "Enable/Disable Tun Mode",
"entry_lightweight_mode": "Entry Lightweight Mode",
"Backup Setting": "Backup Setting",
"Backup Setting Info": "Support WebDAV backup configuration files",
"Runtime Config": "Runtime Config",
@ -451,6 +458,7 @@
"Global Mode": "Global Mode",
"Direct Mode": "Direct Mode",
"Enable Tray Speed": "Enable Tray Speed",
"Enable Tray Icon": "Enable Tray Icon",
"LightWeight Mode": "Lightweight Mode",
"LightWeight Mode Info": "Close the GUI and keep only the kernel running",
"LightWeight Mode Settings": "LightWeight Mode Settings",
@ -484,8 +492,9 @@
"Validation Failed": "Validation Failed",
"Service Administrator Prompt": "Clash Verge requires administrator privileges to reinstall the system service",
"DNS Settings": "DNS Settings",
"DNS settings saved": "DNS settings saved",
"DNS Overwrite": "DNS Overwrite",
"DNS Settings Warning": "If you are not familiar with these settings, please do not modify them and keep DNS Settings enabled",
"DNS Settings Warning": "If you are not familiar with these settings, please do not modify them and keep DNS Overwrite enabled",
"Enable DNS": "Enable DNS",
"DNS Listen": "DNS Listen",
"Enhanced Mode": "Enhanced Mode",
@ -546,9 +555,19 @@
"OS Info": "OS Info",
"Running Mode": "Running Mode",
"Sidecar Mode": "User Mode",
"Administrator Mode": "Administrator Mode",
"Administrator + Service Mode": "Admin + Service Mode",
"Last Check Update": "Last Check Update",
"Click to import subscription": "Click to import subscription",
"Update subscription successfully": "Update subscription successfully",
"Update failed, retrying with Clash proxy...": "Update failed, retrying with Clash proxy...",
"Update with Clash proxy successfully": "Update with Clash proxy successfully",
"Update failed even with Clash proxy": "Update failed even with Clash proxy",
"Profile creation failed, retrying with Clash proxy...": "Profile creation failed, retrying with Clash proxy...",
"Profile creation succeeded with Clash proxy": "Profile creation succeeded with Clash proxy",
"Import failed, retrying with Clash proxy...": "Import failed, retrying with Clash proxy...",
"Profile Imported with Clash proxy": "Profile Imported with Clash proxy",
"Import failed even with Clash proxy": "Import failed even with Clash proxy",
"Current Node": "Current Node",
"No active proxy node": "No active proxy node",
"Network Settings": "Network Settings",
@ -570,7 +589,6 @@
"No": "No",
"Failed": "Failed",
"Completed": "Completed",
"Bahamut Anime": "Bahamut Anime",
"Disallowed ISP": "Disallowed ISP",
"Originals Only": "Originals Only",
"No (IP Banned By Disney+)": "No (IP Banned By Disney+)",

View File

@ -16,12 +16,13 @@
"Delete": "Удалить",
"Enable": "Включить",
"Disable": "Отключить",
"Label-Home": "Главная",
"Label-Proxies": "Прокси",
"Label-Profiles": "Профили",
"Label-Connections": "Соединения",
"Label-Rules": "Правила",
"Label-Logs": "Логи",
"Label-Test": "Тест",
"Label-Unlock": "Тест",
"Label-Settings": "Настройки",
"Proxies": "Прокси",
"Proxy Groups": "Группы прокси",
@ -39,12 +40,12 @@
"Sort by name": "Сортировать по названию",
"Delay check URL": "URL проверки задержки",
"Delay check to cancel fixed": "Проверка задержки для отмены фиксированного",
"Proxy basic": "Резюме о прокси",
"Proxy detail": "Подробности о прокси",
"Proxy basic": "Отображать меньше сведений о прокси",
"Proxy detail": "Отображать больше сведений о прокси",
"Profiles": "Профили",
"Update All Profiles": "Обновить все профили",
"View Runtime Config": "Просмотреть используемый конфиг",
"Reactivate Profiles": "Реактивировать профили",
"Reactivate Profiles": "Перезапустить профиль",
"Paste": "Вставить",
"Profile URL": "URL профиля",
"Import": "Импорт",
@ -135,19 +136,19 @@
"Hidden": "Скрытый",
"Group Name Required": "Требуется имя группы",
"Group Name Already Exists": "Имя группы уже существует",
"Extend Config": "Изменить Merge.",
"Extend Config": "Изменить Merge",
"Extend Script": "Изменить Script",
"Global Merge": "Глобальный расширенный Настройки",
"Global Script": "Глобальный расширенный скрипт",
"Type": "Тип",
"Name": "Название",
"Descriptions": "Описания",
"Descriptions": "Описание",
"Subscription URL": "URL подписки",
"Update Interval": "Интервал обновления",
"Choose File": "Выбрать файл",
"Use System Proxy": "Использовать системный прокси для обновления",
"Use Clash Proxy": "Использовать прокси Clash для обновления",
"Accept Invalid Certs (Danger)": "Принимать недействительные сертификаты (Опасно)",
"Accept Invalid Certs (Danger)": "Принимать недействительные сертификаты (ОПАСНО)",
"Refresh": "Обновить",
"Home": "Главная",
"Select": "Выбрать",
@ -162,17 +163,19 @@
"To Top": "Наверх",
"To End": "Вниз",
"Connections": "Соединения",
"Table View": "Tablichnyy vid",
"List View": "Spiskovyy vid",
"Table View": "Отображать в виде таблицы",
"List View": "Отображать в виде списка",
"Close All": "Закрыть всё",
"Default": "По умолчанию",
"Download Speed": "Скорость загрузки",
"Upload": "Загрузка",
"Download": "Скачивание",
"Download Speed": "Скорость скачивания",
"Upload Speed": "Скорость загрузки",
"Host": "Хост",
"Downloaded": "Скачано",
"Uploaded": "Загружено",
"DL Speed": "Скорость загрузки",
"UL Speed": "Скорость выгрузки",
"DL Speed": "Скорость скачивания",
"UL Speed": "Скорость загрузки",
"Active Connections": "Активные соединения",
"Chains": "Цепочки",
"Rule": "Правило",
"Process": "Процесс",
@ -182,31 +185,43 @@
"DestinationPort": "Целевой порт",
"Close Connection": "Закрыть соединение",
"Rules": "Правила",
"Rule Provider": "Провайдер правило",
"Rule Provider": "Провайдеры правил",
"Logs": "Логи",
"Pause": "Пауза",
"Clear": "Очистить",
"Test": "Тест",
"Test All": "Тест Все",
"Test All": "Тестировать все",
"Testing...": "Тестирование ...",
"Create Test": "Создать тест",
"Edit Test": "Редактировать тест",
"Icon": "Икона",
"Test URL": "Тестовый URL",
"Icon": "Иконка",
"Test URL": "URL проверка",
"Settings": "Настройки",
"System Setting": "Настройки системы",
"Tun Mode": "Tun (виртуальный сетевой адаптер) режим",
"Reset to Default": "Сбросить настройки по умолчанию",
"Tun Mode Info": "Режим Tun (виртуальный сетевой адаптер): захватывает весь системный трафик, при включении нет необходимости включать системный прокси-сервер.",
"Tun Mode": "Режим TUN",
"TUN requires Service Mode": "Режим TUN требует установленную службу Clash Verge",
"Install Service": "Установить службу",
"Reset to Default": "Сбросить настройки",
"Tun Mode Info": "Режим Tun: захватывает весь системный трафик, при включении нет необходимости включать системный прокси-сервер.",
"System Proxy Enabled": "Системный прокси включен, ваши приложения будут получать доступ к сети через него",
"System Proxy Disabled": "Системный прокси отключен, большинству пользователей рекомендуется включить эту опцию",
"TUN Mode Enabled": "Режим TUN включен, приложения будут получать доступ к сети через виртуальную сетевую карту",
"TUN Mode Disabled": "Режим TUN отключен",
"TUN Mode Service Required": "Режим TUN требует установленную службу Clash Verge",
"TUN Mode Intercept Info": "Режим TUN может перехватить трафик всех приложений, подходит для приложений, которые не работают в режиме системного прокси.",
"rule Mode Description": "Направляет трафик в соответствии с предустановленными правилами",
"global Mode Description": "Направляет весь трафик через прокси-серверы",
"direct Mode Description": "Весь трафик обходит прокси, но передается ядром Clash для целевых серверов, подходит для конкретных сценариев, требующих распределения трафика ядра",
"Stack": "Стек",
"System and Mixed Can Only be Used in Service Mode": "Система и смешанные могут использоваться только в сервисном режиме",
"System and Mixed Can Only be Used in Service Mode": "Стэк System и Mixed могут использоваться только в режиме системной службы",
"Device": "Имя устройства",
"Auto Route": "Автоматическая маршрутизация",
"Strict Route": "Строгий маршрут",
"Strict Route": "Строгая маршрутизация",
"Auto Detect Interface": "Автоопределение интерфейса",
"DNS Hijack": "DNS-перехват",
"MTU": "Максимальная единица передачи",
"Service Mode": "Режим сервиса",
"Service Mode Info": "Установите сервисный режим перед включением режима TUN. Процесс ядра, запущенный службой, может получить разрешение на установку виртуальной сетевой карты (режим TUN).",
"MTU": "MTU",
"Service Mode": "Режим системной службы",
"Service Mode Info": "Установите режим системной службы перед включением режима TUN. Процесс ядра, запущенный службой, может получить разрешение на установку виртуальной сетевой карты (режим TUN).",
"Current State": "Текущее состояние",
"pending": "Ожидающий",
"installed": "Установленный",
@ -216,7 +231,7 @@
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Информация: Пожалуйста, убедитесь, что сервис Clash Verge Service установлен и включен",
"Install": "Установить",
"Uninstall": "Удалить",
"Disable Service Mode": "Отключить режим обслуживания",
"Disable Service Mode": "Отключить режим системной службы",
"System Proxy": "Системный прокси",
"System Proxy Info": "Разрешить изменение настроек прокси-сервера операционной системы. Если разрешение не удастся, измените настройки прокси-сервера операционной системы вручную",
"System Proxy Setting": "Настройка системного прокси",
@ -226,30 +241,30 @@
"Disabled": "Отключено",
"Server Addr": "Адрес сервера: ",
"Not available": "Недоступно",
"Proxy Guard": "Защита прокси",
"Proxy Guard Info": "Включите эту функцию чтобы предотвратить изменение настроек прокси-сервера операционной системы другим программным обеспечением",
"Proxy Guard": "Proxy Guard",
"Proxy Guard Info": "Включите эту функцию чтобы предотвратить изменение настроек прокси-сервера операционной системы другим ПО",
"Guard Duration": "Период защиты",
"Always use Default Bypass": "Всегда использовать стандартное обходное решение",
"Use Bypass Check": "Используйте проверку обхода",
"Proxy Bypass": "Игнорирование прокси: ",
"Bypass": "Игнорирование: ",
"Proxy Bypass": "Игнорируемые адреса: ",
"Bypass": "Игнорируемые адреса: ",
"Use PAC Mode": "Используйте режим PAC",
"PAC Script Content": "Содержание сценария PAC",
"PAC URL": "Адрес PAC: ",
"Auto Launch": "Автозапуск",
"Silent Start": "Тихий запуск",
"Silent Start Info": "Запускать программу в фоновом режиме без отображения панели",
"TG Channel": "Канал Telegram",
"TG Channel": "Telegram-канал",
"Manual": "Документация",
"Github Repo": "GitHub репозиторий",
"Clash Setting": "Настройки Clash",
"Allow Lan": "Разрешить локальную сеть",
"Allow Lan": "Разрешить доступ из локальной сети",
"Network Interface": "Сетевой интерфейс",
"Ip Address": "IP адрес",
"Mac Address": "MAC адрес",
"IPv6": "IPv6",
"Unified Delay": "Общий задержка",
"Unified Delay Info": "Когда унифицированная задержка включена, будут выполнены два теста задержки, чтобы устранить различия в задержке между разными типами узлов, вызванные подтверждением соединения и т. д",
"Unified Delay": "Точная задержка",
"Unified Delay Info": "Когда унифицированная(точная) задержка включена, будут выполнены два теста задержки, чтобы устранить различия в задержке между разными типами узлов, вызванные подтверждением соединения и т. д",
"Log Level": "Уровень логов",
"Log Level Info": "Это действует только на файлы журнала ядра в служебном файле в каталоге журналов.",
"Port Config": "Настройка порта",
@ -259,32 +274,34 @@
"Http Port": "Порт Http(s)-прокси",
"Redir Port": "Порт прозрачного прокси Redir",
"Tproxy Port": "Порт прозрачного прокси Tproxy",
"External": "Внешний",
"External": "Внешний контроллер",
"External Controller": "Адрес прослушивания внешнего контроллера",
"Core Secret": "Секрет",
"Recommended": "Рекомендуется",
"Open URL": "Открыть URL",
"Replace host, port, secret with %host, %port, %secret": "Замените хост, порт, секрет на %host, %port, %secret",
"Support %host, %port, %secret": "Поддержка %host, %port, %secret",
"Clash Core": "Ядра Clash",
"Upgrade": "Обновлять",
"Restart": "Перезапуск",
"Open URL": "Перейти по адресу",
"Replace host, port, secret with %host, %port, %secret": "Замените хост, порт и секрет на %host, %port, %secret",
"Support %host, %port, %secret": "Поддерживаются %host, %port, %secret",
"Clash Core": "Ядро Clash",
"Upgrade": "Обновить",
"Restart": "Перезапустить",
"Release Version": "Официальная версия",
"Alpha Version": "Альфа-версия",
"Please Enable Service Mode": "Пожалуйста, сначала установите и включите режим обслуживания",
"Please Enable Service Mode": "Пожалуйста, сначала установите и включите режим системной службы",
"Please enter your root password": "Пожалуйста, введите ваш пароль root",
"Grant": "Предоставить",
"Open UWP tool": "Открыть UWP инструмент",
"Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",
"Update GeoData": "Обновление GeoData",
"Verge Setting": "Настройки Verge",
"Update GeoData": "Обновить GeoData",
"Verge Basic Setting": "Основные настройки Verge",
"Verge Advanced Setting": "Расширенные настройки Verge",
"Language": "Язык",
"Theme Mode": "Режим темы",
"Theme Mode": "Цветовая тема",
"theme.light": "Светлая",
"theme.dark": "Тёмная",
"theme.system": "Системная",
"Tray Click Event": "Событие щелчка в лотке",
"Tray Click Event": "Событие при щелчке по иконке в трее",
"Show Main Window": "Показать главное окно",
"Show Tray Menu": "Показать меню в трее",
"Copy Env Type": "Скопировать тип Env",
"Copy Success": "Скопировано",
"Start Page": "Главная страница",
@ -293,8 +310,8 @@
"Theme Setting": "Настройки темы",
"Primary Color": "Основной цвет",
"Secondary Color": "Вторичный цвет",
"Primary Text Color": "Основной текст",
"Secondary Text Color": "Вторичный текст",
"Primary Text": "Первичный текст",
"Secondary Text": "Вторичный текст",
"Info Color": "Информационный цвет",
"Warning Color": "Цвет предупреждения",
"Error Color": "Цвет ошибки",
@ -306,50 +323,53 @@
"Memory Usage": "Использование памяти",
"Memory Cleanup": "Нажмите, чтобы очистить память",
"Proxy Group Icon": "Иконка Группы прокси",
"Nav Icon": "Иконка навигации",
"Monochrome": "Монохромный",
"Colorful": "Полноцветный",
"Tray Icon": "Иконка лотка",
"Common Tray Icon": "Общий значок в лотке",
"System Proxy Tray Icon": "Значок системного прокси в лотке",
"Tun Tray Icon": "Значок туннеля в лотке",
"Miscellaneous": "Настройки Прочие",
"Nav Icon": "Иконки навигации",
"Monochrome": "Монохромные",
"Colorful": "Цветные",
"Tray Icon": "Иконка в трее",
"Common Tray Icon": "Общий значок в трее",
"System Proxy Tray Icon": "Значок системного прокси в трее",
"Tun Tray Icon": "Значок TUN в трее",
"Miscellaneous": "Расширенные настройки",
"App Log Level": "Уровень журнала приложения",
"Auto Close Connections": "Автоматическое закрытие соединений",
"Auto Close Connections Info": "Завершить установленные соединения при изменении выбора группы прокси или режима прокси",
"Auto Close Connections Info": "Закрыть установленные соединения при изменении выбора группы прокси или режима прокси",
"Auto Check Update": "Автоматическая проверка обновлений",
"Enable Builtin Enhanced": "Включить встроенные улучшения",
"Enable Builtin Enhanced Info": "Обработка совместимости для файла конфигурации",
"Proxy Layout Columns": "Количество столбцов в макете прокси",
"Auto Columns": "Авто колонки",
"Auto Log Clean": "Автоматическая очистка журналов",
"Auto Log Clean": "Автоматическая очистка логов",
"Never Clean": "Никогда не очищать",
"Retain _n Days": "Сохранять {{n}} дней",
"Default Latency Test": "Ссылка на тестирование задержки по умолчанию",
"Default Latency Test": "Ссылка на тест задержки",
"Default Latency Test Info": "Используется только для тестирования HTTP-запросов клиента и не влияет на файл конфигурации",
"Default Latency Timeout": "Таймаут задержки по умолчанию",
"Hotkey Setting": "Настройки клавиатурных сокращений",
"Hotkey Setting": "Настройки сочетаний клавиш",
"Enable Global Hotkey": "Включить глобальную горячую клавишу",
"open_or_close_dashboard": "Открыть/Закрыть панель управления",
"clash_mode_rule": "Режим правил",
"clash_mode_global": "Глобальный режим",
"clash_mode_direct": "Прямой режим",
"toggle_system_proxy": "Включить/Отключить системный прокси",
"toggle_tun_mode": "Включить/Отключить режим туннеля",
"toggle_tun_mode": "Включить/Отключить режим TUN",
"entry_lightweight_mode": "Вход в LightWeight Mode",
"Backup Setting": "Настройки резервного копирования",
"Backup Setting Info": "Поддерживает файлы конфигурации резервного копирования WebDAV",
"Runtime Config": "Используемый конфиг",
"Open Conf Dir": "Открыть папку приложения",
"Open Conf Dir Info": "Если программное обеспечение работает ненормально, сделайте резервную копию и удалите все файлы в этой папке, а затем перезапустите программное обеспечение",
"Open Conf Dir Info": "Если программное обеспечение работает неправильно, сделайте резервную копию и удалите все файлы в этой папке, а затем перезапустите ПО",
"Open Core Dir": "Открыть папку ядра",
"Open Logs Dir": "Открыть папку логов",
"Check for Updates": "Проверить обновления",
"Go to Release Page": "Перейти на страницу релизов",
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную",
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените файлы вручную",
"Break Change Update Error": "Это крупное обновление, которое не поддерживает обновление внутри приложения. Пожалуйста, удалите его и загрузите установочный файл вручную.",
"Open Dev Tools": "Открыть инструменты разработчика",
"Open Dev Tools": "Открыть Dev Tools",
"Export Diagnostic Info": "Экспорт диагностической информации",
"Export Diagnostic Info For Issue Reporting": "Экспорт диагностической информации для отчета об ошибке",
"Exit": "Выход",
"Verge Version": "Версия Verge",
"Verge Version": "Версия Clash Verge Rev",
"ReadOnly": "Только для чтения",
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
"Filter": "Фильтр",
@ -359,23 +379,24 @@
"Use Regular Expression": "Использовать регулярные выражения",
"Profile Imported Successfully": "Профиль успешно импортирован",
"Profile Switched": "Профиль изменен",
"Profile Reactivated": "Профиль повторно активирован",
"Profile Reactivated": "Профиль перезапущен",
"Only YAML Files Supported": "Поддерживаются только файлы YAML",
"Settings Applied": "Применены настройки",
"Settings Applied": "Настройки применены",
"Installing Service...": "Установка службы...",
"Service Installed Successfully": "Служба успешно установлена",
"Service Uninstalled Successfully": "Служба успешно удалена",
"Proxy Daemon Duration Cannot be Less than 1 Second": "Продолжительность работы прокси-демона не может быть меньше 1 секунды",
"Invalid Bypass Format": "Неверный формат обхода",
"Clash Port Modified": "Clash порт изменен",
"Clash Port Modified": "Порт Clash изменен",
"Port Conflict": "Конфликт портов",
"Restart Application to Apply Modifications": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
"External Controller Address Modified": "Изменен адрес внешнего контроллера",
"External Controller Address Modified": "Настройки внешнего контроллера изменены",
"Permissions Granted Successfully for _clash Core": "Разрешения успешно предоставлены для ядра {{core}}",
"Core Version Updated": "Обновлена версия ядра",
"Clash Core Restarted": "Clash ядра перезапущено",
"GeoData Updated": "GeoData Обновлена",
"Currently on the Latest Version": "В настоящее время используется последняя версия",
"Import subscription successful": "Импорт подписки успешно",
"Core Version Updated": "Ядро обновлено до последней версии",
"Clash Core Restarted": "Ядро перезапущено",
"GeoData Updated": "Файлы GeoData обновлены",
"Currently on the Latest Version": "Обновление не требуется",
"Import Subscription Successful": "Подписка успешно импортирована",
"WebDAV Server URL": "URL-адрес сервера WebDAV http(s)://",
"Username": "Имя пользователя",
"Password": "Пароль",
@ -410,12 +431,12 @@
"PAC File": "PAC файл",
"Web UI": "Веб-интерфейс",
"Hotkeys": "Горячие клавиши",
"Verge Mixed Port": "Смешанный порт Verge",
"Verge Socks Port": "Порт Verge Socks",
"Verge Redir Port": "Порт перенаправления Verge",
"Verge Tproxy Port": "Порт Verge Tproxy",
"Verge Mixed Port": "Mixed порт",
"Verge Socks Port": "Порт Socks",
"Verge Redir Port": "Порт Redir",
"Verge Tproxy Port": "Порт Tproxy",
"Verge Port": "Порт Verge",
"Verge HTTP Enabled": "HTTP Verge включен",
"Verge HTTP Enabled": "HTTP включен",
"WebDAV URL": "URL WebDAV",
"WebDAV Username": "Имя пользователя WebDAV",
"WebDAV Password": "Пароль WebDAV",
@ -432,9 +453,16 @@
"Rule Mode": "Режим правил",
"Global Mode": "Глобальный режим",
"Direct Mode": "Прямой режим",
"Enable Tray Speed": "Включить скорость в лотке",
"LightWeight Mode": "Облегченный режим",
"LightWeight Mode Info": "Закройте графический интерфейс и оставьте работать только ядро",
"Enable Tray Speed": "Показывать скорость в трее",
"Enable Tray Icon": "Показывать значок в трее",
"LightWeight Mode": "LightWeight Mode",
"LightWeight Mode Info": "Режим, в котором работает только ядро Clash, а графический интрефейс закрыт",
"LightWeight Mode Settings": "Настройки LightWeight Mode",
"Enter LightWeight Mode Now": "Войти в LightWeight Mode",
"Auto Enter LightWeight Mode": "Автоматический вход в LightWeight Mode",
"Auto Enter LightWeight Mode Info": "Автоматически включать LightWeight Mode, если окно закрыто определенное время",
"Auto Enter LightWeight Mode Delay": "Задержка включения LightWeight Mode",
"When closing the window, LightWeight Mode will be automatically activated after _n minutes": "При закрытии окна LightWeight Mode будет автоматически активирован через {{n}} минут",
"Config Validation Failed": "Ошибка проверки конфигурации подписки, проверьте файл конфигурации, изменения отменены, ошибка:",
"Boot Config Validation Failed": "Ошибка проверки конфигурации при запуске, используется конфигурация по умолчанию, проверьте файл конфигурации, ошибка:",
"Core Change Config Validation Failed": "Ошибка проверки конфигурации при смене ядра, используется конфигурация по умолчанию, проверьте файл конфигурации, ошибка:",
@ -443,12 +471,112 @@
"Script Missing Main": "Ошибка скрипта, изменения отменены",
"File Not Found": "Файл не найден, изменения отменены",
"Script File Error": "Ошибка файла скрипта, изменения отменены",
"Core Changed Successfully": "Ядро успешно сменено",
"Core Changed Successfully": "Ядро успешно изменено",
"Failed to Change Core": "Не удалось сменить ядро",
"Verge Basic Setting": "Основные настройки Verge",
"Verge Advanced Setting": "Расширенные настройки Verge",
"TUN requires Service Mode": "Режим TUN требует обслуживания",
"Install Service": "Установить службу",
"Installing Service...": "Установка службы...",
"Service Administrator Prompt": "Clash Verge требует прав администратора для переустановки системной службы"
"YAML Syntax Error": "Ошибка синтаксиса YAML, откат изменений",
"YAML Read Error": "Ошибка чтения YAML, откат изменений",
"YAML Mapping Error": "Ошибка YAML Mapping, откат изменений",
"YAML Key Error": "Ошибка ключа YAML, откат изменений",
"YAML Error": "Ошибка YAML, откат изменений",
"Merge File Syntax Error": "Ошибка синтаксиса Merge File, откат изменений",
"Merge File Mapping Error": "Ошибка сопоставления в Merge File, откат изменений",
"Merge File Key Error": "Ошибка ключа в Merge File, откат изменений",
"Merge File Error": "Ошибка Merge File, откат изменений",
"Validate YAML File": "Проверить YAML файл",
"Validate Merge File": "Проверить Merge File",
"Validation Success": "Файл успешно проверен",
"Validation Failed": "Проверка не удалась",
"Service Administrator Prompt": "Clash Verge требует прав администратора для переустановки системной службы",
"DNS Settings": "Настройки DNS",
"DNS Overwrite": "Переопределение настроек DNS",
"DNS Settings Warning": "Если вы не знакомы с этими настройками, пожалуйста, не изменяйте и не отключайте их",
"Enable DNS": "Включить DNS",
"DNS Listen": "Прослушивание DNS",
"Enhanced Mode": "Enhanced Mode",
"Fake IP Range": "Диапазон FakeIP",
"Fake IP Filter Mode": "FakeIP Filter Mode",
"Prefer H3": "Предпочитать H3",
"DNS DOH使用HTTP/3": "DNS DOH использует http/3",
"Respect Rules": "Приоритизировать правила",
"DNS连接遵守路由规则": "Соединения DNS следуют правилам маршрутизации",
"Use Hosts": "Использовать файл Hosts",
"Enable to resolve hosts through hosts file": "Включить разрешение хостов через файл Hosts",
"Use System Hosts": "Использовать системный файл Hosts",
"Enable to resolve hosts through system hosts file": "Включить разрешение хостов через системный файл Hosts",
"Direct Nameserver Follow Policy": "Прямой сервер имен следует политике",
"是否遵循nameserver-policy": "Следовать ли политике DNS-серверов",
"Default Nameserver": "DNS-сервер по умолчанию",
"Default DNS servers used to resolve DNS servers": "DNS-серверы по умолчанию, используемые для разрешения адресов серверов DNS",
"Nameserver": "DNS-сервер",
"List of DNS servers": "Список DNS-серверов, разделенных запятой",
"Fallback": "Fallback",
"List of fallback DNS servers": "Список резервных DNS-серверов, разделенных запятой",
"Proxy Server Nameserver": "Proxy Server Nameserver",
"Proxy Node Nameserver": "DNS-серверы для разрешения домена прокси-узлов",
"Direct Nameserver": "DNS-сервер для прямых соединений",
"Direct outbound Nameserver": "Список DNS-серверов для прямых соединений, разделенных запятой",
"Fake IP Filter": "Фильтр FakeIP",
"Domains that skip fake IP resolution": "Домены, которые пропускают разрешение FakeIP, разделенные запятой",
"Nameserver Policy": "Политика серверов имен",
"Domain-specific DNS server": "DNS-сервер, специфичный для домена, несколько серверов разделяются знаком ';'",
"Fallback Filter Settings": "Настройки фильтра Fallback",
"GeoIP Filtering": "Фильтрация GeoIP",
"Enable GeoIP filtering for fallback": "Включить фильтрацию GeoIP",
"GeoIP Code": "Код GeoIP",
"Fallback IP CIDR": "Fallback IP CIDR",
"IP CIDRs not using fallback servers": "Диапазоны IP-адресов, не использующие резервные серверы, разделенные запятой",
"Fallback Domain": "Fallback домены",
"Domains using fallback servers": "Домены, использующие резервные серверы, разделенные запятой",
"Enable Alpha Channel": "Включить альфа-канал",
"Alpha versions may contain experimental features and bugs": "Альфа-версии могут содержать экспериментальные функции и ошибки",
"Home Settings": "Настройки главной страницы",
"Profile Card": "Карточка профиля",
"Current Proxy Card": "Карточка текущего прокси",
"Network Settings Card": "Карточка настроек сети",
"Proxy Mode Card": "Карточка режима работы",
"Clash Mode Card": "Карточка режима Clash",
"Traffic Stats Card": "Карточка статистики по трафику",
"Clash Info Cards": "Информация о Clash",
"System Info Cards": "Информация о системе",
"Website Tests Card": "Карточка тестов доступности веб-сайтов",
"Traffic Stats": "Статистика по трафику",
"Website Tests": "Проверка доступности веб-сайтов",
"Clash Info": "Информация о Clash",
"Core Version": "Версия ядра",
"System Proxy Address": "Адрес системного прокси",
"Uptime": "Время работы",
"Rules Count": "Количество правил",
"System Info": "Информация о системе",
"OS Info": "Версия ОС",
"Running Mode": "Режим работы",
"Sidecar Mode": "Пользовательский режим",
"Last Check Update": "Последняя проверка обновлений",
"Click to import subscription": "Нажмите, чтобы импортировать подписку",
"Update subscription successfully": "Подписка успешно обновлена",
"Current Node": "Текущий сервер",
"No active proxy node": "Нет активного прокси-узла",
"Network Settings": "Настройки сети",
"Proxy Mode": "Режим работы",
"Group": "Группа",
"Proxy": "Прокси",
"IP Information Card": "Информация об IP",
"IP Information": "Информация об IP",
"Failed to get IP info": "Не удалось получить информацию об IP",
"ISP": "ISP",
"ASN": "ASN",
"ORG": "ORG",
"Location": "Location",
"Timezone": "Timezone",
"Auto refresh": "Автоматическое обновление через",
"Unlock Test": "Тест доступности веб-сайтов",
"Pending": "В ожидании",
"Yes": "Да",
"No": "Нет",
"Failed": "Ошибка",
"Completed": "Завершено",
"Disallowed ISP": "ISP заблокирован",
"Originals Only": "Только Originals",
"No (IP Banned By Disney+)": "Нет (IP забанен Disney+)",
"Unsupported Country": "Страна не поддерживается",
"Failed (Network Connection)": "Ошибка подключения"
}

View File

@ -27,6 +27,7 @@
"Proxies": "代理",
"Proxy Groups": "代理组",
"Proxy Provider": "代理集合",
"Proxy Count": "节点数量",
"Update All": "更新全部",
"Update At": "更新于",
"rule": "规则",
@ -201,12 +202,16 @@
"Tun Mode": "虚拟网卡模式",
"TUN requires Service Mode": "TUN 模式需要安装服务",
"Install Service": "安装服务",
"Install Service failed": "安装服务失败",
"Restart Core failed": "重启核心失败",
"Reset to Default": "重置为默认值",
"Tun Mode Info": "TUN虚拟网卡模式接管系统所有流量启用时无须打开系统代理",
"System Proxy Enabled": "系统代理已启用,您的应用将通过代理访问网络",
"System Proxy Disabled": "系统代理已关闭,建议大多数用户打开此选项",
"TUN Mode Enabled": "TUN 模式已启用,应用将通过虚拟网卡访问网络",
"TUN Mode Disabled": "TUN 模式已关闭,适用于特殊应用",
"TUN Mode Service Required": "TUN模式需要服务模式请先安装服务",
"TUN Mode Intercept Info": "TUN模式可以接管所有应用流量适用于特殊应用",
"TUN Mode Intercept Info": "TUN模式可以接管所有应用流量适用于特殊不遵循系统代理设置的应用",
"rule Mode Description": "基于预设规则智能判断流量走向,提供灵活的代理策略",
"global Mode Description": "所有流量均通过代理服务器,适用于需要全局科学上网的场景",
"direct Mode Description": "所有流量不经过代理节点但经过Clash内核转发连接目标服务器适用于需要通过内核进行分流的特定场景",
@ -250,6 +255,7 @@
"PAC Script Content": "PAC 脚本内容",
"PAC URL": "PAC 地址:",
"Auto Launch": "开机自启",
"Administrator mode does not support auto launch": "管理员模式不支持开机自启",
"Silent Start": "静默启动",
"Silent Start Info": "程序启动时以后台模式运行,不显示程序面板",
"TG Channel": "Telegram 频道",
@ -351,6 +357,7 @@
"clash_mode_direct": "直连模式",
"toggle_system_proxy": "打开/关闭系统代理",
"toggle_tun_mode": "打开/关闭 TUN 模式",
"entry_lightweight_mode": "进入轻量模式",
"Backup Setting": "备份设置",
"Backup Setting Info": "支持 WebDAV 备份配置文件",
"Runtime Config": "当前配置",
@ -451,6 +458,7 @@
"Global Mode": "全局模式",
"Direct Mode": "直连模式",
"Enable Tray Speed": "启用托盘速率",
"Enable Tray Icon": "启用托盘图标",
"LightWeight Mode": "轻量模式",
"LightWeight Mode Info": "关闭GUI界面仅保留内核运行",
"LightWeight Mode Settings": "轻量模式设置",
@ -482,8 +490,9 @@
"Validate Merge File": "验证覆写文件",
"Validation Success": "验证成功",
"Validation Failed": "验证失败",
"Service Administrator Prompt": "Clash Verge 需要使用管理员权限来重新安装系统服务",
"Service Administrator Prompt": "Clash Verge 需要管理员权限安装系统服务",
"DNS Settings": "DNS 设置",
"DNS settings saved": "DNS 设置已保存",
"DNS Overwrite": "DNS 覆写",
"DNS Settings Warning": "如果你不清楚这里的设置请不要修改,并保持 DNS 覆写开启",
"Enable DNS": "启用 DNS",
@ -546,9 +555,19 @@
"OS Info": "操作系统信息",
"Running Mode": "运行模式",
"Sidecar Mode": "用户模式",
"Administrator Mode": "管理员模式",
"Administrator + Service Mode": "管理员 + 服务模式",
"Last Check Update": "最后检查更新",
"Click to import subscription": "点击导入订阅",
"Update subscription successfully": "订阅更新成功",
"Update failed, retrying with Clash proxy...": "订阅更新失败,尝试使用 Clash 代理更新",
"Update with Clash proxy successfully": "使用 Clash 代理更新成功",
"Update failed even with Clash proxy": "使用 Clash 代理更新也失败",
"Profile creation failed, retrying with Clash proxy...": "订阅创建失败,尝试使用 Clash 代理创建",
"Profile creation succeeded with Clash proxy": "使用 Clash 代理创建订阅成功",
"Import failed, retrying with Clash proxy...": "订阅导入失败,尝试使用 Clash 代理导入",
"Profile Imported with Clash proxy": "使用 Clash 代理导入订阅成功",
"Import failed even with Clash proxy": "使用 Clash 代理导入订阅也失败",
"Current Node": "当前节点",
"No active proxy node": "暂无激活的代理节点",
"Network Settings": "网络设置",
@ -570,7 +589,6 @@
"No": "不支持",
"Failed": "测试失败",
"Completed": "检测完成",
"Bahamut Anime": "动画疯",
"Disallowed ISP": "不允许的 ISP",
"Originals Only": "仅限原创",
"No (IP Banned By Disney+)": "不支持IP被Disney+禁止)",

View File

@ -19,6 +19,7 @@ import {
ThemeModeProvider,
UpdateStateProvider,
} from "./services/states";
import { AppDataProvider } from "./providers/app-data-provider";
const mainElementId = "root";
const container = document.getElementById(mainElementId);
@ -51,9 +52,11 @@ createRoot(container).render(
<React.StrictMode>
<ComposeContextProvider contexts={contexts}>
<BaseErrorBoundary>
<BrowserRouter>
<Layout />
</BrowserRouter>
<AppDataProvider>
<BrowserRouter>
<Layout />
</BrowserRouter>
</AppDataProvider>
</BaseErrorBoundary>
</ComposeContextProvider>
</React.StrictMode>

View File

@ -249,8 +249,8 @@ const Layout = () => {
? {
borderRadius: "8px",
border: "1px solid var(--divider-color)",
width: "calc(100vw - 0px)",
height: "calc(100vh - 0px)",
width: "calc(100vw - 4px)",
height: "calc(100vh - 4px)",
}
: {},
]}

View File

@ -11,7 +11,6 @@ import {
} from "@mui/icons-material";
import { closeAllConnections } from "@/services/api";
import { useConnectionSetting } from "@/services/states";
import { useClashInfo } from "@/hooks/use-clash";
import { BaseEmpty, BasePage } from "@/components/base";
import { ConnectionItem } from "@/components/connection/connection-item";
import { ConnectionTable } from "@/components/connection/connection-table";
@ -25,10 +24,9 @@ import {
type SearchState,
} from "@/components/base/base-search-box";
import { BaseStyledSelect } from "@/components/base/base-styled-select";
import useSWRSubscription from "swr/subscription";
import { createSockette, createAuthSockette } from "@/utils/websocket";
import { useTheme } from "@mui/material/styles";
import { useVisibility } from "@/hooks/use-visibility";
import { useAppData } from "@/providers/app-data-provider";
const initConn: IConnections = {
uploadTotal: 0,
@ -40,13 +38,15 @@ type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[];
const ConnectionsPage = () => {
const { t } = useTranslation();
const { clashInfo } = useClashInfo();
const pageVisible = useVisibility();
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
const [match, setMatch] = useState(() => (_: string) => true);
const [curOrderOpt, setOrderOpt] = useState("Default");
// 使用全局数据
const { connections } = useAppData();
const [setting, setSetting] = useConnectionSetting();
const isTableLayout = setting.layout === "table";
@ -66,99 +66,37 @@ const ConnectionsPage = () => {
const [isPaused, setIsPaused] = useState(false);
const [frozenData, setFrozenData] = useState<IConnections | null>(null);
const { data: connData = initConn } = useSWRSubscription<
IConnections,
any,
"getClashConnections" | null
>(
clashInfo && pageVisible ? "getClashConnections" : null,
(_key, { next }) => {
const { server = "", secret = "" } = clashInfo!;
if (!server) {
console.warn("[Connections] 服务器地址为空,无法建立连接");
next(null, initConn);
return () => {};
}
console.log(`[Connections] 正在连接: ${server}/connections`);
// 设置较长的超时时间,确保连接可以建立
const s = createAuthSockette(`${server}/connections`, secret, {
timeout: 8000, // 8秒超时
onmessage(event) {
const data = JSON.parse(event.data) as IConnections;
next(null, (old = initConn) => {
const oldConn = old.connections;
const maxLen = data.connections?.length;
const connections: IConnectionsItem[] = [];
const rest = (data.connections || []).filter((each) => {
const index = oldConn.findIndex((o) => o.id === each.id);
if (index >= 0 && index < maxLen) {
const old = oldConn[index];
each.curUpload = each.upload - old.upload;
each.curDownload = each.download - old.download;
connections[index] = each;
return false;
}
return true;
});
for (let i = 0; i < maxLen; ++i) {
if (!connections[i] && rest.length > 0) {
connections[i] = rest.shift()!;
connections[i].curUpload = 0;
connections[i].curDownload = 0;
}
}
return { ...data, connections };
});
},
onerror(event) {
console.error("[Connections] WebSocket 连接错误", event);
// 报告错误但提供空数据避免UI崩溃
next(null, initConn);
},
onclose(event) {
console.log("[Connections] WebSocket 连接关闭", event);
},
onopen(event) {
console.log("[Connections] WebSocket 连接已建立");
},
});
return () => {
console.log("[Connections] 清理WebSocket连接");
try {
s.close();
} catch (e) {
console.error("[Connections] 关闭连接时出错", e);
}
};
},
);
// 使用全局连接数据
const displayData = useMemo(() => {
return isPaused ? (frozenData ?? connData) : connData;
}, [isPaused, frozenData, connData]);
if (!pageVisible) return initConn;
if (isPaused) {
return frozenData ?? {
uploadTotal: connections.uploadTotal,
downloadTotal: connections.downloadTotal,
connections: connections.data
};
}
return {
uploadTotal: connections.uploadTotal,
downloadTotal: connections.downloadTotal,
connections: connections.data
};
}, [isPaused, frozenData, connections, pageVisible]);
const [filterConn] = useMemo(() => {
const orderFunc = orderOpts[curOrderOpt];
let connections = displayData.connections.filter((conn) => {
let conns = displayData.connections.filter((conn) => {
const { host, destinationIP, process } = conn.metadata;
return (
match(host || "") || match(destinationIP || "") || match(process || "")
);
});
if (orderFunc) connections = orderFunc(connections);
if (orderFunc) conns = orderFunc(conns);
return [connections];
return [conns];
}, [displayData, match, curOrderOpt]);
const onCloseAll = useLockFn(closeAllConnections);
@ -172,13 +110,17 @@ const ConnectionsPage = () => {
const handlePauseToggle = useCallback(() => {
setIsPaused((prev) => {
if (!prev) {
setFrozenData(connData);
setFrozenData({
uploadTotal: connections.uploadTotal,
downloadTotal: connections.downloadTotal,
connections: connections.data
});
} else {
setFrozenData(null);
}
return !prev;
});
}, [connData]);
}, [connections]);
return (
<BasePage

View File

@ -37,7 +37,11 @@ import { BasePage } from "@/components/base";
import { ClashInfoCard } from "@/components/home/clash-info-card";
import { SystemInfoCard } from "@/components/home/system-info-card";
import { useLockFn } from "ahooks";
import { entry_lightweight_mode, openWebUrl, patchVergeConfig } from "@/services/cmds";
import {
entry_lightweight_mode,
openWebUrl,
patchVergeConfig,
} from "@/services/cmds";
import { TestCard } from "@/components/home/test-card";
import { IpInfoCard } from "@/components/home/ip-info-card";
@ -199,7 +203,7 @@ const HomeSettingsDialog = ({
);
};
const HomePage = () => {
export const HomePage = () => {
const { t } = useTranslation();
const { verge } = useVerge();
const { current, mutateProfiles } = useProfiles();
@ -260,7 +264,11 @@ const HomePage = () => {
header={
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={t("LightWeight Mode")} arrow>
<IconButton onClick={() => entry_lightweight_mode()} size="small" color="inherit">
<IconButton
onClick={async () => await entry_lightweight_mode()}
size="small"
color="inherit"
>
<HistoryEduOutlined />
</IconButton>
</Tooltip>
@ -314,7 +322,6 @@ const HomePage = () => {
title={t("Traffic Stats")}
icon={<SpeedOutlined />}
iconColor="secondary"
minHeight={280}
>
<EnhancedTrafficStats />
</EnhancedCard>

View File

@ -142,15 +142,36 @@ const ProfilePage = () => {
setLoading(true);
try {
// 尝试正常导入
await importProfile(url);
Notice.success(t("Profile Imported Successfully"));
setUrl("");
setLoading(false);
mutateProfiles();
await onEnhance(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
setLoading(false);
// 首次导入失败,尝试使用自身代理
const errmsg = err.message || err.toString();
Notice.info(t("Import failed, retrying with Clash proxy..."));
try {
// 使用自身代理尝试导入
await importProfile(url, {
with_proxy: false,
self_proxy: true
});
// 回退导入成功
Notice.success(t("Profile Imported with Clash proxy"));
setUrl("");
mutateProfiles();
await onEnhance(false);
} catch (retryErr: any) {
// 回退导入也失败
const retryErrmsg = retryErr?.message || retryErr.toString();
Notice.error(
`${t("Import failed even with Clash proxy")}: ${retryErrmsg}`,
);
}
} finally {
setDisabled(false);
setLoading(false);

View File

@ -16,6 +16,12 @@ const ProxyPage = () => {
const { data: clashConfig, mutate: mutateClash } = useSWR(
"getClashConfig",
getClashConfig,
{
revalidateOnFocus: false,
revalidateIfStale: true,
dedupingInterval: 1000,
errorRetryInterval: 5000
}
);
const { verge } = useVerge();

View File

@ -1,28 +1,27 @@
import useSWR from "swr";
import { useState, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Virtuoso, VirtuosoHandle } from "react-virtuoso";
import { Box } from "@mui/material";
import { getRules } from "@/services/api";
import { BaseEmpty, BasePage } from "@/components/base";
import RuleItem from "@/components/rule/rule-item";
import { ProviderButton } from "@/components/rule/provider-button";
import { BaseSearchBox } from "@/components/base/base-search-box";
import { useTheme } from "@mui/material/styles";
import { ScrollTopButton } from "@/components/layout/scroll-top-button";
import { useAppData } from "@/providers/app-data-provider";
const RulesPage = () => {
const { t } = useTranslation();
const { data = [] } = useSWR("getRules", getRules);
const { rules = [] } = useAppData();
const theme = useTheme();
const isDark = theme.palette.mode === "dark";
const [match, setMatch] = useState(() => (_: string) => true);
const virtuosoRef = useRef<VirtuosoHandle>(null);
const [showScrollTop, setShowScrollTop] = useState(false);
const rules = useMemo(() => {
return data.filter((item) => match(item.payload));
}, [data, match]);
const filteredRules = useMemo(() => {
return rules.filter((item) => match(item.payload));
}, [rules, match]);
const scrollToTop = () => {
virtuosoRef.current?.scrollTo({
@ -64,11 +63,11 @@ const RulesPage = () => {
<BaseSearchBox onSearch={(match) => setMatch(() => match)} />
</Box>
{rules.length > 0 ? (
{filteredRules.length > 0 ? (
<>
<Virtuoso
ref={virtuosoRef}
data={rules}
data={filteredRules}
style={{
flex: 1,
}}

View File

@ -0,0 +1,299 @@
import { createContext, useContext, useMemo } from "react";
import useSWR from "swr";
import useSWRSubscription from "swr/subscription";
import { getProxies, getConnections, getRules, getClashConfig, getProxyProviders, getRuleProviders } from "@/services/api";
import { getSystemProxy, getRunningMode, getAppUptime } from "@/services/cmds";
import { useClashInfo } from "@/hooks/use-clash";
import { createAuthSockette } from "@/utils/websocket";
import { useVisibility } from "@/hooks/use-visibility";
// 定义AppDataContext类型 - 使用宽松类型
interface AppDataContextType {
proxies: any;
clashConfig: any;
rules: any[];
sysproxy: any;
runningMode?: string;
uptime: number;
proxyProviders: any;
ruleProviders: any;
connections: {
data: any[];
count: number;
uploadTotal: number;
downloadTotal: number;
};
traffic: {up: number; down: number};
memory: {inuse: number};
refreshProxy: () => Promise<any>;
refreshClashConfig: () => Promise<any>;
refreshRules: () => Promise<any>;
refreshSysproxy: () => Promise<any>;
refreshProxyProviders: () => Promise<any>;
refreshRuleProviders: () => Promise<any>;
refreshAll: () => Promise<any>;
}
// 创建上下文
const AppDataContext = createContext<AppDataContextType | null>(null);
// 全局数据提供者组件
export const AppDataProvider = ({ children }: { children: React.ReactNode }) => {
const { clashInfo } = useClashInfo();
const pageVisible = useVisibility();
// 基础数据 - 中频率更新 (5秒)
const { data: proxiesData, mutate: refreshProxy } = useSWR(
"getProxies",
getProxies,
{
refreshInterval: 5000,
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
const { data: clashConfig, mutate: refreshClashConfig } = useSWR(
"getClashConfig",
getClashConfig,
{
refreshInterval: 5000,
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
// 提供者数据
const { data: proxyProviders, mutate: refreshProxyProviders } = useSWR(
"getProxyProviders",
getProxyProviders,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
const { data: ruleProviders, mutate: refreshRuleProviders } = useSWR(
"getRuleProviders",
getRuleProviders,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
// 低频率更新数据
const { data: rulesData, mutate: refreshRules } = useSWR(
"getRules",
getRules,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
const { data: sysproxy, mutate: refreshSysproxy } = useSWR(
"getSystemProxy",
getSystemProxy,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
const { data: runningMode } = useSWR(
"getRunningMode",
getRunningMode,
{
revalidateOnFocus: false,
suspense: false,
errorRetryCount: 3
}
);
// 高频率更新数据 (1秒)
const { data: uptimeData } = useSWR(
"appUptime",
getAppUptime,
{
refreshInterval: 1000,
revalidateOnFocus: false,
suspense: false
}
);
// 连接数据 - 使用WebSocket实时更新
const { data: connectionsData = { connections: [], uploadTotal: 0, downloadTotal: 0 } } =
useSWRSubscription(
clashInfo && pageVisible ? "connections" : null,
(_key, { next }) => {
if (!clashInfo || !pageVisible) return () => {};
const { server = "", secret = "" } = clashInfo;
if (!server) return () => {};
const socket = createAuthSockette(`${server}/connections`, secret, {
timeout: 5000,
onmessage(event) {
try {
const data = JSON.parse(event.data);
// 处理连接数据,计算当前上传下载速度
next(null, (prev: any = { connections: [], uploadTotal: 0, downloadTotal: 0 }) => {
const oldConns = prev.connections || [];
const newConns = data.connections || [];
// 计算当前速度
const processedConns = newConns.map((conn: any) => {
const oldConn = oldConns.find((old: any) => old.id === conn.id);
if (oldConn) {
return {
...conn,
curUpload: conn.upload - oldConn.upload,
curDownload: conn.download - oldConn.download
};
}
return { ...conn, curUpload: 0, curDownload: 0 };
});
return {
...data,
connections: processedConns
};
});
} catch (err) {
console.error("[Connections] 解析数据错误:", err);
}
},
onerror() {
next(null, { connections: [], uploadTotal: 0, downloadTotal: 0 });
}
});
return () => socket.close();
}
);
// 流量和内存数据 - 通过WebSocket获取实时流量数据
const { data: trafficData = { up: 0, down: 0 } } = useSWRSubscription(
clashInfo && pageVisible ? "traffic" : null,
(_key, { next }) => {
if (!clashInfo || !pageVisible) return () => {};
const { server = "", secret = "" } = clashInfo;
if (!server) return () => {};
const socket = createAuthSockette(`${server}/traffic`, secret, {
onmessage(event) {
try {
const data = JSON.parse(event.data);
next(null, data);
} catch (err) {
console.error("[Traffic] 解析数据错误:", err);
}
}
});
return () => socket.close();
}
);
const { data: memoryData = { inuse: 0 } } = useSWRSubscription(
clashInfo && pageVisible ? "memory" : null,
(_key, { next }) => {
if (!clashInfo || !pageVisible) return () => {};
const { server = "", secret = "" } = clashInfo;
if (!server) return () => {};
const socket = createAuthSockette(`${server}/memory`, secret, {
onmessage(event) {
try {
const data = JSON.parse(event.data);
next(null, data);
} catch (err) {
console.error("[Memory] 解析数据错误:", err);
}
}
});
return () => socket.close();
}
);
// 提供统一的刷新方法
const refreshAll = async () => {
await Promise.all([
refreshProxy(),
refreshClashConfig(),
refreshRules(),
refreshSysproxy(),
refreshProxyProviders(),
refreshRuleProviders()
]);
};
// 聚合所有数据
const value = useMemo(() => ({
// 数据
proxies: proxiesData,
clashConfig,
rules: rulesData || [],
sysproxy,
runningMode,
uptime: uptimeData || 0,
// 提供者数据
proxyProviders: proxyProviders || {},
ruleProviders: ruleProviders || {},
// 连接数据
connections: {
data: connectionsData.connections || [],
count: connectionsData.connections?.length || 0,
uploadTotal: connectionsData.uploadTotal || 0,
downloadTotal: connectionsData.downloadTotal || 0
},
// 实时流量数据
traffic: trafficData,
memory: memoryData,
// 刷新方法
refreshProxy,
refreshClashConfig,
refreshRules,
refreshSysproxy,
refreshProxyProviders,
refreshRuleProviders,
refreshAll
}), [
proxiesData, clashConfig, rulesData, sysproxy,
runningMode, uptimeData, connectionsData,
trafficData, memoryData, proxyProviders, ruleProviders,
refreshProxy, refreshClashConfig, refreshRules, refreshSysproxy,
refreshProxyProviders, refreshRuleProviders
]);
return (
<AppDataContext.Provider value={value}>
{children}
</AppDataContext.Provider>
);
};
// 自定义Hook访问全局数据
export const useAppData = () => {
const context = useContext(AppDataContext);
if (!context) {
throw new Error("useAppData必须在AppDataProvider内使用");
}
return context;
};

View File

@ -3,13 +3,9 @@ import { getClashInfo } from "./cmds";
import { invoke } from "@tauri-apps/api/core";
import { useLockFn } from "ahooks";
let axiosIns: AxiosInstance = null!;
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (axiosIns && !force) return axiosIns;
let instancePromise: Promise<AxiosInstance> = null!;
async function getInstancePromise() {
let server = "";
let secret = "";
@ -26,13 +22,22 @@ export const getAxios = async (force: boolean = false) => {
if (info?.secret) secret = info?.secret;
} catch {}
axiosIns = axios.create({
const axiosIns = axios.create({
baseURL: `http://${server}`,
headers: secret ? { Authorization: `Bearer ${secret}` } : {},
timeout: 15000,
});
axiosIns.interceptors.response.use((r) => r.data);
return axiosIns;
}
/// initialize some information
/// enable force update axiosIns
export const getAxios = async (force: boolean = false) => {
if (!instancePromise || force) {
instancePromise = getInstancePromise();
}
return instancePromise;
};
/// Get Version

View File

@ -1,4 +1,3 @@
import dayjs from "dayjs";
import { invoke } from "@tauri-apps/api/core";
import { Notice } from "@/components/base";
@ -37,10 +36,10 @@ export async function saveProfileFile(index: string, fileData: string) {
return invoke<void>("save_profile_file", { index, fileData });
}
export async function importProfile(url: string) {
export async function importProfile(url: string, option?: IProfileOption) {
return invoke<void>("import_profile", {
url,
option: { with_proxy: true },
option: option || { with_proxy: true },
});
}
@ -320,15 +319,39 @@ export const getAppUptime = async () => {
return invoke<number>("get_app_uptime");
};
// 安装/重装系统服务
// 安装系统服务
export const installService = async () => {
return invoke<void>("install_service");
};
// 卸载系统服务
export const uninstallService = async () => {
return invoke<void>("uninstall_service");
};
// 重装系统服务
export const reinstallService = async () => {
return invoke<void>("reinstall_service");
};
// 修复系统服务
export const repairService = async () => {
return invoke<void>("repair_service");
};
export const entry_lightweight_mode = async () => {
return invoke<void>("entry_lightweight_mode");
}
};
export const exit_lightweight_mode = async () => {
return invoke<void>("exit_lightweight_mode");
}
};
export const isAdmin = async () => {
try {
return await invoke<boolean>("is_admin");
} catch (error) {
console.error("检查管理员权限失败:", error);
return false;
}
};

View File

@ -738,6 +738,7 @@ interface IVergeConfig {
sysproxy_tray_icon?: boolean;
tun_tray_icon?: boolean;
enable_tray_speed?: boolean;
enable_tray_icon?: boolean;
enable_tun_mode?: boolean;
enable_auto_light_weight_mode?: boolean;
auto_light_weight_minutes?: number;

View File

@ -36,11 +36,96 @@ export default defineConfig({
entry: "monaco-yaml/yaml.worker",
},
],
globalAPI: false,
}),
],
build: {
outDir: "../dist",
emptyOutDir: true,
target: "es2020",
minify: "terser",
chunkSizeWarningLimit: 4000,
reportCompressedSize: false,
sourcemap: false,
cssCodeSplit: true,
cssMinify: true,
rollupOptions: {
treeshake: {
preset: "recommended",
moduleSideEffects: (id) => !/\.css$/.test(id),
tryCatchDeoptimization: false,
},
output: {
compact: true,
experimentalMinChunkSize: 30000,
dynamicImportInCjs: true,
manualChunks(id) {
if (id.includes("node_modules")) {
// Monaco Editor should be a separate chunk
if (id.includes("monaco-editor")) return "monaco-editor";
// React-related libraries (react, react-dom, react-router-dom, etc.)
if (
id.includes("react") ||
id.includes("react-dom") ||
id.includes("react-router-dom") ||
id.includes("react-transition-group") ||
id.includes("react-error-boundary") ||
id.includes("react-hook-form") ||
id.includes("react-markdown") ||
id.includes("react-virtuoso")
) {
return "react";
}
// Utilities chunk: group commonly used utility libraries
if (
id.includes("axios") ||
id.includes("lodash-es") ||
id.includes("dayjs") ||
id.includes("js-base64") ||
id.includes("js-yaml") ||
id.includes("cli-color") ||
id.includes("nanoid")
) {
return "utils";
}
// Tauri-related plugins: grouping together Tauri plugins
if (
id.includes("@tauri-apps/api") ||
id.includes("@tauri-apps/plugin-clipboard-manager") ||
id.includes("@tauri-apps/plugin-dialog") ||
id.includes("@tauri-apps/plugin-fs") ||
id.includes("@tauri-apps/plugin-global-shortcut") ||
id.includes("@tauri-apps/plugin-notification") ||
id.includes("@tauri-apps/plugin-process") ||
id.includes("@tauri-apps/plugin-shell") ||
id.includes("@tauri-apps/plugin-updater")
) {
return "tauri-plugins";
}
// Material UI libraries (grouped together)
if (
id.includes("@mui/material") ||
id.includes("@mui/icons-material") ||
id.includes("@mui/lab") ||
id.includes("@mui/x-data-grid")
) {
return "mui";
}
// Small vendor packages
const pkg = id.match(/node_modules\/([^\/]+)/)?.[1];
if (pkg && pkg.length < 8) return "small-vendors";
// Large vendor packages
return "large-vendor";
}
},
},
},
},
resolve: {
alias: {