Compare commits

..

2266 Commits

Author SHA1 Message Date
Tunglies
f6b9ccc774 ```
nothing changes
```
2025-03-11 01:15:37 +08:00
Tunglies
d001bbc2eb ```
No changes made
```
2025-03-11 01:12:20 +08:00
Tunglies
2d1807cd4d feat: enhance tag retrieval and detection for stable and pre-release versions 2025-03-11 01:09:25 +08:00
Tunglies
c6dce2d6cb feat: increase tag retrieval limit and refine alpha tag identification 2025-03-11 00:56:35 +08:00
Tunglies
3c7768b379 feat: add support for alpha updates and enhance updater functionality 2025-03-11 00:24:31 +08:00
Tunglies
d470bf2dd9 rm: label issues workflow 2025-03-10 01:01:12 +08:00
Tunglies
b5e8a1ddb5 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
08f3357d9a feat: add scheduled workflow and commit change check to alpha build 2025-03-09 17:42:13 +08:00
Tunglies
107c54921d refactor: update MihomoManager to handle traffic WebSocket URL and authorization 2025-03-09 14:44:15 +08:00
wonfen
ca1f7a2b31 feat: enhance latency test logging and error handling 2025-03-09 04:22:34 +08:00
wonfen
95e8fe38f2 refactor: improve webSocket connection handling and error recovery 2025-03-09 04:22:01 +08:00
wonfen
cd6eb26a41 feat: add icon file content check to prevent saving failed downloads 2025-03-09 02:34:57 +08:00
Tunglies
9f2c4b4f35 refactor: migrate clash client info retrieval to MihomoManager 2025-03-09 00:40:16 +08:00
Tunglies
adbf98cb15 rm: clash-api dead code 2025-03-09 00:29:14 +08:00
Tunglies
c9b3fec09c refactor: improve request handling and response processing in MihomoManager 2025-03-09 00:27:12 +08:00
Tunglies
364fb16b7b refactor: streamline clash delay test and improve API interactions 2025-03-09 00:04:48 +08:00
Tunglies
634ed9cb46 refactor: streamline clash mode handling and improve API interactions 2025-03-08 22:41:14 +08:00
wonfen
cddb117402 feat: optimize icon cache download and DNS view styling 2025-03-08 13:31:20 +08:00
wonfen
bd6c4e00c4 refactor: restructure DNS setting logic 2025-03-08 11:25:00 +08:00
wonfen
bbcfb4901f refactor: refine DNS handling to follow config and merge settings 2025-03-08 03:34:25 +08:00
wonfen
230a0eeb64 fix: regex for first character matching 2025-03-07 14:07:10 +08:00
wonfen
c4c49f61df perf: faster app exit 2025-03-07 13:44:07 +08:00
TianHua Liu
6d5c4f86d3
fix: Notice @ts-ignore (#2896)
thx
2025-03-07 12:46:30 +08:00
0XE
59f22b9cd2
feat: Add x-data-grid component localization (#2925)
thx
2025-03-07 12:45:56 +08:00
wonfen
6caf2cd057 test: remove entitlements.plist items 2025-03-07 06:49:24 +08:00
Tunglies
684128ca68 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
6925adc66f feat: enable dns settings by default 2025-03-06 14:40:35 +08:00
wonfen
e1e9d91d86 feat: add dns settings 2025-03-06 14:30:43 +08:00
Christine.
881c136b1e
add: missing i18n text. (#2917) 2025-03-05 22:28:28 +08:00
0XE
5bc699b19c
fix: ISSUES #2727 (#2913) 2025-03-05 14:20:07 +08:00
Christine.
52ba1c5a30
workflow: remove renaming behavior. (#2909) 2025-03-05 11:41:15 +08:00
Tunglies
817d68546e
feat(mihomo): refactor MihomoManager for global access and improve proxy retrieval (#2906) 2025-03-05 10:58:54 +08:00
wonfen
c0f5c231ad feat: add admin permission prompt for system service 2025-03-05 10:22:57 +08:00
Tunglies
9677d8670b test: crate_mihomo_api additional headers 2025-03-05 08:14:37 +08:00
Tunglies
b01630d31c fix: extern controler api secert with headers 2025-03-05 08:09:42 +08:00
Tunglies
f771f4720f
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
f0becb5003
fix: issues #2838 (#2886) 2025-03-04 20:46:17 +08:00
Tunglies
405e8df825
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
49fc7ff7ca fix: sync system proxy status indicator with hotkey 2025-03-04 06:57:42 +08:00
wonfen
fde9c8aaee fix: correct type declarations for getProxiesInner and getProxyProviders 2025-03-04 02:26:26 +08:00
Tunglies
8f95c28050 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
2cd97c7785
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
6e48781687 Revert "refactor: improve proxy group UI and spacing (#2835)"
This reverts commit b31cbda61507f68473762e5b5e86e8ddcab7b3c6.
2025-03-03 14:47:05 +08:00
wonfen
dd510b2ee9 feat: show service mode installation prompts in user mode 2025-03-03 14:42:31 +08:00
Tunglies
fadae3f7dc fix: speed format runns by docs 2025-03-03 11:36:21 +08:00
Tunglies
fdf8a6b2ba Revert "style: update box styling in settings page for improved layout (#2857)"
This reverts commit 4382df7951aefc973ec6f9d9cba0fafc88134e93.
2025-03-03 08:00:10 +08:00
Tunglies
4382df7951
style: update box styling in settings page for improved layout (#2857) 2025-03-03 06:38:32 +08:00
Tunglies
6cb7d48530
feat: add export diagnostic info functionality (#2856) 2025-03-03 05:58:12 +08:00
wonfen
9d74b93ee0 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
d370868222 fix: windows/linux runtime crash 2025-03-03 02:27:45 +08:00
Christine.
9278f9e193
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
a708db6f2b Revert "chore: alpha ci should remove old builds"
This reverts commit d2c4a44297d98ca3b7df308f2d2422c88e9aa1aa.
2025-03-03 00:16:17 +08:00
Tunglies
d2c4a44297 chore: alpha ci should remove old builds 2025-03-02 23:46:20 +08:00
Tunglies
3a24623e4b feat: fish env export support 2025-03-02 23:20:10 +08:00
Tunglies
d2084d8d21 version: 2.1.3 a 2025-03-02 20:46:53 +08:00
Tunglies
d9b4ca91e7 version: 2.1.3 alpha 2025-03-02 20:43:54 +08:00
Tunglies
13da571ad2
version: 2.1.3 alpha (#2851)
* version: 2.1.3 alpha
2025-03-02 19:08:27 +08:00
Tunglies
662d60c0f5 chore: automatically label issues (#2844) 2025-03-02 18:50:31 +08:00
Tunglies
368095d2d4
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
496aeeb06d
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
b31cbda615
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
7898f3a119 fix: macos frameless title 2025-03-02 04:30:58 +08:00
wonfen
ba0a291d97 feat: refactor logging system into a global service 2025-03-02 04:20:38 +08:00
wonfen
b3e4defc0f fix: remove macos window title 2025-03-02 04:09:50 +08:00
wonfen
1ae0ad8a0e fix: add bottom padding to prevent jitter 2025-03-02 04:08:13 +08:00
Tunglies
79b20694c7
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
1ad4941ed8
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
b3fd44d165
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.
391a494af6
fix: build failed with Windows (#2825) 2025-03-01 19:52:42 +08:00
Tunglies
623d075ab8 add: issues type template 2025-03-01 19:12:12 +08:00
Tunglies
5d70b77316
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
f5f54c0f0e
fix: macos dock display icon and text (#2818) 2025-03-01 02:29:40 -08:00
wonfen
91b0b1f279 perf: improve scrolling performance and interaction in proxy group list 2025-03-01 08:31:31 +08:00
wonfen
e0fa1f3efe fix: v2 action file rename 2025-03-01 08:04:31 +08:00
wonfen
b00abf3337 chore: updatelog 2025-03-01 03:45:03 +08:00
wonfen
a5d846ce4e feat: add exit status check in core config validation 2025-03-01 03:39:13 +08:00
wonfen
7743c42dd1 Release - 2.1.2 真·臻 2025-03-01 01:49:01 +08:00
Christine.
3f3dab9495
add: i18n text for settings page. (#2815) 2025-03-01 01:36:36 +08:00
Tunglies
2dc9672b20 feat: better setting UI layout (#2814) 2025-03-01 01:29:23 +08:00
wonfen
2c90d1ca69 chore: v2 updater 2025-03-01 01:29:23 +08:00
Tunglies
1555e2910d feat: better setting layout ui (#2809) 2025-03-01 01:29:23 +08:00
Tunglies
19b6bd35f5 feat: quiting when enable tun mode no more blocking system network (#2805) 2025-03-01 01:29:23 +08:00
wonfen
3d4d60edc8 test updater 2025-03-01 01:29:23 +08:00
wonfen
db0a000ecb chore: updater v2 Capabilities 2025-02-28 10:55:53 +08:00
wonfen
24ff48b41c fix: updater 2025-02-28 09:33:50 +08:00
wonfen
18a67d0b2f chore: updatelog 2025-02-28 07:34:56 +08:00
wonfen
e23af1ad58 feat: add ability to check service version and auto-reinstall 2025-02-28 06:45:30 +08:00
wonfen
70af392059 feat: enhance updater script with comprehensive platform support and logging 2025-02-27 17:19:04 +08:00
Christine.
d255df23ee
fix: connection details (#2778) 2025-02-27 14:53:17 +08:00
wonfen
1317a8b2db feat: enhance merge config validation and error handling 2025-02-27 14:49:55 +08:00
wonfen
4cac118442 style: UI tweak 2025-02-27 13:04:46 +08:00
wonfen
2ce43ccd23 Release - 2.1.1 臻fix 2025-02-27 03:18:23 +08:00
Tunglies
d23b2949d8 feat: macos display colorful icon with speed rate (#2771) 2025-02-27 01:51:52 +08:00
wonfen
3471476ba2 feat: improve file type detection for better script recognition 2025-02-26 15:59:19 +08:00
wonfen
cee61e5619 chore: downgrade Tauri updater plugin and add i18n for core switching 2025-02-26 15:04:47 +08:00
wonfen
5026e2bade feat: optimize hotkey behavior and window management logic 2025-02-26 11:03:50 +08:00
wonfen
83f005c256 fix: CI 2025-02-26 09:52:15 +08:00
wonfen
80843c5ee3 Release 2.1.0 - 臻 2025-02-26 08:36:02 +08:00
wonfen
ee00defe43 feat: enhance script validation and error handling 2025-02-26 05:21:14 +08:00
wonfen
7696504d97 feat: Add window state monitoring and auto-save in real-time 2025-02-26 00:36:02 +08:00
wonfen
7a0e38a1b4 perf: Improve config validation error messages and handling 2025-02-25 13:47:29 +08:00
wonfen
bc5d577553 chore: revise translation 2025-02-24 23:26:04 +08:00
wonfen
cfed2b7236 chore: Change default TUN stack from 'mixed' to 'gvisor' 2025-02-24 23:10:09 +08:00
Christine.
e34e6654bc
add: i18n text for config check (#2750) 2025-02-24 22:23:42 +08:00
wonfen
d6b85f1a01 fix: remove node related group info when deleting a node 2025-02-24 11:40:28 +08:00
wonfen
16af7b53cb fix: menu switching issue 2025-02-24 11:37:23 +08:00
wonfen
befc856207 feat: Improve core change configuration validation and error handling 2025-02-24 07:34:03 +08:00
wonfen
5ab8e7a7c7 feat: Enhance configuration validation and error handling during app startup 2025-02-24 06:21:32 +08:00
zhaoyuan
443bfa5928
feat: 通过添加CLASH_VERGE_REV_IP环境变量的方式,修改复制环境变量按钮的IP (#2734)
Co-authored-by: zymouse <zymouse@pixmoving.net>
2025-02-24 03:42:40 +08:00
wonfen
52627575ff 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.
39973f2d24
feat: auto rename alpha version (#2740) 2025-02-23 02:30:15 +08:00
wonfen
2b01857d15 chore: Disable automatic alpha tag update in workflow 2025-02-20 14:25:58 +08:00
wonfen
2650aa845f refactor: Optimize proxy rendering and layout calculation 2025-02-20 14:21:55 +08:00
wonfen
8400a61bf0 refactor: Simplify tray icon event handling across platforms 2025-02-20 07:03:28 +08:00
wonfen
763b6b9003 chore: Update Node.js version and remove updater file generation 2025-02-20 02:55:47 +08:00
wonfen
053f414f3c feat: Add system proxy status indicators 2025-02-19 13:56:22 +08:00
wonfen
2defa2cc56 refactor: Simplify log data management and improve search functionality 2025-02-19 13:06:15 +08:00
wonfen
bb1dbfcfc3 fix: connections page traffic calculation 2025-02-19 02:24:21 +08:00
Tunglies
1378068a30
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
5d9dce7d10 style: tweak 2025-02-18 11:40:41 +08:00
wonfen
acb66a3012 feat: Add persistent column width settings for connection table & process filtering 2025-02-18 11:04:22 +08:00
wonfen
054f902cc6 refactor: Remove unused routing select component from settings page 2025-02-18 09:04:23 +08:00
wonfen
4333153a59 feat: Improve letter item hover interaction with debounce mechanism 2025-02-18 09:00:03 +08:00
wonfen
19e623d7c2 feat: Add pause/resume functionality for connection tracking 2025-02-18 08:14:09 +08:00
wonfen
2dd646537e fix: resolve deprecated warnings in console 2025-02-18 07:10:28 +08:00
wonfen
1ece079978 perf: Improve proxy list interaction and UI responsiveness 2025-02-18 05:54:16 +08:00
wonfen
0d8f779634 feat: Enhance alphabet selector with dynamic tooltip and scrolling 2025-02-18 01:55:44 +08:00
wonfen
a7ffc9fc38 refactor: Improve proxy data update mechanism with optimistic UI and error handling 2025-02-18 00:37:55 +08:00
wonfen
932b29e66f fix: Refine TypeScript types for proxy groups component 2025-02-17 16:27:06 +08:00
wonfen
6be7a3b94c feat: Enhance proxy groups with Initials navigation and performance optimizations 2025-02-17 16:07:46 +08:00
wonfen
8e8dd1ec03 feat: Optimize tray speed rate rendering and update logic 2025-02-17 15:08:19 +08:00
wonfen
772f3268ce feat: Improve Virtuoso list rendering for proxy groups 2025-02-17 14:30:21 +08:00
wonfen
a179591ac2 feat: Add persistent scroll position for proxy groups 2025-02-17 14:24:33 +08:00
wonfen
d071e5971f fix: alpha build win webview pack rename issue 2025-02-17 12:06:57 +08:00
wonfen
a9032f5f20 chore: Update updater artifacts configuration 2025-02-17 11:22:14 +08:00
Akioe Yu
0190702616 fix: check script error (#2453)
(cherry picked from commit 97bde64fd6516019a190d52f05af54ade623d57c)
2025-02-17 03:10:19 +08:00
wonfen
c35bf38420 chore: Add macOS-specific test for format_bytes_speed function 2025-02-17 03:10:03 +08:00
wonfen
89d20e564a Revert "perf: Improve kernel management logic & add more dev mode logs"
This reverts commit 18c7ed1ccc15b98e4873a276ac75e27276ab9d65.
2025-02-15 05:51:46 +08:00
Christine.
3c24ff3afc
add: i18n text for lightweight mode (#2692) 2025-02-13 14:27:12 +08:00
Tunglies
daf0398750
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
6b14c2b763 feat: add lightweight mode 2025-02-13 02:18:17 +08:00
wonfen
5bf2f9b8ed fix(macos): add missing required dependencies 2025-02-12 15:06:42 +08:00
wonfen
10a2655288 fix: remove unused imports to resolve compile warnings 2025-02-12 14:35:49 +08:00
wonfen
ac07397818 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
30d061d00f fix(tray): resolve blank icon issue on Windows and optimize creation logic 2025-02-12 13:56:33 +08:00
Christine.
360d8a5143
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
70e0a5adc8 refactor: Improve tray icon and event handling across platforms & unify click behavior 2025-02-11 14:48:31 +08:00
wonfen
ef52f81494 fix: app crash without a default global_hotkey vaule 2025-02-11 01:50:48 +08:00
wonfen
e5c1de3ad3 fix: deprecated CLI warnings 2025-02-11 01:05:51 +08:00
wonfen
576fd700ae feat: Add independent alpha update channel 2025-02-11 00:53:44 +08:00
wonfen
476df65a1b fix: try to fix linux CI updater problem 2025-02-11 00:53:44 +08:00
Christine.
bd0a4863a8
add: i18n text (#2675) 2025-02-10 00:29:43 -08:00
wonfen
d25fbc05e2 perf: Optimize kernel shutdown speed & logic 2025-02-10 13:18:00 +08:00
wonfen
b8a0b6f1f4 fix: crash caused by global_hotkey 2025-02-10 12:39:07 +08:00
wonfen
18c7ed1ccc perf: Improve kernel management logic & add more dev mode logs 2025-02-09 14:19:15 +08:00
Tunglies
b14db06955
perf: imporve clash mode switching performance on the main window (#2667) 2025-02-09 07:45:46 +08:00
Tunglies
215dcee3f1
feat: option to enable global hotkey (#2665) 2025-02-09 07:45:22 +08:00
Tunglies
37e5c22a5a
fix: duplicate checked tray menu when profile name are same (#2660) 2025-02-08 09:36:04 +08:00
wonfen
bb0b6c3f77 feat: Optimize UI layout and page hierarchy 2025-02-07 09:13:30 +08:00
wonfen
a37df46ce7 perf: optimize node latency refresh rate for faster updates 2025-02-06 07:58:42 +08:00
wonfen
73fbec2514 chore: update deps 2025-02-05 14:19:43 +08:00
Tunglies
f66fa08b2c
Feature: Switch Proxy Profile from Tray Menu (#2644) 2025-02-05 08:52:47 +08:00
wonfen
bae606bc9d fix: restore hotkey functionality after silent startup 2025-02-02 11:37:10 +08:00
wonfen
1a2d5e988a feat: allow users to customize enhanced-mode and fakeip-range in tun mode by wonfen 2025-02-02 05:12:16 +08:00
wonfen
d20bd62b90 style: refine tray speed display 2025-02-02 03:43:15 +08:00
wonfen
204bf43b9a fix: windows rounded corners 2025-01-19 09:47:43 +08:00
wonfen
3ea0d20e2c add donation 2025-01-18 16:08:24 +08:00
wonfen
6d527a1cdb release 2.0.3 2025-01-16 03:30:07 +08:00
白铭骢 (Mingcong Bai)
9c1ece754e
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
9070260d41
chore: update 2025-01-14 12:56:08 +08:00
huzibaca
cd8df52aad
revert: update deps 2025-01-14 12:41:52 +08:00
huzibaca
fa86efcdfb
chore: avoid duplicate updates when tray rate is off 2025-01-14 11:48:43 +08:00
huzibaca
1a61fab79a
feat: maoos tray speed can be closed 2025-01-13 20:48:25 +08:00
huzibaca
41a27641df
fix: fix: try to fix the language pack issue(2) 2025-01-13 17:09:38 +08:00
huzibaca
a39696151d
fix: windows tray icon color not updated 2025-01-13 13:49:56 +08:00
huzibaca
4a4ca5c409
chore: update husky/pre-commit 2025-01-13 13:03:46 +08:00
huzibaca
5bff7ea469
fix: tray tooltip not updating 2025-01-13 13:01:12 +08:00
huzibaca
f2cc116ff9
chore: update 2025-01-13 12:41:07 +08:00
huzibaca
80a18c9172
chore: update deps 2025-01-12 23:10:15 +08:00
huzibaca
a56732e0a3
fix: try to fix the language pack issue 2025-01-12 22:22:06 +08:00
huzibaca
9655f7712b
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
13b63b5d96
fix: fixes #2502 2025-01-11 12:55:20 +08:00
lucidhz
c5989d2735
fix: put_configs response add detail error message (#2492)
感谢pr
2025-01-05 01:27:12 -08:00
huzibaca
fb4032d6ce
fix: modify the external control access key, the tray rate display is abnormal 2025-01-02 15:27:28 +08:00
huzibaca
a29c2d4b14
chore: update deps 2025-01-02 15:02:07 +08:00
huzibaca
aa7e911c63
chore: update 2025-01-01 08:25:31 +08:00
huzibaca
eeff4d41f4
feat: the tray displays the shortcut keys that have been set 2025-01-01 08:14:15 +08:00
huzibaca
086f023ebc
chore: update 2024-12-31 11:11:29 +08:00
huzibaca
b70336c026
fix: unused code 2024-12-31 08:08:52 +08:00
huzibaca
7d84279370
fix: syntax issues 2024-12-31 04:50:12 +08:00
huzibaca
1b2f1b6106
Merge branch 'feat-macos-spped-rate-icon' into dev 2024-12-31 04:43:54 +08:00
huzibaca
fb41c915cc
feat: macos system tray addition rate display 2024-12-31 04:42:55 +08:00
blagodaren
bab291a60b fix: small refactor 2024-12-28 13:57:30 +03:00
blagodaren
8461046a4f fix: add more lang and lang fix for tray 2024-12-28 10:05:30 +03:00
blagodaren
fc925ea032 fix: add system language sup 2024-12-28 08:12:46 +03:00
huzibaca
542baf9d69
fix: fixes #2460 2024-12-28 06:05:09 +08:00
huzibaca
3f02859203
chore:add macos entitlements.list 2024-12-27 04:38:34 +08:00
huzibaca
af97bd15a9
chore: update 2024-12-27 02:42:35 +08:00
huzibaca
2c081e5a04
chore: secret is empty and no parameters are passed 2024-12-25 02:15:06 +08:00
huzibaca
2d2521e434
chore: update 2024-12-24 06:03:23 +08:00
huzibaca
d9291d4f79
refactor: when updating the tray, the logic is split to improve performance. 2024-12-24 04:52:14 +08:00
huzibaca
0669f7a10b
chore: after saving the configuration file, restart the core 2024-12-24 02:22:46 +08:00
huzibaca
f0d953ff59
chore: update default_bypass
1. add 172.29.0.0/16
2024-12-23 06:06:46 +08:00
huzibaca
4f6ca40811
fix: turn off window shadow fixer, fixes #2425 2024-12-23 04:36:41 +08:00
huzibaca
47848099be
chore: update 2024-12-15 01:14:16 +08:00
huzibaca
f839d3bc88
fix: fixer #2346 2024-12-14 17:30:58 +08:00
huzibaca
800f994b10
chore: update deps 2024-12-14 16:21:16 +08:00
Langning Chen
b149084e39
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
e6580f2f05
chorea: update deps 2024-12-10 22:36:13 +08:00
huzibaca
c5c840d378
feat: improve system bypass settings 2024-12-10 14:37:11 +08:00
huzibaca
2e0be4b426
chore: enable default proxy bypass and hide custom bypass settings 2024-12-10 13:59:13 +08:00
huzibaca
460d72f392
chore: update deps(tauri-plugin-autostart) 2024-12-08 22:10:15 +08:00
huzibaca
aa18c4870d
chore: deeplink uses the latest API 2024-12-08 15:54:46 +08:00
huzibaca
61533646ad
chore: update deps 2024-12-08 09:58:59 +08:00
huzibaca
b42d13f573
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
0e3b631118 chore: change fakeip range 2024-12-06 15:34:41 +08:00
huzibaca
38745d4513
chore:set the sysproxy_rs version to a fixed version 2024-12-06 15:03:04 +08:00
huzibaca
d22b37c7bf
fix: extension script dns is overwritten fixer #2235 2024-12-06 12:35:09 +08:00
Myles Mo
d233a84362
feat: add nullshell env variable (#2285) 2024-12-04 19:01:13 +08:00
huzibaca
589324b582
fix: shift hotkey conversion fixer #2278 2024-12-04 13:19:56 +08:00
huzibaca
c11efcb9be
chore: update version 2024-12-04 00:30:46 +08:00
huzibaca
6197249377
fix: set fontLigatures to false fixer #2267 2024-12-04 00:28:33 +08:00
lollapalooza
c71e18e97e
feat: add bypass check feature (#2272) 2024-12-03 16:18:07 +08:00
Christine.
f400f900e6
fix: #2126 (#2233) 2024-12-03 16:03:21 +08:00
wonfen
ae5b2cfb79 release 2.0.2 2024-12-01 11:00:09 +08:00
huzibaca
0bb8786ef2
fix: resource file initialization failed 2024-12-01 09:38:29 +08:00
huzibaca
f7d5be774d
fix: resolve service permission failed(2) 2024-11-30 10:45:53 +08:00
huzibaca
c0a0b82fa6
chore: update 2024-11-30 10:38:15 +08:00
huzibaca
277ef51375
fix: resolve service permission failed 2024-11-30 10:29:16 +08:00
huzibaca
67b1cf9e1b
feat: user uploaded icons can use templates, provided they are monochrome icons fixer #2213 2024-11-30 09:49:43 +08:00
huzibaca
4f797eb7b8
chore: update 2024-11-30 07:04:09 +08:00
huzibaca
29ccabc054
refactor: update_core_config, simplify logic and delete invalid notifications 2024-11-30 05:43:59 +08:00
yyhhyy
857574cbc1
🐛 fix: Dynamically set IPv6 DNS configuration based on existing config (#2198)
thanks
2024-11-30 00:55:15 +08:00
wonfen
acc97f28b5 chore: change mac tun dns 2024-11-29 11:10:41 +08:00
huzibaca
02e32aec41
chore: update 2024-11-29 03:46:37 +08:00
huzibaca
fe0fdc5603
chore: remove debug code 2024-11-28 14:44:13 +08:00
Yu-Haifeng
66941a18c0
fix: macOS tray icon not changed when use system proxy or tun mode (#2176) 2024-11-28 14:42:05 +08:00
huzibaca
f772f92b88
fix: the deadlock caused by incorrect call of window_state due to document error 2024-11-28 12:54:55 +08:00
huzibaca
d80db8c91e
chore: clean startup registry keys for older versions(2) 2024-11-28 06:40:44 +08:00
huzibaca
a1e67820c7
chore: clean startup registry keys for older versions 2024-11-28 05:34:56 +08:00
wonfen
ebe0cfbb0c chore: set tun default stack 2024-11-28 04:22:13 +08:00
huzibaca
59c52199e3
chore: update 2024-11-27 14:27:16 +08:00
wonfen
148def4c0f release 2.0.2 2024-11-27 12:41:51 +08:00
huzibaca
3177ea0f4d
chore: update 2024-11-27 11:34:52 +08:00
huzibaca
7d65ce847a
fix: when tun is closed, the full profile configuration is not restored 2024-11-27 10:35:42 +08:00
huzibaca
2ac27fcfa7
refactor: backup implementation
1. timeouts can be set for different operations
2. performance optimization
2024-11-27 07:34:34 +08:00
huzibaca
373c8ec0e5
chore: update 2024-11-27 07:09:03 +08:00
huzibaca
7df5ca1912
fix: linux build failed 2024-11-27 05:54:48 +08:00
huzibaca
d8dc60ff0d
fix: fixer #2158 2024-11-27 05:47:08 +08:00
huzibaca
56deafee61
chore: update version 2024-11-27 05:09:46 +08:00
huzibaca
4c29850e1c
fix: build failed. #2156 2024-11-27 05:07:02 +08:00
huzibaca
90bf5e0782
chore: timeout adjusted to 15 seconds 2024-11-26 15:09:58 +08:00
huzibaca
c2d07ce4c1
fix: add scroll bar and scroll to top button to the test page . fixer #2118 2024-11-26 14:44:43 +08:00
wonfen
564d81fd31 style: adjust 2024-11-26 12:11:20 +08:00
huzibaca
f8b7b82ba9
chore: remove window title 2024-11-26 10:11:55 +08:00
huzibaca
71cb2a97c0
fix: kernel-caused silent mode failure to start windows 2024-11-26 09:00:01 +08:00
huzibaca
1cc6472002
chore: update 2024-11-26 05:46:34 +08:00
huzibaca
9b2c80494d
refactor: Implement using third-party libraries 2024-11-26 04:51:19 +08:00
huzibaca
4ccc2a2961
chore: update 2024-11-26 03:30:15 +08:00
huzibaca
17a8dfb58a
fix: file drag and drop import cannot be used 2024-11-26 03:07:25 +08:00
wonfen
184b588f20 style: Increased light color contrast to prevent blurring on some displays 2024-11-26 01:05:30 +08:00
huzibaca
87c4ebe0da
chore: replace mui grid with mui grid2 2024-11-25 12:51:13 +08:00
wonfen
18a0a15e16 update change log 2024-11-25 06:46:29 +08:00
huzibaca
c8fd62f388
fix: Try to fix vcruntime duplicate installation 2024-11-25 06:33:47 +08:00
huzibaca
97f03de295
fix: update failed(updater.install called before updater.download) 2024-11-25 05:27:26 +08:00
huzibaca
2c0a5666fc
fix: when restoring webdav, the current username, password and url are not preserved 2024-11-25 03:41:35 +08:00
huzibaca
5c3f0d5b60
fix: password failed due to character escaping 2024-11-25 02:48:56 +08:00
huzibaca
746763ed34
fix: remove comments from svg icons to prevent front-end crashes. fixer #2093 2024-11-25 01:58:02 +08:00
wonfen
e3800a575f fix: fakeip dns 2024-11-25 01:50:30 +08:00
huzibaca
c78c936762
chore: replace mui grid with mui grid2 2024-11-25 01:34:18 +08:00
huzibaca
ea5d6f9c46
fix: tun allocates the wrong private network segment, causing conflicts. 2024-11-25 01:06:51 +08:00
wonfen
97f8022276 release 2.0.1 2024-11-24 09:00:20 +08:00
huzibaca
d718bd9141
fix: If an older version of the executable exists, delete it(2) 2024-11-24 08:25:46 +08:00
huzibaca
8ddd48eda1
feat: try to use vscode first, if not found then use system default app 2024-11-24 08:20:00 +08:00
huzibaca
3e20404959
fix: If an older version of the executable exists, delete it 2024-11-24 01:24:51 +08:00
huzibaca
dde7ead751
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
be719680b0
fix: windows cannot open yaml file 2024-11-23 19:46:53 +08:00
wonfen
d6f5a79ac9 fix: release workflow 2024-11-23 12:36:16 +08:00
wonfen
a3140f48b4 release 2.0.0 2024-11-23 11:34:17 +08:00
huzibaca
335883f9f0
chore: set the request timeout to 3 seconds 2024-11-23 06:50:28 +08:00
huzibaca
f2b1b88242
chore: remove notes 2024-11-23 06:47:43 +08:00
huzibaca
4163ee484c
chore: update 2024-11-23 06:42:05 +08:00
huzibaca
a14c3f81b0
fix: restore the window state first, then set the window size 2024-11-23 06:39:25 +08:00
huzibaca
f08d2c998d
chore: update 2024-11-23 06:22:46 +08:00
huzibaca
1b0cf938d9
chore: add debug codes & logs(2) 2024-11-23 06:20:13 +08:00
huzibaca
03d714f2d4
chore: add debug codes & logs 2024-11-23 05:43:13 +08:00
huzibaca
4feef53cb0
chore: set the request timeout to 5 seconds 2024-11-22 14:15:31 +08:00
huzibaca
397e6a9d57
feat: added scroll top button for agent and rule pages 2024-11-22 09:22:44 +08:00
huzibaca
9df2ceba84
chore: remove debug code 2024-11-22 09:08:25 +08:00
huzibaca
1b65dc7256
fix: macOS DNS restore failed 2024-11-22 03:09:39 +08:00
huzibaca
4ffadab4c5
chore: hide DNS cache file 2024-11-22 02:42:55 +08:00
huzibaca
7ed90ff7f8
fix: husky - install command is DEPRECATED 2024-11-21 12:06:52 +08:00
huzibaca
ec79126b4a
chore: remove unused code 2024-11-21 11:44:19 +08:00
huzibaca
ee13bb559e
chore: remove unused code 2024-11-21 11:24:19 +08:00
huzibaca
ffbc0cdff4
fix: password should not be trimmed 2024-11-21 11:14:40 +08:00
huzibaca
801b79b728
chore: remove unused code 2024-11-21 11:13:11 +08:00
huzibaca
cd1d719a92
fix: serde::json passing IVerge to the front end without deserialization 2024-11-21 06:01:56 +08:00
huzibaca
cf3848f15d
chore: update 2024-11-20 07:37:30 +08:00
huzibaca
df5d43283b
chore: remove notes 2024-11-20 07:37:03 +08:00
huzibaca
91f0f9f65e
feat: encryption configuration properties 2024-11-20 07:27:42 +08:00
huzibaca
708e5d1941
fix: auto launch does not worki 2024-11-20 03:52:19 +08:00
huzibaca
8773117e50
chore: update 2024-11-20 01:15:03 +08:00
huzibaca
8840e63c1c
fix: exit_app does not work 2024-11-20 01:04:55 +08:00
huzibaca
4e2d9d6acd
fix: windows cannot save window state(2) 2024-11-20 00:27:53 +08:00
huzibaca
94259f9515
fix: windows cannot save window state 2024-11-19 23:32:32 +08:00
huzibaca
5b0ff01cbf
chore: update deps 2024-11-19 13:35:57 +08:00
huzibaca
3ae1109a31
chore: format 2024-11-19 13:28:17 +08:00
wonfen
f50b9655ae feat: improve set dns logic 2024-11-19 11:38:23 +08:00
huzibaca
081daee53f
chore: update web:build comman, use tsc --noEmit 2024-11-19 04:42:37 +08:00
huzibaca
95123aceb5
feat: add logger highlighting, support regular and case matching 2024-11-19 04:10:10 +08:00
huzibaca
2a7b22c96f
feat: add logger highlighting 2024-11-19 03:29:44 +08:00
huzibaca
c1b15490c1
fix: log pause button not working 2024-11-19 02:56:58 +08:00
huzibaca
f8cb84a706
feat: logger support all level filters 2024-11-19 01:40:45 +08:00
huzibaca
063f9034a1
chore: update deps 2024-11-18 23:51:43 +08:00
huzibaca
9c1b2ff89c
chore: remove unused code 2024-11-18 23:48:02 +08:00
Christine.
44c05d9fd9
fix: field error, #2044 (#2045) 2024-11-18 07:34:57 -08:00
huzibaca
dd3c149e98
refactor: remove useSWRSubscription and use useEffect 2024-11-18 16:41:17 +08:00
huzibaca
0623fe4dc3
chore: remove unused code 2024-11-18 08:16:31 +08:00
huzibaca
2746ff68c8
feat: Log level status is saved to local storage 2024-11-18 08:14:21 +08:00
huzibaca
62eb070c1b
refactor: use zustand store, rewrite log clearing logic 2024-11-18 06:48:23 +08:00
huzibaca
3b8147b6ad
chore: typo warn -> warning 2024-11-18 06:01:51 +08:00
huzibaca
221b732472
refactor: logger fetch logic 2024-11-18 05:58:06 +08:00
huzibaca
132641f269
fix: the CJS build of Vite's Node API is deprecated, part 2 2024-11-18 01:21:00 +08:00
huzibaca
d8cf585fc5
fix: the CJS build of Vite's Node API is deprecated 2024-11-18 01:07:16 +08:00
Christine.
7f0d9952aa
chore: Replace test URL to support iPv4&iPv6 (#2033) 2024-11-18 00:20:21 +08:00
huzibaca
415b4879f1
fix: webdav list interface compatibility issue 2024-11-17 23:57:28 +08:00
huzibaca
7e5d6ef9b6
fix: webdav refreshes data and clears the original data when an error occurs. 2024-11-17 23:50:34 +08:00
huzibaca
ff297957c7
chore: update 2024-11-17 01:01:36 +08:00
huzibaca
7489f5e62d
chore: update 2024-11-17 00:46:35 +08:00
huzibaca
3575c16326
fix: fixes #1968 2024-11-16 23:09:10 +08:00
wonfen
04f4f5f713 release rc.7 2024-11-16 10:15:50 +08:00
huzibaca
eb78af7c77
chore: remove debug code 2024-11-16 07:35:34 +08:00
huzibaca
114d5ab541
fix: fixes #1940 2024-11-16 06:08:49 +08:00
huzibaca
f13140e39e
fix: When the shortcut key closes the window, the window is minimized 2024-11-16 04:46:20 +08:00
huzibaca
97717c648e
fix: windows arm64 vsruntime is not installed 2024-11-16 03:12:11 +08:00
huzibaca
ec6705eaf4
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
1e11eff811
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
13fb8c5d7d
chore: update 2024-11-15 17:19:14 +08:00
huzibaca
e87e06dd37
chore: unified code format 2024-11-14 03:21:18 +08:00
huzibaca
b9bb79b4dc
fix: call parameter error 2024-11-14 03:16:32 +08:00
wonfen
d0f907f349 chore: update info & i18n 2024-11-14 03:01:37 +08:00
huzibaca
867c83b84c
chore: update dep(tauri-bubild) 2024-11-13 21:49:53 +08:00
huzibaca
632d389411
chore: update deps 2024-11-13 21:39:55 +08:00
huzibaca
b4df1b6e42
feat: add refresh button 2024-11-13 00:53:52 +08:00
huzibaca
176620e3bb
fix: InputProps is deprecated 2024-11-13 00:30:30 +08:00
huzibaca
c37f22dc65
fix: mui grid has been deprecated 2024-11-13 00:21:22 +08:00
huzibaca
9d9cf27460
fix: application restart, window status not saved 2024-11-12 22:49:08 +08:00
huzibaca
6e8def3ef7
fix: syntax issues caused by upgrading mui5 2024-11-12 20:05:28 +08:00
huzibaca
ec0d872f11
chore: update 2024-11-12 19:44:57 +08:00
huzibaca
9e4a8708e7
fix: import error caused by dependency upgrade 2024-11-12 19:35:56 +08:00
huzibaca
a681fdeee1
fix: undefined error 2024-11-12 19:34:53 +08:00
huzibaca
8763a76475
chore: update deps 2024-11-12 19:06:04 +08:00
wonfen
4306fba997 release rc.6 2024-11-12 03:49:21 +08:00
huzibaca
3759239dac
feat: add webdav backup 2024-11-12 02:55:02 +08:00
huzibaca
4ec0b1d6e4
chore: update 2024-11-09 12:13:50 +08:00
huzibaca
6271726f07
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
26dd097962 update: Readme 2024-11-09 09:00:53 +08:00
wonfen
e5740579f4 fix: translation 2024-11-09 08:59:30 +08:00
huzibaca
ca97e3a3e6
chore: update 2024-11-09 06:56:58 +08:00
huzibaca
d3f6822080
chore: update 2024-11-09 05:50:51 +08:00
huzibaca
9d9a6dfddb
chore: update 2024-11-08 23:42:00 +08:00
huzibaca
c3e24d7b96
chore: update 2024-11-08 21:46:15 +08:00
huzibaca
20d163cf3a
Merge branch 'feat-add-unified-delay' into fix-migrate-tauri2-errors 2024-11-06 13:56:36 +08:00
huzibaca
cf90f3abc9
chore: update 2024-11-06 13:52:36 +08:00
huzibaca
8871660f0e
chore: update 2024-11-06 10:07:02 +08:00
huzibaca
97a030c22e
fix: @use rules must be written before any other rules. 2024-11-06 09:48:31 +08:00
huzibaca
8a6ce0f0db
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
285b6b9287
chore: update 2024-11-05 21:40:02 +08:00
huzibaca
3515cc8e63
chore: update 2024-11-05 21:35:17 +08:00
huzibaca
b0132c9718
chore: update 2024-11-05 18:38:50 +08:00
huzibaca
ef84c31761
chore: update 2024-11-05 18:30:28 +08:00
huzibaca
4953d4b4d0
chore: add tooltip 2024-11-05 17:39:59 +08:00
huzibaca
2833718c90
feat: add unified delay 2024-11-05 16:24:58 +08:00
huzibaca
ab7775e1ef
fix: wrong window state save point 2024-11-04 09:53:40 +08:00
huzibaca
a5c4562f59
chore: update deps 2024-11-02 10:25:38 +08:00
huzibaca
00bd5b0e1e
chore: update 2024-11-02 07:27:08 +08:00
huzibaca
b66c07bd96
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
d8d0a59371
chore: update 2024-11-01 06:03:01 +08:00
The1111mp
953153ea1a
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
fc03ce1247
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
08bf70681c
feat: use tauri_plugin_window_state 2024-10-30 18:52:53 +08:00
huzibaca
00bc6a6301
revert: feat: use tauri-plugin-persisted-scope 2024-10-30 18:28:55 +08:00
huzibaca
c5916cf5ec
feat: use tauri-plugin-persisted-scope 2024-10-30 16:37:47 +08:00
huzibaca
4213a5fad1
chore: window hide replaces window minimize 2024-10-30 13:51:58 +08:00
wonfen
17fc9cf0eb release: rc.5 2024-10-30 13:08:37 +08:00
wonfen
eb1a1b3786 chore: update bug report template 2024-10-30 12:59:19 +08:00
huzibaca
dce72a16f0
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
7550c2321c
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
f0c4360210
chore: restore linux custom close button 2024-10-30 06:30:49 +08:00
huzibaca
d10323701a
fix: performance issues caused by closing windows on mac 2024-10-30 06:27:29 +08:00
huzibaca
94c437c3e3
chore: update 2024-10-29 10:15:07 +08:00
huzibaca
202fb19ab1
chore: update 2024-10-29 10:13:53 +08:00
huzibaca
2bb3498602
chore: disable websocket logging by default to reduce performance consumption. 2024-10-29 09:19:21 +08:00
huzibaca
9d476d7add
fix incorrect usage of useCustomTheme 2024-10-29 09:07:08 +08:00
huzibaca
014829e69a
chore: optimised the logic of dns processing 2024-10-28 13:09:55 +08:00
huzibaca
a127cd6444
fix: dns not restored when exiting the app 2024-10-28 00:24:57 +08:00
Christine.
486ffbdc08
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
526c5bdd91
fix: unused variable 2024-10-27 10:19:42 +08:00
huzibaca
db11651f85
chore: update 2024-10-27 06:47:16 +08:00
huzibaca
4ce7d9f7a9
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
df3c296850
Revert "chore: update resource"
This reverts commit e3c52781f34b9bb921ac590ff4d672d1f1d5ec06.
2024-10-27 05:10:26 +08:00
huzibaca
5ef9220175
chore: update 2024-10-27 05:06:42 +08:00
huzibaca
e3c52781f3
chore: update resource 2024-10-27 04:46:24 +08:00
huzibaca
1cb1946497
chore: update deps 2024-10-27 01:24:13 +08:00
huzibaca
9e486e6836
chore: update 2024-10-26 19:23:49 +08:00
wonfen
e5dc4a55b0 release rc.4 2024-10-26 12:05:09 +08:00
huzibaca
76d04805a4
fix:public DNS not set in macos, tun+fake-ip 2024-10-26 08:55:00 +08:00
huzibaca
652a20c523
chore: update 2024-10-26 01:44:06 +08:00
huzibaca
6c2af1bef5
chore: update 2024-10-25 21:25:06 +08:00
huzibaca
ba2aa9d6e7
chore: update 2024-10-25 14:13:30 +08:00
huzibaca
a1c72fe780
chore: update 2024-10-25 14:12:27 +08:00
huzibaca
197f9fd964
fix: the tray icon does not display the correct colours on macos. 2024-10-25 13:58:22 +08:00
wonfen
fc2c10bb3f chore: add mac sun ico 2024-10-25 12:27:22 +08:00
wonfen
d7dd797494 release 2.0rc3 2024-10-24 12:52:20 +08:00
huzibaca
4f954657a1
chore: update 2024-10-24 11:14:18 +08:00
huzibaca
3eddcafeb6
Revert "chore: global mode doesn't show proxy groups, use placeholder images instead"
This reverts commit 3df4718709c49084a635ab190892d14b1744574b.
2024-10-24 07:44:21 +08:00
huzibaca
3df4718709
chore: global mode doesn't show proxy groups, use placeholder images instead 2024-10-24 07:30:30 +08:00
huzibaca
b5fddf8fa1
chore: update 2024-10-24 06:58:21 +08:00
huzibaca
fdc8a83f25
fix: proxy view display error 2024-10-24 06:54:27 +08:00
huzibaca
e79121e46a
refactor: the logic of profiles activation 2024-10-24 05:02:47 +08:00
huzibaca
aab71e55de
fix: restart app failure 2024-10-24 02:54:57 +08:00
huzibaca
caf4ee6863
fix: tun mode switch is not effective 2024-10-24 02:16:28 +08:00
huzibaca
30b0c45539
refactor: IRuntime::patch_config() 2024-10-23 10:34:14 +08:00
huzibaca
2ab75db9c9
chore: update 2024-10-23 09:26:14 +08:00
huzibaca
f9009de894
chore: update 2024-10-23 05:48:01 +08:00
huzibaca
da8ed1ac5a
fix: failed to install service on macos 2024-10-23 04:46:47 +08:00
huzibaca
afde7e4a44
feat: add windows uninstall script 2024-10-22 04:46:08 +08:00
huzibaca
3e94adb4b1
feat: add linux uninstall script 2024-10-22 03:17:04 +08:00
huzibaca
5c577e533f
fix: debian executable file has no permissions 2024-10-22 02:37:36 +08:00
huzibaca
c64b9d5a2b
chore: update 2024-10-22 01:34:26 +08:00
huzibaca
545ea16ae8
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
bf245a4ee1
fix: pac url error ,fixes #1889 2024-10-21 22:52:10 +08:00
wonfen
b28fc26ae6 chore: update bug_report template 2024-10-21 11:30:07 +08:00
huzibaca
750dfdb3a8
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
5b93c2b907
chore: update 2024-10-21 04:35:21 +08:00
wonfen
bbd72a79cb chore: update to 2.0 rc2 2024-10-21 03:15:02 +08:00
huzibaca
babf5d840c
chore: replace sudo with pkexec execution 2024-10-21 02:31:43 +08:00
huzibaca
90091db3b1
chore: update 2024-10-21 02:17:41 +08:00
huzibaca
ef03dc12bb
chore: update 2024-10-21 01:35:28 +08:00
huzibaca
a35014f0b5
chore: update 2024-10-21 01:30:13 +08:00
huzibaca
abb0a049a8
chore: update 2024-10-21 01:12:52 +08:00
huzibaca
3de18e2a86
chore: update 2024-10-21 00:50:43 +08:00
huzibaca
399bbd56ec
chore: update deps 2024-10-21 00:45:06 +08:00
huzibaca
38999d284a
chore: update 2024-10-21 00:34:28 +08:00
huzibaca
b61b63d100
fix: failed to start system proxy with PAC mode 2024-10-20 23:13:23 +08:00
huzibaca
0dc92fbc99
chore: update deps 2024-10-20 22:35:16 +08:00
huzibaca
1cb7cd8859
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
199700cf09
fix: mac commad+q global hijack is not released 2024-10-20 06:06:47 +08:00
huzibaca
ee78b7898b
chore: try to fix service not started on linux 2024-10-20 04:48:42 +08:00
huzibaca
a47ded7cf2
chore: update 2024-10-18 22:50:03 +08:00
huzibaca
770f031b8b
fix: try to fix service not started on linux 2024-10-18 07:08:34 +08:00
wonfen
18139c7114 chore: adjust translation 2024-10-17 05:09:00 +08:00
huzibaca
4a64654f49
chore: update deps 2024-10-16 22:49:24 +08:00
huzibaca
377ff279b8
chore: update 2024-10-16 02:55:23 +08:00
huzibaca
d5d39e1c2a
fix: system proxy cannot be closed on mac 2024-10-16 02:52:48 +08:00
huzibaca
f7b1da7557
chore: update 2024-10-16 01:55:22 +08:00
huzibaca
5e8c6c6b23
chore : update version 2024-10-16 01:53:33 +08:00
huzibaca
64634ef07b
chore: t emporarily cancel window closure and change to minimize 2024-10-16 01:53:16 +08:00
huzibaca
7de2474611
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
ea2b6a9ad8
fix: path escape issue 2024-10-15 02:35:50 +08:00
wonfen
6ee7be7cae chore: update changelog and texts 2024-10-15 02:32:25 +08:00
huzibaca
12690ed464
chore: remove windows esc shortcut key 2024-10-14 13:45:26 +08:00
huzibaca
90361242ac
chore: update 2024-10-13 03:01:32 +08:00
huzibaca
cd8daa929a
chore: update deps 2024-10-13 02:55:35 +08:00
huzibaca
a07c579497
chore: adjust tun default parameters 2024-10-13 02:42:37 +08:00
huzibaca
e78f619be1
fox: external-controller cors error 2024-10-11 22:01:57 +08:00
huzibaca
5e7ff36e8e
chore: complete field identifier 2024-10-11 14:33:11 +08:00
huzibaca
e7e0caaa41
Merge branch 'main' into fix-migrate-tauri2-errors 2024-10-11 02:56:30 +08:00
Lvc Revincx
00c6e41239
fix: task bar icon misssing under linux wayland (#1816) 2024-10-11 02:52:57 +08:00
huzibaca
c604ae38dd
fix: installation service error 2024-10-11 00:59:34 +08:00
huzibaca
25e25c2285
chore: update 2024-10-10 23:34:55 +08:00
huzibaca
b4180b5b48
chore:update 2024-10-10 18:52:20 +08:00
huzibaca
d14bda7e7a
chore: update 2024-10-10 18:40:39 +08:00
huzibaca
045ddc5ca5
chore: update 2024-10-10 02:21:22 +08:00
huzibaca
3f3fad0db7
feat: Modify startup logic and install services by default 2024-10-10 00:34:36 +08:00
huzibaca
30c77b891d
chore: update 2024-10-09 01:14:03 +08:00
huzibaca
5e126364ed
refactor: core logic 2024-10-08 02:39:17 +08:00
huzibaca
7f2f9588ac
chore: update 2024-10-06 02:03:32 +08:00
huzibaca
e299e246e3
chore: revert 2024-10-06 01:09:59 +08:00
huzibaca
0541a0c69f
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
1178f7c892
refactor: init_log 2024-10-05 12:19:44 +08:00
huzibaca
a3e78bd76d
refactor: get_bypass func 2024-10-05 02:58:41 +08:00
huzibaca
bcf33b779e
chore: update 2024-10-05 00:03:11 +08:00
huzibaca
d0ba506011
chore: update deps 2024-10-04 23:56:50 +08:00
huzibaca
477d1f2aee
feat: add color to log 2024-10-04 22:38:06 +08:00
huzibaca
aa4b1ad8e8
chore: update 2024-10-04 05:37:53 +08:00
huzibaca
6b902a7ad8
chore: update 2024-10-04 05:27:59 +08:00
huzibaca
a626f2ce29
chore: update 2024-10-03 14:31:40 +08:00
huzibaca
2685a02e1a
chore: update 2024-10-03 12:01:06 +08:00
huzibaca
bf1f201d4b
feat: windows uses sysproxy.exe for system proxy 2024-10-03 02:09:22 +08:00
huzibaca
335578dd91
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
3a3fc986c4
chore: replace sysproxy-rs repo 2024-10-02 17:43:14 +08:00
huzibaca
0e1e54aff4
feat: test item, when icon is svg, add svg format check 2024-10-01 20:49:03 +08:00
huzibaca
0b8f24a92e
chore: optimize lock 2024-10-01 00:27:08 +08:00
huzibaca
3cbcd4630c
chore: remove the manual release lock 2024-09-30 23:07:46 +08:00
wonfen
709ab3dd88 chore: add service mode info 2024-09-30 01:04:15 +08:00
huzibaca
b3d93d0761
chore: update 2024-09-28 20:20:40 +08:00
huzibaca
79d1539149
refactor: logic optimization 2024-09-28 12:37:01 +08:00
huzibaca
ba1a7e0fd6
,fix: when updating the verge configuration, notification error 2024-09-27 22:25:23 +08:00
wonfen
30ebfd84cf chore: update bug report template 2024-09-27 14:08:50 +08:00
huzibaca
aef215d810
chore: update 2024-09-27 10:05:31 +08:00
huzibaca
cee872e944
chore: remove compatibility code 2024-09-27 10:04:23 +08:00
huzibaca
c50e8f9de8
refactor: logic optimization 2024-09-27 00:24:05 +08:00
huzibaca
11a8e3465f
chore: update 2024-09-26 19:47:25 +08:00
huzibaca
e8ed7c1e3e
chore: remove compatibility code 2024-09-26 19:47:00 +08:00
huzibaca
5b822d238d
chore: update 2024-09-26 19:23:16 +08:00
huzibaca
053754fea8
chore: update 2024-09-26 12:20:57 +08:00
huzibaca
7a9fd118a3
chore: update 2024-09-25 21:20:36 +08:00
huzibaca
2aa3c8cfe1
chore: update 2024-09-25 21:07:01 +08:00
huzibaca
ec2bf37ad4
Revert "chore: cover panic error"
This reverts commit b8cb48aeada25ae71371ee48a0010f6fa9b68c8c.
2024-09-25 20:22:49 +08:00
huzibaca
fc5c959a55
chore: unified hotkey registration 2024-09-25 15:45:12 +08:00
huzibaca
b8cb48aead
chore: cover panic error 2024-09-25 15:44:05 +08:00
huzibaca
4eccedcd78
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
08b1160d63
chore: remove useless exit codes 2024-09-25 11:47:01 +08:00
huzibaca
3bcd8b8b2c
fix: code lint 2024-09-24 20:11:33 +08:00
huzibaca
4b20b65a22
fix: code lint 2024-09-24 20:09:30 +08:00
huzibaca
4b234a3211
fix: code lint 2024-09-24 20:06:25 +08:00
huzibaca
2ca7310baf
chore: add cross-env dev dependency 2024-09-24 16:35:19 +08:00
huzibaca
5d742a9037
chore: update 2024-09-23 23:57:08 +08:00
huzibaca
a3465d292c
refactor: unify and simplify the call of app_handle(2) 2024-09-23 23:15:51 +08:00
huzibaca
8a3a094414
chore: update 2024-09-23 17:15:28 +08:00
huzibaca
52d5f2c992
chore: update 2024-09-23 16:53:00 +08:00
huzibaca
60e0f972d0
refactor: unify and simplify the call of app_handle 2024-09-23 16:31:58 +08:00
huzibaca
0a9182519d
chore: update 2024-09-22 22:15:36 +08:00
huzibaca
bfded924d7
chore: rename method 2024-09-22 20:29:43 +08:00
huzibaca
c653c458b9
Revert "chore: update"
This reverts commit 4b7ffa146525d035434f3104a82319b6ad527ce9.
2024-09-22 16:46:24 +08:00
huzibaca
4b7ffa1465
chore: update 2024-09-22 15:59:59 +08:00
huzibaca
a5c871e933
chore: update 2024-09-22 15:39:52 +08:00
huzibaca
56983c4d2a
chore: update 2024-09-22 14:08:08 +08:00
huzibaca
e9721ecc4d
Revert "chore: remove useless exit codes and hooks"
This reverts commit 40ff3fd4bf59f52f23904c3150de6ff17974d23f.
2024-09-22 00:41:20 +08:00
huzibaca
84fe9c84a8
fix: remove unused variable 2024-09-22 00:32:40 +08:00
huzibaca
40ff3fd4bf
chore: remove useless exit codes and hooks 2024-09-21 21:05:34 +08:00
huzibaca
e92074e586
chore: try adjusting the triggering of the tray mouse button 2024-09-21 15:38:14 +08:00
huzibaca
424c1f7d8f
chore : update 2024-09-20 18:02:19 +08:00
huzibaca
868707dbde
fix: tray menu text was not aligned 2024-09-20 17:59:24 +08:00
huzibaca
29d54fe589
chore: update 2024-09-20 17:49:31 +08:00
huzibaca
c246ae3bcf
Fix: Custom tray icon not working
1. Contains potential deadlock
2024-09-20 16:26:23 +08:00
huzibaca
ab887c656a
fix: function parameter call error 2024-09-20 08:27:06 +08:00
Sukka
eefeec7f0a
chore: drop fs-extra (#1739)
nb!
2024-09-20 02:01:36 +08:00
huzibaca
74777df344
chore: update dev dependencis 2024-09-18 21:34:20 +08:00
huzibaca
c21ce578f5
fix: exit hotkey conflict 2024-09-18 15:16:43 +08:00
huzibaca
7d1b7adda5
fix: whether the window starts as fullscreen or not. 2024-09-18 13:23:27 +08:00
huzibaca
f64528dd87
chore: revert 2024-09-18 12:24:52 +08:00
huzibaca
3ca1d03d34
refactor: exit app 2024-09-18 02:48:55 +08:00
huzibaca
49929bd473
fix: the shortcut key to exit cannot be used 2024-09-18 01:41:46 +08:00
huzibaca
25f15e12cf
fix: hotkey status not accurately processed, resulting in two triggers 2024-09-17 10:59:39 +08:00
huzibaca
b6d15c27b6
chore: update 2024-09-17 08:28:13 +08:00
huzibaca
2f8c5c9b20
chore: update 2024-09-17 08:02:29 +08:00
huzibaca
bee1373f92
chore: add window to allow full screen configuration 2024-09-16 15:25:01 +08:00
huzibaca
8a4535d55e
chore: update 2024-09-16 07:08:22 +08:00
huzibaca
613fb60859
chore: update 2024-09-16 06:43:25 +08:00
huzibaca
18f9978fcf
chore: update 2024-09-16 06:37:39 +08:00
huzibaca
9b527cb53c
chore: update 2024-09-16 06:17:22 +08:00
huzibaca
c06e4af79d
chore: update 2024-09-16 05:50:05 +08:00
huzibaca
e89c3ed65e
chore: modify application description definition 2024-09-16 05:38:52 +08:00
huzibaca
eb60cfb083
fox: try to restore the processing logic of windows custom scheme 2024-09-16 05:30:29 +08:00
huzibaca
65ce7429b7
chore: update dev workflow 2024-09-15 07:57:34 +08:00
huzibaca
4b4a4927c4
chore: add log 2024-09-15 07:03:04 +08:00
huzibaca
f57d2df5ec
chore: follow official standards and adjust the main file 2024-09-15 06:24:53 +08:00
wonfen
d70e18ba8d chore: update change log and texts 2024-09-15 00:33:54 +08:00
huzibaca
962aeb1c75
fix: when importing subscriptions, force the window to open 2024-09-14 10:03:19 +08:00
huzibaca
6d7711f3ea
fix: after importing a subscription, it cannot be automatically switched to the current subscription. 2024-09-13 18:17:14 +08:00
huzibaca
ba487611ee
chore: update action(rust-toolchain) 2024-09-13 14:21:51 +08:00
huzibaca
6a636444a7
chore: update action(rust-toolchain) 2024-09-13 14:14:27 +08:00
wonfen
25c766ed94 chore: change folder name 2024-09-13 12:03:28 +08:00
huzibaca
dbc3723bdc
feat: Added web notification of successful subscription import 2024-09-13 11:24:49 +08:00
huzibaca
3963c60fc1
fix: when the service is not installed, the tray hides the Tun mode 2024-09-13 04:12:25 +08:00
huzibaca
b888c7729e
refactor: url scheme implementation 2024-09-13 03:21:55 +08:00
huzibaca
a39ece6156
chore: delete useless emebd server api 2024-09-12 19:01:08 +08:00
huzibaca
624eb5b085
fix:tray click event 2024-09-12 17:02:17 +08:00
huzibaca
9ec8c903c5
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
2396a6b35a
feat: log panic 2024-09-12 07:59:51 +08:00
huzibaca
1fc54e49d9
chore: update deps 2024-09-12 06:14:23 +08:00
huzibaca
30ee7a9e85
fix: missing items in system tray 2024-09-12 05:45:15 +08:00
huzibaca
b9be5e8c44
feat: migrate tauri 2.0 2024-09-11 08:15:03 +08:00
huzibaca
ae1c6d4617
chore: update 2024-09-04 07:53:16 +08:00
huzibaca
7b11e157c4
chore: update capabilities 2024-09-04 05:57:37 +08:00
wonfen
5783b7b392 chore: 2.0.0 beta release 2024-09-03 00:00:12 +08:00
huzibaca
507d52bcb9
chore: update 2024-09-02 19:33:17 +08:00
wonfen
d45929c604 chore: use in-house update proxy 2024-08-24 02:02:17 +08:00
wonfen
632c32addb Style: fix macos new version btn position 2024-08-22 07:33:51 +08:00
wonfen
7d2b18759e Release 1.7.7 2024-08-22 03:47:33 +08:00
wonfen
aa8f6f4ad6 chore: Shorten text to prevent Windows tooltip truncation. 2024-08-22 03:41:16 +08:00
wonfen
592167ffb7 fix: profiles will not be selected after import 2024-08-22 00:57:04 +08:00
wonfen
12a6bfad00 Release 1.7.6 2024-08-18 00:49:23 +08:00
tony-sung
1ea3b956ad
Update web-ui-viewer.tsx Fix yacd parameter hostname error (#1484) 2024-08-18 00:23:24 +08:00
MystiPanda
1b7dafe743
feat: auto select profile 2024-07-30 09:06:12 +08:00
MystiPanda
624eb2a2ba
chore: prepend new added rules 2024-07-24 14:25:06 +08:00
MystiPanda
88d98517c8
fix: wrap the password in quotes
#1460
2024-07-24 14:22:23 +08:00
Avan
9528606906
style: AddressDisplay padding (#1457) 2024-07-23 22:17:51 +08:00
nhsmw
09b44a3af3
fix: update test url 2024-07-19 10:32:41 +08:00
MystiPanda
dc861eca7d
chore: remove unnecessary console log 2024-07-18 23:37:54 +08:00
MystiPanda
e749fe70e2
fix: use group testUrl
#1384
2024-07-18 15:04:55 +08:00
MystiPanda
d20c082deb
fix: windows x86 updater url 2024-07-18 14:38:31 +08:00
MystiPanda
c4abc3ade3
fix: error command status 2024-07-16 18:37:56 +08:00
MystiPanda
ff28aab56e
fix: MacOS service install error 2024-07-16 13:01:27 +08:00
MystiPanda
1a6f842492
fix(#1335): support cidr 2024-07-16 12:22:11 +08:00
MystiPanda
af0cd4a342
fix: try to fix install service 2024-07-16 12:07:23 +08:00
MystiPanda
d98b3224cf
chore: disable okBtn 2024-07-15 23:38:49 +08:00
MystiPanda
f2e5f0754c
ci: fix winget uploader 2024-07-15 23:27:38 +08:00
MystiPanda
ec1601f317
Release 1.7.5 2024-07-15 22:35:33 +08:00
MystiPanda
54116608dc
ci: fix winget uploader 2024-07-15 22:33:57 +08:00
MystiPanda
fea6735ea1
fix: copy necessary files 2024-07-15 22:25:04 +08:00
MystiPanda
2f77a07b3d
Release 1.7.4 2024-07-15 20:56:18 +08:00
MystiPanda
4cd7ccda57
fix: unified switch style 2024-07-15 20:42:00 +08:00
MystiPanda
ef337ffb69
feat: optimize the service mode interaction logic. 2024-07-15 20:02:05 +08:00
MystiPanda
9ff5d8d4e0
Revert "fix: try to fix abnormal stuck"
This reverts commit e14ddbe23cae0bc5437b9ba9184e35909e6825c4.
2024-07-14 12:49:06 +08:00
Sukka
199a5eed60
fix(#1387): avoid catastrophic backtracking (#1396) 2024-07-14 12:26:02 +08:00
MystiPanda
e14ddbe23c
fix: try to fix abnormal stuck
#1387
2024-07-13 21:41:04 +08:00
MystiPanda
3878e66ea4
chore: update locale 2024-07-13 19:15:07 +08:00
MystiPanda
c14a33444b
chore: unified icon style 2024-07-13 19:01:16 +08:00
MystiPanda
48f7c15035
feat: display network interface 2024-07-13 14:15:13 +08:00
Avan
32212a46e2
feature: copy clash env (#1391)
* feat: copy clash env

* style: use ContentCopyRounded replace CopyAll
2024-07-13 01:03:46 +08:00
Avan
b4025c45da
style: EditorViewer title align center (#1390) 2024-07-12 23:29:55 +08:00
MystiPanda
bd589c4422
fix: service install failed on macos 2024-07-12 20:54:31 +08:00
dongchengjie
f85f7758e6 chore: checkbox items' title & button icons 2024-07-12 18:24:19 +08:00
dongchengjie
49e36b6e00 chore: editor resizing debounce 2024-07-08 23:18:06 +08:00
dongchengjie
df6e245e5c chore: remove dialog overflow-scroll 2024-07-08 22:07:43 +08:00
dongchengjie
4e4f9af5b7 fix: editor fails to resize after toggling on macos 2024-07-08 21:40:56 +08:00
MystiPanda
aa9e63b51a
fix dev.yml 2024-07-08 16:17:23 +08:00
MystiPanda
82340582b2
ci: add dev workflow 2024-07-08 13:20:36 +08:00
Akioe Yu
9d045e24db
fix: dnd box z-index (#1353)
* fix: dnd box z-index

* fix: dnd boxes
2024-07-08 10:49:57 +08:00
MystiPanda
1dcc95d16a
perf: optimized response speed 2024-07-08 00:29:49 +08:00
dongchengjie
78c32d51db chore: group types locale 2024-07-07 21:56:20 +08:00
❤是纱雾酱哟~
545a2c6688
ci: Integrate "Winget Releaser" (#1326) 2024-07-07 20:42:17 +08:00
MystiPanda
359d9285fe
perf: change port too slow 2024-07-07 18:22:02 +08:00
MystiPanda
1bd51be99c
feat: get network interface 2024-07-07 18:13:10 +08:00
dongchengjie
ee56080af0 fix: usage percent style 2024-07-07 18:08:02 +08:00
MystiPanda
18196c4a77
fix: trojan uri parser 2024-07-07 11:47:49 +08:00
MystiPanda
14cd3b99cc
fix: tray icon size
#1341
2024-07-07 11:18:22 +08:00
MystiPanda
ff75fe47ee
chore: disable browser autocomplete 2024-07-07 11:16:59 +08:00
MystiPanda
7e2cc180c3
Release 1.7.3 2024-07-06 18:48:25 +08:00
MystiPanda
bef9eea87b
fix: bypass reg error
#1335
2024-07-06 18:23:23 +08:00
MystiPanda
9cd1aef1db
fix: limite cipher types 2024-07-06 10:49:42 +08:00
MystiPanda
ab25cf0637
fix: rule parser 2024-07-06 10:35:29 +08:00
MystiPanda
32f37720dd
Revert "chore: add base64 decode step"
This reverts commit 38eb3123beb9820e19cbeba3cbd015e38ee282cf.
2024-07-06 10:12:51 +08:00
MystiPanda
766cf3aeae
chore: decode base64 2024-07-06 10:12:40 +08:00
MystiPanda
905353d540
fix: some error 2024-07-06 09:54:16 +08:00
MystiPanda
38eb3123be
chore: add base64 decode step 2024-07-06 00:57:38 +08:00
MystiPanda
0183edd450
fix: style 2024-07-06 00:45:21 +08:00
MystiPanda
2f640a946e
fix: type 2024-07-06 00:23:52 +08:00
MystiPanda
ec6c2adf9b
chore: Improve URI parser 2024-07-05 22:44:05 +08:00
MystiPanda
5a1edc5ffb
feat: finish rpoxies editor 2024-07-05 19:49:32 +08:00
MystiPanda
149d482c7d
fix: groups config type error
feat(unfinished): add proxy editor
2024-07-05 00:38:50 +08:00
MystiPanda
c9f784e4fa
feat: add profile name param for script 2024-07-04 23:11:54 +08:00
MystiPanda
9edafa75e7
build: remove unused resource file 2024-07-04 22:34:48 +08:00
MystiPanda
08baba545d
chore: disable unnecessary ports by default 2024-07-04 18:58:34 +08:00
MystiPanda
60d0b29236
feat: support cache for groups editor 2024-07-04 18:53:39 +08:00
MystiPanda
f5ee4fb5b5
chore: unified style 2024-07-04 18:50:21 +08:00
MystiPanda
5b7b3be6f9
fix: useseq error 2024-07-04 14:30:34 +08:00
MystiPanda
c933560102
fix: locale typo 2024-07-04 14:18:21 +08:00
MystiPanda
12a80f35fe
feat: support visual edit for proxy group 2024-07-04 14:13:19 +08:00
MystiPanda
43f901eeb9
fix: rules editor get groups error 2024-07-04 11:03:51 +08:00
MystiPanda
e8440e06a1
feat: support get merged rule-set name 2024-07-03 22:13:24 +08:00
dongchengjie
19e9e9d032 fix: remove rule condition where is not required 2024-07-03 17:03:12 +08:00
MystiPanda
8ff2d687e4
Release 1.7.2 2024-07-03 13:26:06 +08:00
MystiPanda
e616e8f9aa
fix: Insertion order
#1300
2024-07-03 13:13:54 +08:00
MystiPanda
1fbc67fe98
fix: dialog styles 2024-07-03 13:13:29 +08:00
MystiPanda
a49e4d8d0c
fix: panic 2024-07-03 12:49:29 +08:00
MystiPanda
fa4ac00504
feat: support edit rules file 2024-07-03 12:37:08 +08:00
MystiPanda
dc066edec4
fix: edit groups error 2024-07-03 10:58:57 +08:00
MystiPanda
eaed7b2899
Release 1.7.1 2024-07-03 09:52:19 +08:00
MystiPanda
123ecc3548
fix: rules drag error 2024-07-03 09:45:14 +08:00
MystiPanda
c857fcf035
fix: compatibility 2024-07-03 08:15:13 +08:00
dongchengjie
923f710e53 fix: search-box takes no effect in rule-editor #1288 2024-07-03 03:08:01 +08:00
dongchengjie
8d8c2ed262 fix: sub usage percent fails to display when number is too small #1290 2024-07-03 02:59:32 +08:00
MystiPanda
8558673a5a
Release 1.7.0 2024-07-03 00:32:14 +08:00
MystiPanda
7372f330a4
perf: optimize performance of the rule editor 2024-07-02 23:55:29 +08:00
dongchengjie
07c145c661 feat: editor add tool-tar buttons 2024-07-02 23:24:44 +08:00
dongchengjie
f69e1d2a0c refactor: editor-viewer using react-monaco-editor 2024-07-02 21:02:29 +08:00
dongchengjie
0021fc24bb chore: bump schema to 1.18.5-alpha7 2024-07-02 19:38:44 +08:00
MystiPanda
82bc1d5da5
chore: disable autocomplete 2024-07-02 19:02:05 +08:00
MystiPanda
ffc343b471
fix: default value of global extend config 2024-07-02 18:46:23 +08:00
dongchengjie
86e43123b0 chore: add rule list search-box 2024-07-02 17:32:49 +08:00
dongchengjie
b558202441 chore: rule types locale 2024-07-02 17:04:22 +08:00
MystiPanda
83947b6725
chore: check the validity of the rule content 2024-07-02 13:11:54 +08:00
MystiPanda
7e8b65e61f
chore: Adjust the chain processing execution order and default value 2024-07-02 12:40:28 +08:00
MystiPanda
cff0ea425c
build: update depends 2024-07-02 12:21:17 +08:00
MystiPanda
f6ba6d0310
chore: update locale 2024-07-01 23:30:14 +08:00
MystiPanda
7b9bf9e552
feat: allow set bypass without using default value 2024-07-01 22:53:32 +08:00
Sukka
c91ad1e016
ci: speed up cargo install by enabling cache (webview2) (#1283) 2024-07-01 14:23:21 +08:00
MystiPanda
b1444b8635
feat: global merge and script 2024-07-01 08:25:03 +08:00
MystiPanda
51a49b94d8
feat: drag to reorder rules 2024-06-30 23:16:21 +08:00
MystiPanda
720b46d790
chore: select rule-set name 2024-06-30 22:46:11 +08:00
MystiPanda
9089c30d57
feat: display rules list 2024-06-30 22:34:26 +08:00
MystiPanda
e2d522803c
fix: editor init error 2024-06-30 18:05:38 +08:00
Sukka
babcb00621
ci: speed up cargo install by enabling cache (#1279) 2024-06-30 17:21:55 +08:00
MystiPanda
901a983150
feat: rules editor 2024-06-30 17:17:04 +08:00
MystiPanda
955182b05b
chore: locale 2024-06-30 13:03:36 +08:00
MystiPanda
f4dfe8eeb4
feat(unfinished): rules editor 2024-06-30 12:46:31 +08:00
MystiPanda
93904b8278
refactor: pure merge 2024-06-30 07:58:44 +08:00
MystiPanda
495580ae2b
fix: delete logic 2024-06-30 07:37:52 +08:00
MystiPanda
3051004217
chore: update locale 2024-06-30 07:18:10 +08:00
MystiPanda
b854b5e1ac
feat: support seq editor 2024-06-30 00:22:05 +08:00
MystiPanda
cf61a96ef6
refactor: Associate Profile with Merge/Script. 2024-06-29 23:07:44 +08:00
MystiPanda
1293d25e1b
refactor: use async instead of block_on 2024-06-29 19:02:37 +08:00
dongchengjie
c1a201f358 fix: do not reactive when changed profile is not current 2024-06-29 11:22:11 +08:00
dongchengjie
9ee5390ec7 feat: reactive after save when profile content changes 2024-06-29 09:21:50 +08:00
dongchengjie
3f1caa702b fix: #1261 2024-06-29 05:02:06 +08:00
wonfen
24f9573c05 chore: add TG link & descriptions 2024-06-29 04:55:43 +08:00
dongchengjie
83b25920ea chore: add descriptions for Miscellaneous 2024-06-28 15:54:27 +08:00
MystiPanda
834edcd03e
fix: unset dns when exit 2024-06-28 12:18:18 +08:00
MystiPanda
8f3801e3c2
feat: set dns by service 2024-06-28 11:45:44 +08:00
dongchengjie
b5f7c58276 chore: remove duplicate locales 2024-06-26 19:11:39 +08:00
dongchengjie
66ae293ddd fix: await compatibility in #8e78b9e 2024-06-26 18:29:50 +08:00
dongchengjie
8e78b9e405 feat: support manual memory cleanup when running in debug mode 2024-06-26 17:44:42 +08:00
dongchengjie
9da1759247 chore: cleanup 2024-06-26 08:24:43 +08:00
dongchengjie
704c41c0f2 refactor: extract tooltip icon as component 2024-06-26 08:10:18 +08:00
dongchengjie
1f422afe3d chore: tooltips and locales 2024-06-26 05:33:06 +08:00
dongchengjie
709a23cf09 chore: fix connection table bg 2024-06-23 20:40:09 +08:00
dongchengjie
bfa3fa293f 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
d8b878b1bb
feat: handle break change update 2024-06-22 21:05:46 +08:00
dongchengjie
b2589dbc04 chore: hotkeys display delimiter 2024-06-21 18:24:58 +08:00
MystiPanda
2a4b605794
chore: hide delay for preset outbound 2024-06-21 00:16:41 +08:00
MystiPanda
a3080a3373
revert: just kill 2024-06-20 14:27:13 +08:00
MystiPanda
b4b9ae5d7f
feat: custom dmg background 2024-06-20 13:50:28 +08:00
dongchengjie
bb9a93e462 chore: fix table header bg in connections 2024-06-20 02:01:51 +08:00
dongchengjie
e55fbf675e chore: disable shortcuts #1239 2024-06-20 01:39:51 +08:00
MystiPanda
0342477733
fix: runtime error 2024-06-19 11:10:40 +08:00
MystiPanda
e6e2b1f142
refactor: change core binary name 2024-06-19 10:04:28 +08:00
MystiPanda
95c23a93cd
refactor: kill core by process name 2024-06-18 12:36:58 +08:00
MystiPanda
ae784cb985
fix: reg error 2024-06-17 16:38:30 +08:00
Sukka
17765bdba9
fix(#1226): missing conn unsub (#1228) 2024-06-17 13:36:35 +08:00
Sukka
45f791b3c8
refactor(connections): use swr subscription (#1226) 2024-06-17 13:14:36 +08:00
dongchengjie
b0ad39ed0b fix: resizing reset when data changes 2024-06-17 13:03:47 +08:00
Sukka
1988aeb945
refactor(log): use swr subscription (#1220) 2024-06-17 10:48:37 +08:00
MystiPanda
f48a5710aa
chore: update depends 2024-06-17 10:46:18 +08:00
Sukka
c8d2410c27
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
a9ef32c32f
Revert "feat: disable running with admin permission and check service mode"
This reverts commit a0f9fb90ee7034043f1d8279db9e31f753efe74d.
2024-06-16 12:06:23 +08:00
Sukka
19b6f78c8a
ci(alpha): avoid race by cancel non-latest concurrent runs (#1213) 2024-06-16 01:10:30 +08:00
Sukka
4ab507fd43
fix: hide save button in readonly editor view (#1208) 2024-06-15 19:24:26 +08:00
Sukka
2662df2547
fix(#1203): correct types (#1207) 2024-06-15 19:23:58 +08:00
Sukka
0332415ac9
perf: replace Array#map Array#filter chain w/ Array#reduce (#1203) 2024-06-15 12:22:33 +08:00
MystiPanda
a0f9fb90ee
feat: disable running with admin permission and check service mode 2024-06-14 23:15:49 +08:00
MystiPanda
1b8c4cb832
build: downgrade auto-launch 2024-06-14 21:26:30 +08:00
MystiPanda
cfd50d281b
fix: fix bypass check regex 2024-06-14 21:23:58 +08:00
Sukka
1667856894
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
c5c76ab539 chore: update metacubexd #1200 2024-06-14 17:09:12 +08:00
dongchengjie
eb060d2e43 feat: local profile name autofill #1191 2024-06-13 16:29:25 +08:00
MystiPanda
b5556613cf
refactor: remove grant logic 2024-06-13 16:07:56 +08:00
MystiPanda
ad1a057edb
fix: check service 2024-06-13 12:58:47 +08:00
MystiPanda
8342f985e5
ci: fix release 2024-06-12 12:25:12 +08:00
MystiPanda
9d32e58915
chore: update publisher 2024-06-12 11:51:44 +08:00
MystiPanda
84027cbae1
Release 1.6.6 2024-06-12 10:33:20 +08:00
MystiPanda
5343040ef3
fix: start param error 2024-06-12 10:19:23 +08:00
MystiPanda
75cd7dd4b9
chore: update & fmt & clippy 2024-06-12 10:00:22 +08:00
dongchengjie
fb6ffd8e09 chore: missing locale 2024-06-11 16:19:25 +08:00
dongchengjie
aca9e61f39 chore: locale "Invalid Bypass Format" 2024-06-09 20:02:20 +08:00
MystiPanda
56078098a4
fix: start param error 2024-06-09 20:00:17 +08:00
MystiPanda
1f197965e0
feat: check bypass format 2024-06-09 13:37:47 +08:00
MystiPanda
183f0f6c42
feat: keep default bypass 2024-06-09 12:45:57 +08:00
dongchengjie
76624c7d83 chore: missing locale 2024-06-09 11:16:13 +08:00
dongchengjie
ab47c56ec5 chore: locale reorganization 2024-06-09 09:12:14 +08:00
Eric Huang
ad35ed96c8
feat(settings page): add loading state (#1157)
* feat(settings page): add loading state

* fix: type
2024-06-09 06:26:07 +08:00
wonfen
97bd51cf5f chore: update ci with apple notarization 2024-06-09 02:01:33 +08:00
MystiPanda
f4a110cfd7
ci: fix error 2024-06-08 20:58:57 +08:00
MystiPanda
e9ed06adfd
ci: update 2024-06-08 20:45:28 +08:00
MystiPanda
bc5eaf34fe
feat: add download button on updater dialog
#1129
2024-06-08 20:35:23 +08:00
MystiPanda
3e8b891dd0
fix: run app as normal user 2024-06-08 20:20:47 +08:00
MystiPanda
3f2ea4bc64
build: update depends 2024-06-08 20:02:25 +08:00
MystiPanda
ae81b37c1d
build: remove appimage 2024-06-08 19:36:58 +08:00
MystiPanda
b4349afb1b
ci: codesign 2024-06-08 18:52:34 +08:00
dongchengjie
0cffe8e3f7 chore: disbale Meta+Q on macOS 2024-06-08 13:12:24 +08:00
Sukka
7a1f4f9abe
refactor: simplify useConnectionSetting (#1141) 2024-06-07 17:23:53 +08:00
wonfen
5768b01786 fix: apple sign error again 2024-06-07 17:19:44 +08:00
MystiPanda
7e9079467f
fix: sign error 2024-06-07 16:39:06 +08:00
MystiPanda
615e96922e
fix: sign error 2024-06-07 16:37:58 +08:00
Eric Huang
8b94c452fb
feat: make SettingItem clickable (#1138)
* feat: make `SettingItem` clickable

* clean up
2024-06-07 15:51:51 +08:00
Sukka
66dd510acc
refactor: replace recoil (#1137) 2024-06-07 12:27:37 +08:00
wonfen
96e044566c Style: modify proxy pages 2024-06-07 10:32:27 +08:00
MystiPanda
726ad84d7f
fix: sign error 2024-06-06 16:06:58 +08:00
wonfen
4fc422f880 chore: apple developer sign test 2024-06-06 15:21:53 +08:00
dongchengjie
1938024ab3 chore: test_delay trace 2024-06-05 19:17:43 +08:00
dongchengjie
05b1de6fd5 chore: theme colors to uppercase 2024-06-05 14:20:24 +08:00
dongchengjie
0bf8807b50 build: no legacy chunks 2024-06-05 09:04:08 +08:00
Sukka
23668ee74e
fix(#1126): add Object.hasOwn polyfill (#1127) 2024-06-05 00:12:06 +08:00
dongchengjie
6bc5c5a808 fix: csp missing asset: 2024-06-04 15:26:39 +08:00
MystiPanda
330004dc72
build: update depends 2024-06-04 12:16:42 +08:00
MystiPanda
a900d6d742
Release 1.6.5 2024-06-04 12:11:38 +08:00
MystiPanda
bfea70eec9
fix: package error 2024-06-04 10:47:17 +08:00
MystiPanda
13babbf330
feat: support rpm package 2024-06-04 10:16:25 +08:00
MystiPanda
0a6fe382ac
build: use latest tauri 2024-06-04 10:09:04 +08:00
Zhenfu Shi
dd6dea9dfc
chore: Enable Ad Hoc signing on macOS (#1117)
Closes #1116
2024-06-03 09:01:33 +08:00
wonfen
11434fba68 Revert "chore: change default test url"
This reverts commit 69b65d06a4cd0988f28ed52f91c13c833583b0dc.
2024-06-03 08:25:06 +08:00
dongchengjie
10bf90c92a chore: use icons files instead of hard coding 2024-06-03 07:21:40 +08:00
dongchengjie
5193fe4bab fix: group selected be overwritten when saving test 2024-06-03 06:38:20 +08:00
dongchengjie
4df4995623 chore: improve UI in connections page 2024-06-02 21:52:31 +08:00
aixiao0621
f5f4ecf46a fix: a display error on the connections page 2024-06-02 17:26:23 +08:00
MystiPanda
69b65d06a4
chore: change default test url 2024-06-01 23:14:47 +08:00
MystiPanda
e6bf4e836d
build: update depends 2024-06-01 23:01:27 +08:00
dongchengjie
01d67eb239 refactor: polyfills review 2024-05-30 20:27:12 +08:00
dongchengjie
3514cfbd44 fix: research when search box mode changes 2024-05-30 10:45:24 +08:00
dongchengjie
cade35fe10 chore: Content-Security-Policy 2024-05-29 10:20:56 +08:00
dongchengjie
2c6df77f5a ci: fix heap oom 2024-05-29 09:49:11 +08:00
dongchengjie
0276ae91b5 build: polyfills 2024-05-29 09:39:26 +08:00
dongchengjie
60fee2accb build: import babel 2024-05-28 09:59:41 +08:00
dongchengjie
03e2632294 fix: try to fix #1084 2024-05-27 23:19:44 +08:00
dongchengjie
194fe59458 typo: inconsistent style in layout-viewer 2024-05-27 16:37:48 +08:00
MystiPanda
e400111f8a Release 1.6.4 2024-05-26 22:07:53 +08:00
MystiPanda
8bc7607783 Revert "refactor: use axios tauri adapter"
This reverts commit 7e361cb3f195f18353446947b66775c36644237b.
2024-05-26 21:18:02 +08:00
dongchengjie
4108451ebe chore: %mixed-port% hint for PAC script 2024-05-26 19:53:42 +08:00
MystiPanda
0f56257aa1 Release 1.6.3 2024-05-26 19:43:26 +08:00
MystiPanda
2a575284b9 chore: add content type 2024-05-26 19:26:57 +08:00
MystiPanda
444bcbfb30 fix: auto proxy changed by guard 2024-05-26 19:07:14 +08:00
MystiPanda
212021c878 feat: Support PAC Mode 2024-05-26 17:59:39 +08:00
dongchengjie
600b687253 chore: emoji display support in editor 2024-05-25 18:02:32 +08:00
farzadhallaji
ae3aada172
Update fa.json (#1060) 2024-05-24 19:39:40 +08:00
MystiPanda
50cc8af8c3 ci: use rust 1.77.0 2024-05-23 11:47:41 +08:00
dongchengjie
1809cdbf37 fix: no snippets and warnings in runtime config editor 2024-05-21 06:18:09 +08:00
dongchengjie
fb38047769 chore: update locale in connection 2024-05-20 13:42:45 +08:00
dongchengjie
08e0d6a34a chore: text overflow word-wrap & cleanup 2024-05-20 12:38:41 +08:00
MystiPanda
b4fddaec7f build: use compatible 2024-05-19 19:30:53 +08:00
MystiPanda
3f19d22941 fix: installer package error 2024-05-19 19:13:21 +08:00
MystiPanda
c7d51cfc4c build: use latest core 2024-05-19 13:00:27 +08:00
dongchengjie
6829d34e90 chore: schema 1.18.5-alpha 2024-05-19 12:37:18 +08:00
dongchengjie
021e430103 chore: fix select component bg in connection 2024-05-18 18:42:51 +08:00
dongchengjie
ae6f42a7fb chore: fix search-box bg 2024-05-18 18:20:05 +08:00
MystiPanda
da98097394 chore: support more asset scope 2024-05-18 15:47:45 +08:00
oomeow
43f0b935cf
perf: memoize the proxy col items (#1029) 2024-05-18 15:14:22 +08:00
oomeow
db5d14e0ce
fix: clipboard doesn't work and set_shadow method is not supported in Linux (#1030) 2024-05-18 15:14:00 +08:00
dongchengjie
b25cf5eadb feat: editor import PAC definition 2024-05-18 14:59:17 +08:00
dongchengjie
bd3b41c809 chore: hint for canceling fixed #840 2024-05-17 20:44:18 +08:00
dongchengjie
31d301064a chore: use search-box in logs and connections 2024-05-17 19:44:42 +08:00
dongchengjie
b95b9bdaf3 chore: component base-search-box 2024-05-17 19:13:33 +08:00
dongchengjie
937f0b5687 chore: fix editor dialog scroll overflow 2024-05-17 04:03:49 +08:00
dongchengjie
ffbc892e44 fix: missing locale 2024-05-15 23:27:31 +08:00
dongchengjie
9e872932d1 chore: current bypass wrap 2024-05-15 22:59:53 +08:00
MystiPanda
ecb2fbf900 feat: support monochrome tray icon 2024-05-15 14:50:10 +08:00
dongchengjie
72edd2e15d chore: logo svg 2024-05-14 16:53:39 +08:00
dongchengjie
c3d22c554f feat: doc reference link on settings header 2024-05-14 14:40:47 +08:00
dongchengjie
e72f4a1582 chore: use coding fonts in editor 2024-05-14 14:20:50 +08:00
dongchengjie
5e43c060fa fix: switch missing break 2024-05-14 01:24:23 +08:00
dongchengjie
e8dee1ddd9 chore: @ts-ignore schema check 2024-05-14 01:21:28 +08:00
dongchengjie
d85b8c776f chore: revert schema to beta4 2024-05-14 00:59:05 +08:00
dongchengjie
fb518b6218 chore: palette locale 2024-05-13 23:12:29 +08:00
dongchengjie
8d510cde21 typo: unused import 2024-05-13 22:59:24 +08:00
dongchengjie
da4bf167ee feat: css injection editor 2024-05-13 22:58:25 +08:00
Remember
dc1abf8dcb
Update 172.16.0.0/12 on Windows (#1013) 2024-05-13 18:28:41 +08:00
MystiPanda
3fc969a50b fix: use default bypass when empty 2024-05-13 18:27:19 +08:00
MystiPanda
4064129d49 chore: Adapt Mac icon 2024-05-13 13:11:05 +08:00
MystiPanda
22b4e0bfff docs: Update README 2024-05-12 22:17:05 +08:00
MystiPanda
53fa1f88fd chore: Change HomePage Logo 2024-05-12 21:17:21 +08:00
MystiPanda
5fefcd92f3 chore: Try a new icon 2024-05-12 19:31:06 +08:00
dongchengjie
d20bc936da chore: ru locale 2024-05-12 16:18:49 +08:00
RikudouPatrickstar
e79f036a70
chore: update notification message and zh translation (#1011) 2024-05-12 14:40:18 +08:00
MystiPanda
0f2af91a04 perf: Disable Tun mode before shutting down 2024-05-12 11:06:44 +08:00
MystiPanda
7e361cb3f1 refactor: use axios tauri adapter 2024-05-11 21:54:56 +08:00
MystiPanda
1f7acd4027 feat: Allow disable unused ports 2024-05-09 09:22:32 +08:00
MystiPanda
dddca7bf4b chore: Update locale 2024-05-08 23:45:55 +08:00
MystiPanda
e520be80c0 perf: Limit drawing frame rate 2024-05-08 22:33:13 +08:00
MystiPanda
0b78dedf04 fix: bundle error 2024-05-08 14:52:38 +08:00
MystiPanda
8f409477c8 build: fix bundle error 2024-05-08 14:20:30 +08:00
MystiPanda
9c22f7ce69 fix: depend error 2024-05-08 13:24:34 +08:00
MystiPanda
05fa916915 Release 1.6.2 2024-05-08 13:14:00 +08:00
MystiPanda
9a593d7a61 feat: support upgrade for release core 2024-05-08 12:48:27 +08:00
MystiPanda
30017eeaa0 docs: Update FAQ URL 2024-05-08 11:44:57 +08:00
MystiPanda
9b03cacc94 ci: update ci 2024-05-08 11:12:06 +08:00
MystiPanda
557970cf54 refactor: remove prepend and append for provider 2024-05-08 00:22:14 +08:00
MystiPanda
d90dac60ef feat: deep merge
#983
2024-05-08 00:13:32 +08:00
MystiPanda
b4b2e67260 build: Add portable for fixed webview2 2024-05-06 19:13:08 +08:00
MystiPanda
bcd5e935e7 ci: debug ci 2024-05-06 16:16:47 +08:00
MystiPanda
b322627609 ci: fix ci script 2024-05-06 15:30:00 +08:00
MystiPanda
09861095ff ci: update release body 2024-05-06 15:15:07 +08:00
MystiPanda
47e567fb40 docs: update url 2024-05-06 15:05:56 +08:00
MystiPanda
5bb30ad28f build: try support fixed webview2 runtime 2024-05-06 14:18:12 +08:00
MystiPanda
a46bbf05ec ci: try resupport x86 2024-05-06 13:04:29 +08:00
dongchengjie
04a4705dbd 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
3271c96531 fix: enhance when change tun config 2024-05-04 16:12:10 +08:00
dongchengjie
05fa6b9aba chore: profile template typo 2024-05-04 14:24:11 +08:00
MystiPanda
7edd7f27cb fix: try to set env 2024-05-03 18:00:55 +08:00
dongchengjie
4a6b12eda1 fix: #974 2024-05-03 12:50:04 +08:00
MystiPanda
a354e1a0eb chore: update locale 2024-05-02 23:44:09 +08:00
dongchengjie
c2c419522a chore: add service mode locale 2024-05-02 22:16:05 +08:00
MystiPanda
7a3cc7d242 feat: Support drag and drop local files 2024-05-02 20:41:43 +08:00
MystiPanda
bd6f02f6af fix: change script execution path 2024-05-01 11:39:09 +08:00
MystiPanda
bd33728fd7 Release 1.6.1 2024-04-30 23:43:51 +08:00
MystiPanda
8400ee8a3d fix: MacOS tray click 2024-04-30 23:21:25 +08:00
lxk955
80ee7aef8e
feat: Support for using regular expressions on the log page #707 (#959) 2024-04-30 22:59:42 +08:00
MystiPanda
41762be3a5 fix: service install path 2024-04-30 20:20:39 +08:00
MystiPanda
605dda7b76 build: Restore core version 2024-04-30 09:42:25 +08:00
dongchengjie
8d6e3b5a58 fixup: wrong version 2024-04-30 02:13:24 +08:00
dongchengjie
53920fc368
chore: update pnpm-lock.yaml (#952)
* chore: update schema version

* Update pnpm-lock.yaml
2024-04-30 01:44:05 +08:00
dongchengjie
96d10423d9
chore: update schema version (#950) 2024-04-29 22:46:29 +08:00
MystiPanda
42be9b5b51 chore: Config issue template 2024-04-29 15:43:43 +08:00
MystiPanda
0add041516
docs: Update FAQ URL 2024-04-28 20:04:03 +08:00
MystiPanda
dee76cac8d
build: cargo update 2024-04-28 19:59:46 +08:00
MystiPanda
9646fab22c fix: Open the link with browser 2024-04-28 18:46:49 +08:00
dongchengjie
a54e9cb244
fixup: can't edit file (#943) 2024-04-28 17:00:33 +08:00
dongchengjie
f8339c7a9a
chore: disable WebView keyboard shortcuts (#942) 2024-04-28 16:19:13 +08:00
lxk955
9e2c864117
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
e1ccd71455
fix: disable left click menu on macOS (#930) 2024-04-27 20:52:29 +08:00
LiuTianYu
0e82426ea1
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
6c7afd168a Release 1.6.0 2024-04-23 14:25:09 +08:00
MystiPanda
e014fdf3da
feat: support ico format for tray icon (#911) 2024-04-22 20:43:15 +08:00
dongchengjie
2074da05c8
fix: #907 (#908) 2024-04-22 01:40:25 +08:00
dongchengjie
84cd87b70a
chore: update locale (#904)
* chore: missing locale

* chore: External Controller locale
2024-04-21 14:10:34 +08:00
dongchengjie
75f87044f6
chore: Proxy Bypass placeholder (#901) 2024-04-20 19:27:17 +08:00
dongchengjie
f71b51e64a
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
9d741cdd63 fix: startup script blocking 2024-04-20 17:52:54 +08:00
MystiPanda
56be17000a fix: default value for tun 2024-04-20 17:42:36 +08:00
MystiPanda
f6aacbc31d build: add depends 2024-04-19 15:55:46 +08:00
MystiPanda
ad4b57c327 chore: update lock file 2024-04-19 15:38:32 +08:00
dongchengjie
3794b2f0de
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
e0e8412728 refactor: change default value of mtu 2024-04-18 10:27:09 +08:00
dongchengjie
05b8175aad
chore: 修改Merge配置文件默认内容 (#889) 2024-04-17 22:56:54 +08:00
MystiPanda
339c89dd1c chore: Update pnpm lockfile 2024-04-17 22:16:50 +08:00
dongchengjie
402299e9c8
feat: Clash配置、Merge配置提供JSON Schema语法支持、[连接]界面调整 (#887) 2024-04-17 21:19:37 +08:00
MystiPanda
d0e8f450fd fix: SymbolicLink
#750
2024-04-13 15:46:11 +08:00
MystiPanda
e97b1ccd7a fix: service install path 2024-04-13 15:12:41 +08:00
汐殇
68691b91ef
fix: Allow core files in specific directories to be upgraded normally (#857) 2024-04-12 01:16:57 +08:00
dongchengjie
213d417481
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
2ec841eb61 fix: Update the home while updating profile 2024-04-04 15:07:55 +08:00
MystiPanda
60b5c54be1 fix: Use System Browser 2024-04-04 14:56:10 +08:00
Damian Johnson
448412a191
style: adjust confirm dialog & web ui settings dialog (#821) 2024-04-03 23:19:00 +08:00
Damian Johnson
e06327936e
fix: service mode install script download not found (#822) 2024-04-03 23:18:10 +08:00
dongchengjie
5f044e1aed
feat: support URL Schema 'profile-web-page-url' (#816) 2024-04-01 19:28:28 +08:00
HZ is not Chatty
d90fb6fc9b
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
1c583d2ea9 feat: Try support service mode for MacOS 2024-03-31 23:16:47 +08:00
MystiPanda
ddab131102
fix: script error 2024-03-31 16:37:33 +08:00
HZ is not Chatty
3b06cf8a2a
feat: Service Mode for Linux (#804) 2024-03-31 16:16:23 +08:00
wonfen
c5d7c29f3d Sytle: fix mac logo padding 2024-03-30 16:31:17 +08:00
Damian Johnson
26af860eac
fix: icon not change when toggle window maxinized (#799) 2024-03-30 01:16:40 +08:00
cismous
ec9852eb98
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
f2b8c6d0ca
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
2cf7a048cf Release 1.5.11 2024-03-28 12:06:41 +08:00
MystiPanda
3dbb71a076 feat: Support Persian
#715
2024-03-21 20:51:33 +08:00
MystiPanda
ddf6760543 fix: Duplicate icon display error
#719
2024-03-21 20:29:16 +08:00
MystiPanda
a6fa323f33 fix: The update button cannot be clicked
#716
2024-03-21 19:56:30 +08:00
MystiPanda
2df8f1acdb fix: Check if the install directory is empty when uninstall 2024-03-21 15:24:12 +08:00
MystiPanda
74a3290abc
chore: fix typo 2024-03-21 14:09:59 +08:00
MystiPanda
ab306d21f3
Release 1.5.10 2024-03-21 13:47:39 +08:00
MystiPanda
1bd7795851
chore: Change a little 2024-03-21 12:55:22 +08:00
MystiPanda
302c142346
build: Update depends 2024-03-21 11:53:25 +08:00
MystiPanda
7c254c9b45
fix: a little 2024-03-21 11:43:16 +08:00
MystiPanda
b1c458359d
feat: Confirm before deletion
#703
2024-03-21 11:39:01 +08:00
MystiPanda
2d925c62f5
feat: Support config redir port and tproxy port 2024-03-21 10:54:56 +08:00
MystiPanda
ef4711987f chore: limit port config
#699
2024-03-20 21:23:10 +08:00
MystiPanda
8910dfaecf fix: Limit icon width
#697
2024-03-20 20:38:45 +08:00
MystiPanda
e8b2e79c7d fix: service logs are not cleared
#695
2024-03-20 20:31:00 +08:00
MystiPanda
333ee1e1f7
docs: Improve the issue template 2024-03-20 18:06:02 +08:00
MystiPanda
ba013bbe96
feat: Optimize Linux tray menu 2024-03-18 11:11:18 +08:00
MystiPanda
636e66fe7a
Revert "fix: remove activation policy" 2024-03-18 09:53:48 +08:00
MystiPanda
372a45e667
Release 1.5.9 2024-03-17 11:25:25 +08:00
MystiPanda
ab6b796ce2 fix: drag error
#643
2024-03-16 20:37:39 +08:00
MystiPanda
71e6c02897
fix: drag error 2024-03-16 17:06:59 +08:00
MystiPanda
3edeaa7038 fix: Try to fix touch drag
#456
2024-03-16 10:54:19 +08:00
MystiPanda
f2f75d4015 build: Update to Tauri 1.6.1 2024-03-16 00:03:34 +08:00
MystiPanda
69adade738 ci: update alpha script 2024-03-15 21:52:56 +08:00
MystiPanda
1bd88828e4 ci: remove 2024-03-15 21:42:55 +08:00
MystiPanda
977fcbe6cd feat: Try to support more architecture 2024-03-15 21:14:05 +08:00
MystiPanda
aa76a5c436 refactor: Try to migrate to boa_engine
#634
2024-03-15 19:58:22 +08:00
MystiPanda
ea5fad428d fix: Avoid empty user-agent 2024-03-15 18:27:24 +08:00
MystiPanda
0e1e27b35a feat: Try to cache remote images
#603
2024-03-15 16:43:39 +08:00
screw-hand
d41f67a156
fix: mac setting theme font-famil not working (#632) 2024-03-15 15:17:05 +08:00
MystiPanda
6f10dec808 fix: remove activation policy
#592
2024-03-15 12:44:25 +08:00
xkww3n
9b79da2cdf
fix: Set REJECT-DROP policy the same text color as REJECT (#622) 2024-03-14 18:54:26 +08:00
MystiPanda
c11b8519f1 Release 1.5.8 2024-03-13 14:17:06 +08:00
wonfen
b9e23a0b59 Sytle: A little tweak 2024-03-13 14:09:08 +08:00
MystiPanda
75d41b6fe5 feat: Add border-radius for window on linux 2024-03-13 13:58:29 +08:00
Amnesiash
4256590735
update profile ui (#594) 2024-03-13 10:53:39 +08:00
MystiPanda
462fb05ea8 fix: Try to fix #577 again 2024-03-11 22:52:29 +08:00
MystiPanda
812dbfd836 fix: script 2024-03-11 22:10:20 +08:00
MystiPanda
cfca837777 fix: typo 2024-03-11 22:05:56 +08:00
MystiPanda
ac5fb1948a fix: Try to fix #577 2024-03-11 22:03:45 +08:00
MystiPanda
22d8b73625 feat: Allow open devtools 2024-03-11 20:19:21 +08:00
MystiPanda
edde40c298 docs: Update Readme 2024-03-11 18:38:58 +08:00
MystiPanda
50ade92238 Release 1.5.7 2024-03-11 17:09:01 +08:00
MystiPanda
b598d00aef fix: display error 2024-03-11 16:18:39 +08:00
MystiPanda
97bee17e4e chore: Change IP 2024-03-11 16:05:09 +08:00
MystiPanda
6aa5c79332 fix: a little 2024-03-11 15:02:41 +08:00
MystiPanda
7c5ce756f9 fix: Change DNS for MacOS Tun Mode
#568
2024-03-11 14:55:00 +08:00
MystiPanda
43d53a9ffa fix: default value 2024-03-11 13:17:45 +08:00
MystiPanda
e6589bee5b fix: Try to fix touch drag
#456
2024-03-11 12:36:20 +08:00
MystiPanda
9f94cad615 feat: Allow to control whether auto check update 2024-03-11 12:17:46 +08:00
MystiPanda
7778d9f773 style: fix a little 2024-03-10 23:56:04 +08:00
MystiPanda
564bd55147 style: fix styles 2024-03-10 23:47:12 +08:00
MystiPanda
a7ff806522 fix styles 2024-03-10 22:13:25 +08:00
MystiPanda
ac3b074d66 chore: proxy group header height 2024-03-10 21:56:49 +08:00
MystiPanda
da949c2604 chore: adjust proxy group font style 2024-03-10 21:52:40 +08:00
MystiPanda
20a9775dc6 chore: Adjust Profile Style 2024-03-10 21:37:52 +08:00
MystiPanda
8ec19e058f chore: Add Default value 2024-03-10 21:15:22 +08:00
MystiPanda
a5c4549722 style: Adjust icon color 2024-03-10 20:48:13 +08:00
Amnesiash
c7d302aa16
style: Adjust colorful icon (#558)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-10 20:37:00 +08:00
MystiPanda
77ac565ff3 chore: i18n 2024-03-10 20:33:27 +08:00
MystiPanda
2e6f834a50 chore: Update Icon 2024-03-10 20:14:51 +08:00
MystiPanda
5b044cca9e feat: Add option to control menu icon 2024-03-10 19:54:47 +08:00
MystiPanda
2ab5623809 chore: Adjust styles 2024-03-10 13:24:38 +08:00
MystiPanda
2d07f0d77c Revert "chore: Adjust secondary text style (#545)"
This reverts commit 8ba7f9f70216ac3ae5ff2ac859ec2170737d0cd1.
2024-03-10 13:08:09 +08:00
Amnesiash
8ba7f9f702
chore: Adjust secondary text style (#545) 2024-03-10 12:54:34 +08:00
MystiPanda
460ac7a86b style: Adjust delay fontSize
#544
2024-03-10 12:51:53 +08:00
MystiPanda
f93a1ab3eb docs: update preview 2024-03-10 12:32:08 +08:00
MystiPanda
6b02696aa8 chore: Change Default Font 2024-03-10 12:23:11 +08:00
MystiPanda
25f20d6a85 chore: delete debug output 2024-03-10 11:47:55 +08:00
MystiPanda
8387964bae Release 1.5.6 2024-03-10 11:43:37 +08:00
MystiPanda
926278617f feat: Add default webui
#530
2024-03-10 11:41:22 +08:00
MystiPanda
1933737a0c Adjust styles 2024-03-10 11:21:17 +08:00
MystiPanda
b3e5288bde Adjust styles 2024-03-10 11:12:54 +08:00
wonfen
ed6e966b2f Sytle: UI improvement & Update Readme 2024-03-10 07:00:24 +08:00
MystiPanda
9315fe36b6 fix: fix a little 2024-03-10 00:41:18 +08:00
black23eep
41fbf5ba36
Update layout-traffic.tsx (#528) 2024-03-10 00:35:26 +08:00
MystiPanda
40db481453 chore: update 2024-03-10 00:34:22 +08:00
MystiPanda
1b5e295744 fix: fontSize and some styles 2024-03-10 00:22:22 +08:00
Amnesiash
b42a52f7f6
fix settings.svg (#526) 2024-03-10 00:04:06 +08:00
MystiPanda
8268df84d0 chore: center 2024-03-09 23:38:03 +08:00
Charles
eb6fa5f1f1
tweak(ui): menu icon use svg component (#524) 2024-03-09 23:13:08 +08:00
MystiPanda
025c8856ed fix: img path error 2024-03-09 22:43:53 +08:00
MystiPanda
ed421445e9 Release 1.5.5 2024-03-09 22:02:36 +08:00
Amnesiash
0cda07106b
refactor: Upgrade to the new UI (#521)
Co-authored-by: MystiPanda <mystipanda@proton.me>
2024-03-09 21:37:21 +08:00
wonfen
f335941b62 UI: change paste icon, delete default profile name 2024-03-09 02:34:33 +08:00
Pylogmon
f0719f8bde
feat: Merge Providers (#508) 2024-03-08 11:37:52 +08:00
MystiPanda
27fe28661c
feat: Add animation for provider update 2024-03-02 13:52:48 +08:00
MystiPanda
bb13b12de6
chore: fix style 2024-02-28 18:25:50 +08:00
MystiPanda
1117e28b2e
feat: Try to support touch drag 2024-02-27 23:41:52 +08:00
MystiPanda
0f48873c25
chore: Remove unnecessary hotkey 2024-02-27 11:18:52 +08:00
MystiPanda
faf61b4af3
fix: Image display failed on Linux 2024-02-26 13:31:51 +08:00
Cyenoch
e29ce93d32
Feat: Provide a switch for allowing invalid certificates (#450) 2024-02-25 16:07:06 +08:00
MystiPanda
38ab68492e
chore: change style 2024-02-24 15:39:29 +08:00
MystiPanda
53b1576e5f
Release 1.5.4 2024-02-24 14:00:01 +08:00
MystiPanda
2835e79973
feat: Support Open/Close Dashboard Hotkey
#439
2024-02-24 13:39:30 +08:00
MystiPanda
8e4d7e989b
feat: Show current proxy for group node
#444
2024-02-24 13:09:53 +08:00
MystiPanda
603c6b826c
feat: allow disable group icon 2024-02-24 12:38:17 +08:00
MystiPanda
51bcc77cb3
refactor: Optimize implementation of Custom tray icon 2024-02-24 11:25:22 +08:00
MystiPanda
a30d07b924
feat: Support Custom Tray Icon 2024-02-24 00:52:21 +08:00
MystiPanda
11faa5fe39
Release 1.5.3 2024-02-22 00:23:56 +08:00
MystiPanda
ce8383b26f
feat: add reset button 2024-02-22 00:19:45 +08:00
MystiPanda
bd8f07c90a
fix: Do not set autolaunch at init
#423 #424
2024-02-21 23:50:50 +08:00
MystiPanda
fee077bebd
chore: rm -m arg 2024-02-21 23:38:01 +08:00
MystiPanda
558f4d93ba
chore: change default value of dns hijack 2024-02-21 16:40:47 +08:00
MystiPanda
e77d126349
chore: change default value of strict route 2024-02-21 11:32:51 +08:00
MystiPanda
cd8667d6de
chore: fix placeholder 2024-02-21 11:13:28 +08:00
MystiPanda
8a2d06d010
chore: Add Translation 2024-02-21 11:06:32 +08:00
MystiPanda
92741fc733
feat: Disable system stack when service mode is turned off 2024-02-21 10:52:03 +08:00
MystiPanda
1cff162649
fix: Config data display error
#417
2024-02-21 10:02:28 +08:00
MystiPanda
f59be465ea
Release 1.5.2 2024-02-21 00:04:11 +08:00
MystiPanda
27305317ce
chore: Auto Update config.yml 2024-02-20 23:54:02 +08:00
MystiPanda
8378320e50
feat: Support Tun Config (#416) 2024-02-20 23:27:03 +08:00
MystiPanda
86a69952db
chore: Update patch file 2024-02-20 18:39:36 +08:00
MystiPanda
e212ebfdb9
fix: Allow program run with administrator to start at startup 2024-02-20 18:35:39 +08:00
MystiPanda
f235125d48
chore: add tooltip for tun mode 2024-02-19 18:10:10 +08:00
MystiPanda
8efa815eb9
feat: Support custom delay timeout (#397) 2024-02-18 11:11:22 +08:00
MystiPanda
e54d42576b
build: Try restart windows service after install (#395) 2024-02-18 10:19:54 +08:00
MystiPanda
1e18539862 fix: Merge profile unexpect behavior 2024-02-17 15:58:52 +08:00
MystiPanda
673fdf4721 ci: Update Release Note 2024-02-15 20:05:00 +08:00
MystiPanda
57d6bfba51 ci: change tag 2024-02-15 19:45:37 +08:00
MystiPanda
8fa23aecda chore: update ci script 2024-02-15 19:14:35 +08:00
MystiPanda
f9c10533e8 chore: Change CI Name 2024-02-15 18:45:13 +08:00
MystiPanda
c891c3723e ci: Support alpha package 2024-02-15 18:43:20 +08:00
MystiPanda
a0625ef2e7 chore: remove 32bit package 2024-02-15 18:22:15 +08:00
MystiPanda
b2774abb54 chore: default show proxy detials 2024-02-15 18:17:21 +08:00
wonfen
fa089bbc40 doc: remove 32bit package 2024-02-12 02:25:18 +08:00
MystiPanda
8437d1763f fix: build error 2024-02-11 20:51:00 +08:00
MystiPanda
0532b0f9df Release v1.5.1 2024-02-11 20:32:25 +08:00
MystiPanda
5f8eb853f0 style: clear script 2024-02-11 20:03:14 +08:00
MystiPanda
9125a85382 fix: Custom GLOBAL group display error 2024-02-11 18:27:27 +08:00
MystiPanda
5b3dc44c72 feat: Show proxies count for provider 2024-02-10 13:13:27 +08:00
MystiPanda
ee5147f111 feat: Save window maximize state 2024-02-10 12:51:30 +08:00
MystiPanda
f67137fd5e style: change config name
#350
2024-02-08 10:23:54 +08:00
MystiPanda
b67db4a896 fix: Script profile invalid
#347
2024-02-06 08:55:44 +08:00
MystiPanda
067018828c Release 1.5.0 2024-02-06 00:41:12 +08:00
MystiPanda
e990530ada refactor: Remove clash field filter 2024-02-05 17:28:36 +08:00
wonfen
45f863a72a chore: enable clash field TLS fingerprint 2024-02-04 13:07:25 +08:00
MystiPanda
519febbeb2 fix: Try to fix traffic parse error
#337
2024-02-04 10:24:37 +08:00
wonfen
854b1a4caf chore: update changelog 2024-02-03 16:14:26 +08:00
MystiPanda
08eb95e73a fix: Copy Env Type Select Error 2024-02-02 17:57:56 +08:00
MystiPanda
66f8fbf08c chore: typo 2024-02-02 17:47:28 +08:00
MystiPanda
86a30bbb29 chore: Update Changelog 2024-02-02 17:34:15 +08:00
MystiPanda
966c453c27 fix: prevent_exit 2024-02-02 17:26:31 +08:00
MystiPanda
c7bcaf0193 Release 1.4.11 2024-02-02 16:56:39 +08:00
MystiPanda
0ac91e36a0 fix: exit_app event 2024-02-02 16:32:19 +08:00
MystiPanda
6f1299ce9e chore: fix styles 2024-02-02 16:15:23 +08:00
MystiPanda
83ca29d649 chore: Remove prevent_close 2024-02-02 15:57:03 +08:00
MystiPanda
d0206044dc fix: Fix Nisi Error 2024-02-02 15:53:28 +08:00
ycjcl868
d4623e4175
chore: unused clash core (#284) 2024-01-23 11:22:15 +08:00
MystiPanda
68ef03377d chore: fix style 2024-01-21 13:49:07 +08:00
MystiPanda
d3f9d3d033 revert: some style 2024-01-21 13:49:07 +08:00
wonfen
a6ccd04471 Sytle: UI improvement 2024-01-20 16:05:01 +08:00
Lai Zn
0fd8174e77
fix: Solve the confliction issues on auto updater failure (#273) 2024-01-20 13:03:48 +08:00
MystiPanda
ebc63f479a revert: Support both registry and api for windows sysproxy 2024-01-20 12:16:46 +08:00
MystiPanda
570c39c20a build: Update lock file 2024-01-18 22:34:14 +08:00
MystiPanda
1d57257cf5 Release v1.4.10 2024-01-18 22:26:23 +08:00
MystiPanda
82d7baee0b feat: Add exit button on setting page 2024-01-18 22:19:14 +08:00
MystiPanda
1d91e1f1f7 build: Update Depends 2024-01-18 16:26:38 +08:00
MystiPanda
15e8894614 feat: Support Custom Start Page 2024-01-18 15:37:02 +08:00
Lai Zn
0ae96918e9
feat: Use url path name as fallback subscription name (#255) 2024-01-18 14:36:37 +08:00
MystiPanda
e970880059 feat: Show SubInfo for Proxy Provider
#211
2024-01-18 14:26:57 +08:00
Lai Zn
69ae86aba8
fix: make port change set to system proxy immediately (#256) 2024-01-18 09:37:46 +08:00
MystiPanda
8142e1be49 fix: use nanoid to compatible with old devices 2024-01-18 01:16:39 +08:00
MystiPanda
cbc7612451 feat: Optimizing Provider Support 2024-01-18 01:02:30 +08:00
Kiri
09e6a7b7cc
feat: Add support for loong64 (#246) 2024-01-17 22:30:51 +08:00
Lai Zn
082e35668a
docs: Add guidelines for windows development (#250) 2024-01-17 19:02:39 +08:00
MystiPanda
c9e78c837b fix: Do not use proxy when test on tun mode 2024-01-17 18:08:09 +08:00
MystiPanda
2d171db672 Release v1.4.9 2024-01-17 15:43:15 +08:00
MystiPanda
a8b11abec8 feat: Support Startup Script 2024-01-17 15:06:16 +08:00
MystiPanda
b08333dccd feat: Support proxy group icon 2024-01-17 13:32:56 +08:00
MystiPanda
98bad48971 fix: Fix connection table sort error
#108
2024-01-17 12:54:54 +08:00
MystiPanda
53375bb536 chore: Update I18n 2024-01-17 11:30:19 +08:00
MystiPanda
88798093e1 fix: csp error 2024-01-17 11:08:14 +08:00
MystiPanda
45a28751af feat: Add Test Page 2024-01-17 11:02:17 +08:00
MystiPanda
1670c44464 fix: fix column width 2024-01-16 19:02:38 +08:00
MystiPanda
a286ac85dc Release v1.4.8 2024-01-16 15:36:04 +08:00
MystiPanda
1606adc0ab feat: Support both registry and api for windows sysproxy 2024-01-16 15:11:53 +08:00
MystiPanda
b4ce557d92 fix: Can not use specify update time when create profile 2024-01-16 10:29:04 +08:00
MystiPanda
2ba7fff8d4 refactor: rm theme blur 2024-01-15 17:13:55 +08:00
MystiPanda
98efc93610 Revert Use Tauri Http Api 2024-01-15 10:18:04 +08:00
MystiPanda
2680507eae Revert Use Tauri Websocket 2024-01-15 10:17:00 +08:00
MystiPanda
df72392ad2 fix: Fix connections sort issue and add total traffic info 2024-01-15 00:46:19 +08:00
MystiPanda
a6acb15a00 refactor: Use Tauri Http API 2024-01-14 19:35:03 +08:00
MystiPanda
ba7242a815 refactor: Use Tauri WebSocket 2024-01-14 17:30:18 +08:00
MystiPanda
99740c1324 fix patch error 2024-01-11 14:47:25 +08:00
MystiPanda
c04b20d1fa Release v1.4.7 2024-01-11 13:19:05 +08:00
MystiPanda
e127067878 feat: Disable updater for portable 2024-01-11 12:44:30 +08:00
MystiPanda
6f03b72368 feat: Support hide group
#214
2024-01-11 12:34:05 +08:00
Morris Li
7bd3ee9340
Fix expired Tauri domain (#222) 2024-01-11 10:17:56 +08:00
MystiPanda
c94db606e5 build: Use old sysproxy 2024-01-10 19:24:14 +08:00
MystiPanda
4d2474226b refactor: cargo clippy 2024-01-10 17:36:35 +08:00
MystiPanda
1ffc4f538b refactor: Optimizing the implementation of Linux URL Scheme registration 2024-01-10 16:34:35 +08:00
MystiPanda
6702ac957b fix: resolve scheme error 2024-01-10 15:37:40 +08:00
MystiPanda
ba42b2e77d fix: linux build error 2024-01-10 15:22:08 +08:00
MystiPanda
fba18ca40a feat: Support URL Scheme for MacOS 2024-01-10 14:04:06 +08:00
MystiPanda
3a2a7a1476 feat: Support URL Scheme for Linux 2024-01-10 13:03:34 +08:00
MystiPanda
3c6d6b90bc build: fix macos build script 2024-01-09 22:49:11 +08:00
MystiPanda
47dc9ee304 fix patch error 2024-01-09 22:25:25 +08:00
MystiPanda
0045bf206c Release v1.4.6 2024-01-09 22:17:29 +08:00
MystiPanda
669a1a6953 feat: Support URL Scheme for Windows
#165
2024-01-09 21:57:06 +08:00
MystiPanda
e28452cc7b feat: Optimize control button style 2024-01-09 16:04:56 +08:00
MystiPanda
5ceac03db1 feat: Add pin button 2024-01-09 15:13:59 +08:00
MystiPanda
96215e5950 fix: Fix some compile error 2024-01-09 14:52:43 +08:00
wonfen
7a030b9224 Style: UI improvement & 1.4.6 ready 2024-01-09 13:57:53 +08:00
wonfen
e743478a4d chore: UI adjustment 2024-01-08 19:32:21 +08:00
MystiPanda
bc29c80d44 chore: Use Latest and compatible core 2023-12-23 12:09:19 +08:00
MystiPanda
82b8a474d7 fix: Cargo clippy 2023-12-21 16:49:21 +08:00
MystiPanda
99851b297d fix: Get filename error
#165
2023-12-19 19:52:13 +08:00
MystiPanda
e3a500e12c fix: portable flag 2023-12-15 21:39:34 +08:00
MystiPanda
378666e3cc chore: update service url 2023-12-15 20:38:17 +08:00
MystiPanda
b65ad1ebd7 fix: user-agent version error 2023-12-15 15:18:01 +08:00
MystiPanda
933a821b5f fix: Subinfo parse error 2023-12-15 11:35:10 +08:00
MystiPanda
159337ddbd Release 1.4.5 2023-12-14 17:40:48 +08:00
MystiPanda
2e25a4333a fix: Window control button icon issue
#136
2023-12-14 14:59:55 +08:00
MystiPanda
64fafb0795 chore: Remove unsafe function 2023-12-14 13:56:51 +08:00
MystiPanda
5927e0bb3b fix: icon size 2023-12-14 13:20:01 +08:00
MystiPanda
e21596db83 feat: Update MacOS tray icon 2023-12-14 13:11:46 +08:00
MystiPanda
8a608c3c3e chore: Optimize service path 2023-12-14 13:03:52 +08:00
MystiPanda
76f9db8516 chore: Optimize upgrade process 2023-12-14 11:24:26 +08:00
MystiPanda
90f4809b7c chore: default enable clash field filter 2023-12-14 10:49:58 +08:00
MystiPanda
3ff1af9fd6 chore: Delete unnecessary drag area 2023-12-13 11:09:19 +08:00
MystiPanda
711dd520f7 chore: Change service path 2023-12-13 10:59:07 +08:00
MystiPanda
99503c836a fix: Save wrong window size 2023-12-12 16:42:19 +08:00
MystiPanda
2c8bc51862 Revert "fix: Change PID file path"
This reverts commit d64bdf02de5bb0c34c2165e3dddcb239e6c26034.
2023-12-12 15:59:00 +08:00
MystiPanda
5e7aa15232 fix: Can't switch env type 2023-12-11 10:51:59 +08:00
MystiPanda
d0761869b6 build: fix update issue 2023-12-10 22:24:46 +08:00
MystiPanda
bb99b89228 build: fix patch 2023-12-10 21:40:57 +08:00
MystiPanda
7b88beb0b5 v1.4.4 2023-12-10 21:36:22 +08:00
MystiPanda
82c630bd0e
feat: Support windows aarch64 (#112) 2023-12-10 20:45:27 +08:00
MystiPanda
bb4f11e1b8 feat: Patch for windows aarch64 2023-12-10 19:50:01 +08:00
MystiPanda
3182d20b7c build: Update Depends 2023-12-10 17:14:48 +08:00
MystiPanda
a6f15e2474 chore: Hide script mode for alpha core 2023-12-10 16:31:55 +08:00
MystiPanda
0c3f03680c Support upgrade alpha core 2023-12-10 15:57:10 +08:00
MystiPanda
a66e8dfc9f feat: Support update geodata
#37
2023-12-10 15:22:04 +08:00
zclkkk
556232aac4
chore: Allow user to choose where to install (#110) 2023-12-10 09:50:43 +08:00
MystiPanda
7fcfaadd91 chore: Remove script mode 2023-12-09 17:04:03 +08:00
MystiPanda
18d388e1e2 docs: Update ISSUE_TEMPLATE 2023-12-09 12:00:01 +08:00
MystiPanda
7de6622d74 feat: Support different tray icon for macos 2023-12-09 11:22:02 +08:00
MystiPanda
9af1f90990 feat: Support different tray icon for linux 2023-12-09 11:03:19 +08:00
Pylogmon
15ab43963d
feat: Optimize copy environment variable logic (#106) 2023-12-08 22:16:42 +08:00
MystiPanda
6b7465a4b0 perf: Improves window creation speed 2023-12-08 13:10:35 +08:00
MystiPanda
d64bdf02de fix: Change PID file path
#99
2023-12-08 11:59:40 +08:00
MystiPanda
4a2c94993a docs: Update README 2023-12-07 16:49:59 +08:00
MystiPanda
86c318d86b feat: Add AppImage for x86 linux 2023-12-07 16:38:39 +08:00
MystiPanda
bffc1ad41d ci: fix build script 2023-12-07 16:17:34 +08:00
MystiPanda
2a685c116f chore: Fix build error 2023-12-07 16:02:29 +08:00
MystiPanda
0e271d2924 ci: Fix Linux CI Script 2023-12-07 15:57:53 +08:00
MystiPanda
7127c4d590 ci: Fix Linux Build Script 2023-12-07 15:34:49 +08:00
MystiPanda
a82929996b chore: change default port to 7897 2023-12-07 14:52:14 +08:00
MystiPanda
3f01f52c7b refactor: fix depends 2023-12-07 14:44:44 +08:00
MystiPanda
9c94494622 refactor: Remove unnecessary changes 2023-12-07 13:43:53 +08:00
MystiPanda
c8a508f35c Revert "chore: change default port to 7897"
This reverts commit 4ec73ef1c3e546d36706a034f65781cf0694deed.
2023-12-07 13:32:49 +08:00
wonfen
4ec73ef1c3 chore: change default port to 7897 2023-12-06 03:37:16 +08:00
MystiPanda
3465b79e5b docs: Update README 2023-12-05 17:02:54 +08:00
MystiPanda
726a0a33ae docs: UPDATELOG 2023-12-05 16:47:42 +08:00
MystiPanda
ba05810d80 Release 1.4.3 2023-12-05 16:28:50 +08:00
MystiPanda
10bb53e7de chore: fix appid 2023-12-05 15:41:13 +08:00
MystiPanda
dd8a083546 refactor: Change app_home to standard locations of app_data 2023-12-05 15:39:44 +08:00
MystiPanda
84131a71c9 fix: Stop core before install update 2023-12-05 15:01:15 +08:00
MystiPanda
4909a11896 docs: Update README 2023-12-05 13:33:23 +08:00
MystiPanda
4069357b81 refactor: Remove page animation 2023-12-05 13:30:55 +08:00
MystiPanda
e6e87bcc40 refactor: Change Tun Icon Color 2023-12-05 13:23:54 +08:00
MystiPanda
7ba22045e8 build: Dependency downgrade 2023-12-05 12:55:20 +08:00
Pylogmon
d4040b61c4
refactor: Change Portable Config Path (#66) 2023-12-05 12:52:26 +08:00
Pylogmon
7534f8fc37
refactor: Hide Clash Field Option when Disable Filter (#63) 2023-12-05 08:34:57 +08:00
MystiPanda
7ced009e92
Update README.md 2023-12-04 14:51:46 +08:00
MystiPanda
cc13c71e40 ci: fix updater 2023-12-04 13:51:46 +08:00
MystiPanda
d2f09209a7 chore: Use Nsis 2023-12-04 13:45:28 +08:00
Pylogmon
a241b04d99
fix: Open File (#56) 2023-12-04 12:15:12 +08:00
Pylogmon
4f35d907cb
fix: Get Profile Filename (#54) 2023-12-04 12:15:01 +08:00
MystiPanda
9e50d144a3
chore: change linux meta core to compatible 2023-12-04 11:38:54 +08:00
wonfen
8514bb48c4 chore: change windows meta core to compatible 2023-12-04 06:00:31 +08:00
WhizPanda
3ae633a2a1 fix: alpha core can't display memory 2023-12-03 18:55:38 +08:00
WhizPanda
2799cb9118 style: fix icon size 2023-12-03 18:28:41 +08:00
WhizPanda
d20346b6ac docs: Update README.md 2023-12-03 16:42:49 +08:00
Pylogmon
2f00666a68
style: improve drag icon style (#51) 2023-12-03 16:27:13 +08:00
WhizPanda
80f4570093 ci: Fix Portable Script 2023-12-03 15:36:39 +08:00
wonfen
3f0a2ba48f docs: update readme 2023-12-03 15:25:33 +08:00
WhizPanda
fc90094e8a
feat: Support Both Stable and Alpha Version (#47) 2023-12-03 14:26:03 +08:00
wonfen
20d580ade8 release: 1.4.2, tweak UI, fix emoji on mac 2023-12-03 14:01:53 +08:00
WhizPanda
290a024a9e chore: Adjust style 2023-12-02 23:20:04 +08:00
WhizPanda
64a283c3a6 chore: Replace Repo Name 2023-12-02 23:04:04 +08:00
WhizPanda
3e93f0ecbc build: Update Depends 2023-12-02 22:36:04 +08:00
WhizPanda
b12bcc1c49 style: Improve Style 2023-12-02 16:23:53 +08:00
WhizPanda
05d7313dcc feat: Support New Clash Field
#46
2023-12-02 15:18:54 +08:00
WhizPanda
c0b6e549f0 build: Update Cargo Depends 2023-12-01 15:52:49 +08:00
WhizPanda
31f9bf219e build: Update Depends 2023-12-01 14:49:32 +08:00
WhizPanda
4906ca7059 feat: support random mixed port 2023-12-01 12:56:18 +08:00
WhizPanda
b963a7a0e5
fix: Fix Updater Script 2023-12-01 11:19:49 +08:00
WhizPanda
5d6dadda76
Remove unnecessary conditions 2023-12-01 11:11:20 +08:00
Pylogmon
77f69fd223
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
7f5052bc87
feat: Use Latest Meta Core mihomo (#41) 2023-12-01 02:44:18 +08:00
Pylogmon
3009c1f4f6
feat: Support cross-compiling to aarch64 (#40)
#19
2023-11-30 22:46:09 +08:00
Pylogmon
5425872bba
feat: Support Disable Tray Click Event (#38)
#21
2023-11-30 22:45:02 +08:00
Pylogmon
0759e17295
feat: Set different tray icon on tun mode (#33)
好看!
2023-11-30 01:29:11 +08:00
Pylogmon
56a59d25df
feat: Add Download Progress for Updater (#34) 2023-11-30 01:28:46 +08:00
Pylogmon
887f92babe
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
197f942b3f
Merge pull request #27 from Pylogmon/emoji-font
feat: Embed Emoji fonts
2023-11-28 10:41:35 +08:00
Pylogmon
a5be7a35e9 feat: Embed Emoji fonts 2023-11-28 10:36:32 +08:00
wonfen
e347675265 fix: Adjust font order 2023-11-28 10:03:01 +08:00
wonfen
02a084d571 chore: update preview gif 2023-11-28 08:49:22 +08:00
wonfen
7fbcc23d94 Merge branch 'main' of github.com:wonfen/clash-verge-rev 2023-11-28 07:53:11 +08:00
wonfen
bda87167a3 update clashmeta core, Imporve UI, merge PR, reset icons, fix CI 2023-11-28 07:49:44 +08:00
wonfen
34f53c287e
Merge pull request #24 from Pylogmon/tray
fix: Tray Icon Tooltip is Empty
2023-11-28 01:46:41 +08:00
wonfen
9ab07f37d7
Merge pull request #21 from Pylogmon/tray-event
feat: Config Tray Click Event
2023-11-28 01:46:16 +08:00
Pylogmon
36399b39fb chore: fix style 2023-11-27 20:10:31 +08:00
Pylogmon
1e015287a1 chore: Add Translation 2023-11-27 20:04:47 +08:00
Pylogmon
a590aa8485 fix: Tray Icon Tooltip is Empty 2023-11-27 19:55:42 +08:00
Pylogmon
c0582fde66 chore: Remove Debug Info 2023-11-26 18:49:48 +08:00
Pylogmon
d8d75b4afa feat: Config Tray Click Event 2023-11-26 18:35:21 +08:00
wonfen
9d4942723c fix: portable CI again 2023-11-23 15:01:30 +08:00
wonfen
e5b1ece5c0 chore: change preview gif and fix portable CI 2023-11-23 14:34:11 +08:00
wonfen
1b25bfbf5a fix: CI portable 2023-11-23 13:42:16 +08:00
wonfen
7c64d9f42a
Merge pull request #3 from Kuingsmile/main
fix: Downgrade runas to fix service install bug
2023-11-23 11:11:06 +08:00
wonfen
61f9f4ef58 chore: update clash.meta core to 2023.11.23, change win icons. 2023-11-23 11:10:32 +08:00
Kuingsmile
b8eac56213 fix: Downgrade runas to fix service install bug 2023-11-22 17:17:12 -08:00
wonfen
ab5fb5749b
Merge pull request #2 from Kuingsmile/main
feat: Update DialogContent width in EditorViewer
2023-11-23 07:46:12 +08:00
Kuingsmile
e0fb9a1b25 feat: Update DialogContent width in EditorViewer
component
2023-11-22 06:49:47 -08:00
wonfen
97e717f646
Merge pull request #1 from Kuingsmile/main
feat: add UWP loopback tools
2023-11-22 16:23:38 +08:00
Kuingsmile
caa82ad1e6 feat: add UWP loopback tools 2023-11-22 00:15:41 -08:00
wonfen
7ec251ea6d chore: UI adjustment, add translation, fix CI 2023-11-22 14:52:14 +08:00
wonfen
675b0c3cca chore: update readme & fix updater issue 2023-11-22 07:31:38 +08:00
wonfen
05e54d4b7f chore: remove old updater CI 2023-11-22 07:06:08 +08:00
wonfen
b4527c90e5 Merge remote-tracking branch 'nyanpasu/main' 2023-11-22 06:48:30 +08:00
wonfen
dbc626734d chore: delete clash core, update CI, change profile name, change URL test link 2023-11-22 02:56:47 +08:00
keiko233
3f2ce3cb80 chore: fix typos 2023-11-16 11:31:06 +08:00
keiko233
0820d6d6fb chore: add updater workflow 2023-11-16 10:59:27 +08:00
keiko233
a8c74e39c2 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
d58c29907d chore: no need for second build 2023-11-16 10:41:28 +08:00
keiko233
2322733427 chore: fix missing assets type 2023-11-16 10:37:26 +08:00
keiko233
d76e8fe85a chore: remove un supported platform 2023-11-16 10:30:25 +08:00
keiko233
0c2e4fe34a chore: fix typos 2023-11-16 10:30:01 +08:00
keiko233
30b6c87a49 docs: add preview gif 2023-11-15 15:17:51 +08:00
keiko233
540221467a chore: clean up workflows 2023-11-15 14:37:10 +08:00
keiko233
d44d331a78 Bump Version 1.4.0 2023-11-15 14:06:29 +08:00
keiko233
595554f18a chore: add release build 2023-11-15 13:52:56 +08:00
keiko233
84d3c3f7eb fix: rust lint 2023-11-15 13:49:06 +08:00
keiko233
890f55c9dc chore: test: use pnpm 2023-11-13 14:49:28 +08:00
keiko233
575a14e1f3 feat: minor tweaks 2023-11-12 00:10:23 +08:00
keiko233
91074bebd6 feat: Nyanpasu Misc 2023-11-12 00:09:32 +08:00
keiko233
3459a16b48 chore: switch updater endpoints 2023-11-12 00:08:23 +08:00
keiko233
88c9b6849f fix: valid with unified-delay & tcp-concurrent 2023-11-11 22:34:30 +08:00
keiko233
d87bb25baf chore: delete current release assets 2023-11-11 22:27:28 +08:00
keiko233
34cb796505 feat: add baseContentIn animation 2023-11-11 19:34:03 +08:00
keiko233
1eb34e0662 feat: add route transition 2023-11-11 18:32:11 +08:00
keiko233
01d631033f feat: Material You! 2023-11-11 17:12:57 +08:00
keiko233
84b2c07340 fix: touchpad scrolling causes blank area to appear 2023-11-11 15:03:41 +08:00
keiko233
a86eeb636d chore: drop upload-artifact & use prerelease 2023-11-11 15:00:54 +08:00
keiko233
417a5a8214 chore: fix: typos
* ↑ baka
2023-11-10 11:50:01 +08:00
keiko233
bcf40dde8c fix: typos 2023-11-10 11:35:38 +08:00
keiko233
cad87484c7 fix: download clash core from backup repo 2023-11-10 11:26:05 +08:00
keiko233
b8ad641ed6 chore: add dev workflow 2023-11-10 10:38:41 +08:00
keiko233
ae8197be8b feat: default disable ipv6 2023-11-10 09:10:15 +08:00
keiko233
eafb0274a7 feat: default enable unified-delay & tcp-concurrent with use meta core 2023-11-10 09:08:17 +08:00
keiko233
71a23a4e02 refactor: copy_clash_env 2023-11-09 10:52:52 +08:00
keiko233
26fd90dfa3 feat: support copy CMD & PowerShell proxy env 2023-11-09 09:58:17 +08:00
keiko233
e393ebede2 fix: use meta Country.mmdb 2023-11-09 09:35:41 +08:00
keiko233
2981bb3f19 feat: default use meta core
* :)
2023-11-09 09:34:03 +08:00
keiko233
6e9f05abb1 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
9df1115380 chore: fix check 2023-11-03 16:00:34 +08:00
GyDi
f22e360cbb chore: fix check script 2023-11-03 15:52:36 +08:00
MZhao
67769af6f4
feat: new windows tray icons (#899) 2023-11-03 15:01:46 +08:00
Aromia
b1a9a1d6d9
chore: update download links in README.md (#896) 2023-11-03 11:21:20 +08:00
Goooler
cf0606ecb7
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
7287edcd6f
chore: add fedora linux install description (#874) 2023-11-02 20:13:24 +08:00
GyDi
e0d26203dd feat: support auto clean log files 2023-11-02 20:12:46 +08:00
GyDi
7e3a85e9da
fix: latency url empty 2023-11-01 23:22:30 +08:00
GyDi
5a0fed9c93 feat: increase the concurrency of latency test 2023-11-01 20:52:38 +08:00
GyDi
1f1e743912
fix: change default port 2023-10-31 14:56:19 +08:00
GyDi
b4301ed0d5
fix: csp 2023-10-31 12:00:57 +08:00
GyDi
b5391560fc
v1.3.8 2023-10-31 01:29:20 +08:00
GyDi
718989cbcf
chore: update log 2023-10-31 01:28:57 +08:00
GyDi
d0aee76962
fix: add default valid key 2023-10-30 17:10:51 +08:00
GyDi
fb08af96bd
fix: page null exception, close #821 2023-10-30 00:53:24 +08:00
GyDi
510a0c5e70
feat: adjust the delay display interval and color, close #836 2023-10-29 23:01:05 +08:00
keiko233
89bdc8ec75 chore: update dependencies 2023-10-22 00:44:01 +08:00
keiko233
ae25ade318 chore: tsconfig: skip Lib Check 2023-10-21 23:53:57 +08:00
keiko233
dd5e46a8a7 feat: theme: change color 2023-10-21 17:30:50 +08:00
keiko233
f7218aaa9e chore: update README.md 2023-10-21 17:02:13 +08:00
keiko233
ee79bcfc44 feat: profiles: import btn with loading state 2023-10-21 16:50:46 +08:00
keiko233
2c2c174874 feat: profile-viewer: handleOk with loading state 2023-10-21 16:47:39 +08:00
keiko233
f57c49ce3a feat: base-dialog: okBtn use LoadingButton 2023-10-21 16:47:10 +08:00
keiko233
06121acfac chore: import MUI Lab 2023-10-21 16:43:30 +08:00
keiko233
2078ce7446 chore: Update dependencies 2023-10-21 16:43:16 +08:00
keiko233
49f41abfdb feat: Nyanpasu Misc 2023-10-20 11:52:40 +08:00
keiko233
70fcfe6d6c feat: Theme support modify --background-color 2023-10-20 11:12:49 +08:00
keiko233
b060b4b9bf feat: settings use Grid layout 2023-10-20 10:05:03 +08:00
keiko233
ee2135bfb3 chore: Use your own update endpoints and public keys 2023-10-19 09:37:31 +08:00
keiko233
7daa322441 chore: readme remove ad 2023-10-18 13:38:58 +08:00
GyDi
74252cb66b chore: fix rust lint 2023-10-18 13:38:03 +08:00
GyDi
5fe2be031f
chore: fix rust lint 2023-10-11 14:55:20 +08:00
GyDi
2ba3aaba47
chore: fix alpha ci 2023-10-11 14:31:44 +08:00
GyDi
ad8903991c
fix: try fix csp 2023-10-11 14:21:56 +08:00
GyDi
3e5624c570
chore: update readme 2023-10-11 00:04:56 +08:00
keiko233
88d3bba300 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
f5ee6f3537
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
afc77e7adc
chore: update readme 2023-10-10 10:25:57 +08:00
GyDi
024f42fce6
chore: update readme 2023-10-10 10:25:01 +08:00
GyDi
8a5f12b97c
chore: update clash meta 2023-10-08 21:45:12 +08:00
GyDi
954b21cf39
chore: update readme 2023-09-11 10:24:44 +08:00
GyDi
74d095774d
chore: fix updater 2023-09-11 10:16:54 +08:00
GyDi
17a2722e6d
chore: fix updater 2023-09-11 10:12:40 +08:00
GyDi
c843bddbfe
chore: fix clash map, close #736 2023-09-10 19:06:02 +08:00
GyDi
3f22a49755
v1.3.7 2023-09-10 19:02:10 +08:00
GyDi
7af2ffcebf
chore: update log 2023-09-10 19:01:59 +08:00
GyDi
de90c959e0
chore: update auto launch 2023-09-10 18:45:17 +08:00
GyDi
9987dc1eb4
fix: i18n 2023-09-10 15:03:29 +08:00
GyDi
3efd575dd2
feat: add Open Dashboard to the hotkey, close #723 2023-09-10 14:46:03 +08:00
GyDi
f4c7b17a87
feat: add check for updates button, close #766 2023-09-10 14:30:31 +08:00
GyDi
16d80718cb
fix: fix page undefined exception, close #770 2023-09-10 13:45:18 +08:00
GyDi
ad228d53b7
feat: add paste and clear icon 2023-09-09 16:52:00 +08:00
Majokeiko
15ee1e531b
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
1c8fb3392a
chore: update mmdb 2023-08-28 15:06:13 +08:00
GyDi
8647866a32
fix: set min window size, close #734 2023-08-28 15:00:27 +08:00
GyDi
23351c4f1c
chore: change ubuntu ci version 2023-08-28 14:44:09 +08:00
GyDi
1367c304cf
chore: update clash 2023-08-28 14:22:50 +08:00
GyDi
26d6bcb074
chore: update release link 2023-08-14 11:11:15 +08:00
GyDi
b0d651ece1
chore: update clash meta 2023-08-14 11:09:19 +08:00
GyDi
b6d50ba6a4
fix: rm debug code 2023-08-12 19:16:20 +08:00
GyDi
b3ab6a9166
v1.3.6 2023-08-12 16:12:03 +08:00
GyDi
f39a5ac9c2
chore: update log 2023-08-12 16:11:50 +08:00
GyDi
38a9a9240d
fix: use sudo when pkexec not found 2023-08-12 15:58:37 +08:00
GyDi
241b22a465
chore: update ci 2023-08-12 15:41:26 +08:00
GyDi
741abc0366
feat: show loading when change profile 2023-08-05 22:07:30 +08:00
GyDi
7854775de5
fix: remove div 2023-08-05 21:43:58 +08:00
GyDi
e62eaa6b4b
fix: list key 2023-08-05 21:43:05 +08:00
GyDi
b4cce23ef4
feat: support proxy provider update 2023-08-05 21:38:44 +08:00
GyDi
2bcaf90fc8
feat: add repo link 2023-08-05 19:54:59 +08:00
GyDi
96ffbe2f84
feat: support clash meta memory usage display 2023-08-05 19:40:23 +08:00
GyDi
6f5acee1c3
fix: websocket disconnect when window focus 2023-08-05 17:21:15 +08:00
GyDi
54e491d8bf
feat: supports show connection detail 2023-08-05 16:52:14 +08:00
GyDi
ab6374e278
chore: update geo data to meta, close #707 2023-08-05 13:42:37 +08:00
GyDi
2fda4c9f67
chore: fix faq 2023-08-05 13:41:30 +08:00
GyDi
5138a45b0f
chore: add faq and download link 2023-08-05 13:38:19 +08:00
GyDi
b224d4fa8a
chore: add promotion 2023-08-05 12:57:48 +08:00
whitemirror33
a552e44483
feat: update connection table with wider process column and click to show full detail (#696) 2023-08-04 14:36:28 +08:00
GyDi
0cf3bba118
feat: more trace logs 2023-08-04 14:15:15 +08:00
Andrei Shevchuk
2c48ea3508
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
b9b6212b75
fix: try fix undefined error 2023-07-28 09:26:06 +08:00
GyDi
b978aaec21
chore: alpha ci 2023-07-26 17:07:20 +08:00
GyDi
af704681d9
feat: center window when out of monitor 2023-07-24 20:55:26 +08:00
GyDi
1443ddfe6c
chore: fix test ci 2023-07-24 09:59:13 +08:00
GyDi
54457a3e1b
chore: fix test ci 2023-07-24 09:47:05 +08:00
GyDi
bf180e6a2c
chore: test ci 2023-07-24 09:28:54 +08:00
GyDi
864a5820c9
chore: test ci 2023-07-24 09:13:19 +08:00
GyDi
4d3ca49c3f
v1.3.5 2023-07-23 13:31:51 +08:00
GyDi
c49c3cf7f0
chore: update log 2023-07-23 13:31:34 +08:00
GyDi
5d5ab57469
fix: blurry tray icon in Windows 2023-07-23 13:25:54 +08:00
GyDi
31978d8de0
chore: update clash core 2023-07-23 13:11:35 +08:00
GyDi
e8eb68bf24
chore: fix check script 2023-07-23 13:11:17 +08:00
GyDi
9ea08f4fed
chore: update issue templates 2023-07-22 23:04:13 +08:00
GyDi
fe078a5c5b
v1.3.4 2023-07-22 20:38:08 +08:00
GyDi
61933954f3
chore: update log 2023-07-22 20:37:22 +08:00
GyDi
4c243638cb
fix: enable context menu in editable element 2023-07-22 17:21:04 +08:00
GyDi
02ba04b5d8
feat: support copy environment variable 2023-07-22 15:35:32 +08:00
GyDi
4f158a4829
fix: save window size and pos in Windows 2023-07-22 13:13:16 +08:00
GyDi
177a22df59
feat: save window size and position 2023-07-22 10:58:16 +08:00
GyDi
6b0ca2966e
feat: app log level add silent 2023-07-22 09:25:54 +08:00
GyDi
aadfaf7150
feat: overwrite resource file according to file modified 2023-07-22 09:18:54 +08:00
GyDi
b307b9a66b
feat: support app log level settings 2023-07-22 08:53:37 +08:00
Kimiblock Moe
6c1ab6002d
feat: Use polkit to elevate permission instaed of sudo (#678) 2023-07-21 23:05:33 +08:00
GyDi
9638eefc91
chore: update check script 2023-07-11 13:25:55 +08:00
GyDi
9e9c4ad587
fix: optimize traffic graph high CPU usage when hidden 2023-07-10 23:44:09 +08:00
GyDi
ce231431b9
fix: remove fallback group select status, close #659 2023-07-10 23:16:19 +08:00
GyDi
06e1e14e02
chore: update clash 2023-07-10 13:36:17 +08:00
GyDi
416e7884f5
feat: add unified-delay field 2023-06-30 13:58:51 +08:00
GyDi
d579222007
v1.3.3 2023-06-30 09:38:33 +08:00
GyDi
30243c84cd
chore: update log 2023-06-30 09:35:49 +08:00
GyDi
3557a77645
fix: error boundary with key 2023-06-30 09:17:59 +08:00
GyDi
97be28638b
chore: update clash meta 2023-06-30 09:02:26 +08:00
GyDi
aba0826c38
fix: connections is null 2023-06-30 09:02:17 +08:00
GyDi
f032228d0e
fix: font family not works in some interfaces, close #639 2023-06-29 14:21:14 +08:00
GyDi
6cf174c5ed
fix: encodeURIComponent secret 2023-06-29 14:15:57 +08:00
GyDi
c2109d245f
chore: update clash & clash meta 2023-06-08 13:52:40 +08:00
GyDi
6a9745171e
feat: add error boundary to the app root 2023-06-08 13:50:45 +08:00
GyDi
f9a68e8b23
fix: encode controller secret, close #601 2023-06-08 13:48:58 +08:00
GyDi
6e391df5ee
fix: linux not change icon 2023-05-28 18:14:11 +08:00
GyDi
f5edca94d3
fix: try fix blank error 2023-05-28 17:35:00 +08:00
GyDi
60046abec3
fix: close all connections when change mode 2023-05-28 17:07:39 +08:00
GyDi
cafc2060b8 fix: macos not change icon 2023-05-28 16:46:17 +08:00
GyDi
b1f45752cf chore: update deps 2023-05-28 16:45:43 +08:00
GyDi
ed17551170 chore: update clash 2023-05-28 16:44:07 +08:00
w568w
ef5adab638
feat: show tray icon variants in different status (#537) 2023-05-28 10:55:39 +08:00
GyDi
fb653ff99d
fix: error message null 2023-05-19 10:53:11 +08:00
GyDi
78fc47a9c4
chore: update gh proxy 2023-05-19 09:44:31 +08:00
GyDi
2a124cea61
v1.3.2 2023-05-19 00:57:22 +08:00
GyDi
4c7cc563dc
chore: update log 2023-05-19 00:57:10 +08:00
GyDi
6114af4f93
fix: profile data undefined error, close #566 2023-05-18 20:02:46 +08:00
GyDi
8c31629655
chore: update clash core 2023-05-05 20:45:52 +08:00
GyDi
03c8a8edb2
chore: update deps 2023-05-05 20:27:46 +08:00
yettera765
3eeaee154f
fix: import url error (#543)
use rustls instead of depending user's system tls
2023-05-05 12:08:08 +08:00
GyDi
8cf8fa7c80
v1.3.1 2023-04-11 10:04:55 +08:00
GyDi
6b4f6fc71e
chore: update log 2023-04-11 10:04:28 +08:00
Tatius Titus
30c2680b6f
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
fb7b1800cc
fix: linux DEFAULT_BYPASS (#503) 2023-04-07 12:47:13 +08:00
GyDi
ff573bf377
chore: update deps 2023-04-02 11:20:18 +08:00
GyDi
0a33bb861e
fix: open file with vscode 2023-04-02 11:19:48 +08:00
GyDi
728756289b
chore: update deps 2023-04-02 10:09:14 +08:00
GyDi
56ccd3a0ac
chore: update clash meta 2023-04-02 09:43:32 +08:00
Tatius Titus
66f3f0ba07
fix: Do not render div as a descendant of p (#494) 2023-04-02 09:37:09 +08:00
Srinivas Gowda
af5e0d589e
chore: update clash core version (#476) 2023-03-27 10:59:27 +08:00
GyDi
533dc99e7d
fix: use replace instead 2023-03-17 08:37:45 +08:00
GyDi
fc5ca965ba
fix: escape path space 2023-03-17 08:36:19 +08:00
John Smith
9c4a46bcdb
fix: escape the space in path (#451) 2023-03-17 08:20:35 +08:00
GyDi
52658886e7
fix: add target os linux 2023-03-16 23:57:34 +08:00
GyDi
8174ab7616
chore: fix ci 2023-03-16 23:54:14 +08:00
GyDi
2b6acedae1
fix: appimage path unwrap panic 2023-03-16 23:45:48 +08:00
GyDi
d00fe9c5f4
v1.3.0 2023-03-16 21:33:51 +08:00
GyDi
88aa270728
chore: update log 2023-03-16 21:33:14 +08:00
GyDi
4ae409c7f4
feat: auto restart core after grand permission 2023-03-16 21:32:39 +08:00
GyDi
9a29c9abdd
feat: add restart core button 2023-03-16 20:56:37 +08:00
GyDi
66d93ea037
fix: remove esc key listener in macOS 2023-03-16 20:42:45 +08:00
GyDi
6c0066dbfb
feat: support update all profiles 2023-03-16 20:32:41 +08:00
GyDi
4fde644733
chore: rm file 2023-03-16 20:31:48 +08:00
GyDi
e7841c60df
fix: adjust style 2023-03-16 19:32:59 +08:00
hybo
94f647b24a
chore: evaluate error context lazily (#447) 2023-03-16 17:06:52 +08:00
GyDi
630249d22a
fix: adjust swr option 2023-03-16 17:03:12 +08:00
GyDi
db99b4cb54
fix: infinite retry when websocket error 2023-03-16 17:03:00 +08:00
GyDi
c77db23586
fix: type error 2023-03-16 13:51:46 +08:00
GyDi
daf66bcec4
chore: update deps 2023-03-16 13:42:27 +08:00
GyDi
8caf36349f
chore: fix ci 2023-03-16 13:18:05 +08:00
GyDi
6934de58e5
chore: fix ci 2023-03-16 13:16:30 +08:00
GyDi
54a5007c01
feat: support to grant permission to clash core 2023-03-16 11:16:54 +08:00
GyDi
e25a455698
fix: do not parse log except the clash core 2023-03-16 10:34:28 +08:00
GyDi
ab429dfeb6
feat: support clash fields filter in ui 2023-03-15 08:43:24 +08:00
GyDi
c5289dc0e8
fix: field sort for filter 2023-03-15 08:43:03 +08:00
boatrainlsz
d191877002
chore: update README (#435) 2023-03-07 22:55:38 +08:00
GyDi
4d979160c2
chore: update deps 2023-03-07 00:10:49 +08:00
GyDi
d00e8f6e19
chore: update clash 2023-03-06 23:35:30 +08:00
GyDi
91b77e5237
fix: add meta fields 2023-02-18 00:46:03 +08:00
GyDi
5b8c246d53
chore: update clash 2023-02-18 00:09:40 +08:00
GyDi
b374b9b91c
feat: open dir on the tray 2023-02-17 00:15:21 +08:00
GyDi
403717117e
fix: runtime config user select 2023-02-17 00:00:23 +08:00
GyDi
027295d995
feat: support to disable clash fields filter 2023-02-16 23:52:55 +08:00
GyDi
c9b7eccbc1
fix: app_handle as_ref 2023-02-15 17:25:35 +08:00
GyDi
2b6d9348cd
fix: use crate 2023-02-11 23:31:55 +08:00
GyDi
692f8c8454
fix: appimage auto launch, close #403 2023-02-11 23:19:08 +08:00
inRm3D
6783355c4d
chore: add openssl depend (#395) 2023-02-02 10:54:15 +08:00
GyDi
fb9cca1e99
v1.2.3 2023-02-01 22:11:34 +08:00
GyDi
eb770ede1a
chore: update log 2023-02-01 22:11:06 +08:00
GyDi
2643e853af
chore: aarch script 2023-02-01 21:41:15 +08:00
GyDi
b79456e91b
chore: update clash 2023-01-30 20:50:08 +08:00
GyDi
ac66c086f8
chore: meta ci 2023-01-30 20:40:57 +08:00
GyDi
ebccf401dd
chore: fix ci 2023-01-22 13:31:34 +08:00
GyDi
66494845b7
chore: fix ci 2023-01-19 18:02:08 +08:00
GyDi
e6c36ad602
chore: alpha ci 2023-01-18 23:39:27 +08:00
inRm3D
26379182db
chore: try fix missing libssl3 (#378)
* Update ci.yml

* use package from source
2023-01-18 23:36:18 +08:00
GyDi
bba03d14d4
fix: compatible with UTF8 BOM, close #283 2023-01-17 19:51:02 +08:00
GyDi
23b728a762
feat: adjust macOS window style 2023-01-16 22:57:53 +08:00
GyDi
819c5207d2
fix: use selected proxy after profile changed 2023-01-15 21:33:03 +08:00
GyDi
311358544e
fix: error log 2023-01-15 19:11:51 +08:00
GyDi
4480ecc96d
chore: ci 2023-01-14 22:22:18 +08:00
GyDi
6c5f70a205
v1.2.2 2023-01-14 22:01:28 +08:00
GyDi
99adfb4a9e
chore: update log 2023-01-14 21:56:27 +08:00
GyDi
7909cf4067
fix: adjust fields order 2023-01-14 14:57:55 +08:00
GyDi
780ab20aeb
fix: add meta fields 2023-01-14 14:51:29 +08:00
GyDi
73119bb7c5
chore: ci 2023-01-14 12:20:17 +08:00
GyDi
f20f0f064e
chore: update clash meta 2023-01-14 12:16:09 +08:00
GyDi
1b44ae098c
fix: add os platform value 2023-01-14 12:07:31 +08:00
GyDi
453c230716
feat: recover core after panic, close #353 2023-01-14 11:45:47 +08:00
GyDi
439d885ee1
chore: allow unused 2023-01-13 23:11:48 +08:00
GyDi
43dee3ef76
fix: reconnect traffic websocket 2023-01-13 23:02:45 +08:00
GyDi
c71ba6ff8d
chore: update deps 2023-01-13 21:29:21 +08:00
GyDi
fb7a36eb73
feat: use decorations in Linux, close #354 2023-01-12 00:42:33 +08:00
GyDi
e7f294a065
fix: parse bytes precision, close #334 2023-01-11 14:05:49 +08:00
GyDi
d5037f180e
fix: trigger new profile dialog, close #356 2023-01-11 13:45:16 +08:00
GyDi
e90158809a
fix: parse log cause panic 2023-01-11 13:30:14 +08:00
GyDi
0cb802ed9a
chore: update clash meta 2023-01-09 21:55:03 +08:00
GyDi
d0b47204f4
v1.2.1 2022-12-23 22:44:27 +08:00
GyDi
351cb391e5
chore: update log 2022-12-23 22:44:00 +08:00
GyDi
051be927cd
fix: avoid setting login item repeatedly, close #326 2022-12-23 22:39:27 +08:00
GyDi
8bad2c2113
fix: adjust code 2022-12-15 21:54:48 +08:00
GyDi
2bcf6fb3eb
fix: adjust delay check concurrency 2022-12-15 12:23:57 +08:00
GyDi
d1ba0ed2b2
chore: adjust code 2022-12-15 12:22:20 +08:00
GyDi
6e421e60c5
fix: change default column to auto 2022-12-14 16:56:33 +08:00
GyDi
8385050804
fix: change default app version 2022-12-14 16:15:53 +08:00
GyDi
bfe4f08232
fix: adjust rule ui 2022-12-14 15:59:41 +08:00
GyDi
132f914b0d
fix: adjust log ui 2022-12-14 15:49:05 +08:00
GyDi
97d82b03ab
fix: keep delay data 2022-12-14 15:29:02 +08:00
GyDi
f06fa3f9b7
fix: use list item button 2022-12-14 15:16:49 +08:00
GyDi
6337788a22
fix: proxy item style 2022-12-14 15:15:44 +08:00
GyDi
024db4358b
feat: auto proxy layout column 2022-12-14 15:07:51 +08:00
GyDi
173f35487e
chore: fix ci 2022-12-13 18:13:28 +08:00
GyDi
74cbe82dd1
chore: fix ci 2022-12-13 18:11:43 +08:00
GyDi
a8c30d30a9
chore: fix ci 2022-12-13 18:02:23 +08:00
GyDi
1b7a52d5af
chore: alpha ci 2022-12-13 17:44:46 +08:00
GyDi
e5109789bf
chore: alpha ci 2022-12-13 17:41:53 +08:00
GyDi
4d2b35e09d
feat: support to change proxy layout column 2022-12-13 17:34:39 +08:00
GyDi
5c5177ec57
feat: support to open core dir 2022-12-13 00:44:24 +08:00
GyDi
d93b00cd15
chore: update deps 2022-12-12 00:00:45 +08:00
MoeShin
39ade59174
fix: Virtuoso no work in legacy browsers (#318) 2022-12-08 10:47:42 +08:00
GyDi
0309c815b9
chore: update deps 2022-12-04 20:54:43 +08:00
GyDi
ce613098db
fix: adjust ui 2022-12-04 00:45:39 +08:00
GyDi
ab34044196
feat: profile page ui 2022-11-28 22:29:58 +08:00
GyDi
17f724748f
chore: deps 2022-11-28 22:27:23 +08:00
GyDi
f3a917b5e7
chore: update clash version 2022-11-26 13:25:52 +08:00
GyDi
376011ea08
fix: refresh websocket 2022-11-25 22:22:57 +08:00
GyDi
2ce7624c14
fix: adjust ui 2022-11-24 18:24:34 +08:00
GyDi
c62dddd5b9
feat: save some fields in the runtime config, close #292 2022-11-24 17:52:25 +08:00
GyDi
490ba9f140
chore: alpha ci 2022-11-24 11:14:03 +08:00
GyDi
f76890cc56
fix: parse bytes base 1024 2022-11-24 11:11:31 +08:00
GyDi
e1c8f1fed9
fix: add clash fields 2022-11-24 10:46:21 +08:00
GyDi
6e19a4ab8b
chore: ci 2022-11-24 10:41:03 +08:00
GyDi
b156523a7f
chore: ci 2022-11-24 10:35:22 +08:00
GyDi
d20b745ae5
fix: direct mode hide proxies 2022-11-24 10:35:09 +08:00
GyDi
c51e9e6b2c
feat: add meta feature 2022-11-24 10:26:41 +08:00
GyDi
1a31fa9067
fix: profile can not edit 2022-11-24 10:26:25 +08:00
GyDi
558b8499af
chore: ci 2022-11-23 23:46:32 +08:00
GyDi
986c162988
v1.2.0 2022-11-23 23:15:39 +08:00
GyDi
770d5cd11c
chore: ci 2022-11-23 23:15:20 +08:00
GyDi
446d2ab3af
chore: update log 2022-11-23 23:13:44 +08:00
GyDi
2f9bf7f063
feat: display proxy group type 2022-11-23 18:27:57 +08:00
GyDi
72ff9c0964
chore: rm dead code 2022-11-23 17:45:49 +08:00
GyDi
33b1a11d85
fix: parse logger time 2022-11-23 17:45:43 +08:00
GyDi
06dabf1e4e
fix: adjust service mode ui 2022-11-23 17:45:22 +08:00
GyDi
28d3691e0b
feat: add use clash hook 2022-11-23 17:44:40 +08:00
GyDi
ffa21fbfd2
fix: adjust style 2022-11-23 17:42:01 +08:00
GyDi
db028665fd
fix: check hotkey and optimize hotkey input, close #287 2022-11-23 17:30:19 +08:00
GyDi
6bc83d9f27
fix: mutex dead lock 2022-11-23 16:04:25 +08:00
GyDi
790d832155
fix: adjust item ui 2022-11-23 15:29:42 +08:00
GyDi
f477cecdeb
fix: regenerate config before change core 2022-11-23 09:57:21 +08:00
GyDi
e031389021
fix: close connections when profile change 2022-11-23 00:20:57 +08:00
GyDi
e00f826eb8
fix: lint 2022-11-22 23:02:18 +08:00
GyDi
24f4e8ab99
chore: fix check script 2022-11-22 23:01:04 +08:00
GyDi
1550d528bd
fix: windows service mode 2022-11-22 22:01:34 +08:00
GyDi
40c041031e
fix: init config file 2022-11-22 22:01:19 +08:00
GyDi
3e555ec9f1
chore: fix mmdb url 2022-11-22 21:15:45 +08:00
GyDi
5098f14aab
fix: service mode error and fallback to sidecar 2022-11-22 20:47:21 +08:00
GyDi
a355a9c85e
fix: service mode viewer ui 2022-11-22 20:45:17 +08:00
GyDi
e7db2a8573
chore: update check script 2022-11-22 20:44:44 +08:00
GyDi
9b18bd0b48
fix: create theme error, close #294 2022-11-22 16:01:33 +08:00
GyDi
f95ddd594e
feat: guard the mixed-port and external-controller 2022-11-22 15:45:17 +08:00
GyDi
fe8168784f
feat: adjust builtin script and support meta guard script 2022-11-22 12:00:48 +08:00
MoeShin
4046f143f6
fix: matchMedia().addEventListener #258 (#296) 2022-11-22 09:25:39 +08:00
GyDi
e4e16999c8
chore: alpha release add portable 2022-11-22 00:16:30 +08:00
GyDi
10f3ba4ff4
chore: alpha ci 2022-11-21 23:19:25 +08:00
GyDi
3cd2be5081
fix: check config 2022-11-21 23:11:56 +08:00
GyDi
c9359978f9
fix: show global when no rule groups 2022-11-21 23:06:32 +08:00
GyDi
781c67b31a
fix: service viewer ref 2022-11-21 23:02:48 +08:00
GyDi
020bd129fb
fix: service ref error 2022-11-21 22:33:06 +08:00
GyDi
8086b6d78c
feat: disable script mode when use clash meta 2022-11-21 22:28:57 +08:00
GyDi
48e14b36b8
feat: check config when change core 2022-11-21 22:27:55 +08:00
GyDi
b3c1c56579
fix: group proxies render list is null 2022-11-21 22:10:24 +08:00
GyDi
bd0e932910
feat: support builtin script for enhanced mode 2022-11-21 21:05:00 +08:00
GyDi
525e5f88ae
chore: update ci 2022-11-20 23:17:05 +08:00
GyDi
005eeb0e0b
chore: update ci node version 2022-11-20 23:14:43 +08:00
GyDi
d21bb015e8
fix: pretty bytes 2022-11-20 23:08:30 +08:00
GyDi
7338838b0e
feat: adjust profiles page ui 2022-11-20 22:37:34 +08:00
GyDi
8bb4803ff9
refactor: adjust base components export 2022-11-20 22:03:55 +08:00
GyDi
892b919cf3
refactor: adjust setting dialog component 2022-11-20 21:48:39 +08:00
GyDi
572d81ecef
fix: use verge hook 2022-11-20 20:12:58 +08:00
GyDi
a4ce7a4037
feat: optimize proxy page ui 2022-11-20 19:46:16 +08:00
GyDi
6eafb15cf9
chore: adjust type 2022-11-19 17:22:29 +08:00
GyDi
e19fe5ce1c
fix: adjust notice 2022-11-19 01:24:07 +08:00
GyDi
9d2017e598
feat: add error boundary 2022-11-19 01:22:19 +08:00
GyDi
f33c419ed9
chore: rm polyfill 2022-11-19 01:22:00 +08:00
GyDi
f425fbaf9d
feat: adjust clash log 2022-11-18 22:08:06 +08:00
GyDi
bcc5ec897a
fix: windows issue 2022-11-18 20:15:34 +08:00
GyDi
f5f2fe3472
fix: change dev log level 2022-11-18 18:37:29 +08:00
GyDi
b7c3863882
fix: patch clash config 2022-11-18 18:37:17 +08:00
GyDi
d759f48ee8
fix: cmds params 2022-11-18 18:26:55 +08:00
GyDi
3a37075e71
Merge branch 'refactor' 2022-11-18 18:21:49 +08:00
GyDi
58366c0b87
chore: rm code 2022-11-18 18:19:26 +08:00
GyDi
2667ed13f1
refactor: done 2022-11-18 18:18:41 +08:00
GyDi
34daffbc96
refactor: adjust all path methods and reduce unwrap 2022-11-18 10:26:39 +08:00
GyDi
be81cd72af
fix: adjust singleton detect 2022-11-18 08:24:27 +08:00
GyDi
4ae00714d2
fix: change template 2022-11-18 08:04:26 +08:00
GyDi
f24cbb6692
refactor: rm code 2022-11-17 23:21:13 +08:00
GyDi
5a35c5b928
refactor: fix 2022-11-17 22:53:41 +08:00
GyDi
1880da6351
refactor: rm dead code 2022-11-17 22:52:22 +08:00
GyDi
df93cb103c
refactor: for windows 2022-11-17 20:19:40 +08:00
GyDi
63b474a32c
refactor: wip 2022-11-17 17:07:13 +08:00
GyDi
abdbf158d1
refactor: wip 2022-11-16 01:26:41 +08:00
GyDi
ee68d80d0a
refactor: wip 2022-11-15 01:33:50 +08:00
GyDi
c8e6f3a627
refactor: rm update item block_on 2022-11-14 23:07:51 +08:00
GyDi
dc941575fe
refactor: fix 2022-11-14 22:50:47 +08:00
GyDi
e64103e5f2
fix: copy resource file 2022-11-14 21:11:42 +08:00
GyDi
f0ab03a9fb
chore: tmpl add clash core 2022-11-14 21:10:29 +08:00
GyDi
09965f1cc6
feat: add draft 2022-11-14 19:31:22 +08:00
GyDi
d2852bb34a
refactor: fix 2022-11-14 01:45:58 +08:00
GyDi
b03c52a501
refactor: wip 2022-11-14 01:26:33 +08:00
GyDi
fd6633f536
fix: MediaQueryList addEventListener polyfill 2022-11-13 10:27:26 +08:00
GyDi
320ac81f48
chore: fix ci 2022-11-12 17:07:47 +08:00
GyDi
70bcd2428f
chore: alpha ci 2022-11-12 17:06:26 +08:00
GyDi
71f5ada0a3
chore: lock version 2022-11-12 17:06:18 +08:00
GyDi
aab5141404
chore: fix ci 2022-11-12 16:54:57 +08:00
GyDi
ff6b119f27
chore: alpha ci 2022-11-12 16:47:27 +08:00
GyDi
7ef4b7eeb8
fix: change default tun dns-hijack 2022-11-12 11:48:02 +08:00
GyDi
4668be6e24
chore: format rust code 2022-11-12 11:37:23 +08:00
GyDi
a211fc7c97
fix: something 2022-11-11 22:45:32 +08:00
GyDi
54af0b675d
chore: update deps 2022-11-11 21:28:34 +08:00
GyDi
8c8171e774
feat: change default latency test url 2022-11-11 01:24:18 +08:00
GyDi
0bb1790206
fix: provider proxy sort by delay 2022-11-11 01:21:23 +08:00
GyDi
45fc84d8be
chore: rm dead code 2022-11-10 22:58:46 +08:00
GyDi
f7500f4cad
chore: clash meta compatible and geosite.dat 2022-11-10 22:58:34 +08:00
GyDi
0cfd718d8a
feat: auto close connection when proxy changed 2022-11-10 01:27:05 +08:00
GyDi
5f486d0f51
fix: profile item menu ui dense 2022-11-09 15:53:42 +08:00
GyDi
6e5a2f85a1
fix: disable auto scroll to proxy 2022-11-09 11:44:09 +08:00
GyDi
e66a89208d
feat: support to change external controller 2022-11-06 23:23:26 +08:00
GyDi
7f65c501c6
feat: add sub-rules 2022-11-05 15:50:06 +08:00
GyDi
22c2382765
feat(macOS): support cmd+w and cmd+q 2022-11-04 00:51:46 +08:00
GyDi
38e1a4febf
chore: update clash meta 2022-11-04 00:36:32 +08:00
GyDi
8db554b377
chore: fix ci 2022-11-03 01:34:23 +08:00
GyDi
5f5cc55331
chore: compatible ci 2022-11-03 01:32:16 +08:00
GyDi
2f8b39186f
chore: update ci 2022-11-03 01:15:50 +08:00
GyDi
a3a724e2e6
chore: test ci 2022-11-03 00:51:49 +08:00
GyDi
73235c8699
chore: test ci 2022-11-03 00:48:27 +08:00
GyDi
7e5999e862
chore: add x 2022-11-02 00:57:27 +08:00
GyDi
afa244dcb0
v1.1.2 2022-11-02 00:54:51 +08:00
GyDi
4ea5bb2390
fix: check remote profile 2022-11-02 00:51:25 +08:00
GyDi
e545d552f6
chore: update log 2022-11-02 00:44:45 +08:00
GyDi
56fe7b3596
feat: add version on tray 2022-11-01 23:29:59 +08:00
GyDi
eb28ec866a
feat: add animation 2022-10-29 23:36:10 +08:00
GyDi
4649454282
fix: remove smoother 2022-10-29 20:05:55 +08:00
angrylid
a45dc6efda
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
9e56b9fbb5
fix: icon button color 2022-10-29 19:22:15 +08:00
GyDi
5504994cb9
feat: check remote profile field 2022-10-28 13:00:52 +08:00
GyDi
acff6d0432
fix: init system proxy correctly 2022-10-28 01:33:21 +08:00
GyDi
eea9cb7c5b
fix: open file 2022-10-28 01:26:45 +08:00
GyDi
c0ddddfb1f
fix: reset proxy 2022-10-28 01:06:54 +08:00
GyDi
f7dab3ca56
fix: init config error 2022-10-28 01:02:47 +08:00
GyDi
e11b4038a3
feat: system tray support zh language 2022-10-28 00:40:29 +08:00
GyDi
b635e64803
fix: adjust reset proxy 2022-10-27 21:14:14 +08:00
GyDi
20a194b49a
fix: adjust code 2022-10-27 21:13:27 +08:00
GyDi
33a5fb8837
fix: add https proxy 2022-10-26 17:11:54 +08:00
GyDi
59dae640db
fix: auto scroll into view when sorted proxies changed 2022-10-26 01:24:06 +08:00
GyDi
a6ac75e97b
feat: display delay check result timely 2022-10-26 01:11:02 +08:00
GyDi
cc5b33a8ec
fix: refresh proxies interval, close #235 2022-10-26 01:08:34 +08:00
GyDi
6e1a627b84
fix: style 2022-10-25 23:54:21 +08:00
GyDi
62d4c65e1c
chore: update deps 2022-10-25 23:54:10 +08:00
GyDi
600134a3ac
fix: fetch profile with system proxy, close #249 2022-10-25 23:45:24 +08:00
LooSheng
df14af7337
fix: The profile is replaced when the request fails. (#246) 2022-10-23 17:22:26 +08:00
GyDi
2f740b570d
fix: default dns config 2022-10-22 22:26:40 +08:00
GyDi
294d980b52
fix: kill clash when exit in service mode, close #241 2022-10-22 13:55:06 +08:00
GyDi
c9d9909d74
chore: update mmdb 2022-10-22 13:30:08 +08:00
GyDi
90eeabae7b
feat: update profile with system proxy/clash proxy 2022-10-18 23:19:21 +08:00
GyDi
a32c77c5f1
chore: adjust code 2022-10-17 23:26:25 +08:00
GyDi
910846f2ce
fix: icon button color inherit 2022-10-16 14:40:45 +08:00
GyDi
35d0438261
fix: app version to string 2022-10-16 14:38:57 +08:00
GyDi
515af472ce
fix: break loop when core terminated 2022-10-14 14:46:15 +08:00
GyDi
f062f7f9fe
feat: change global mode ui, close #226 2022-10-13 11:54:52 +08:00
GyDi
f68378041f
feat: default user agent same with app version 2022-10-12 11:28:47 +08:00
GyDi
30ef3057ac
v1.1.1 2022-10-11 23:03:12 +08:00
GyDi
48f3a934c9
chore: update log 2022-10-11 23:02:49 +08:00
GyDi
38d1fde84f
fix: api error handle 2022-10-11 22:51:18 +08:00
GyDi
dd78670a4b
fix: clash meta not load geoip, close #212 2022-10-11 22:24:54 +08:00
GyDi
b8a8190a43
fix: sort proxy during loading, close #221 2022-10-11 22:06:55 +08:00
GyDi
2d0989342f
fix: not create windows when enable slient start 2022-10-11 21:50:00 +08:00
GyDi
15ff9b06a1
fix: root background color 2022-10-11 20:49:03 +08:00
GyDi
41b19f69de
fix: create window correctly 2022-10-11 00:57:34 +08:00
GyDi
f2bd6f1fce
fix: set_activation_policy 2022-10-10 22:15:21 +08:00
GyDi
ec94218a4b
chore: rm aarch64 ci 2022-09-28 18:50:33 +08:00
GyDi
c2e5c7cf38
chore: test ci 2022-09-28 17:26:32 +08:00
GyDi
bac5734527
chore: test ci 2022-09-28 17:12:27 +08:00
GyDi
bef4033d94
chore: fix ci 2022-09-28 16:04:05 +08:00
GyDi
64216cba67
chore: fix test ci 2022-09-28 15:19:50 +08:00
Particle_G
a9d6167a9f
chore: add support for windows arm64, close #216 (#209) 2022-09-28 15:13:24 +08:00
GyDi
6c6b40548f
fix: disable spell check 2022-09-28 14:15:22 +08:00
苏业钦
a916d88e85
chore: add support linux arm64 (#215) 2022-09-28 10:40:41 +08:00
GyDi
ab2f0548a3
chore: clash meta linux use compatible version 2022-09-27 00:16:22 +08:00
GyDi
e30ba07285
feat: optimize config feedback 2022-09-26 20:46:29 +08:00
GyDi
1b336d973d
fix: adjust init launch on dev 2022-09-26 01:35:19 +08:00
GyDi
7b1866737f
v1.1.0 2022-09-26 01:27:56 +08:00
GyDi
eedc4ab648
chore: update log 2022-09-26 01:27:32 +08:00
GyDi
5e429c7a94
fix: ignore disable auto launch error 2022-09-26 01:09:05 +08:00
GyDi
57d23eb043
fix(macos): set auto launch path to application 2022-09-25 23:36:55 +08:00
GyDi
5ebd9be89a
fix: i18n 2022-09-25 01:41:46 +08:00
GyDi
5a743779e2
feat: show connections with table layout 2022-09-25 01:35:21 +08:00
GyDi
0495062110
fix: style 2022-09-24 20:55:40 +08:00
GyDi
d522191f69
fix: save enable log on localstorage 2022-09-24 19:03:14 +08:00
Priestch
bbbdc8b7a6
fix: typo in api.ts (#207) 2022-09-24 18:48:11 +08:00
GyDi
c00ed8aa5a
chore: use ubuntu latest 2022-09-24 16:08:39 +08:00
GyDi
0beaa94068
feat: show loading on proxy group delay check 2022-09-24 15:31:09 +08:00
GyDi
96e76665d6
fix: refresh clash ui await patch 2022-09-24 14:01:28 +08:00
Shun Li
6423a29600
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
8bddf30dcf
refactor(hotkey): use tauri global shortcut 2022-09-23 15:31:01 +08:00
GyDi
a6b2db182d
feat: adjust connection page ui 2022-09-23 00:08:52 +08:00
GyDi
b9f3f9d859
Merge pull request #197 from FoundTheWOUT/main
Support ordering connection
2022-09-22 22:55:46 +08:00
GyDi
6331447dcd
feat: yaml merge key 2022-09-21 22:15:24 +08:00
GyDi
4213ee660f
feat: toggle log ws 2022-09-20 22:15:28 +08:00
GyDi
3bd9287c5d
chore: update deps 2022-09-20 21:26:38 +08:00
GyDi
b23d3f7c8b
feat: add rule page 2022-09-18 23:19:02 +08:00
GyDi
f8d9e5e027
feat: hotkey viewer 2022-09-18 15:52:53 +08:00
GyDi
8fa7fb3b1f
feat: refresh ui when hotkey clicked 2022-09-18 15:50:03 +08:00
FoundTheWOUT
63d92a0872 Support ordering connection 2022-09-15 15:52:12 +08:00
GyDi
509d83365e
feat: support hotkey (wip) 2022-09-14 01:19:02 +08:00
GyDi
d6ab73c905
fix: remove dead code 2022-09-12 13:42:21 +08:00
GyDi
5a93ba05d5
fix: style 2022-09-12 13:42:13 +08:00
GyDi
2d2fdf0b1e
fix: handle is none 2022-09-12 00:45:19 +08:00
GyDi
cf96622261
fix: unused 2022-09-12 00:02:25 +08:00
GyDi
47c8ccb0e5
refactor: optimize 2022-09-11 20:58:55 +08:00
GyDi
02fdb8778b
fix: style 2022-09-11 18:38:51 +08:00
GyDi
6bed7f0e66
fix: windows logo size 2022-09-11 17:50:58 +08:00
GyDi
7597d335b9
feat: hide window on macos 2022-09-09 16:42:35 +08:00
GyDi
f32c5ba244
fix: do not kill sidecar during updating 2022-09-09 16:33:04 +08:00
GyDi
ab53ab21e2
fix: delay update config 2022-09-09 16:30:27 +08:00
GyDi
acfe5dbb49
fix: reduce logo size 2022-09-09 16:19:13 +08:00
GyDi
f9b91fa189
feat: system proxy setting 2022-09-07 01:51:43 +08:00
GyDi
2462e68ba1
fix: window center 2022-09-06 22:18:06 +08:00
GyDi
019b2a1681
chore: update resource 2022-09-06 15:32:22 +08:00
GyDi
e94a07b677
fix: log level warn value 2022-09-06 14:42:58 +08:00
GyDi
c058c29755
feat: change default singleton port and support to change the port 2022-09-06 00:45:01 +08:00
GyDi
9e7c7ac163
feat: log info 2022-09-05 20:30:39 +08:00
GyDi
fcee41f00d
feat: kill clash by pid 2022-09-05 16:30:29 +08:00
GyDi
ff2c1bf8ed
feat: change clash port in dialog 2022-09-05 02:12:25 +08:00
GyDi
a2cf26e7ed
feat: add proxy item check loading 2022-09-04 23:53:48 +08:00
GyDi
71e6900375
feat: compatible with proxy providers health check 2022-09-04 22:55:54 +08:00
GyDi
3bdc98bd12 v1.0.6 2022-09-03 00:40:50 +08:00
GyDi
68d2a6e951
chore: update log 2022-09-03 00:39:39 +08:00
GyDi
acf47ac947
chore: update deps 2022-09-02 23:06:00 +08:00
GyDi
fda24e5f5a
chore: update deps 2022-09-02 01:35:06 +08:00
GyDi
05eca8e4d8
chore: use ubuntu 18.04 2022-09-02 01:27:06 +08:00
GyDi
b915f3b1a9
feat: add empty ui 2022-09-02 01:09:38 +08:00
GyDi
ab58968f4d
feat: complete i18n 2022-09-02 01:05:45 +08:00
GyDi
820d1e7570
fix: increase delay checker concurrency 2022-09-02 00:41:43 +08:00
GyDi
a120c8cf98
fix: external controller allow lan 2022-09-02 00:24:19 +08:00
GyDi
5a35ea116f
chore: update clash meta version 2022-09-02 00:11:52 +08:00
GyDi
e72ad1f030
fix: remove useless optimizations 2022-08-31 21:44:23 +08:00
GyDi
a17362437a
chore: test ci 2022-08-29 17:26:09 +08:00
GyDi
8643dc43b1
chore: update clash 2022-08-29 11:11:55 +08:00
GyDi
e1c869a358
chore: add test ci os version 2022-08-29 11:02:14 +08:00
GyDi
2f5b8d9abe Merge branch 'main' of github.com:zzzgydi/clash-verge 2022-08-24 22:41:36 +08:00
GyDi
db324f54eb
chore: update auto launch 2022-08-24 22:41:12 +08:00
GyDi
3242efb1a2
fix: reduce unsafe unwrap 2022-08-23 15:19:04 +08:00
FoundTheWOUT
30f9f1a021 fix: timer restore at app launch 2022-08-23 10:35:36 +08:00
GyDi
3f58d05aa7
fix: adjust log text 2022-08-19 17:11:59 +08:00
GyDi
c611a51575
fix: only script profile can display console 2022-08-17 01:45:37 +08:00
GyDi
73458dcd28 v1.0.5 2022-08-16 23:56:29 +08:00
GyDi
23ebeb1cc0
chore: update log 2022-08-16 23:56:04 +08:00
GyDi
a7ba9f1886
fix: fill button title attr 2022-08-16 23:46:33 +08:00
GyDi
d5192e2244
feat: windows portable version do not check update 2022-08-16 01:53:40 +08:00
GyDi
7eb595170f
fix: do not reset system proxy when consistent 2022-08-16 01:27:32 +08:00
GyDi
bd576ca808
fix: adjust web ui item style 2022-08-16 01:08:54 +08:00
GyDi
2f8146b11f
fix: clash field state error 2022-08-16 00:48:06 +08:00
GyDi
7f321c89cb
feat: adjust clash info parsing logs 2022-08-15 20:21:43 +08:00
GyDi
fa65f606b8
fix: badge color error 2022-08-15 20:15:44 +08:00
GyDi
7a3285adaf
fix: web ui port value error 2022-08-15 20:14:33 +08:00
GyDi
8bf78fef10
fix: delay show window 2022-08-15 01:37:26 +08:00
GyDi
78f97ce4df
feat: adjust runtime config 2022-08-15 01:30:37 +08:00
GyDi
66ccbf70f8
feat: support restart app on tray 2022-08-15 01:22:39 +08:00
GyDi
cb48600b40
fix: adjust dialog action button variant 2022-08-15 00:55:35 +08:00
GyDi
33ce235713
feat: optimize profile page 2022-08-14 23:10:19 +08:00
GyDi
f1a68ece01
fix: script code error 2022-08-12 23:41:25 +08:00
GyDi
dd563360af
fix: script exception handle 2022-08-12 11:14:34 +08:00
GyDi
7f6dac4271
feat: refactor 2022-08-12 03:20:55 +08:00
GyDi
178fd8e828
feat: adjust tun mode config 2022-08-11 03:26:08 +08:00
GyDi
1641e02a7d
feat: reimplement enhanced mode 2022-08-11 02:55:10 +08:00
GyDi
cfd04e9bb4
feat: use rquickjs crate 2022-08-10 13:05:48 +08:00
GyDi
38effaf740
feat: reimplement enhanced mode 2022-08-09 21:15:55 +08:00
GyDi
a68eb4a73e
chore: format 2022-08-09 14:33:47 +08:00
GyDi
c02990ef98
fix: change fields 2022-08-09 14:31:59 +08:00
FoundTheWOUT
5aa7d5ffe9
fix: silent start (#150) 2022-08-09 14:14:06 +08:00
GyDi
4942b0fca5
fix: save profile when update 2022-08-08 23:17:22 +08:00
GyDi
aed1bdff5a
fix: list compare wrong 2022-08-08 23:16:28 +08:00
GyDi
929c840006
fix: button color 2022-08-08 23:15:05 +08:00
GyDi
57aef1d3c2
chore: rm code 2022-08-08 22:30:13 +08:00
GyDi
c1734a094c
fix: limit theme mode value 2022-08-08 22:28:44 +08:00
GyDi
99c46685ac
feat: finish clash field control 2022-08-08 22:14:03 +08:00
GyDi
066b08040a
feat: clash field viewer wip 2022-08-08 01:51:30 +08:00
GyDi
35de2334fb fix: add valid clash field 2022-08-07 20:52:50 +08:00
GyDi
5564c966a5
feat: support web ui 2022-08-06 21:56:54 +08:00
GyDi
0891b5e7b7
feat: adjust setting page style 2022-08-06 03:48:03 +08:00
GyDi
f3341f201f
refactor: ts path alias 2022-08-06 02:35:11 +08:00
GyDi
bf0dafabe2
fix: icon style 2022-08-06 01:44:33 +08:00
GyDi
60f6587169
fix: reduce unwrap 2022-07-30 23:32:52 +08:00
GyDi
a9bf32919e
feat: runtime config viewer 2022-07-25 01:20:13 +08:00
GyDi
7633f9f88b
chore: fix updater script 2022-07-18 12:55:59 +08:00
GyDi
9b56233938
v1.0.4 2022-07-17 17:56:07 +08:00
GyDi
65074264b8
chore: update log 2022-07-17 17:52:41 +08:00
GyDi
4f6fceb87f
feat: improve log rule 2022-07-17 17:39:44 +08:00
GyDi
659fdd1d37
fix: import mod 2022-07-17 16:51:54 +08:00
GyDi
8bce2ce040
feat: theme mode support follows system 2022-07-17 16:02:17 +08:00
GyDi
55cc83a5d4
chore: update deps 2022-07-17 14:54:09 +08:00
GyDi
6a51e93ded
refactor: mode manage on tray 2022-07-13 02:26:54 +08:00
GyDi
fcf570e96e
chore: rm code 2022-07-13 01:07:29 +08:00
GyDi
cbc184e953
fix: add tray separator 2022-07-13 01:05:53 +08:00
GyDi
98f9063352
Merge pull request #128 from Limsanity/main
feat: support switch proxy mode
2022-07-13 01:02:14 +08:00
limsanity
c278f1af00 style: resolve formatting problem 2022-07-13 00:54:47 +08:00
limsanity
fbb17a0ba5 feat(system tray): support switch rule/global/direct/script mode in system tray 2022-07-13 00:43:27 +08:00
GyDi
8637a9823e
chore: update deps 2022-07-11 00:41:21 +08:00
GyDi
d717fe7e8c
chore: update clash version 2022-07-08 01:42:14 +08:00
GyDi
5e8dfe7267
chore: update aarch rule 2022-07-07 02:54:20 +08:00
GyDi
aaa4fbcdbd
chore: aarch upload script 2022-07-07 02:53:53 +08:00
GyDi
150f0cf486
fix: instantiate core after init app, close #122 2022-07-06 15:14:33 +08:00
GyDi
711b220a05
chore: update clash version 2022-07-06 00:54:01 +08:00
GyDi
b615c485f7
chore: update deps 2022-07-06 00:49:09 +08:00
GyDi
2f3b6b29ae
fix: rm macOS transition props 2022-07-05 01:24:23 +08:00
GyDi
7aecd83c4a
chore: update deps 2022-07-05 00:52:22 +08:00
GyDi
3b460ab91f
chore: change tray icon 2022-06-22 01:26:25 +08:00
GyDi
661c0eb970 v1.0.3 2022-06-20 02:09:12 +08:00
GyDi
f4a1f1fdc8
chore: update log 2022-06-20 02:06:46 +08:00
GyDi
b5432c3728
chore: update clash version 2022-06-20 01:47:15 +08:00
GyDi
6d0625c409
fix: improve external-controller parse and log 2022-06-20 01:36:56 +08:00
GyDi
2f1ea08b8a
chore: update macos icon 2022-06-18 18:41:53 +08:00
GyDi
a092da1943
fix: show windows on click 2022-06-18 17:28:37 +08:00
GyDi
43ef3cc562
chore: new logo for mac 2022-06-18 17:28:05 +08:00
GyDi
047774475c
chore: update alpha ci 2022-06-17 01:18:19 +08:00
GyDi
91b8504df5
chore: update tauri 2022-06-17 01:16:46 +08:00
GyDi
a4fb2dfcf8
fix: adjust update profile notice error 2022-06-15 02:42:04 +08:00
GyDi
ba16ec02e5
fix: style issue on mac 2022-06-15 02:41:37 +08:00
GyDi
55b7af2623
chore: update version 2022-06-15 01:15:05 +08:00
GyDi
b428eff10e
feat: improve yaml file error log 2022-06-14 01:40:02 +08:00
GyDi
35aee15b6d
chore: update deps 2022-06-14 01:23:58 +08:00
GyDi
6f53c1bfde
Merge pull request #104 from FoundTheWOUT/main
fix: check script run on all OS
2022-06-12 22:25:05 +08:00
FoundTheWOUT
db0230ed75 fix: check script run on all OS 2022-06-05 23:55:41 +08:00
GyDi
7fa3c1e12a
feat: save proxy page state 2022-06-04 18:55:39 +08:00
GyDi
3b5993652f
chore: portable script 2022-06-02 00:56:05 +08:00
GyDi
be4ad8947f
chore: updater script 2022-06-01 10:06:08 +08:00
GyDi
2ead15e78e v1.0.2 2022-06-01 01:26:05 +08:00
GyDi
aa0740ff94
chore: update log 2022-06-01 01:25:39 +08:00
GyDi
6d3f837820
fix: macOS disable transparent 2022-06-01 01:04:46 +08:00
GyDi
359b82c29c
chore: rm some files 2022-06-01 00:49:52 +08:00
GyDi
b54171bc2c
chore: rust cache 2022-06-01 00:47:04 +08:00
GyDi
936b2131e0
chore: adjust ci and script 2022-06-01 00:43:57 +08:00
GyDi
aaef6a9e9c chore: enable meta by default 2022-06-01 00:31:12 +08:00
GyDi
9327006e61
chore: rm file 2022-05-30 00:58:48 +08:00
GyDi
5225c841ae
chore: adjust code 2022-05-30 00:57:31 +08:00
GyDi
8464e319fd
chore: update deps 2022-05-29 23:35:22 +08:00
GyDi
98b8bd90ea
fix: window transparent and can not get hwnd 2022-05-25 19:06:06 +08:00
GyDi
ae94993b09
fix: create main window 2022-05-25 16:45:18 +08:00
GyDi
72f10aaed1
chore: update deps 2022-05-25 16:12:48 +08:00
GyDi
4a74bae8c7
chore: update clash core 2022-05-25 16:08:58 +08:00
ctaoist
5164aec37b
feat: light mode wip (#96)
* 关闭窗口释放UI资源

* windows 还有左键点击事件

* 兼容enhance profile

* bug 修复
2022-05-25 16:06:39 +08:00
GyDi
1581e9b1cd
chore: update clash version 2022-05-18 14:14:37 +08:00
GyDi
536e3ffb11
chore: alpha ci 2022-05-18 14:07:41 +08:00
GyDi
d6103191ba
chore: error text 2022-05-18 09:58:20 +08:00
GyDi
49e37a19a5
chore: ci 2022-05-17 12:35:42 +08:00
GyDi
d02431e260
chore: fix alpha ci 2022-05-17 12:34:30 +08:00
GyDi
f24f6ead92
chore: alpha ci 2022-05-17 11:41:45 +08:00
GyDi
b7bdb7ae50
chore: meta 2022-05-17 02:46:15 +08:00
GyDi
2dcf8ac96f
chore: fix ci 2022-05-17 02:21:58 +08:00
GyDi
5421c94853
chore: test ci 2022-05-17 02:17:02 +08:00
GyDi
10f6bc092a
chore: alpha 2022-05-17 02:11:29 +08:00
GyDi
be9ea4ea8e
feat: clash meta core supports 2022-05-17 01:59:49 +08:00
GyDi
f5c6fa842a
fix: adjust notice 2022-05-17 00:51:37 +08:00
GyDi
e0943ce905
fix: label text 2022-05-16 20:26:13 +08:00
GyDi
61e7df77a7
feat: script mode 2022-05-16 20:18:56 +08:00
GyDi
a5434360bc
chore: test ci 2022-05-16 17:55:14 +08:00
GyDi
ba29c66e3b
chore: update deps 2022-05-16 17:54:21 +08:00
GyDi
b3a72d55ae
feat: clash meta core support (wip) 2022-05-16 01:52:50 +08:00
GyDi
c382ad1cc8
fix: icon path 2022-05-13 12:28:36 +08:00
GyDi
363e28f323
fix: icon issue 2022-05-13 02:46:06 +08:00
GyDi
d695656b8c
fix: notice ui blocking 2022-05-13 02:29:43 +08:00
GyDi
31c6cbc0a2
fix: service mode error 2022-05-13 02:11:03 +08:00
GyDi
b93284bc2f
chore: fix linux build ci 2022-05-11 12:39:41 +08:00
GyDi
99c855b01b
chore: test ci 2022-05-11 12:20:20 +08:00
GyDi
c2eaedc959
chore: test ci 2022-05-11 12:12:55 +08:00
GyDi
a631cd67ec
chore: test ci fix deps 2022-05-11 11:30:19 +08:00
GyDi
66fccd3c68
chore: test ci 2022-05-11 11:22:09 +08:00
GyDi
9a4ebf4daa
chore: test ci 2022-05-11 10:51:28 +08:00
GyDi
91c14211c6
chore: continue on error 2022-05-11 01:26:11 +08:00
GyDi
1ea07c458b
chore: test linux 2022-05-11 00:52:20 +08:00
GyDi
591add6e0c
chore: test ci 2022-05-11 00:31:19 +08:00
GyDi
b99fff66df v1.0.1 2022-05-10 21:40:33 +08:00
GyDi
f54ba05b00
chore: update log 2022-05-10 21:40:07 +08:00
GyDi
6596fb00c7
fix: win11 drag lag 2022-05-09 14:41:26 +08:00
GyDi
df0b5a80dc
chore: change default height 2022-05-09 14:01:46 +08:00
GyDi
8cdbb31dbe
fix: rm unwrap 2022-05-09 14:01:14 +08:00
GyDi
0be4b1222d
feat: reduce gpu usage when hidden 2022-05-07 14:43:52 +08:00
GyDi
18a6bfd73a
feat: interval update from now field 2022-05-07 14:43:08 +08:00
GyDi
5e2271b237
feat: adjust theme 2022-05-06 14:04:39 +08:00
GyDi
798999d490
fix: edit profile info 2022-05-06 12:46:27 +08:00
GyDi
0e68c5e8bc
feat: supports more remote headers close #81 2022-05-06 10:52:59 +08:00
GyDi
9694af82f4
feat: check the remote profile 2022-05-06 01:26:24 +08:00
GyDi
28a4386975
fix: change window default size 2022-05-06 01:15:15 +08:00
GyDi
a238f7beba
chore: update deps 2022-05-06 01:14:13 +08:00
GyDi
75ba16281b
chore: change default user agent 2022-05-06 00:42:20 +08:00
GyDi
ad65a278d4
chore: merge
feat: remove outdated config
2022-04-28 16:28:18 +08:00
tianyoulan
a2320b3f8d feat: fix typo 2022-04-28 15:35:17 +08:00
tianyoulan
ae12853ad0 feat: remove trailing comma 2022-04-28 15:05:10 +08:00
tianyoulan
68adf6dc2f feat: remove outdated config 2022-04-28 15:02:37 +08:00
GyDi
f88989bd4b
fix: change service installer and uninstaller 2022-04-27 15:46:44 +08:00
GyDi
77ef3847ce v1.0.0 2022-04-26 00:50:48 +08:00
GyDi
b8291837fc
chore: update log 2022-04-26 00:49:07 +08:00
GyDi
423a7f951a
fix: adjust connection scroll 2022-04-26 00:37:28 +08:00
GyDi
557f5fe364
fix: adjust something 2022-04-25 20:00:11 +08:00
GyDi
5308970ad8
fix: adjust debounce wait time 2022-04-25 19:39:21 +08:00
GyDi
6b368953f4
feat: windows service mode ui 2022-04-25 16:12:04 +08:00
GyDi
2d0b63c29d
feat: add some commands 2022-04-24 21:03:47 +08:00
GyDi
34e941c8cb
feat: windows service mode 2022-04-24 21:00:17 +08:00
GyDi
76cf007fff
chore: update check script 2022-04-24 15:35:30 +08:00
GyDi
321963be83
wip: windows service mode 2022-04-23 17:26:32 +08:00
GyDi
c733bda6c3
test: windows service 2022-04-21 21:28:44 +08:00
GyDi
e67b50b976
chore: adjust guard log 2022-04-21 19:51:20 +08:00
GyDi
9e3c080909
fix: adjust dns config 2022-04-21 19:50:22 +08:00
GyDi
cb661aaebd
feat: add update interval 2022-04-21 14:26:41 +08:00
GyDi
573571978c
chore: adjust 2022-04-21 14:24:35 +08:00
GyDi
dc492a2a0a
chore: update clash version 2022-04-20 21:02:41 +08:00
GyDi
e47747dd0e
feat: refactor and supports cron tasks 2022-04-20 20:39:27 +08:00
GyDi
4de944b41e
feat: supports cron update profiles 2022-04-20 20:37:16 +08:00
GyDi
5f7a1fa5cd
refactor: verge 2022-04-20 11:17:54 +08:00
GyDi
b8ad328cde
refactor: wip 2022-04-20 01:44:47 +08:00
GyDi
3076fd19c1
refactor: mutex 2022-04-19 14:38:59 +08:00
GyDi
fac437b8c1
fix: traffic graph adapt to different fps 2022-04-19 13:55:26 +08:00
GyDi
697c25015e
refactor: wip 2022-04-19 01:41:20 +08:00
GyDi
d83b404fc3
chore: check script proxy agent supports 2022-04-17 00:37:21 +08:00
GyDi
ab7313cbc4
feat: optimize traffic graph quadratic curve 2022-04-16 22:32:44 +08:00
GyDi
1b8d70322b
feat: optimize the animation of the traffic graph 2022-04-16 17:28:30 +08:00
GyDi
844ffab4ed
fix: optimize clash launch 2022-04-15 01:29:25 +08:00
GyDi
f5d0513d1f
fix: reset after exit 2022-04-14 01:29:33 +08:00
GyDi
557abd4285
fix: adjust code 2022-04-14 01:29:02 +08:00
GyDi
f4f1a0fbc6 v0.0.29 2022-04-13 01:30:08 +08:00
GyDi
74e10dc012
chore: update log 2022-04-13 01:29:48 +08:00
GyDi
359812b7ed
chore: update dep 2022-04-13 01:21:49 +08:00
GyDi
c2449e53c4
chore: rename green to portable 2022-04-13 01:17:40 +08:00
GyDi
1a91249da2
feat: system tray add tun mode 2022-04-13 01:09:51 +08:00
GyDi
b9162f9576
fix: adjust log 2022-04-13 00:49:30 +08:00
GyDi
f726e8a7b3
feat: supports change config dir 2022-04-12 23:09:32 +08:00
GyDi
2f284cfdc9
chore: update clash version 2022-04-12 23:04:19 +08:00
GyDi
b74696adba
feat: add default user agent 2022-04-12 01:05:51 +08:00
GyDi
c8ccba0192
chore: rm console 2022-04-11 02:26:09 +08:00
GyDi
41b0e05f62
feat: connections page supports filter 2022-04-11 02:25:34 +08:00
GyDi
847d5f1b3b
feat: log page supports filter 2022-04-11 01:46:33 +08:00
GyDi
0445f9dfc2
fix: check button hover style 2022-04-10 03:33:17 +08:00
GyDi
3001c780bd
feat: optimize delay checker concurrency strategy 2022-04-10 02:58:48 +08:00
GyDi
68ad5e2320
feat: support sort proxy node and custom test url 2022-04-10 02:09:36 +08:00
GyDi
b5e229b19c
chore: update deps 2022-04-10 01:31:05 +08:00
GyDi
453d798fcf
refactor: proxy head 2022-04-08 01:35:18 +08:00
GyDi
451afdb660 v0.0.28 2022-04-07 01:28:30 +08:00
GyDi
d298bda92c
chore: update log 2022-04-07 01:27:48 +08:00
GyDi
fd99ba6255
feat: handle remote clash config fields 2022-04-07 01:20:44 +08:00
GyDi
6ade0b2b1a
fix: icon button color inherit 2022-04-06 22:52:00 +08:00
GyDi
9902003da9
fix: remove the lonely zero 2022-04-06 22:48:10 +08:00
GyDi
0ff2fcac11
chore: readme 2022-04-06 01:52:20 +08:00
GyDi
e80be8e7b6 v0.0.27 2022-04-06 01:21:33 +08:00
GyDi
fe0ad0f5cb
chore: update log 2022-04-06 01:19:13 +08:00
GyDi
cb8e162f4e
refactor: update profile menu 2022-04-06 01:13:06 +08:00
GyDi
ae8c30fe57
chore: add updater script 2022-04-05 23:24:52 +08:00
GyDi
ab82db9e22
chore: updater json supports arch field 2022-04-05 23:19:54 +08:00
GyDi
cb9d3098de
chore: add ci 2022-04-05 20:21:53 +08:00
GyDi
c927419c99
refactor: enhanced mode ui component 2022-04-03 01:41:48 +08:00
GyDi
c009026961
feat: add text color 2022-04-02 17:18:38 +08:00
GyDi
51cf442fa5
fix: i18n add value 2022-04-02 13:49:48 +08:00
GyDi
954e3553ee
fix: proxy page first render 2022-04-02 01:23:31 +08:00
GyDi
cde17385b4
feat: control final tun config 2022-04-01 23:55:44 +08:00
GyDi
5b9e078061
chore: add debug feature 2022-04-01 23:43:35 +08:00
GyDi
0290d9ddfc
feat: support css injection 2022-04-01 02:08:42 +08:00
GyDi
eab671d102
fix: console warning 2022-04-01 01:16:23 +08:00
GyDi
f9a96ff914
feat: support theme setting 2022-03-31 23:38:00 +08:00
GyDi
3ec2b46d28
feat: add text color 2022-03-31 23:34:36 +08:00
GyDi
aec30b89e0
feat: add theme setting 2022-03-31 23:11:50 +08:00
GyDi
309c33e190
refactor: ui theme 2022-03-30 12:36:39 +08:00
GyDi
9c0276f97b
chore: add api 2022-03-30 01:30:22 +08:00
GyDi
0a3402ff43
fix: icon button title 2022-03-30 01:29:25 +08:00
GyDi
bd82308024
chore: update tauri 2022-03-29 23:05:32 +08:00
GyDi
db3b634e62
fix: macOS transition flickers close #47 2022-03-29 01:39:54 +08:00
GyDi
40bcb22977
chore: update deps 2022-03-29 01:33:14 +08:00
GyDi
4bd94092f1
fix: csp image data 2022-03-28 23:17:11 +08:00
GyDi
cac1ce6895
chore: readme 2022-03-28 01:33:58 +08:00
GyDi
5c3696123a v0.0.26 2022-03-28 01:30:54 +08:00
GyDi
4140fc86ee
chore: update log 2022-03-28 01:18:18 +08:00
GyDi
2ad771e7fd
fix: close dialog after save 2022-03-28 01:09:34 +08:00
GyDi
5a38468144
refactor: optimize enhance mode strategy 2022-03-28 01:07:14 +08:00
GyDi
0a9c81772f
fix: change to deep copy 2022-03-28 00:56:48 +08:00
GyDi
c9649ac501
feat: enhanced mode supports more fields 2022-03-27 20:36:13 +08:00
GyDi
6ea567742b
chore: profile release 2022-03-27 01:38:40 +08:00
GyDi
f31349eaa0
feat: supports edit profile file 2022-03-27 00:58:17 +08:00
GyDi
9d44668d5f
feat: supports silent start 2022-03-26 18:56:16 +08:00
GyDi
a12f58c1c7
feat: use crate open 2022-03-23 23:00:14 +08:00
GyDi
b5283eaaed
fix: window style close #45 2022-03-23 21:43:13 +08:00
GyDi
4b6189af5f
feat: enhance connections display order 2022-03-23 14:02:08 +08:00
GyDi
5d0ffbe453
chore: adjust style 2022-03-23 13:58:57 +08:00
GyDi
502706931e
fix: manage global proxy correctly 2022-03-23 12:49:34 +08:00
GyDi
57c411288f
fix: tauri csp 2022-03-23 01:47:35 +08:00
GyDi
b09b7b11a1
feat: save global selected 2022-03-22 12:38:59 +08:00
GyDi
1a7b3c7294 v0.0.25 2022-03-22 01:51:06 +08:00
GyDi
4678fc7dde
chore: update log 2022-03-22 01:50:24 +08:00
GyDi
9f492fad49
chore: update clash core 2022-03-22 01:50:15 +08:00
GyDi
bd0a959e18
fix: windows style 2022-03-22 01:36:06 +08:00
GyDi
dd605e2610
fix: update state 2022-03-22 01:27:22 +08:00
inRm3D
9910f6b817
chore: readme (#43) 2022-03-21 21:55:45 +08:00
GyDi
991897aff4
chore: tauri feature 2022-03-21 11:40:52 +08:00
GyDi
3cde019208
fix: profile item loading state 2022-03-21 11:40:27 +08:00
GyDi
2f6efbed63
chore: tauri allowList 2022-03-20 13:16:50 +08:00
GyDi
5cff4e299b
chore: update clash version 2022-03-20 12:37:55 +08:00
GyDi
bce33639da v0.0.24 2022-03-20 01:50:39 +08:00
GyDi
366c465cad
chore: update log 2022-03-20 01:50:15 +08:00
GyDi
acc6e05bdc
feat: system tray supports system proxy setting 2022-03-20 01:36:43 +08:00
GyDi
4ce15577cd
feat: prevent context menu on Windows close #22 2022-03-19 19:28:53 +08:00
GyDi
98fa4d5e65
feat: create local profile with selected file 2022-03-19 19:21:55 +08:00
GyDi
7fe94076c7
feat: reduce the impact of the enhanced mode 2022-03-19 16:02:47 +08:00
GyDi
b8b0c8fa63
fix: adjust windows style 2022-03-19 15:35:59 +08:00
GyDi
e585e87bec
feat: parse update log 2022-03-19 15:31:58 +08:00
GyDi
6a4924bb16
chore: update log supports 2022-03-19 14:04:58 +08:00
GyDi
ab0d516d91
chore: updater proxy 2022-03-19 11:14:12 +08:00
GyDi
b756ae39d0
chore: ci support linux 2022-03-19 10:50:44 +08:00
GyDi
c15c38ea8f
chore: readme 2022-03-18 19:02:53 +08:00
GyDi
743963318f
feat: fill i18n 2022-03-18 14:59:23 +08:00
GyDi
ed3fc50858
feat: dayjs i18n 2022-03-18 14:45:24 +08:00
GyDi
5b886fe6be
feat: connections page simply support 2022-03-18 14:43:22 +08:00
GyDi
7074bbc405
feat: add wintun.dll by default 2022-03-18 11:49:00 +08:00
GyDi
ef314c1707
fix: change mixed port error 2022-03-18 11:39:02 +08:00
GyDi
a3e7626dd9
chore: add dev feature 2022-03-18 10:59:35 +08:00
GyDi
5cb5d74eed
chore: rename temp dir 2022-03-18 10:20:24 +08:00
GyDi
6eee10d46d
chore: resolve wintun.dll 2022-03-17 19:29:30 +08:00
GyDi
2d95f2b0d6
fix: auto launch path 2022-03-16 18:44:25 +08:00
GyDi
ec41bb9c70
fix: tun mode config 2022-03-15 14:23:57 +08:00
GyDi
2d1780b1cf
fix: adjsut open cmd error 2022-03-15 12:51:39 +08:00
GyDi
d9ce99887c
chore: fix ci env 2022-03-15 00:00:20 +08:00
GyDi
9b9cc90414 v0.0.23 2022-03-14 01:34:26 +08:00
GyDi
c1eb539a5c
feat: event emit when clash config update 2022-03-13 01:37:41 +08:00
GyDi
e1793f57ef
chore: fix ci 2022-03-13 00:59:42 +08:00
GyDi
dbd09a8743
chore: update tauri action 2022-03-13 00:58:49 +08:00
GyDi
0d189ca617
chore: green version bundle 2022-03-13 00:52:31 +08:00
GyDi
dc9bcc40ee
chore: rm then 2022-03-12 23:58:20 +08:00
GyDi
4991f7ff39
feat: i18n supports 2022-03-12 23:07:45 +08:00
GyDi
a393b8b122
fix: parse external-controller 2022-03-12 21:35:20 +08:00
GyDi
c73b354386
fix: config file case close #18 2022-03-12 21:03:17 +08:00
GyDi
392ecee3ff
chore: merge 2022-03-12 18:45:07 +08:00
ttyS3
bae721c49e
chore: show open app and log dir failed message 2022-03-12 18:04:56 +08:00
GyDi
4e806e21a6
chore: adjust error log 2022-03-12 15:57:16 +08:00
GyDi
ec0fdf83b2
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
cb94d8414f
feat: add Linux open dir support
refactor(logging): refine open dir failed log message
2022-03-12 03:18:25 +08:00
GyDi
8890051c17
fix: patch item option 2022-03-11 19:56:56 +08:00
GyDi
cf00c9476f
fix: user agent not works 2022-03-11 19:50:51 +08:00
GyDi
b2a24c7abd v0.0.22 2022-03-10 02:27:00 +08:00
GyDi
732a1f4694
chore: rm dead code 2022-03-10 02:25:35 +08:00
GyDi
4c5aa7084e
fix: external-controller 2022-03-10 02:19:06 +08:00
GyDi
fe1fea671c
feat: support more options for remote profile 2022-03-10 02:03:55 +08:00
GyDi
04c754c0ac
chore: reduce icon size on macOS 2022-03-10 01:04:53 +08:00
GyDi
754c22c84e
chore: adjust icon 2022-03-10 00:04:59 +08:00
GyDi
629331870b
chore: add linux link 2022-03-09 20:14:15 +08:00
GyDi
78774315cb
feat: linux system proxy 2022-03-09 19:48:32 +08:00
GyDi
36b9c07928
chore: update deps 2022-03-09 02:36:40 +08:00
GyDi
40a818630d
fix: change proxy bypass on mac 2022-03-09 02:36:03 +08:00
GyDi
568511a4cf
fix: kill sidecars after install still in test 2022-03-09 02:09:51 +08:00
GyDi
109fb39e09
fix: log some error 2022-03-09 02:00:56 +08:00
GyDi
68450d2042
chore: rename productName 2022-03-09 01:53:34 +08:00
GyDi
8a052bbed6
chore: update tauri version 2022-03-09 01:50:37 +08:00
GyDi
3afbb56640
fix: apply_blur parameter 2022-03-08 10:44:41 +08:00
GyDi
c0ad84a491
fix: limit enhanced profile range 2022-03-08 01:45:46 +08:00
GyDi
c72f17605c
chore: update deps 2022-03-08 01:18:20 +08:00
GyDi
42fbee0cdb
chore: add default bypass 2022-03-08 00:39:09 +08:00
GyDi
e9b7ec735f
fix: profile updated field 2022-03-07 01:41:42 +08:00
GyDi
743788135f
fix: profile field check 2022-03-07 01:30:32 +08:00
GyDi
8ea3e6fa26
fix: create dir panic 2022-03-07 01:06:21 +08:00
GyDi
f23c83e681 v0.0.21 2022-03-06 18:36:02 +08:00
GyDi
b615bda17e
chore: default release body 2022-03-06 18:34:38 +08:00
GyDi
f7c7cd1d3c chore: update deps 2022-03-06 18:04:44 +08:00
GyDi
c7e7be4379
fix: only error when selected 2022-03-06 17:06:45 +08:00
GyDi
d63d49f246
feat: enhance profile status 2022-03-06 17:02:29 +08:00
GyDi
dad94edb20
feat: menu item refresh enhanced mode 2022-03-06 15:46:16 +08:00
GyDi
7108d5f3ab
fix: enhanced profile consistency 2022-03-06 15:40:16 +08:00
GyDi
ef47a74920
feat: profile enhanced mode 2022-03-06 14:59:25 +08:00
GyDi
a43dab8057
feat: profile enhanced ui 2022-03-05 22:54:39 +08:00
GyDi
f44039b628
feat: profile item adjust 2022-03-05 19:04:20 +08:00
GyDi
08fa5205b0
fix: simply compatible with proxy providers 2022-03-05 16:18:44 +08:00
GyDi
b91daebd92
fix: component warning 2022-03-05 15:48:39 +08:00
GyDi
9cd6c5c624
chore: update copyright 2022-03-05 01:51:29 +08:00
GyDi
650e017b72
fix: when updater failed 2022-03-04 02:10:27 +08:00
GyDi
18f9d6dec5 fix: log file 2022-03-04 02:08:26 +08:00
GyDi
4df6571ad9
chore: update deps 2022-03-03 20:37:06 +08:00
GyDi
0f5923a10a
chore: enhance wip 2022-03-03 01:58:05 +08:00
GyDi
bcdae1169e
fix: result 2022-03-03 01:56:47 +08:00
GyDi
f260d5df49
feat: enhanced profile (wip) 2022-03-03 01:52:02 +08:00
GyDi
808b861dd1 v0.0.20 2022-03-02 01:59:53 +08:00
GyDi
17f1c487a8
feat: edit profile item 2022-03-02 01:58:16 +08:00
GyDi
8dc2c1a38f
fix: cover profile extra 2022-03-02 01:45:00 +08:00
GyDi
220a494692
chore: adjust log field 2022-03-02 01:13:31 +08:00
GyDi
db6bc10196
chore: rm file 2022-03-02 00:58:07 +08:00
GyDi
1880363aeb
feat: use nanoid 2022-03-02 00:56:05 +08:00
GyDi
19c7b59883
feat: compatible profile config 2022-03-01 11:05:33 +08:00
GyDi
749df89229
refactor: profile config 2022-03-01 08:58:47 +08:00
GyDi
444f2172fa v0.0.19 2022-02-28 01:59:53 +08:00
GyDi
77a77c0ea7
wip: refactor profile 2022-02-28 01:59:13 +08:00
GyDi
dbf380a0d1
refactor: use anyhow to handle error 2022-02-28 01:34:25 +08:00
GyDi
ade34f5217
fix: display menu only on macos 2022-02-27 01:47:56 +08:00
GyDi
e89607799a
fix: proxy global showType 2022-02-27 01:33:22 +08:00
GyDi
d05d8d6a9e
feat: native menu supports 2022-02-27 01:29:57 +08:00
GyDi
b6aa50d3dc
chore: update deps 2022-02-27 01:16:43 +08:00
GyDi
9df361935f
feat: filter proxy and display type 2022-02-27 00:58:14 +08:00
GyDi
98b8a122b6
feat: use lock fn 2022-02-26 17:39:36 +08:00
GyDi
c7232522ee
feat: refactor proxy page 2022-02-26 17:39:08 +08:00
GyDi
5280f1d745
feat: proxy group auto scroll to current 2022-02-26 01:21:39 +08:00
GyDi
b52a081e7b v0.0.18 2022-02-25 02:13:41 +08:00
GyDi
f981a44861
chore: ci 2022-02-25 02:12:55 +08:00
GyDi
d7c5ce0750
feat: clash tun mode supports 2022-02-25 02:09:39 +08:00
GyDi
9ccc66ca1e
feat: use enhanced guard-state 2022-02-25 01:22:33 +08:00
GyDi
8606af3616
feat: guard state supports debounce guard 2022-02-25 01:21:13 +08:00
GyDi
f6e821ba6b
feat: adjust clash version display 2022-02-24 23:04:18 +08:00
GyDi
e8dbcf819b
feat: hide command window 2022-02-24 22:46:12 +08:00
GyDi
bbe2ef4e8e
fix: use full clash config 2022-02-23 23:23:07 +08:00
GyDi
dd15455031
feat: enhance log data 2022-02-23 02:00:45 +08:00
GyDi
12ac7bb338
chore: update readme 2022-02-22 21:54:33 +08:00
GyDi
46ef348f0d v0.0.17 2022-02-22 02:08:31 +08:00
GyDi
1a55cca8af
fix: reconnect websocket when restart clash 2022-02-22 02:05:22 +08:00
GyDi
81ee989f1f
chore: enhance publish ci 2022-02-22 01:56:06 +08:00
GyDi
c9c06f8a3d
fix: wrong exe path 2022-02-21 22:33:37 +08:00
GyDi
72127979c3
chore: use tauri cli 2022-02-21 22:31:00 +08:00
GyDi
1ad3ddef94 v0.0.16 2022-02-21 01:33:11 +08:00
GyDi
97ec5eabf7
feat: change window style 2022-02-20 23:46:13 +08:00
GyDi
e12e3a3f2d
feat: fill verge template 2022-02-20 20:12:55 +08:00
GyDi
4ff625f23b
feat: enable customize guard duration 2022-02-20 20:11:39 +08:00
GyDi
e38dcd85ac
feat: system proxy guard 2022-02-20 18:53:21 +08:00
GyDi
0245baf1b6
feat: enable show or hide traffic graph 2022-02-19 18:14:40 +08:00
GyDi
10b55c043c
fix: patch verge config 2022-02-19 17:32:23 +08:00
GyDi
457655b416
feat: traffic line graph 2022-02-19 17:02:24 +08:00
GyDi
7e4506c860
chore: update deps 2022-02-19 00:34:09 +08:00
GyDi
794d376348
feat: adjust profile item ui 2022-02-19 00:23:47 +08:00
GyDi
c60578f5b5
feat: adjust fetch profile url 2022-02-19 00:09:36 +08:00
GyDi
3a9a392a77
feat: inline config file template 2022-02-18 23:57:13 +08:00
GyDi
a13d4698be
chore: update check script 2022-02-18 23:49:39 +08:00
GyDi
c046a1993e
fix: fetch profile panic 2022-02-17 13:44:26 +08:00
GyDi
f709117cc4
feat: kill sidecars when update app 2022-02-17 02:10:25 +08:00
GyDi
30dd298fca
feat: delete file 2022-02-17 01:58:12 +08:00
GyDi
0ff8bb8090
feat: lock some async functions 2022-02-17 01:42:25 +08:00
GyDi
74bbd3c3a2
chore: tauri updater env 2022-02-16 11:02:00 +08:00
GyDi
2e82eaf59a
chore: add pubkey 2022-02-16 10:59:31 +08:00
GyDi
7f1df1f1bd v0.0.15 2022-02-16 03:22:25 +08:00
GyDi
3c79238a44
feat: support open dir 2022-02-16 03:21:34 +08:00
GyDi
0aa2565df3
fix: spawn command 2022-02-16 02:43:52 +08:00
GyDi
22b11db16e
feat: change allow list 2022-02-16 02:42:56 +08:00
GyDi
d0e678b5e9
feat: support check delay 2022-02-16 02:22:01 +08:00
GyDi
e7bba968b3 fix: import error 2022-02-15 01:36:19 +08:00
GyDi
4934a24293
feat: scroll to proxy item 2022-02-15 01:34:10 +08:00
GyDi
ccb68bcda9
chore: update deps 2022-02-15 00:33:58 +08:00
GyDi
66bf4ba3ad
fix: not open file when new profile 2022-02-15 00:21:34 +08:00
GyDi
78a0cfd052
feat: edit system proxy bypass 2022-02-14 01:26:24 +08:00
GyDi
8548373742
feat: disable user select 2022-02-13 19:52:35 +08:00
GyDi
2dfd725ee0
fix: reset value correctly 2022-02-13 19:40:31 +08:00
GyDi
5b779b4f14
feat: new profile able to edit name and desc 2022-02-13 19:33:24 +08:00
GyDi
fc48aa7155 chore: adjust files 2022-02-13 19:27:24 +08:00
GyDi
6193a842f4
feat: update tauri version 2022-02-13 18:45:03 +08:00
GyDi
eb86b471fe
fix: something 2022-02-12 21:12:39 +08:00
GyDi
2b52584547
fix: menu without fragment 2022-02-10 01:42:52 +08:00
GyDi
6e3cc57f48
feat: display clash core version 2022-02-10 01:35:55 +08:00
GyDi
f2c04621a5
feat: adjust profile item menu 2022-02-10 01:14:57 +08:00
GyDi
3ed6938d4a v0.0.14 2022-02-09 02:11:01 +08:00
GyDi
99fec25ed5
feat: profile item ui 2022-02-09 02:08:27 +08:00
GyDi
73758ad1fd
fix: proxy list error 2022-02-09 02:02:29 +08:00
GyDi
c53fe0ed1f
feat: support new profile 2022-02-07 17:26:05 +08:00
GyDi
6082c2bcac
feat: support open command for viewing 2022-02-07 16:45:20 +08:00
GyDi
069abed784
chore: add item template yaml 2022-02-02 20:56:06 +08:00
GyDi
7d8fa4d78a
fix: something 2022-01-31 23:34:58 +08:00
GyDi
76081f8d89
fix: macos auto launch fail 2022-01-31 23:32:41 +08:00
GyDi
5ef4285558
chore: update deps and app name 2022-01-31 23:27:11 +08:00
GyDi
aa7df4282e
chore: cargo update 2022-01-28 22:02:17 +08:00
GyDi
0ef1d5d0de
chore: update clash version 2022-01-28 22:00:15 +08:00
GyDi
cceb4bb81f v0.0.13 2022-01-25 02:11:00 +08:00
GyDi
f0f45e007d
feat: global proxies use virtual list 2022-01-25 02:08:10 +08:00
GyDi
6a8ffe1642
feat: enable change proxy mode 2022-01-25 01:51:44 +08:00
GyDi
3a73868c10
feat: update styles 2022-01-25 01:48:26 +08:00
GyDi
ab1b5897a6
feat: manage clash mode 2022-01-24 23:13:13 +08:00
GyDi
f94734a5c8 chore: update readme 2022-01-22 23:57:39 +08:00
GyDi
61b86c9584 v0.0.12 2022-01-21 03:09:21 +08:00
GyDi
1ac1d6e903
chore: update ci 2022-01-21 03:08:40 +08:00
GyDi
b6c58f74c0
fix: type error 2022-01-21 03:08:20 +08:00
GyDi
c88e99d87c v0.0.12 2022-01-21 02:59:33 +08:00
GyDi
8d7ab9d05e
refactor: rename profiles & command state 2022-01-21 02:57:15 +08:00
GyDi
47155a4a29
feat: change system porxy when changed port 2022-01-21 02:50:13 +08:00
GyDi
d0b87fd7c3
feat: enable change mixed port 2022-01-21 02:32:23 +08:00
GyDi
d49fd37656
feat: manage clash config 2022-01-21 02:31:44 +08:00
GyDi
0bd29d71be
chore: add ahooks 2022-01-21 02:29:45 +08:00
GyDi
4b5b62c8ae
fix: restart clash should update something 2022-01-21 00:29:33 +08:00
GyDi
65fb2ca2d5
feat: enable update clash info 2022-01-20 23:41:08 +08:00
GyDi
0d5bfc0997
feat: rename edit as view 2022-01-19 23:58:34 +08:00
GyDi
4f02c373c2
chore: adjust ci 2022-01-19 23:55:05 +08:00
GyDi
209a5b1207
fix: script error... 2022-01-19 00:49:23 +08:00
GyDi
95349eacab
fix: tag error 2022-01-19 00:46:43 +08:00
GyDi
82ba604b99
fix: script error 2022-01-19 00:42:37 +08:00
GyDi
46a8dec655
feat: test auto gen update.json ci 2022-01-19 00:37:59 +08:00
GyDi
b5af234524 v0.0.11 2022-01-17 02:53:07 +08:00
GyDi
b5c41750f7
fix: remove cargo test 2022-01-17 02:50:19 +08:00
GyDi
6083824eec v0.0.11 2022-01-17 02:44:08 +08:00
GyDi
40977785c3
feat: adjust setting typography 2022-01-17 02:42:52 +08:00
GyDi
5eddf4f1aa
feat: enable force select profile 2022-01-17 02:28:23 +08:00
GyDi
99a8e25411
feat: support edit profile item 2022-01-17 02:16:17 +08:00
GyDi
08587d8f2f
fix: reduce proxy item height 2022-01-17 02:15:54 +08:00
GyDi
3480d50f61
feat: adjust control ui 2022-01-17 02:15:06 +08:00
GyDi
43af55252d
feat: update profile supports noproxy 2022-01-16 22:57:42 +08:00
GyDi
9c43b31fc0
refactor: something 2022-01-16 18:30:25 +08:00
GyDi
9ec7184aa1
fix: put profile request with no proxy 2022-01-16 18:20:01 +08:00
GyDi
4e2cb30db7
refactor: notice caller 2022-01-16 17:56:43 +08:00
GyDi
9ca83d3291
chore: change ci 2022-01-16 16:03:53 +08:00
GyDi
6b2172d873
fix: ci strategy 2022-01-16 14:30:49 +08:00
GyDi
1a5d9f7dad
chore: enhance ci 2022-01-16 14:27:41 +08:00
GyDi
a8425862f0
feat: rename page 2022-01-16 03:25:50 +08:00
GyDi
a3a3db6abb
refactor: setting page 2022-01-16 03:22:37 +08:00
GyDi
d6c3bc57c0
feat: refactor and adjust ui 2022-01-16 03:11:07 +08:00
GyDi
59c09f90f9
feat: rm some commands 2022-01-16 03:09:36 +08:00
GyDi
d982b83e14
feat: change type 2022-01-15 21:58:13 +08:00
GyDi
cc0e930d34
feat: supports auto launch on macos and windows 2022-01-15 21:55:05 +08:00
GyDi
3c3d77fbea
chore: add auto-launch 2022-01-15 21:00:19 +08:00
GyDi
da7453fdbf v0.0.10 2022-01-13 02:53:29 +08:00
GyDi
5fcd25506e feat: adjust proxy page 2022-01-13 02:51:30 +08:00
GyDi
4979a472de feat: press esc hide the window 2022-01-13 02:11:50 +08:00
GyDi
4e8d4f4591 fix: version update error 2022-01-12 22:19:44 +08:00
GyDi
0f5d2b15e0 v0.0.9 2022-01-12 02:55:07 +08:00
GyDi
7fc9631434 feat: show system proxy info 2022-01-12 02:54:50 +08:00
GyDi
df5953dd7b feat: support blur window 2022-01-12 02:27:29 +08:00
GyDi
8f5b2b4a0e chore: add macos startup todo 2022-01-11 23:14:43 +08:00
GyDi
43c63ffa70 chore: cargo update 2022-01-11 23:13:02 +08:00
GyDi
6779bc7459 v0.0.8 2022-01-11 02:27:04 +08:00
GyDi
664be2d0ba chore: update ci 2022-01-11 02:26:50 +08:00
GyDi
6113898b69 feat: windows support startup 2022-01-11 02:24:43 +08:00
GyDi
79aad6b5c2 feat: window self startup 2022-01-11 02:21:51 +08:00
GyDi
6da7757d36 fix: text 2022-01-10 22:57:21 +08:00
GyDi
dbb3cb8cc8 chore: ignore update json 2022-01-10 22:08:57 +08:00
GyDi
579f36a1dd fix: update profile after restart clash 2022-01-10 22:08:18 +08:00
GyDi
72c2b306cf fix: get proxies multiple times 2022-01-10 21:25:41 +08:00
GyDi
c2673cd396 docs: fix img width 2022-01-10 02:52:12 +08:00
GyDi
8eb152816a docs: update 2022-01-10 02:49:42 +08:00
GyDi
a0bc8a21a5 v0.0.7 2022-01-10 02:17:35 +08:00
GyDi
08e4d72758 feat: use tauri updater 2022-01-10 02:15:38 +08:00
GyDi
66340a27fa feat: support update checker 2022-01-10 02:05:35 +08:00
GyDi
83fe9835b6 v0.0.6 2022-01-09 21:36:43 +08:00
GyDi
4c1a50a3ca chore: ci add macos 2022-01-09 21:34:14 +08:00
GyDi
fe44a7b3bc feat: support macos proxy config 2022-01-09 21:19:35 +08:00
GyDi
e86d192db7 feat: custom window decorations 2022-01-08 22:23:48 +08:00
GyDi
ea8f1c52f9 feat: profiles add menu and delete button 2022-01-08 16:52:18 +08:00
GyDi
a4c1573c45 fix: delete profile item command 2022-01-08 14:21:12 +08:00
GyDi
182bf49ad0 chore: temp 2022-01-08 02:54:37 +08:00
GyDi
13e1ddbccd v0.0.5 2022-01-08 02:12:56 +08:00
GyDi
327b9a1757 chore: enhance ci 2022-01-08 02:12:12 +08:00
GyDi
18c48db7f7 chore: rename script 2022-01-08 01:56:28 +08:00
GyDi
b6543bd87f chore: update publish script 2022-01-08 01:51:24 +08:00
GyDi
9ad8f71d7c refactor: rename 2022-01-08 01:27:25 +08:00
GyDi
e369311fc2 refactor: impl structs methods 2022-01-07 23:29:20 +08:00
GyDi
72ff261fe3 chore: update clash version 2022-01-06 23:40:57 +08:00
GyDi
774c6f7e05 fix: initialize profiles state 2022-01-05 23:30:18 +08:00
GyDi
771af6ae08 chore: fixed tauri rev 2022-01-05 23:27:26 +08:00
GyDi
b3cd207444 feat: delay put profiles and retry 2022-01-05 02:01:32 +08:00
GyDi
03f9fa4bc2 refactor: impl as struct methods 2022-01-05 02:00:59 +08:00
GyDi
e32bfd9aab feat: Window Send and Sync 2022-01-04 21:29:04 +08:00
GyDi
7e47f8f893 chore: update tauri 2022-01-04 21:25:00 +08:00
GyDi
cb816e9653 feat: support restart sidecar tray event 2021-12-31 18:24:50 +08:00
GyDi
6b3e7cbc08 feat: prevent click same 2021-12-31 18:21:35 +08:00
GyDi
db4993ae9b fix: item header bgcolor 2021-12-31 18:19:53 +08:00
GyDi
4dc3cf6c6b feat: scroller stable 2021-12-31 18:19:17 +08:00
GyDi
2b84bbf3a8 feat: compatible with macos(wip) 2021-12-29 18:49:38 +08:00
GyDi
26ef4c9961 chore: change tauri to git repo 2021-12-29 18:47:29 +08:00
GyDi
4f56c38599 fix: null type error 2021-12-29 01:01:22 +08:00
GyDi
240f4dcfb1 chore: dev support macos 2021-12-29 01:01:09 +08:00
GyDi
ac6abd81c9 v0.0.4 2021-12-28 01:48:25 +08:00
GyDi
14bda4f3a5 feat: record selected proxy 2021-12-28 01:47:43 +08:00
GyDi
61b9670b45 feat: display version 2021-12-28 01:35:58 +08:00
GyDi
01e8db317e chore: save lock file 2021-12-28 00:14:15 +08:00
GyDi
92fc09493e chore: post version script 2021-12-27 23:07:56 +08:00
GyDi
d927209db7 v0.0.3 2021-12-27 23:06:46 +08:00
GyDi
e94007b21f v0.0.3 2021-12-27 23:06:45 +08:00
GyDi
18750f275a
chore: update version 2021-12-27 10:21:49 +08:00
GyDi
d686a853f4
chore: update version 2021-12-27 10:21:10 +08:00
144 changed files with 8367 additions and 15957 deletions

View File

@ -12,16 +12,14 @@ 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. 请 **务必** 尝试 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本,确定问题是否仍然存在
6. 请 **务必** 按照模板规范详细描述问题以及尝试更新 Alpha 版本否则issue将会被直接关闭
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
5. 请 **务必** 按照模板规范详细描述问题否则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 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
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
- type: textarea
id: description
@ -57,9 +55,20 @@ body:
description: 请提供你的操作系统版本Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
validations:
required: true
- type: textarea
- type: checkboxes
id: os-labels
attributes:
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")
label: 系统标签 / OS Labels
description: 请选择受影响的操作系统(至少选择一个) / Please select the affected operating system(s) (select at least one)
options:
- label: windows
- label: macos
- label: linux
validations:
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")
validations:
required: true

View File

@ -3,8 +3,8 @@ name: Alpha Build
on:
workflow_dispatch:
schedule:
# UTC+8 0,6,12,18
- cron: "0 16,22,4,10 * * *"
# UTC+8 00:00 (UTC 16:00 previous day) and UTC+8 12:00 (UTC 04:00)
- cron: "0 16,4 * * *"
permissions: write-all
env:
CARGO_INCREMENTAL: 0
@ -25,7 +25,7 @@ jobs:
with:
fetch-depth: 2
- name: Check if version changed or src changed
- name: Check if commit changed
id: check
run: |
# For manual workflow_dispatch, always run
@ -34,182 +34,21 @@ jobs:
exit 0
fi
# Store current version from package.json
CURRENT_VERSION=$(cat package.json | jq -r '.version')
echo "Current version: $CURRENT_VERSION"
# Check if current commit is different from the previous one
CURRENT_COMMIT=$(git rev-parse HEAD)
PREVIOUS_COMMIT=$(git rev-parse HEAD~1)
# Get the previous commit's package.json version
git checkout HEAD~1 package.json
PREVIOUS_VERSION=$(cat package.json | jq -r '.version')
echo "Previous version: $PREVIOUS_VERSION"
# Reset back to current commit
git checkout HEAD package.json
# Check if version changed
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"
if [ "$CURRENT_COMMIT" != "$PREVIOUS_COMMIT" ]; then
echo "New commit detected: $CURRENT_COMMIT"
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "Version and source directories unchanged"
echo "No new commits since last run"
echo "should_run=false" >> $GITHUB_OUTPUT
fi
delete_old_assets:
alpha:
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Release Assets
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const releaseTag = 'alpha';
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
});
console.log(`Found release with ID: ${release.id}`);
// Delete each asset
if (release.assets && release.assets.length > 0) {
console.log(`Deleting ${release.assets.length} assets`);
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
});
}
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_assets
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Fetch Alpha update logs
id: fetch_alpha_logs
run: |
# 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 '/^## v/{if(flag) exit; flag=1} flag' UPDATELOG.md)
if [ -n "$ALPHA_LOGS" ]; then
echo "Found alpha update logs"
echo "ALPHA_LOGS<<EOF" >> $GITHUB_ENV
echo "$ALPHA_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No alpha sections found in UPDATELOG.md"
fi
else
echo "UPDATELOG.md file not found"
fi
shell: bash
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- run: |
# 检查 ALPHA_LOGS 是否存在,如果不存在则使用默认消息
if [ -z "$ALPHA_LOGS" ]; then
echo "No alpha logs found, using default message"
ALPHA_LOGS="More new features are now supported. Check for detailed changelog soon."
else
echo "Using found alpha logs"
fi
# 生成 release.txt 文件
cat > release.txt << EOF
$ALPHA_LOGS
## 我应该下载哪个版本?
### MacOS
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg
### Linux
- Linux 64位: amd64.deb/amd64.rpm
- Linux arm64 architecture: arm64.deb/aarch64.rpm
- Linux armv7架构: armhf.deb/armhfp.rpm
### Windows (不再支持Win7)
#### 正常版本(推荐)
- 64位: x64-setup.exe
- arm64架构: arm64-setup.exe
#### 便携版问题很多不再提供
#### 内置Webview2版(体积较大仅在企业版系统或无法安装webview2时使用)
- 64位: x64_fixed_webview2-setup.exe
- arm64架构: arm64_fixed_webview2-setup.exe
### FAQ
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
alpha:
needs: update_tag
strategy:
fail-fast: false
matrix:
@ -247,7 +86,7 @@ jobs:
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
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
@ -264,8 +103,8 @@ jobs:
pnpm i
pnpm check ${{ matrix.target }}
- name: Release Alpha Version
run: pnpm release-alpha-version
- name: Alpha Version update
run: pnpm run fix-alpha-version
- name: Tauri build
uses: tauri-apps/tauri-action@v0
@ -290,7 +129,8 @@ jobs:
args: --target ${{ matrix.target }}
alpha-for-linux-arm:
needs: update_tag
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@ -333,9 +173,6 @@ jobs:
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/
@ -359,7 +196,6 @@ 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 }} \
@ -408,6 +244,7 @@ jobs:
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body: "More new features are now supported."
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
@ -415,7 +252,8 @@ jobs:
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-for-fixed-webview2:
needs: update_tag
needs: check_commit
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
strategy:
fail-fast: false
matrix:
@ -456,9 +294,6 @@ jobs:
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
@ -503,6 +338,7 @@ jobs:
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*
@ -511,3 +347,66 @@ jobs:
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --alpha
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: [check_commit, alpha, alpha-for-linux-arm, alpha-for-fixed-webview2]
if: ${{ needs.check_commit.outputs.should_run == 'true' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
# - name: Update Tag
# uses: richardsimko/update-tag@v1
# with:
# tag_name: alpha
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
## 我应该下载哪个版本?
### MacOS
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg
### Linux
- Linux 64位: amd64.deb/amd64.rpm
- Linux arm64 architecture: arm64.deb/aarch64.rpm
- Linux armv7架构: armhf.deb/armhfp.rpm
### Windows (不再支持Win7)
#### 正常版本(推荐)
- 64位: x64-setup.exe
- arm64架构: arm64-setup.exe
#### 便携版问题很多不再提供
#### 内置Webview2版(体积较大仅在企业版系统或无法安装webview2时使用)
- 64位: x64_fixed_webview2-setup.exe
- arm64架构: arm64_fixed_webview2-setup.exe
### FAQ
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: alpha
name: "Clash Verge Rev Alpha"
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true

View File

@ -50,7 +50,7 @@ jobs:
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
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install Node
uses: actions/setup-node@v4
@ -153,7 +153,6 @@ 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

@ -1,16 +1 @@
#!/bin/bash
#pnpm pretty-quick --staged
# 运行 clippy fmt
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
#git add .
# 允许提交
exit 0
pnpm pretty-quick --staged

View File

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

View File

@ -1,174 +1,3 @@
## v2.2.3
#### 已知问题
- 仅在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被截断的问题
- 首页流量统计卡更好的时间戳范围
- 静默启动无法触发自动轻量化计时器
#### 新增了:
- 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. **首页**
- 新增首页功能,默认启动页面改为首页。
- 首页流量图卡片显示上传/下载名称。
- 首页支持轻量模式切换。
- 流量统计数据持久保存。
- 限制首页配置文件卡片URL长度。
2. **DNS 设置与覆写**
- 新增 DNS 覆写功能。
- 默认启用 DNS 覆写。
3. **解锁测试**
- 新增解锁测试页面。
4. **轻量模式**
- 新增轻量模式及设置。
- 添加自动轻量模式定时器。
5. **系统支持**
- Mihomo(meta)内核升级 1.19.3
- macOS 支持 CMD+W 关闭窗口。
- 新增 macOS 应用菜单。
- 添加 macOS 安装服务时候的管理员权限提示。
- 新增 sidecar(用户空间启动内核) 模式。
6. **其他**
- 增强延迟测试日志和错误处理。
- 添加诊断信息导出。
- 新增代理命令。
#### 修复
1. **系统**
- 修复 Windows 热键崩溃。
- 修复 macOS 无框标题。
- 修复 macOS 静默启动崩溃。
- 修复 macOS tray图标错位到左上角的问题。
- 修复 Windows/Linux 运行时崩溃。
- 修复 Win10 阴影和边框问题。
- 修复 升级或重装后开机自启状态检测和同步问题。
2. **构建**
- 修复构建失败问题。
#### 优化
1. **性能**
- 重构后端,巨幅性能优化。
- 优化首页组件性能。
- 优化流量图表资源使用。
- 提升代理组列表滚动性能。
- 加快应用退出速度。
- 加快进入轻量模式速度。
- 优化小数值速度更新。
- 增加请求超时至 60 秒。
- 修复代理节点选择同步。
- 优化修改verge配置性能。
2. **重构**
- 重构后端,巨幅性能优化。
- 优化定时器管理。
- 重构 MihomoManager 处理流量。
- 优化 WebSocket 连接。
3. **其他**
- 更新依赖。
- 默认 TUN 堆栈改为 gvisor。
---
## v2.1.2
**发行代号:臻**
@ -177,14 +6,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: 314 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "2.2.3",
"version": "2.1.3-alpha",
"license": "GPL-3.0-only",
"scripts": {
"dev": "cross-env RUST_BACKTRACE=1 tauri dev -f verge-dev -- --profile fast-dev",
@ -16,11 +16,9 @@
"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/fix-alpha_version.mjs",
"release-version": "node scripts/release_version.mjs",
"release-alpha-version": "node scripts/release-alpha_version.mjs",
"fix-alpha-version": "node scripts/alpha_version.mjs",
"prepare": "husky",
"clippy": "cargo clippy --manifest-path ./src-tauri/Cargo.toml"
"clean": "cd ./src-tauri && cargo clean && cd -"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@ -29,49 +27,46 @@
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@juggle/resize-observer": "^3.4.0",
"@mui/icons-material": "^6.4.8",
"@mui/icons-material": "^6.4.2",
"@mui/lab": "6.0.0-beta.25",
"@mui/material": "^6.4.8",
"@mui/x-data-grid": "^7.28.0",
"@mui/material": "^6.4.2",
"@mui/x-data-grid": "^7.25.0",
"@tauri-apps/api": "2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
"@tauri-apps/plugin-clipboard-manager": "^2.2.1",
"@tauri-apps/plugin-dialog": "^2.2.0",
"@tauri-apps/plugin-fs": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.2.0",
"@tauri-apps/plugin-notification": "^2.2.2",
"@tauri-apps/plugin-notification": "^2.2.1",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-apps/plugin-shell": "2.2.0",
"@tauri-apps/plugin-updater": "2.3.0",
"@types/d3-shape": "^3.1.7",
"@types/json-schema": "^7.0.15",
"ahooks": "^3.8.4",
"axios": "^1.8.3",
"axios": "^1.7.9",
"cli-color": "^2.0.4",
"d3-shape": "^3.2.0",
"dayjs": "1.11.13",
"foxact": "^0.2.44",
"foxact": "^0.2.43",
"glob": "^11.0.1",
"i18next": "^24.2.3",
"i18next": "^24.2.2",
"js-base64": "^3.7.7",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.52.2",
"monaco-yaml": "^5.3.1",
"nanoid": "^5.1.5",
"monaco-yaml": "^5.2.3",
"nanoid": "^5.0.9",
"peggy": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.54.2",
"react-i18next": "^13.5.0",
"react-markdown": "^9.1.0",
"react-monaco-editor": "^0.56.2",
"react-router-dom": "^6.30.0",
"react-markdown": "^9.0.1",
"react-monaco-editor": "^0.56.0",
"react-router-dom": "^6.22.3",
"react-transition-group": "^4.4.5",
"react-virtuoso": "^4.12.5",
"recharts": "^2.15.1",
"react-virtuoso": "^4.6.3",
"sockette": "^2.0.6",
"swr": "^2.3.3",
"swr": "^2.3.0",
"tar": "^7.4.3",
"types-pac": "^1.0.3",
"zustand": "^5.0.3"
@ -85,20 +80,20 @@
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-transition-group": "^4.4.12",
"@vitejs/plugin-legacy": "^6.0.2",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-react": "^4.2.1",
"adm-zip": "^0.5.16",
"cross-env": "^7.0.3",
"https-proxy-agent": "^7.0.6",
"husky": "^9.1.7",
"meta-json-schema": "^1.19.3",
"meta-json-schema": "^1.19.1",
"node-fetch": "^3.3.2",
"prettier": "^3.5.3",
"pretty-quick": "^4.1.1",
"sass": "^1.86.0",
"terser": "^5.39.0",
"typescript": "^5.8.2",
"vite": "^6.2.2",
"prettier": "^3.4.2",
"pretty-quick": "^4.0.0",
"sass": "^1.83.4",
"terser": "^5.37.0",
"typescript": "^5.7.3",
"vite": "^6.0.11",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svgr": "^4.3.0"
},

7606
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +0,0 @@
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);

View File

@ -1,197 +0,0 @@
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,42 +43,3 @@ 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, resolveUpdateLogDefault } from "./updatelog.mjs";
import { resolveUpdateLog } 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.json";
const ALPHA_UPDATE_JSON_PROXY = "update-proxy.json";
const ALPHA_UPDATE_JSON_FILE = "update-alpha.json";
const ALPHA_UPDATE_JSON_PROXY = "update-alpha-proxy.json";
/// generate update.json
/// upload to update tag's release asset
@ -78,235 +78,192 @@ async function resolveUpdater() {
async function processRelease(github, options, tag, isAlpha) {
if (!tag) return;
try {
const { data: release } = await github.rest.repos.getReleaseByTag({
...options,
tag: tag.name,
});
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 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;
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;
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,
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,
);
// 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;
}
} catch (error) {
if (error.status === 404) {
console.log(`Release not found for tag: ${tag.name}, skipping...`);
// 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.error(
`Failed to get release for tag: ${tag.name}`,
error.message,
);
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 {
const { data: updateRelease } = await github.rest.repos.getReleaseByTag({
...options,
tag: releaseTag,
});
// 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,
);
}
}

View File

@ -1 +0,0 @@
avoid-breaking-exported-api = true

1700
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.3"
version = "2.1.2"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0-only"
@ -13,72 +13,70 @@ build = "build.rs"
identifier = "io.github.clash-verge-rev.clash-verge-rev"
[build-dependencies]
tauri-build = { version = "2.1.0", features = [] }
tauri-build = { version = "2.0.6", features = [] }
[dependencies]
warp = "0.3"
anyhow = "1.0.97"
dirs = "6.0"
open = "5.3"
open = "5.1"
log = "0.4"
dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4.40"
sysinfo = "0.34"
sysinfo = "0.33.1"
boa_engine = "0.20.0"
serde_json = "1.0"
serde_yaml = "0.9"
once_cell = "1.21.3"
once_cell = "1.20.3"
port_scanner = "0.1.5"
delay_timer = "0.11.6"
parking_lot = "0.12"
percent-encoding = "2.3.1"
tokio = { version = "1.44", features = [
"rt-multi-thread",
"macros",
"time",
"sync",
] }
window-shadows = { version = "0.2.2" }
tokio = { version = "1.43", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
regex = "1.11.1"
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
image = "0.25.6"
image = "0.25.5"
imageproc = "0.25.0"
tauri = { version = "2.4.0", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
rusttype = "0.9"
tauri = { version = "2.3.1", features = [
"protocol-asset",
"devtools",
"tray-icon",
"image-ico",
"image-png",
] }
network-interface = { version = "2.0.1", features = ["serde"] }
network-interface = { version = "2.0.0", 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.2"
tauri-plugin-clipboard-manager = "2.2.1"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-devtools = "2.0.0"
zip = "2.5.0"
reqwest_dav = "0.1.15"
tauri-plugin-devtools = "2.0.0-rc"
url = "2.5.4"
zip = "2.2.3"
reqwest_dav = "0.1.14"
aes-gcm = { version = "0.10.3", features = ["std"] }
base64 = "0.22.1"
getrandom = "0.3.2"
getrandom = "0.3.1"
tokio-tungstenite = "0.26.2"
futures = "0.3"
sys-locale = "0.3.2"
async-trait = "0.1.88"
mihomo_api = { path = "src_crates/crate_mihomo_api" }
sys-locale = "0.3.1"
async-trait = "0.1.87"
mihomo_api = { path = "./src/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,8 +84,9 @@ 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.6.1"
tauri-plugin-updater = "2.5.1"
tauri-plugin-window-state = "2.2.1"
#openssl
[features]
default = ["custom-protocol"]
@ -105,33 +104,32 @@ 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]
tempfile = "3.19.1"
env_logger = "0.11.0"
mockito = "1.7.0"
tempfile = "3.17.1"
[workspace]
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" }
members = [
"src/crate_mihomo_api"
]

View File

@ -68,6 +68,7 @@
"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",
@ -78,6 +79,7 @@
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text",
"shell:default",
"dialog:default"
"dialog:default",
"notification:default"
]
}

View File

@ -1,9 +1,5 @@
use super::CmdResult;
use crate::{
feat, logging,
utils::{dirs, logging::Type},
wrap_err,
};
use crate::{feat, utils::dirs, wrap_err};
use tauri::Manager;
/// 打开应用程序所在目录
@ -74,53 +70,45 @@ pub fn get_app_dir() -> CmdResult<String> {
Ok(app_home_dir)
}
/// 获取当前自启动状态
#[tauri::command]
pub fn get_auto_launch_status() -> CmdResult<bool> {
use crate::core::sysopt::Sysopt;
wrap_err!(Sysopt::global().get_launch_status())
}
/// 下载图标缓存
#[tauri::command]
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(&name);
// 如果文件已存在,直接返回路径
if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string());
}
// 确保缓存目录存在
if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir);
}
// 使用临时文件名来下载
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
// 下载文件到临时位置
let response = wrap_err!(reqwest::get(&url).await)?;
// 检查内容类型是否为图片
let content_type = response
.headers()
let content_type = response.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let is_image = content_type.starts_with("image/");
// 获取响应内容
let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15
&& (content.starts_with(b"<!DOCTYPE html")
|| content.starts_with(b"<html")
|| content.starts_with(b"<?xml"));
let is_html = content.len() > 15 &&
(content.starts_with(b"<!DOCTYPE html") ||
content.starts_with(b"<html") ||
content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存
if is_image && !is_html {
{
@ -134,14 +122,14 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
}
}
};
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
}
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}
Ok(_) => {},
Err(_) => {
let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() {
@ -152,11 +140,11 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
} else {
let _ = std::fs::remove_file(&temp_path);
}
Ok(icon_path.to_string_lossy().to_string())
} else {
let _ = std::fs::remove_file(&temp_path);
Err(format!("下载的内容不是有效图片: {}", url))
Err(format!("下载的内容不是有效图片: {}", url).into())
}
}
@ -170,7 +158,8 @@ pub struct IconInfo {
/// 复制图标文件
#[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::{fs, path::Path};
use std::fs;
use std::path::Path;
let file_path = Path::new(&path);
@ -198,14 +187,7 @@ 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

@ -24,8 +24,7 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
/// 修改Clash模式
#[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult {
feat::change_clash_mode(payload);
Ok(())
Ok(feat::change_clash_mode(payload))
}
/// 切换Clash核心
@ -99,11 +98,9 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置
#[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::{
config::Config,
core::{handle, CoreManager},
utils::dirs,
};
use crate::config::Config;
use crate::core::{handle, CoreManager};
use crate::utils::dirs;
use tauri::async_runtime;
// 使用spawn来处理异步操作

View File

@ -1,9 +0,0 @@
use crate::module::lightweight;
use super::CmdResult;
#[tauri::command]
pub async fn entry_lightweight_mode() -> CmdResult {
lightweight::entry_lightweight_mode();
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@ -4,35 +4,29 @@ use anyhow::Result;
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 validate;
pub mod uwp;
pub mod webdav;
pub mod app;
pub mod network;
pub mod clash;
pub mod verge;
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 proxy;
// 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 validate::*;
pub use uwp::*;
pub use webdav::*;
pub use app::*;
pub use network::*;
pub use clash::*;
pub use verge::*;
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 proxy::*;

View File

@ -1,8 +1,8 @@
use super::CmdResult;
use crate::wrap_err;
use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use super::CmdResult;
use sysproxy::{Autoproxy, Sysproxy};
use serde_yaml::Mapping;
use network_interface::NetworkInterface;
/// get the system proxy
#[tauri::command]
@ -46,7 +46,8 @@ pub fn get_network_interfaces() -> Vec<String> {
/// 获取网络接口详细信息
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use network_interface::NetworkInterface;
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?;

View File

@ -1,25 +1,40 @@
use super::CmdResult;
use crate::{
config::{Config, IProfiles, PrfItem, PrfOption},
core::{handle, tray::Tray, CoreManager},
feat, logging, ret_err,
utils::{dirs, help, logging::Type},
wrap_err,
config::*,
core::*,
feat,
utils::{dirs, help},
log_err, ret_err, wrap_err,
};
use super::CmdResult;
/// 获取配置文件列表
#[tauri::command]
pub fn get_profiles() -> CmdResult<IProfiles> {
let _ = Tray::global().update_menu();
let _ = tray::Tray::global().update_menu();
Ok(Config::profiles().data().clone())
}
/// 增强配置文件
#[tauri::command]
pub async fn enhance_profiles() -> CmdResult {
wrap_err!(feat::enhance_profiles().await)?;
handle::Handle::refresh_clash();
Ok(())
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(())
}
}
}
/// 导入配置文件
@ -61,114 +76,36 @@ pub async fn delete_profile(index: String) -> CmdResult {
/// 修改profiles的配置
#[tauri::command]
pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "开始修改配置文件");
pub async fn patch_profiles_config(
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone();
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);
}
}
}
}
}
println!("[cmd配置patch] 当前配置: {:?}", current_profile);
// 更新profiles配置
logging!(info, Type::Cmd, true, "正在更新配置草稿");
let _ = Config::profiles().draft().patch_config(profiles);
println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
// 更新配置并进行验证
match CoreManager::global().update_config().await {
Ok((true, _)) => {
logging!(info, Type::Cmd, true, "配置更新成功");
println!("[cmd配置patch] 配置更新成功");
handle::Handle::refresh_clash();
let _ = Tray::global().update_tooltip();
let _ = tray::Tray::global().update_tooltip();
Config::profiles().apply();
wrap_err!(Config::profiles().data().save_file())?;
Ok(true)
}
Ok((false, error_msg)) => {
logging!(warn, Type::Cmd, true, "配置验证失败: {}", error_msg);
println!("[cmd配置patch] 配置验证失败: {}", error_msg);
Config::profiles().discard();
// 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile {
logging!(
info,
Type::Cmd,
true,
"尝试恢复到之前的配置: {}",
prev_profile
);
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
let restore_profiles = IProfiles {
current: Some(prev_profile),
items: None,
@ -177,7 +114,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())?;
logging!(info, Type::Cmd, true, "成功恢复到之前的配置");
println!("[cmd配置patch] 成功恢复到之前的配置");
}
// 发送验证错误通知
@ -185,9 +122,9 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
Ok(false)
}
Err(e) => {
logging!(warn, Type::Cmd, true, "更新过程发生错误: {}", e);
println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", e.to_string());
handle::Handle::notice_message("config_validate::boot_error", &e.to_string());
Ok(false)
}
}
@ -197,14 +134,9 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
#[tauri::command]
pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle,
profile_index: String,
profile_index: String
) -> CmdResult<bool> {
logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index);
let profiles = IProfiles {
current: Some(profile_index),
items: None,
};
let profiles = IProfiles{current: Some(profile_index), items: None};
patch_profiles_config(profiles).await
}

View File

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

View File

@ -1,8 +1,11 @@
use crate::{
config::*,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, wrap_err};
use anyhow::Context;
use serde_yaml::Mapping;
use std::collections::HashMap;
use serde_yaml::Mapping;
/// 获取运行时配置
#[tauri::command]

View File

@ -1,5 +1,10 @@
use crate::{
config::*,
core::*,
utils::dirs,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, core::*, utils::dirs, wrap_err};
use std::fs;
/// 保存profiles的配置
@ -15,7 +20,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件
let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge");
let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
@ -24,20 +29,14 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string();
println!(
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str, is_merge_file
);
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global()
.validate_config_file(&file_path_str, Some(true))
.await
{
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await {
Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置
@ -64,12 +63,9 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
}
}
}
// 非merge文件使用完整验证流程
match CoreManager::global()
.validate_config_file(&file_path_str, None)
.await
{
match CoreManager::global().validate_config_file(&file_path_str, None).await {
Ok((true, _)) => {
println!("[cmd配置save] 验证成功");
Ok(())
@ -78,17 +74,16 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
println!("[cmd配置save] 验证失败: {}", error_msg);
// 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js")
|| error_msg.contains("Script syntax error")
|| error_msg.contains("Script must contain a main function")
|| error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error")
|| error_msg.contains("Failed to read file:")
|| (!file_path_str.ends_with(".js") && !is_script_error)
{
let is_script_error = file_path_str.ends_with(".js") ||
error_msg.contains("Script syntax error") ||
error_msg.contains("Script must contain a main function") ||
error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error") ||
error_msg.contains("Failed to read file:") ||
(!file_path_str.ends_with(".js") && !is_script_error) {
// 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone());
@ -103,7 +98,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg);
}
Ok(())
}
Err(e) => {

View File

@ -1,40 +0,0 @@
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,94 +1,35 @@
use super::CmdResult;
use crate::{
core::{handle, CoreManager},
module::sysinfo::PlatformSpecification,
};
use once_cell::sync::Lazy;
use std::{
sync::atomic::{AtomicI64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use crate::core::handle;
use crate::module::sysinfo::PlatformSpecification;
use tauri_plugin_clipboard_manager::ClipboardExt;
// 存储应用启动时间的全局变量
static APP_START_TIME: Lazy<AtomicI64> = Lazy::new(|| {
// 获取当前系统时间,转换为毫秒级时间戳
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
AtomicI64::new(now)
});
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> {
let sysinfo = PlatformSpecification::new_async().await;
let sysinfo = PlatformSpecification::new();
let info = format!("{:?}", sysinfo);
let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard();
if cliboard.write_text(info).is_err() {
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard");
}
Ok(())
}
#[tauri::command]
pub async fn get_system_info() -> CmdResult<String> {
let sysinfo = PlatformSpecification::new_async().await;
let info = format!("{:?}", sysinfo);
Ok(info)
}
/// 获取当前内核运行模式
#[tauri::command]
pub async fn get_running_mode() -> Result<String, String> {
Ok(CoreManager::global().get_running_mode().await.to_string())
}
/// 获取应用的运行时间(毫秒)
#[tauri::command]
pub fn get_app_uptime() -> CmdResult<i64> {
let start_time = APP_START_TIME.load(Ordering::Relaxed);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as 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)
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)
}

View File

@ -4,7 +4,8 @@ use super::CmdResult;
#[cfg(windows)]
mod platform {
use super::CmdResult;
use crate::{core::win_uwp, wrap_err};
use crate::core::win_uwp;
use crate::wrap_err;
pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await)

View File

@ -1,5 +1,5 @@
use super::CmdResult;
use crate::core::*;
use super::CmdResult;
/// 发送脚本验证通知消息
#[tauri::command]
@ -13,7 +13,7 @@ pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
@ -27,7 +27,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
// 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error"
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg);
}
@ -37,15 +37,12 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
#[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global()
.validate_config_file(&file_path, None)
.await
{
match CoreManager::global().validate_config_file(&file_path, None).await {
Ok(result) => {
handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值
}
Ok(result.0) // 返回验证结果布尔值
},
Err(e) => {
let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
@ -61,10 +58,10 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 {
let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件
let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found"
@ -96,7 +93,7 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
"config_validate::yaml_error"
}
};
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg);

View File

@ -1,5 +1,9 @@
use crate::{
config::*,
feat,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, feat, wrap_err};
/// 获取Verge配置
#[tauri::command]

View File

@ -1,5 +1,10 @@
use crate::{
core,
config::*,
feat,
wrap_err,
};
use super::CmdResult;
use crate::{config::*, core, feat, wrap_err};
use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置

View File

@ -33,7 +33,7 @@ impl IClashTemp {
let mut map = Mapping::new();
let mut tun = Mapping::new();
tun.insert("enable".into(), false.into());
tun.insert("stack".into(), "gvisor".into());
tun.insert("stack".into(), "mixed".into());
tun.insert("auto-route".into(), true.into());
tun.insert("strict-route".into(), false.into());
tun.insert("auto-detect-interface".into(), true.into());

View File

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

View File

@ -8,9 +8,14 @@ mod profiles;
mod runtime;
mod verge;
pub use self::{
clash::*, config::*, draft::*, encrypt::*, prfitem::*, profiles::*, runtime::*, verge::*,
};
pub use self::clash::*;
pub use self::config::*;
pub use self::draft::*;
pub use self::encrypt::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::runtime::*;
pub use self::verge::*;
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";

View File

@ -234,10 +234,10 @@ impl PrfItem {
option: Option<PrfOption>,
) -> Result<PrfItem> {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.is_some_and(|o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.is_some_and(|o| o.self_proxy.unwrap_or(false));
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let accept_invalid_certs =
opt_ref.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone());

View File

@ -472,17 +472,15 @@ impl IProfiles {
/// 获取所有的profiles(uid名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
self.items.as_ref().map(|items| {
items
.iter()
.filter_map(|e| {
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
Some((uid, name))
} else {
None
}
})
.collect()
})
match self.items.as_ref() {
Some(items) => Some(items.iter().filter_map(|e| {
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
Some((uid, name))
} else {
None
}
}).collect()),
None => None,
}
}
}

View File

@ -1,7 +1,7 @@
use crate::{
config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
utils::{dirs, help, i18n},
};
use crate::config::DEFAULT_PAC;
use crate::config::{deserialize_encrypted, serialize_encrypted};
use crate::utils::i18n;
use crate::utils::{dirs, help};
use anyhow::Result;
use log::LevelFilter;
use serde::{Deserialize, Serialize};
@ -101,14 +101,10 @@ pub struct IVerge {
/// hotkey map
/// format: {func},{key}
pub hotkeys: Option<Vec<String>>,
/// enable global hotkey
pub enable_global_hotkey: Option<bool>,
/// 首页卡片设置
/// 控制首页各个卡片的显示和隐藏
pub home_cards: Option<serde_json::Value>,
/// 切换代理时自动关闭连接
pub auto_close_connection: Option<bool>,
@ -189,16 +185,8 @@ 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>,
/// 轻量模式 - 只保留内核运行
pub enable_lite_mode: Option<bool>,
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -260,7 +248,7 @@ impl IVerge {
env_type: Some("bash".into()),
#[cfg(target_os = "windows")]
env_type: Some("powershell".into()),
start_page: Some("/home".into()),
start_page: Some("/".into()),
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_group_icon: Some(true),
@ -300,13 +288,9 @@ 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_lite_mode: Some(false),
enable_dns_settings: Some(true),
home_cards: None,
service_state: None,
..Self::default()
}
}
@ -388,12 +372,8 @@ 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_lite_mode);
patch!(enable_dns_settings);
patch!(home_cards);
patch!(service_state);
}
/// 在初始化前尝试拿到单例端口的值
@ -482,12 +462,8 @@ 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_lite_mode: Option<bool>,
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 {
@ -550,12 +526,8 @@ 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_lite_mode: verge.enable_lite_mode,
enable_dns_settings: verge.enable_dns_settings,
home_cards: verge.home_cards,
service_state: verge.service_state,
}
}
}

View File

@ -1,17 +1,16 @@
use crate::{config::Config, utils::dirs};
use crate::config::Config;
use crate::utils::dirs;
use anyhow::Error;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::{
collections::HashMap,
env::{consts::OS, temp_dir},
fs,
io::Write,
path::PathBuf,
sync::Arc,
time::Duration,
};
use std::collections::HashMap;
use std::env::{consts::OS, temp_dir};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::timeout;
use zip::write::SimpleFileOptions;

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,15 @@
use crate::log_err;
use once_cell::sync::OnceCell;
use parking_lot::RwLock;
use std::{sync::Arc, time::Duration};
use std::sync::Arc;
use tauri::{AppHandle, Emitter, Manager, WebviewWindow};
use crate::{logging, logging_error, utils::logging::Type};
/// 存储启动期间的错误消息
#[derive(Debug, Clone)]
struct ErrorMessage {
status: String,
message: String,
}
use tauri_plugin_shell::process::CommandChild;
#[derive(Debug, Default, Clone)]
pub struct Handle {
pub app_handle: Arc<RwLock<Option<AppHandle>>>,
pub is_exiting: Arc<RwLock<bool>>,
/// 存储启动过程中产生的错误消息队列
startup_errors: Arc<RwLock<Vec<ErrorMessage>>>,
startup_completed: Arc<RwLock<bool>>,
pub core_process: Arc<RwLock<Option<CommandChild>>>,
}
impl Handle {
@ -28,8 +19,7 @@ impl Handle {
HANDLE.get_or_init(|| Handle {
app_handle: Arc::new(RwLock::new(None)),
is_exiting: Arc::new(RwLock::new(false)),
startup_errors: Arc::new(RwLock::new(Vec::new())),
startup_completed: Arc::new(RwLock::new(false)),
core_process: Arc::new(RwLock::new(None)),
})
}
@ -53,115 +43,26 @@ impl Handle {
pub fn refresh_clash() {
if let Some(window) = Self::global().get_window() {
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-clash-config", "yes")
);
log_err!(window.emit("verge://refresh-clash-config", "yes"));
}
}
pub fn refresh_verge() {
if let Some(window) = Self::global().get_window() {
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-verge-config", "yes")
);
log_err!(window.emit("verge://refresh-verge-config", "yes"));
}
}
#[allow(unused)]
pub fn refresh_profiles() {
if let Some(window) = Self::global().get_window() {
logging_error!(
Type::Frontend,
true,
window.emit("verge://refresh-profiles-config", "yes")
);
log_err!(window.emit("verge://refresh-profiles-config", "yes"));
}
}
/// 通知前端显示消息,如果在启动过程中,则将消息存入启动错误队列
pub fn notice_message<S: Into<String>, M: Into<String>>(status: S, msg: M) {
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;
}
});
if let Some(window) = Self::global().get_window() {
log_err!(window.emit("verge://notice-message", (status.into(), msg.into())));
}
}
@ -170,6 +71,21 @@ 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,16 +1,13 @@
use crate::{
config::Config,
core::handle,
feat, logging, logging_error,
module::lightweight::entry_lightweight_mode,
utils::{logging::Type, resolve},
};
use crate::core::handle;
use crate::{config::Config, feat, log_err};
use crate::utils::resolve;
use anyhow::{bail, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc};
use tauri::{async_runtime, Manager};
use tauri::Manager;
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
use tauri::async_runtime;
pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@ -29,27 +26,19 @@ impl Hotkey {
let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
logging!(
debug,
Type::Hotkey,
true,
"Initializing hotkeys with enable: {}",
enable_global_hotkey
);
println!("Initializing hotkeys, global hotkey enabled: {}", 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() {
logging!(
debug,
Type::Hotkey,
true,
"Has {} hotkeys need to register",
hotkeys.len()
);
println!("Found {} hotkeys to register", hotkeys.len());
log::info!(target: "app", "Found {} hotkeys to register", hotkeys.len());
for hotkey in hotkeys.iter() {
let mut iter = hotkey.split(',');
@ -58,52 +47,28 @@ impl Hotkey {
match (key, func) {
(Some(key), Some(func)) => {
logging!(
debug,
Type::Hotkey,
true,
"Registering hotkey: {} -> {}",
key,
func
);
println!("Registering hotkey: {} -> {}", key, func);
log::info!(target: "app", "Registering hotkey: {} -> {}", key, func);
if let Err(e) = self.register(key, func) {
logging!(
error,
Type::Hotkey,
true,
"Failed to register hotkey {} -> {}: {:?}",
key,
func,
e
);
println!("Failed to register hotkey {} -> {}: {:?}", key, func, e);
log::error!(target: "app", "Failed to register hotkey {} -> {}: {:?}", key, func, e);
} else {
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} -> {}",
key,
func
);
println!("Successfully registered hotkey {} -> {}", key, func);
log::info!(target: "app", "Successfully registered hotkey {} -> {}", key, func);
}
}
_ => {
let key = key.unwrap_or("None");
let func = func.unwrap_or("None");
logging!(
error,
Type::Hotkey,
true,
"Invalid hotkey configuration: `{}`:`{}`",
key,
func
);
println!("Invalid hotkey configuration: `{key}`:`{func}`");
log::error!(target: "app", "Invalid hotkey configuration: `{key}`:`{func}`");
}
}
}
self.current.lock().clone_from(hotkeys);
} else {
logging!(debug, Type::Hotkey, true, "No hotkeys configured");
println!("No hotkeys configured");
log::info!(target: "app", "No hotkeys configured");
}
Ok(())
@ -120,60 +85,39 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
logging!(
debug,
Type::Hotkey,
true,
"Attempting to register hotkey: {} for function: {}",
hotkey,
func
);
println!("Attempting to register hotkey: {} for function: {}", hotkey, func);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
if manager.is_registered(hotkey) {
logging!(
debug,
Type::Hotkey,
true,
"Hotkey {} was already registered, unregistering first",
hotkey
);
println!("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" => {
logging!(
debug,
Type::Hotkey,
true,
"Registering open_or_close_dashboard function"
);
println!("Registering open_or_close_dashboard function");
log::info!(target: "app", "Registering open_or_close_dashboard function");
|| {
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation Start ==="
);
println!("=== Hotkey Dashboard Window Operation Start ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ===");
// 使用 spawn_blocking 来确保在正确的线程上执行
async_runtime::spawn_blocking(|| {
logging!(
debug,
Type::Hotkey,
true,
"Toggle dashboard window visibility"
);
println!("Toggle dashboard window visibility");
log::info!(target: "app", "Toggle dashboard window visibility");
// 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏它
if window.is_visible().unwrap_or(false) {
logging!(info, Type::Window, true, "Window is visible, hiding it");
println!("Window is visible, hiding it");
log::info!(target: "app", "Window is visible, hiding it");
let _ = window.hide();
} else {
// 如果窗口不可见,则显示它
logging!(info, Type::Window, true, "Window is hidden, showing it");
println!("Window is hidden, showing it");
log::info!(target: "app", "Window is hidden, showing it");
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
@ -182,36 +126,26 @@ impl Hotkey {
}
} else {
// 如果窗口不存在,创建一个新窗口
logging!(
info,
Type::Window,
true,
"Window does not exist, creating a new one"
);
resolve::create_window(true);
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!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation End ==="
);
println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
}
}
},
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"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(),
_ => {
logging!(error, Type::Hotkey, true, "Invalid function: {}", func);
println!("Invalid function: {}", func);
log::error!(target: "app", "Invalid function: {}", func);
bail!("invalid function \"{func}\"");
}
};
@ -220,32 +154,34 @@ impl Hotkey {
let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey, event| {
if event.state == ShortcutState::Pressed {
logging!(debug, Type::Hotkey, true, "Hotkey pressed: {:?}", hotkey);
println!("Hotkey pressed: {:?}", hotkey);
log::info!(target: "app", "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) {
logging!(debug, Type::Hotkey, true, "Executing quit function");
println!("Executing quit function");
log::info!(target: "app", "Executing quit function");
f();
}
}
} else {
// 直接执行函数,不做任何状态检查
logging!(debug, Type::Hotkey, true, "Executing function directly");
// 获取全局热键状态
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if is_enable_global_hotkey {
println!("Executing function directly");
log::info!(target: "app", "Executing function directly");
// 获取轻量模式状态和全局热键状态
let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false);
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
// 在轻量模式下或配置了全局热键时,始终执行热键功能
if is_lite_mode || is_enable_global_hotkey {
f();
} else if let Some(window) = app_handle.get_webview_window("main") {
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
let is_visible = window.is_visible().unwrap_or(false);
let is_focused = window.is_focused().unwrap_or(false);
if is_focused && is_visible {
f();
}
@ -254,14 +190,8 @@ impl Hotkey {
}
});
logging!(
debug,
Type::Hotkey,
true,
"Successfully registered hotkey {} for {}",
hotkey,
func
);
println!("Successfully registered hotkey {} for {}", hotkey, func);
log::info!(target: "app", "Successfully registered hotkey {} for {}", hotkey, func);
Ok(())
}
@ -269,7 +199,7 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut();
manager.unregister(hotkey)?;
logging!(debug, Type::Hotkey, true, "Unregister hotkey {}", hotkey);
log::debug!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
@ -285,7 +215,7 @@ impl Hotkey {
});
add.iter().for_each(|(key, func)| {
logging_error!(Type::Hotkey, true, self.register(key, func));
log_err!(self.register(key, func));
});
*current = new_hotkeys;
@ -342,13 +272,7 @@ 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() {
logging!(
error,
Type::Hotkey,
true,
"Error unregistering all hotkeys: {:?}",
e
);
log::error!(target:"app", "Error unregistering all hotkeys: {:?}", e);
}
}
}

View File

@ -1,95 +1,16 @@
use crate::{
config::Config,
logging,
utils::{dirs, logging::Type},
};
use crate::config::Config;
use crate::utils::dirs;
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
env::current_exe,
path::PathBuf,
process::Command as StdCommand,
time::{SystemTime, UNIX_EPOCH},
};
use std::collections::HashMap;
use std::path::PathBuf;
use std::{env::current_exe, process::Command as StdCommand};
use tokio::time::Duration;
// Windows only
const SERVICE_URL: &str = "http://127.0.0.1:33211";
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
}
}
const REQUIRED_SERVICE_VERSION: &str = "1.0.1"; // 定义所需的服务版本号
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ResponseBody {
@ -120,42 +41,8 @@ pub struct VersionJsonResponse {
}
#[cfg(target_os = "windows")]
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 uninstall_path = binary_path.with_file_name("uninstall-service.exe");
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let token = Token::with_current_process()?;
let level = token.privilege_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");
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall service");
use deelevate::{PrivilegeLevel, Token};
use runas::Command as RunasCommand;
@ -163,13 +50,25 @@ pub async fn install_service() -> Result<()> {
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 {
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
_ => StdCommand::new(uninstall_path)
.creation_flags(0x08000000)
.status()?,
};
let status = match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
_ => StdCommand::new(install_path)
@ -187,64 +86,24 @@ pub async fn install_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "windows")]
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 = "linux")]
pub async fn uninstall_service() -> Result<()> {
logging!(info, Type::Service, true, "uninstall service");
pub async fn reinstall_service() -> Result<()> {
log::info!(target:"app", "reinstall 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();
@ -256,38 +115,8 @@ pub async fn uninstall_service() -> Result<()> {
.arg(uninstall_shell)
.status()?,
};
logging!(
info,
Type::Service,
true,
"uninstall status code:{}",
status.code().unwrap()
);
log::info!(target:"app", "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())
@ -296,13 +125,6 @@ pub async fn install_service() -> Result<()> {
.arg(install_shell)
.status()?,
};
logging!(
info,
Type::Service,
true,
"install status code:{}",
status.code().unwrap()
);
if !status.success() {
bail!(
@ -314,110 +136,42 @@ pub async fn install_service() -> Result<()> {
Ok(())
}
#[cfg(target_os = "linux")]
#[cfg(target_os = "macos")]
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");
log::info!(target:"app", "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");
if !install_path.exists() {
bail!(format!("installer not found: {install_path:?}"));
}
let install_shell: String = install_path.to_string_lossy().into_owned();
if !uninstall_path.exists() {
bail!(format!("uninstaller not found: {uninstall_path:?}"));
}
let prompt = t("Service Administrator Prompt");
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 '{install_shell}'" with administrator privileges with prompt "{prompt}""#
r#"do shell script "sudo '{uninstall_shell}' && sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""#
);
logging!(debug, Type::Service, true, "install command: {}", command);
log::debug!(target: "app", "command: {}", command);
let status = StdCommand::new("osascript")
.args(vec!["-e", &command])
@ -429,57 +183,9 @@ pub async fn install_service() -> Result<()> {
status.code().unwrap()
);
}
Ok(())
}
#[cfg(target_os = "macos")]
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)
}
}
}
/// check the windows service status
pub async fn check_service() -> Result<JsonResponse> {
let url = format!("{SERVICE_URL}/get_clash");
@ -521,54 +227,19 @@ 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) => {
// 打印更详细的日志,方便排查问题
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 // 服务不可用,需要重装
}
}
}
Ok(version) => version != REQUIRED_SERVICE_VERSION,
Err(_) => true, // 如果无法获取版本或服务未运行,也需要重新安装
}
}
/// 尝试使用现有服务启动核心,不进行重装
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
log::info!(target:"app", "attempting to start core with existing service");
/// 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?;
}
let clash_core = { Config::verge().latest().clash_core.clone() };
let clash_core = clash_core.unwrap_or("verge-mihomo".into());
@ -608,106 +279,6 @@ pub(super) async fn start_with_existing_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");
@ -727,42 +298,9 @@ pub async fn is_service_running() -> Result<bool> {
let resp = check_service().await?;
// 检查服务状态码和消息
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
logging!(debug, Type::Service, "Service is running");
if resp.code == 200 && resp.msg == "success" && resp.data.is_some() {
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,8 +1,7 @@
use crate::core::handle::Handle;
use crate::{
config::{Config, IVerge},
core::handle::Handle,
logging_error,
utils::logging::Type,
log_err,
};
use anyhow::Result;
use once_cell::sync::OnceCell;
@ -127,7 +126,8 @@ impl Sysopt {
if !sys_enable {
return self.reset_sysproxy().await;
}
use crate::{core::handle::Handle, utils::dirs};
use crate::core::handle::Handle;
use crate::utils::dirs;
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@ -185,7 +185,8 @@ impl Sysopt {
#[cfg(target_os = "windows")]
{
use crate::{core::handle::Handle, utils::dirs};
use crate::core::handle::Handle;
use crate::utils::dirs;
use anyhow::bail;
use tauri_plugin_shell::ShellExt;
@ -221,50 +222,15 @@ impl Sysopt {
let enable = enable.unwrap_or(false);
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
log::info!(target: "app", "Setting auto launch to: {}", enable);
println!("enable: {}", enable);
match enable {
true => {
let result = autostart_manager.enable();
if let Err(ref e) = result {
log::error!(target: "app", "Failed to enable auto launch: {}", e);
} else {
log::info!(target: "app", "Auto launch enabled successfully");
}
logging_error!(Type::System, true, result);
}
false => {
let result = autostart_manager.disable();
if let Err(ref e) = result {
log::error!(target: "app", "Failed to disable auto launch: {}", e);
} else {
log::info!(target: "app", "Auto launch disabled successfully");
}
logging_error!(Type::System, true, result);
}
true => log_err!(autostart_manager.enable()),
false => log_err!(autostart_manager.disable()),
};
Ok(())
}
/// 获取当前自启动的实际状态
pub fn get_launch_status(&self) -> Result<bool> {
let app_handle = Handle::global().app_handle().unwrap();
let autostart_manager = app_handle.autolaunch();
match autostart_manager.is_enabled() {
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))
}
}
}
fn guard_proxy(&self) {
let _lock = self.guard_state.lock();
@ -324,7 +290,7 @@ impl Sysopt {
enable: true,
url: format!("http://127.0.0.1:{pac_port}/commands/pac"),
};
logging_error!(Type::System, true, autoproxy.set_auto_proxy());
log_err!(autoproxy.set_auto_proxy());
} else {
let sysproxy = Sysproxy {
enable: true,
@ -333,13 +299,14 @@ impl Sysopt {
bypass: get_bypass(),
};
logging_error!(Type::System, true, sysproxy.set_system_proxy());
log_err!(sysproxy.set_system_proxy());
}
}
#[cfg(target_os = "windows")]
{
use crate::{core::handle::Handle, utils::dirs};
use crate::core::handle::Handle;
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap();

View File

@ -1,34 +1,24 @@
use crate::{
config::Config, core::CoreManager, feat, logging, logging_error, utils::logging::Type,
};
use crate::config::Config;
use crate::feat;
use crate::core::CoreManager;
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, sync::Arc};
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::Arc;
type TaskID = u64;
#[derive(Debug, Clone)]
pub struct TimerTask {
pub task_id: TaskID,
pub interval_minutes: u64,
#[allow(unused)]
pub last_run: i64, // Timestamp of last execution
}
pub struct Timer {
/// cron manager
pub delay_timer: Arc<RwLock<DelayTimer>>,
delay_timer: Arc<Mutex<DelayTimer>>,
/// save the current state - using RwLock for better read concurrency
pub timer_map: Arc<RwLock<HashMap<String, TimerTask>>>,
/// save the current state
timer_map: Arc<Mutex<HashMap<String, (TaskID, u64)>>>,
/// increment id - kept as mutex since it's just a counter
pub timer_count: Arc<Mutex<TaskID>>,
/// Flag to mark if timer is initialized - atomic for better performance
pub initialized: Arc<std::sync::atomic::AtomicBool>,
/// increment id
timer_count: Arc<Mutex<TaskID>>,
}
impl Timer {
@ -36,164 +26,68 @@ impl Timer {
static TIMER: OnceCell<Timer> = OnceCell::new();
TIMER.get_or_init(|| Timer {
delay_timer: Arc::new(RwLock::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(RwLock::new(HashMap::new())),
delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())),
timer_map: Arc::new(Mutex::new(HashMap::new())),
timer_count: Arc::new(Mutex::new(1)),
initialized: Arc::new(std::sync::atomic::AtomicBool::new(false)),
})
}
/// Initialize timer with better error handling and atomic operations
/// restore timer
pub fn init(&self) -> Result<()> {
// Use compare_exchange for thread-safe initialization check
if self
.initialized
.compare_exchange(
false,
true,
std::sync::atomic::Ordering::SeqCst,
std::sync::atomic::Ordering::SeqCst,
)
.is_err()
{
logging!(debug, Type::Timer, "Timer already initialized, skipping...");
return Ok(());
}
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);
logging_error!(Type::Timer, false, "Failed to initialize timer: {}", e);
return Err(e);
}
self.refresh()?;
let cur_timestamp = chrono::Local::now().timestamp();
// Collect profiles that need immediate update
let profiles_to_update = if let Some(items) = Config::profiles().latest().get_items() {
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
if let Some(items) = Config::profiles().latest().get_items() {
items
.iter()
.filter_map(|item| {
let interval = item.option.as_ref()?.update_interval? as i64;
// mins to seconds
let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60;
let updated = item.updated? as i64;
let uid = item.uid.as_ref()?;
if interval > 0 && cur_timestamp - updated >= interval * 60 {
Some(uid.clone())
if interval > 0 && cur_timestamp - updated >= interval {
Some(item)
} else {
None
}
})
.collect::<Vec<String>>()
} else {
Vec::new()
};
// Advance tasks outside of locks to minimize lock contention
if !profiles_to_update.is_empty() {
let timer_map = self.timer_map.read();
let delay_timer = self.delay_timer.write();
for uid in profiles_to_update {
if let Some(task) = timer_map.get(&uid) {
logging!(info, Type::Timer, "Advancing task for uid: {}", uid);
if let Err(e) = delay_timer.advance_task(task.task_id) {
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
.for_each(|item| {
if let Some(uid) = item.uid.as_ref() {
if let Some((task_id, _)) = timer_map.get(uid) {
crate::log_err!(delay_timer.advance_task(*task_id));
}
}
}
}
})
}
logging!(info, Type::Timer, "Timer initialization completed");
Ok(())
}
/// Refresh timer tasks with better error handling
/// Correctly update all cron tasks
pub fn refresh(&self) -> Result<()> {
// Generate diff outside of lock to minimize lock contention
let diff_map = self.gen_diff();
if diff_map.is_empty() {
logging!(debug, Type::Timer, "No timer changes needed");
return Ok(());
}
let mut timer_map = self.timer_map.lock();
let mut delay_timer = self.delay_timer.lock();
logging!(
info,
Type::Timer,
"Refreshing {} timer tasks",
diff_map.len()
);
// Apply changes while holding locks
let mut timer_map = self.timer_map.write();
let mut delay_timer = self.delay_timer.write();
for (uid, diff) in diff_map {
for (uid, diff) in diff_map.into_iter() {
match diff {
DiffFlag::Del(tid) => {
timer_map.remove(&uid);
if let Err(e) = delay_timer.remove_task(tid) {
logging!(
warn,
Type::Timer,
"Failed to remove task {} for uid {}: {}",
tid,
uid,
e
);
} else {
logging!(debug, Type::Timer, "Removed task {} for uid {}", tid, uid);
}
let _ = timer_map.remove(&uid);
crate::log_err!(delay_timer.remove_task(tid));
}
DiffFlag::Add(tid, interval) => {
let task = TimerTask {
task_id: tid,
interval_minutes: interval,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
logging_error!(Type::Timer, "Failed to add task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
logging!(debug, Type::Timer, "Added task {} for uid {}", tid, uid);
}
DiffFlag::Add(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
}
DiffFlag::Mod(tid, interval) => {
// Remove old task first
if let Err(e) = delay_timer.remove_task(tid) {
logging!(
warn,
Type::Timer,
"Failed to remove old task {} for uid {}: {}",
tid,
uid,
e
);
}
// Then add the new one
let task = TimerTask {
task_id: tid,
interval_minutes: interval,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(uid.clone(), task);
if let Err(e) = self.add_task(&mut delay_timer, uid.clone(), tid, interval) {
logging_error!(Type::Timer, "Failed to update task for uid {}: {}", uid, e);
timer_map.remove(&uid); // Rollback on failure
} else {
logging!(debug, Type::Timer, "Updated task {} for uid {}", tid, uid);
}
DiffFlag::Mod(tid, val) => {
let _ = timer_map.insert(uid.clone(), (tid, val));
crate::log_err!(delay_timer.remove_task(tid));
crate::log_err!(self.add_task(&mut delay_timer, uid, tid, val));
}
}
}
@ -201,17 +95,18 @@ impl Timer {
Ok(())
}
/// Generate map of profile UIDs to update intervals
/// generate a uid -> update_interval map
fn gen_map(&self) -> HashMap<String, u64> {
let mut new_map = HashMap::new();
if let Some(items) = Config::profiles().latest().get_items() {
for item in items.iter() {
if let Some(option) = item.option.as_ref() {
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
if interval > 0 {
new_map.insert(uid.clone(), interval);
}
if item.option.is_some() {
let option = item.option.as_ref().unwrap();
let interval = option.update_interval.unwrap_or(0);
if interval > 0 {
new_map.insert(item.uid.clone().unwrap(), interval);
}
}
}
@ -220,50 +115,39 @@ impl Timer {
new_map
}
/// Generate differences between current and new timer configuration
/// generate the diff map for refresh
fn gen_diff(&self) -> HashMap<String, DiffFlag> {
let mut diff_map = HashMap::new();
let timer_map = self.timer_map.lock();
let new_map = self.gen_map();
let cur_map = &timer_map;
// Read lock for comparing current state
let timer_map = self.timer_map.read();
cur_map.iter().for_each(|(uid, (tid, val))| {
let new_val = new_map.get(uid).unwrap_or(&0);
// Find tasks to modify or delete
for (uid, task) in timer_map.iter() {
match new_map.get(uid) {
Some(&interval) if interval != task.interval_minutes => {
// Task exists but interval changed
diff_map.insert(uid.clone(), DiffFlag::Mod(task.task_id, interval));
}
None => {
// Task no longer needed
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
}
_ => {
// Task exists with same interval, no change needed
}
if *new_val == 0 {
diff_map.insert(uid.clone(), DiffFlag::Del(*tid));
} else if new_val != val {
diff_map.insert(uid.clone(), DiffFlag::Mod(*tid, *new_val));
}
}
});
// Find new tasks to add
let mut next_id = *self.timer_count.lock();
let mut count = self.timer_count.lock();
for (uid, &interval) in new_map.iter() {
if !timer_map.contains_key(uid) {
diff_map.insert(uid.clone(), DiffFlag::Add(next_id, interval));
next_id += 1;
new_map.iter().for_each(|(uid, val)| {
if cur_map.get(uid).is_none() {
diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val));
*count += 1;
}
}
// Update counter only if we added new tasks
if next_id > *self.timer_count.lock() {
*self.timer_count.lock() = next_id;
}
});
diff_map
}
/// Add a timer task with better error handling
/// add a cron task
fn add_task(
&self,
delay_timer: &mut DelayTimer,
@ -271,26 +155,12 @@ impl Timer {
tid: TaskID,
minutes: u64,
) -> Result<()> {
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()
.set_task_id(tid)
.set_maximum_parallel_runnable_num(1)
.set_frequency_repeated_by_minutes(minutes)
.spawn_async_routine(move || {
let uid = uid.clone();
async move {
Self::async_task(uid).await;
}
})
// .set_frequency_repeated_by_seconds(minutes) // for test
.spawn_async_routine(move || Self::async_task(uid.to_owned()))
.context("failed to create timer task")?;
delay_timer
@ -300,41 +170,19 @@ impl Timer {
Ok(())
}
/// Async task with better error handling and logging
/// the task runner
async fn async_task(uid: String) {
let task_start = std::time::Instant::now();
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
log::info!(target: "app", "running timer task `{uid}`");
// 使用更轻量级的更新方式
if let Err(e) = feat::update_profile(uid.clone(), None).await {
log::error!(target: "app", "timer task update error: {}", e);
return;
}
// Update profile
let profile_result = feat::update_profile(uid.clone(), None).await;
match profile_result {
Ok(_) => {
// Update configuration
match CoreManager::global().update_config().await {
Ok(_) => {
let duration = task_start.elapsed().as_millis();
logging!(
info,
Type::Timer,
"Timer task completed successfully for uid: {} (took {}ms)",
uid,
duration
);
}
Err(e) => {
logging_error!(
Type::Timer,
"Failed to refresh config after profile update for uid {}: {}",
uid,
e
);
}
}
}
Err(e) => {
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
}
// 只有更新成功后才刷新配置
if let Err(e) = CoreManager::global().update_config().await {
log::error!(target: "app", "timer task refresh error: {}", e);
}
}
}

View File

@ -1,14 +1,13 @@
use once_cell::sync::OnceCell;
use tauri::tray::TrayIconBuilder;
#[cfg(target_os = "macos")]
pub mod speed_rate;
use crate::module::mihomo::Rate;
use crate::{
cmd,
config::Config,
feat,
module::{lightweight::entry_lightweight_mode, mihomo::Rate},
resolve,
utils::{dirs::find_target_icons, i18n::t, logging::Type, resolve::VERSION},
feat, resolve,
utils::resolve::VERSION,
utils::{dirs, i18n::t},
};
use anyhow::Result;
@ -20,136 +19,29 @@ 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::menu::{CheckMenuItem, IsMenuItem};
use tauri::AppHandle;
use tauri::{
menu::{CheckMenuItem, IsMenuItem, MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent},
App, AppHandle, Wry,
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
tray::{MouseButton, MouseButtonState, TrayIconEvent, TrayIconId},
Wry,
};
#[cfg(target_os = "macos")]
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();
@ -159,7 +51,6 @@ 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"))]
@ -175,27 +66,19 @@ impl Tray {
Ok(())
}
pub fn create_systray(&self, app: &App) -> Result<()> {
let mut builder = TrayIconBuilder::with_id("main")
.icon(app.default_window_icon().unwrap().clone())
.icon_as_template(false);
pub fn create_systray(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray_incon_id = TrayIconId::new("main");
let tray = app_handle.tray_by_id(&tray_incon_id).unwrap();
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
if tray_event.as_str() != "tray_menu" {
builder = builder.show_menu_on_left_click(false);
}
}
let tray = builder.build(app)?;
#[cfg(target_os = "macos")]
tray.set_show_menu_on_left_click(false)?;
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);
#[cfg(target_os = "macos")]
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
@ -205,7 +88,22 @@ 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(true),
"main_window" => resolve::create_window(),
_ => {}
}
}
#[cfg(not(target_os = "macos"))]
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
..
} = event
{
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(),
_ => {}
}
}
@ -214,19 +112,6 @@ impl Tray {
Ok(())
}
/// 更新托盘点击行为
pub fn update_click_behavior(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
let tray = app_handle.tray_by_id("main").unwrap();
match tray_event.as_str() {
"tray_menu" => tray.set_show_menu_on_left_click(true)?,
_ => tray.set_show_menu_on_left_click(false)?,
}
Ok(())
}
/// 更新托盘菜单
pub fn update_menu(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
@ -245,7 +130,7 @@ impl Tray {
let profile_uid_and_name = Config::profiles()
.data()
.all_profile_uid_and_name()
.unwrap_or_default();
.unwrap_or(Vec::new());
let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_menu(Some(create_tray_menu(
@ -259,67 +144,114 @@ 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_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false);
let app_handle = handle::Handle::global().app_handle().unwrap();
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 tray = app_handle.tray_by_id("main").unwrap();
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 = 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 tray_icon = verge.tray_icon.clone().unwrap_or("monochrome".to_string());
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(());
}
let rate = if let Some(rate) = rate {
Some(rate)
} else {
let guard = self.speed_rate.lock();
if let Some(rate) = guard.as_ref().unwrap().get_curent_rate() {
Some(rate)
} else {
Some(Rate::default())
}
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(),
};
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);
#[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();
}
}
Ok(())
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
};
#[cfg(target_os = "macos")]
{
let enable_tray_speed = Config::verge().latest().enable_tray_speed.unwrap_or(true);
let is_colorful = tray_icon == "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())
});
// 使用新的方法渲染图标和速率
SpeedRate::add_speed_text(icon_bytes, rate)?
} else {
icon_bytes
};
// 设置系统托盘图标
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&final_icon_bytes)?));
// 只对单色图标使用 template 模式
let _ = tray.set_icon_as_template(!is_colorful);
}
#[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(())
}
/// 更新托盘提示
@ -463,24 +395,21 @@ fn create_tray_menu(
.collect::<std::collections::HashMap<String, String>>()
})
.unwrap_or_default();
let profile_menu_items: Vec<CheckMenuItem<Wry>> = profile_uid_and_name
.iter()
.map(|(profile_uid, profile_name)| {
let is_current_profile = Config::profiles()
.data()
.is_current_profile_index(profile_uid.to_string());
CheckMenuItem::with_id(
app_handle,
format!("profiles_{}", profile_uid),
t(profile_name),
true,
is_current_profile,
None::<&str>,
)
.unwrap()
})
.collect();
let is_current_profile = Config::profiles().data().is_current_profile_index(profile_uid.to_string());
CheckMenuItem::with_id(
app_handle,
&format!("profiles_{}", profile_uid),
t(&profile_name),
true,
is_current_profile,
None::<&str>,
)
.unwrap()
}).collect();
let profile_menu_items: Vec<&dyn IsMenuItem<Wry>> = profile_menu_items
.iter()
.map(|item| item as &dyn IsMenuItem<Wry>)
@ -526,13 +455,12 @@ fn create_tray_menu(
.unwrap();
let profiles = &Submenu::with_id_and_items(
app_handle,
"profiles",
app_handle,
"profiles",
t("Profiles"),
true,
true,
&profile_menu_items,
)
.unwrap();
).unwrap();
let system_proxy = &CheckMenuItem::with_id(
app_handle,
@ -554,15 +482,6 @@ 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();
@ -655,8 +574,6 @@ fn create_tray_menu(
separator,
system_proxy,
tun_mode,
separator,
lighteweight_mode,
copy_env,
open_dir,
more,
@ -675,19 +592,19 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) {
println!("change mode to: {}", mode);
feat::change_clash_mode(mode.into());
}
"open_window" => resolve::create_window(true),
"open_window" => resolve::create_window(),
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"copy_env" => feat::copy_clash_env(),
"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()),
"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()),
"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_") => {
let profile_index = &id["profiles_".len()..];
feat::toggle_proxy_profile(profile_index.into());

View File

@ -1,20 +1,22 @@
use crate::{
module::mihomo::{MihomoManager, Rate},
utils::help::format_bytes_speed,
};
use crate::module::mihomo::Rate;
use crate::module::mihomo::MihomoManager;
use crate::utils::help::format_bytes_speed;
use ab_glyph::FontArc;
use anyhow::Result;
use futures::Stream;
use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex;
use std::{io::Cursor, sync::Arc};
use tokio_tungstenite::tungstenite::{http, Message};
use std::io::Cursor;
use std::sync::Arc;
use tokio_tungstenite::tungstenite::http;
use tokio_tungstenite::tungstenite::Message;
use tungstenite::client::IntoClientRequest;
#[derive(Debug, Clone)]
pub struct SpeedRate {
rate: Arc<Mutex<(Rate, Rate)>>,
last_update: Arc<Mutex<std::time::Instant>>,
// 移除 base_image不再缓存原始图像
}
impl SpeedRate {
@ -37,20 +39,12 @@ impl SpeedRate {
let (current, previous) = &mut *rates;
// Avoid unnecessary float conversions for small value checks
let should_update = if current.up < 1000 && down < 1000 {
// For small values, always update to ensure accuracy
current.up != up || current.down != down
} else {
// For larger values, use integer math to check for >5% change
// Multiply by 20 instead of dividing by 0.05 to avoid floating point
let up_threshold = current.up / 20;
let down_threshold = current.down / 20;
(up > current.up && up - current.up > up_threshold)
|| (up < current.up && current.up - up > up_threshold)
|| (down > current.down && down - current.down > down_threshold)
|| (down < current.down && current.down - down > down_threshold)
// 如果速率变化不大小于10%),则不更新
let should_update = {
let up_change = (current.up as f64 - up as f64).abs() / (current.up as f64 + 1.0);
let down_change =
(current.down as f64 - down as f64).abs() / (current.down as f64 + 1.0);
up_change > 0.1 || down_change > 0.1
};
if !should_update {
@ -76,71 +70,44 @@ impl SpeedRate {
}
// 分离图标加载和速率渲染
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 });
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 });
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 icon_image = image::load_from_memory(&icon_bytes)?;
let (icon_width, icon_height) = (icon_image.width(), icon_image.height());
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 is_colorful =
!crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
// println!(
// "icon_height: {}, icon_wight: {}, total_width: {}",
// icon_height, icon_width, total_width
// );
// 增加文本宽度和间距
let text_width = 580; // 文本区域宽度
let total_width = icon_width + text_width;
// 创建新的透明画布
let mut combined_image = RgbaImage::new(total_width, icon_height);
// 将原始图标绘制到新画布的左侧
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);
}
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([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, 128u8]),
Rgba([0u8, 0u8, 0u8, 160u8]),
)
} else {
// 单色图标使用白色文本和轻微黑色阴影
(
Rgba([255u8, 255u8, 255u8, 255u8]),
Rgba([0u8, 0u8, 0u8, 120u8]),
)
};
// 减小字体大小以适应文本区域
@ -150,30 +117,17 @@ impl SpeedRate {
let scale = ab_glyph::PxScale::from(font_size);
// 使用更简洁的速率格式
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 = format_bytes_speed(rate.up);
let down_text = format_bytes_speed(rate.down);
// 计算文本位置,确保垂直间距合适
// 修改文本位置为居右显示
// 计算右对齐的文本位置
// 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, 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 up_text_x = total_width - up_text_width;
let down_text_x = total_width - down_text_width;
// 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小
let text_height = font_size as i32;

View File

@ -0,0 +1,14 @@
[package]
name = "mihomo_api"
edition = "2024"
[features]
debug = []
[dependencies]
reqwest = { version = "0.12.12", features = ["json"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.140"
[dev-dependencies]
tokio = { version = "1.43.0", features = ["rt", "macros"] }

View File

@ -1,4 +1,4 @@
use reqwest::{Method, header::HeaderMap};
use reqwest::header::HeaderMap;
use serde_json::json;
use std::{
sync::{Arc, Mutex},
@ -15,7 +15,7 @@ impl MihomoManager {
proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null,
})),
headers,
headers: headers,
}
}
@ -45,77 +45,68 @@ impl MihomoManager {
async fn send_request(
&self,
method: Method,
method: &str,
url: String,
data: Option<serde_json::Value>,
) -> Result<serde_json::Value, String> {
let client_response = reqwest::ClientBuilder::new()
.default_headers(self.headers.clone())
.no_proxy()
.timeout(Duration::from_secs(60))
.timeout(Duration::from_secs(2))
.build()
.map_err(|e| e.to_string())?
.request(method.clone(), &url)
.request(
match method {
"GET" => reqwest::Method::GET,
"PUT" => reqwest::Method::PUT,
"POST" => reqwest::Method::POST,
"PATCH" => reqwest::Method::PATCH,
_ => reqwest::Method::GET,
},
&url,
)
.json(&data.unwrap_or(json!({})))
.send()
.await
.map_err(|e| e.to_string())?;
let response = match method {
Method::PATCH => {
let status = client_response.status();
if status.as_u16() == 204 {
json!({"code": 204})
} else {
client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?
}
}
Method::PUT => json!(client_response.text().await.map_err(|e| e.to_string())?),
_ => client_response
.json::<serde_json::Value>()
.await
.map_err(|e| e.to_string())?,
};
Ok(response)
let response = if method != "PUT" {
client_response.json::<serde_json::Value>().await
} else {
client_response.text().await.map(|text| json!(text))
}
.map_err(|e| e.to_string())?;
return Ok(response);
}
pub async fn refresh_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/proxies", self.mihomo_server);
let proxies = self.send_request(Method::GET, url, None).await?;
let proxies = self.send_request("GET", url, None).await?;
self.update_proxies(proxies);
Ok(self)
}
pub async fn refresh_providers_proxies(&self) -> Result<&Self, String> {
let url = format!("{}/providers/proxies", self.mihomo_server);
let providers_proxies = self.send_request(Method::GET, url, None).await?;
let providers_proxies = self.send_request("GET", url, None).await?;
self.update_providers_proxies(providers_proxies);
Ok(self)
}
}
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!({
"path": clash_config_path,
});
let _response = self.send_request(Method::PUT, url, Some(payload)).await?;
let _response = self.send_request("PUT", url, Some(payload)).await?;
Ok(())
}
pub async fn patch_configs(&self, config: serde_json::Value) -> Result<(), String> {
let url = format!("{}/configs", self.mihomo_server);
let response = self.send_request(Method::PATCH, url, Some(config)).await?;
let response = self.send_request("PATCH", url, Some(config)).await?;
if response["code"] == 204 {
Ok(())
} else {
@ -137,26 +128,7 @@ impl MihomoManager {
"{}/proxies/{}/delay?url={}&timeout={}",
self.mihomo_server, name, test_url, timeout
);
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())
}
let response = self.send_request("GET", url, None).await?;
return Ok(response);
}
}

View File

@ -5,10 +5,17 @@ mod script;
pub mod seq;
mod tun;
use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
use crate::{config::Config, utils::tmpl};
use self::chain::*;
use self::field::*;
use self::merge::*;
use self::script::*;
use self::seq::*;
use self::tun::*;
use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>;
@ -260,11 +267,11 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if enable_dns_settings {
use crate::utils::dirs;
use std::fs;
// 尝试读取dns_config.yaml
if let Ok(app_dir) = dirs::app_home_dir() {
let dns_path = app_dir.join("dns_config.yaml");
if dns_path.exists() {
if let Ok(dns_yaml) = fs::read_to_string(&dns_path) {
if let Ok(dns_config) = serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {

View File

@ -108,7 +108,7 @@ proxy-groups:
- "proxy1"
"#;
let mut config: Mapping = serde_yaml::from_str(config_str).unwrap();
let seq = SeqMap {
prepend: Sequence::new(),
append: Sequence::new(),
@ -121,32 +121,16 @@ proxy-groups:
let proxies = config.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(proxies.len(), 1);
assert_eq!(
proxies[0]
.as_mapping()
.unwrap()
.get("name")
.unwrap()
.as_str()
.unwrap(),
proxies[0].as_mapping().unwrap().get("name").unwrap().as_str().unwrap(),
"proxy2"
);
// Check if proxy1 is removed from all groups
let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap();
let group1_proxies = groups[0]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group2_proxies = groups[1]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group1_proxies = groups[0].as_mapping().unwrap()
.get("proxies").unwrap().as_sequence().unwrap();
let group2_proxies = groups[1].as_mapping().unwrap()
.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(group1_proxies.len(), 1);
assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2");

View File

@ -24,7 +24,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new())
});
if enable {
// 读取DNS配置
let dns_key = Value::from("dns");
@ -40,20 +40,20 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 检查现有的 enhanced-mode 设置
let current_mode = dns_val
.get(Value::from("enhanced-mode"))
.get(&Value::from("enhanced-mode"))
.and_then(|v| v.as_str())
.unwrap_or("fake-ip");
// 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置
if current_mode == "fake-ip" || !dns_val.contains_key(Value::from("enhanced-mode")) {
if current_mode == "fake-ip" || !dns_val.contains_key(&Value::from("enhanced-mode")) {
revise!(dns_val, "enable", true);
revise!(dns_val, "ipv6", ipv6_val);
if !dns_val.contains_key(Value::from("enhanced-mode")) {
if !dns_val.contains_key(&Value::from("enhanced-mode")) {
revise!(dns_val, "enhanced-mode", "fake-ip");
}
if !dns_val.contains_key(Value::from("fake-ip-range")) {
if !dns_val.contains_key(&Value::from("fake-ip-range")) {
revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
}
@ -63,7 +63,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
}
}
// 当TUN启用时将修改后的DNS配置写回
revise!(config, "dns", dns_val);
} else {
@ -75,6 +75,6 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 更新TUN配置
revise!(tun_val, "enable", enable);
revise!(config, "tun", tun_val);
config
}

View File

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

View File

@ -1 +0,0 @@

View File

@ -1,9 +1,7 @@
use crate::{
config::{Config, IVerge},
core::backup,
logging_error,
utils::{dirs::app_home_dir, logging::Type},
};
use crate::config::{Config, IVerge};
use crate::core::backup;
use crate::log_err;
use crate::utils::dirs::app_home_dir;
use anyhow::Result;
use reqwest_dav::list_cmd::ListFile;
use std::fs;
@ -69,9 +67,8 @@ 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()?)?;
logging_error!(
Type::Backup,
true,
log_err!(
super::patch_verge(
IVerge {
webdav_url,

View File

@ -1,10 +1,8 @@
use crate::{
config::Config,
core::{handle, tray, CoreManager},
logging_error,
module::mihomo::MihomoManager,
utils::{logging::Type, resolve},
};
use crate::config::Config;
use crate::core::{handle, tray, CoreManager};
use crate::log_err;
use crate::module::mihomo::MihomoManager;
use crate::utils::resolve;
use serde_yaml::{Mapping, Value};
use tauri::Manager;
@ -28,31 +26,12 @@ pub fn restart_clash_core() {
pub fn restart_app() {
tauri::async_runtime::spawn_blocking(|| {
tauri::async_runtime::block_on(async {
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());
log_err!(CoreManager::global().stop_core().await);
});
});
}
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);
}
}
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());
});
}
@ -66,6 +45,7 @@ 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(_) => {
// 更新订阅
@ -73,19 +53,11 @@ pub fn change_clash_mode(mode: String) {
if Config::clash().data().save_config().is_ok() {
handle::Handle::refresh_clash();
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();
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
}
}
Err(err) => println!("{err}"),
Err(err) => log::error!(target: "app", "{err}"),
}
});
}

View File

@ -1,12 +1,10 @@
use crate::{
config::{Config, IVerge},
core::{handle, hotkey, sysopt, tray, CoreManager},
logging_error,
module::lightweight,
utils::logging::Type,
};
use crate::config::{Config, IVerge};
use crate::core::{handle, hotkey, sysopt, tray, CoreManager};
use crate::log_err;
use crate::utils::resolve;
use anyhow::Result;
use serde_yaml::Mapping;
use tauri::Manager;
/// Patch Clash configuration
pub async fn patch_clash(patch: Mapping) -> Result<()> {
@ -19,8 +17,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
CoreManager::global().restart_core().await?;
} else {
if patch.get("mode").is_some() {
logging_error!(Type::Tray, true, tray::Tray::global().update_menu());
logging_error!(Type::Tray, true, tray::Tray::global().update_icon(None));
log_err!(tray::Tray::global().update_menu());
log_err!(tray::Tray::global().update_icon(None));
}
Config::runtime().latest().patch_config(patch);
CoreManager::global().update_config().await?;
@ -41,23 +39,6 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
}
}
// Define update flags as bitflags for better performance
#[derive(Clone, Copy)]
enum UpdateFlags {
None = 0,
RestartCore = 1 << 0,
ClashConfig = 1 << 1,
VergeConfig = 1 << 2,
Launch = 1 << 3,
SysProxy = 1 << 4,
SystrayIcon = 1 << 5,
Hotkey = 1 << 6,
SystrayMenu = 1 << 7,
SystrayTooltip = 1 << 8,
SystrayClickBehavior = 1 << 9,
LighteWeight = 1 << 10,
}
/// Patch Verge configuration
pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
Config::verge().draft().patch_config(patch.clone());
@ -70,6 +51,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let proxy_bypass = patch.system_proxy_bypass;
let language = patch.language;
let mixed_port = patch.verge_mixed_port;
let lite_mode = patch.enable_lite_mode;
#[cfg(target_os = "macos")]
let tray_icon = patch.tray_icon;
#[cfg(not(target_os = "macos"))]
@ -90,31 +72,35 @@ 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();
let enable_auto_light_weight = patch.enable_auto_light_weight_mode;
let res: std::result::Result<(), anyhow::Error> = {
// Initialize with no flags set
let mut update_flags: i32 = UpdateFlags::None as i32;
let mut should_restart_core = false;
let mut should_update_clash_config = false;
let mut should_update_verge_config = false;
let mut should_update_launch = false;
let mut should_update_sysproxy = false;
let mut should_update_systray_icon = false;
let mut should_update_hotkey = false;
let mut should_update_systray_menu = false;
let mut should_update_systray_tooltip = false;
if tun_mode.is_some() {
update_flags |= UpdateFlags::ClashConfig as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
should_update_clash_config = true;
should_update_systray_menu = true;
should_update_systray_tooltip = true;
should_update_systray_icon = true;
}
if enable_global_hotkey.is_some() || home_cards.is_some() {
update_flags |= UpdateFlags::VergeConfig as i32;
if enable_global_hotkey.is_some() {
should_update_verge_config = true;
}
#[cfg(not(target_os = "windows"))]
if redir_enabled.is_some() || redir_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
#[cfg(target_os = "linux")]
if tproxy_enabled.is_some() || tproxy_port.is_some() {
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
if socks_enabled.is_some()
|| http_enabled.is_some()
@ -122,88 +108,91 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
|| http_port.is_some()
|| mixed_port.is_some()
{
update_flags |= UpdateFlags::RestartCore as i32;
should_restart_core = true;
}
if auto_launch.is_some() {
update_flags |= UpdateFlags::Launch as i32;
should_update_launch = true;
}
if system_proxy.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
update_flags |= UpdateFlags::SystrayTooltip as i32;
update_flags |= UpdateFlags::SystrayIcon as i32;
should_update_sysproxy = true;
should_update_systray_menu = true;
should_update_systray_tooltip = true;
should_update_systray_icon = true;
}
if proxy_bypass.is_some() || pac_content.is_some() || pac.is_some() {
update_flags |= UpdateFlags::SysProxy as i32;
should_update_sysproxy = true;
}
if language.is_some() {
update_flags |= UpdateFlags::SystrayMenu as i32;
should_update_systray_menu = true;
}
if common_tray_icon.is_some()
|| sysproxy_tray_icon.is_some()
|| 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;
should_update_systray_icon = true;
}
if patch.hotkeys.is_some() {
update_flags |= UpdateFlags::Hotkey as i32;
update_flags |= UpdateFlags::SystrayMenu as i32;
should_update_hotkey = true;
should_update_systray_menu = true;
}
if tray_event.is_some() {
update_flags |= UpdateFlags::SystrayClickBehavior as i32;
if enable_tray_speed.is_some() {
should_update_systray_icon = true;
}
if enable_auto_light_weight.is_some() {
update_flags |= UpdateFlags::LighteWeight as i32;
}
// Process updates based on flags
if (update_flags & (UpdateFlags::RestartCore as i32)) != 0 {
Config::generate().await?;
if should_restart_core {
CoreManager::global().restart_core().await?;
}
if (update_flags & (UpdateFlags::ClashConfig as i32)) != 0 {
if should_update_clash_config {
CoreManager::global().update_config().await?;
handle::Handle::refresh_clash();
}
if (update_flags & (UpdateFlags::VergeConfig as i32)) != 0 {
if should_update_verge_config {
Config::verge().draft().enable_global_hotkey = enable_global_hotkey;
handle::Handle::refresh_verge();
}
if (update_flags & (UpdateFlags::Launch as i32)) != 0 {
if should_update_launch {
sysopt::Sysopt::global().update_launch()?;
}
if (update_flags & (UpdateFlags::SysProxy as i32)) != 0 {
if should_update_sysproxy {
sysopt::Sysopt::global().update_sysproxy().await?;
}
if (update_flags & (UpdateFlags::Hotkey as i32)) != 0 {
if should_update_hotkey {
hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?;
}
if (update_flags & (UpdateFlags::SystrayMenu as i32)) != 0 {
if should_update_systray_menu {
tray::Tray::global().update_menu()?;
}
if (update_flags & (UpdateFlags::SystrayIcon as i32)) != 0 {
if should_update_systray_icon {
tray::Tray::global().update_icon(None)?;
}
if (update_flags & (UpdateFlags::SystrayTooltip as i32)) != 0 {
if should_update_systray_tooltip {
tray::Tray::global().update_tooltip()?;
}
if (update_flags & (UpdateFlags::SystrayClickBehavior as i32)) != 0 {
tray::Tray::global().update_click_behavior()?;
}
if (update_flags & (UpdateFlags::LighteWeight as i32)) != 0 {
if enable_auto_light_weight.unwrap() {
lightweight::enable_auto_light_weight_mode();
} else {
lightweight::disable_auto_light_weight_mode();
// 处理轻量模式切换
if lite_mode.is_some() {
if let Some(window) = handle::Handle::global().get_window() {
if lite_mode.unwrap() {
// 完全退出 webview 进程
window.close()?; // 先关闭窗口
let app_handle = handle::Handle::global().app_handle().unwrap();
if let Some(webview) = app_handle.get_webview_window("main") {
webview.destroy()?; // 销毁 webview 进程
}
} else {
resolve::create_window(); // 重新创建窗口
}
}
}

View File

@ -1,8 +1,8 @@
use crate::{
cmd,
config::{Config, PrfItem, PrfOption},
core::{handle, CoreManager, *},
};
use crate::cmd;
use crate::config::{Config, PrfItem, PrfOption};
use crate::core::handle;
use crate::core::CoreManager;
use crate::core::*;
use anyhow::{bail, Result};
/// Toggle proxy profile
@ -29,7 +29,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
let profiles = Config::profiles();
let profiles = profiles.latest();
let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote");
if !is_remote {
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);
@ -82,11 +82,3 @@ 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

@ -1,7 +1,6 @@
use crate::{
config::{Config, IVerge},
core::handle,
};
use crate::config::Config;
use crate::config::IVerge;
use crate::core::handle;
use std::env;
use tauri_plugin_clipboard_manager::ClipboardExt;
@ -28,19 +27,6 @@ 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);
@ -86,16 +72,10 @@ pub fn copy_clash_env() {
};
let export_text = match env_type.as_str() {
"bash" => format!(
"export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"
),
"bash" => format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"),
"cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"),
"powershell" => {
format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"")
}
"nushell" => {
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}")
}
"powershell" => format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\""),
"nushell" => format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}"),
"fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"),
_ => {
log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}");
@ -103,7 +83,7 @@ pub fn copy_clash_env() {
}
};
if cliboard.write_text(export_text).is_err() {
if let Err(_) = cliboard.write_text(export_text) {
log::error!(target: "app", "Failed to write to clipboard");
}
}

View File

@ -1,11 +1,11 @@
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::Config,
core::{handle, sysopt, CoreManager},
module::mihomo::MihomoManager,
utils::resolve,
};
use crate::config::Config;
use crate::core::handle;
use crate::core::{sysopt, CoreManager};
use crate::module::mihomo::MihomoManager;
use crate::utils::resolve;
use futures;
use tauri::Manager;
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
/// Open or close the dashboard window
#[allow(dead_code)]
@ -45,10 +45,40 @@ 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(true);
resolve::create_window();
}
}
/// 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", "启动退出流程");
@ -107,13 +137,3 @@ pub fn quit(code: Option<i32>) {
app_handle.exit(code.unwrap_or(0));
});
}
#[cfg(target_os = "macos")]
pub fn hide() {
if let Some(window) = handle::Handle::global().get_window() {
if window.is_visible().unwrap_or(false) {
AppHandleManager::global().set_activation_policy_accessory();
let _ = window.hide();
}
}
}

View File

@ -2,22 +2,18 @@ mod cmd;
mod config;
mod core;
mod enhance;
mod error;
mod feat;
mod module;
mod utils;
use crate::{
core::hotkey,
utils::{resolve, resolve::resolve_scheme, server},
};
mod module;
use crate::core::hotkey;
use crate::utils::{resolve, resolve::resolve_scheme, server};
use config::Config;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use std::sync::{Mutex, Once};
use tauri::AppHandle;
#[cfg(target_os = "macos")]
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 {
@ -61,7 +57,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular);
}
}
pub fn set_activation_policy_accessory(&self) {
#[cfg(target_os = "macos")]
{
@ -70,7 +66,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
}
}
pub fn set_activation_policy_prohibited(&self) {
#[cfg(target_os = "macos")]
{
@ -81,7 +77,6 @@ impl AppHandleManager {
}
}
#[allow(clippy::panic)]
pub fn run() {
// 单例检测
let app_exists: bool = tauri::async_runtime::block_on(async move {
@ -101,6 +96,7 @@ pub fn run() {
#[cfg(debug_assertions)]
let devtools = tauri_plugin_devtools::init();
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(
@ -112,6 +108,7 @@ 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())
@ -121,13 +118,13 @@ pub fn run() {
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
logging_error!(Type::System, true, app.deep_link().register_all());
log_err!(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() {
logging_error!(Type::Setup, true, resolve_scheme(url.to_string()).await);
log_err!(resolve_scheme(url.to_string()).await);
}
});
});
@ -152,14 +149,7 @@ pub fn run() {
cmd::restart_app,
// 添加新的命令
cmd::get_running_mode,
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,
@ -213,13 +203,6 @@ pub fn run() {
cmd::restore_webdav_backup,
// export diagnostic info for issue reporting
cmd::export_diagnostic_info,
// get system info for display
cmd::get_system_info,
// media unlock checker
cmd::get_unlock_items,
cmd::check_media_unlock,
// light-weight model
cmd::entry_lightweight_mode,
]);
#[cfg(debug_assertions)]
@ -227,12 +210,6 @@ pub fn run() {
builder = builder.plugin(devtools);
}
// Macos Application Menu
#[cfg(target_os = "macos")]
{
// Temporary Achived due to cannot CMD+C/V/A
}
let app = builder
.build(tauri::generate_context!())
.expect("error while running tauri application");
@ -242,19 +219,12 @@ pub fn run() {
AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")]
{
if let Some(window) = AppHandleManager::global()
.get_handle()
.get_webview_window("main")
{
let _ = window.set_title("Clash Verge");
}
let main_window = AppHandleManager::global().get_handle().get_webview_window("main").unwrap();
let _ = main_window.set_title("Clash Verge");
}
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
tauri::RunEvent::Reopen { has_visible_windows, .. } => {
if !has_visible_windows {
AppHandleManager::global().set_activation_policy_regular();
}
@ -282,90 +252,45 @@ pub fn run() {
tauri::WindowEvent::Focused(true) => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+Q", "quit")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("CMD+W", "hide")
);
log_err!(hotkey::Hotkey::global().register("CMD+Q", "quit"));
}
#[cfg(not(target_os = "macos"))]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().register("Control+Q", "quit")
);
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
};
{
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
if !is_enable_global_hotkey {
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().init())
log_err!(hotkey::Hotkey::global().init())
}
}
}
tauri::WindowEvent::Focused(false) => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
}
#[cfg(not(target_os = "macos"))]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
};
{
let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
{
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true);
if !is_enable_global_hotkey {
logging_error!(Type::Hotkey, true, hotkey::Hotkey::global().reset())
log_err!(hotkey::Hotkey::global().reset())
}
}
}
tauri::WindowEvent::Destroyed => {
#[cfg(target_os = "macos")]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+Q")
);
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("CMD+W")
);
log_err!(hotkey::Hotkey::global().unregister("CMD+Q"));
}
#[cfg(not(target_os = "macos"))]
{
logging_error!(
Type::Hotkey,
true,
hotkey::Hotkey::global().unregister("Control+Q")
);
log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
};
}
_ => {}

View File

@ -1,142 +0,0 @@
use anyhow::{Context, Result};
use delay_timer::prelude::TaskBuilder;
use tauri::{Listener, Manager};
use crate::{
config::Config,
core::{handle, timer::Timer},
log_err, logging,
utils::logging::Type,
AppHandleManager,
};
const LIGHT_WEIGHT_TASK_UID: &str = "light_weight_task";
pub fn enable_auto_light_weight_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() {
logging!(info, Type::Lightweight, true, "关闭自动轻量模式");
let _ = cancel_light_weight_timer();
cancel_window_close_listener();
}
pub fn entry_lightweight_mode() {
if let Some(window) = handle::Handle::global().get_window() {
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();
logging!(
info,
Type::Lightweight,
true,
"监听到关闭请求,开始轻量模式计时"
);
});
return handler;
}
0
}
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());
logging!(
info,
Type::Lightweight,
true,
"监听到窗口获得焦点,取消轻量模式计时"
);
});
return handler;
}
0
}
fn cancel_window_close_listener() {
if let Some(window) = handle::Handle::global().get_window() {
window.unlisten(setup_window_close_listener());
logging!(info, Type::Lightweight, true, "取消了窗口关闭监听");
}
}
fn setup_light_weight_timer() -> Result<()> {
Timer::global().init()?;
let mut timer_map = Timer::global().timer_map.write();
let delay_timer = Timer::global().delay_timer.write();
let mut timer_count = Timer::global().timer_count.lock();
let task_id = *timer_count;
*timer_count += 1;
let once_by_minutes = Config::verge()
.latest()
.auto_light_weight_minutes
.unwrap_or(10);
let task = TaskBuilder::default()
.set_task_id(task_id)
.set_maximum_parallel_runnable_num(1)
.set_frequency_once_by_minutes(once_by_minutes)
.spawn_async_routine(move || async move {
logging!(info, Type::Timer, true, "计时器到期,开始进入轻量模式");
entry_lightweight_mode();
})
.context("failed to create timer task")?;
delay_timer
.add_task(task)
.context("failed to add timer task")?;
let timer_task = crate::core::timer::TimerTask {
task_id,
interval_minutes: once_by_minutes,
last_run: chrono::Local::now().timestamp(),
};
timer_map.insert(LIGHT_WEIGHT_TASK_UID.to_string(), timer_task);
logging!(
info,
Type::Timer,
true,
"计时器已设置,{} 分钟后将自动进入轻量模式",
once_by_minutes
);
Ok(())
}
fn cancel_light_weight_timer() -> Result<()> {
let mut timer_map = Timer::global().timer_map.write();
let delay_timer = Timer::global().delay_timer.write();
if let Some(task) = timer_map.remove(LIGHT_WEIGHT_TASK_UID) {
delay_timer
.remove_task(task.task_id)
.context("failed to remove timer task")?;
logging!(info, Type::Timer, true, "计时器已取消");
}
Ok(())
}

View File

@ -65,6 +65,6 @@ impl MihomoManager {
.unwrap()
.to_string();
let token = http::header::HeaderValue::from_str(&auth).unwrap();
(ws_url, token)
return (ws_url, token);
}
}

View File

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

View File

@ -1,7 +1,4 @@
use crate::{
cmd::system,
core::{handle, CoreManager},
};
use crate::core::{handle, CoreManager};
use std::fmt::{self, Debug, Formatter};
use sysinfo::System;
@ -12,15 +9,14 @@ 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: {}\nIs Admin: {}",
self.system_name, self.system_version, self.system_kernel_version, self.system_arch, self.verge_version, self.running_mode, self.is_admin
"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
)
}
}
@ -30,16 +26,23 @@ impl PlatformSpecification {
let system_name = System::name().unwrap_or("Null".into());
let system_version = System::long_os_version().unwrap_or("Null".into());
let system_kernel_version = System::kernel_version().unwrap_or("Null".into());
let system_arch = System::cpu_arch();
let system_arch = std::env::consts::ARCH.to_string();
let handler = handle::Handle::global().app_handle().unwrap();
let config = handler.config();
let verge_version = config.version.clone().unwrap_or("Null".into());
// 使用默认值避免在同步上下文中执行异步操作
let running_mode = "NotRunning".to_string();
// 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 is_admin = system::is_admin().unwrap_or_default();
Self {
system_name,
@ -48,17 +51,6 @@ 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

@ -1,7 +1,8 @@
use crate::core::handle;
use anyhow::Result;
use once_cell::sync::OnceCell;
use std::{fs, path::PathBuf};
use std::fs;
use std::path::PathBuf;
use tauri::Manager;
#[cfg(not(feature = "verge-dev"))]
@ -77,36 +78,6 @@ 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, logging, utils::logging::Type};
use crate::enhance::seq::SeqMap;
use anyhow::{anyhow, bail, Context, Result};
use nanoid::nanoid;
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::Mapping;
use serde_yaml::{Mapping, Value};
use std::{fs, path::PathBuf, str::FromStr};
/// read data from yaml as struct T
@ -22,41 +22,19 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
})
}
/// read mapping from yaml
/// read mapping from yaml fix #165
pub fn read_mapping(path: &PathBuf) -> Result<Mapping> {
if !path.exists() {
bail!("file not found \"{}\"", path.display());
}
let mut val: Value = read_yaml(path)?;
val.apply_merge()
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
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)
}
}
Ok(val
.as_mapping()
.ok_or(anyhow!(
"failed to transform to yaml mapping \"{}\"",
path.display()
))?
.to_owned())
}
/// read mapping from yaml fix #165
@ -158,6 +136,52 @@ 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 {
@ -183,22 +207,21 @@ macro_rules! t {
/// # Examples
/// ```not_run
/// format_bytes_speed(1000) // returns "1000B/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s"
/// ```
/// ```
#[cfg(target_os = "macos")]
pub fn format_bytes_speed(speed: u64) -> String {
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;
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)
}
format!("{:.1}{}/s", size, UNITS[unit_index])
}
#[cfg(target_os = "macos")]

View File

@ -1,4 +1,5 @@
use crate::{config::Config, utils::dirs};
use crate::config::Config;
use crate::utils::dirs;
use once_cell::sync::Lazy;
use serde_json::Value;
use std::{collections::HashMap, fs, path::PathBuf};

View File

@ -1,21 +1,16 @@
use crate::{
config::*,
core::handle,
utils::{dirs, help},
};
use crate::config::*;
use crate::core::handle;
use crate::utils::{dirs, help};
use anyhow::Result;
use chrono::{Local, TimeZone};
use log::LevelFilter;
use log4rs::{
append::{console::ConsoleAppender, file::FileAppender},
config::{Appender, Logger, Root},
encode::pattern::PatternEncoder,
};
use std::{
fs::{self, DirEntry},
path::PathBuf,
str::FromStr,
};
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;
use std::fs::{self, DirEntry};
use std::path::PathBuf;
use std::str::FromStr;
use tauri_plugin_shell::ShellExt;
/// initialize this instance's log file
@ -147,101 +142,69 @@ fn init_dns_config() -> Result<()> {
("enable".into(), Value::Bool(true)),
("listen".into(), Value::String(":53".into())),
("enhanced-mode".into(), Value::String("fake-ip".into())),
(
"fake-ip-range".into(),
Value::String("198.18.0.1/16".into()),
),
(
"fake-ip-filter-mode".into(),
Value::String("blacklist".into()),
),
("fake-ip-range".into(), Value::String("198.18.0.1/16".into())),
("fake-ip-filter-mode".into(), Value::String("blacklist".into())),
("prefer-h3".into(), Value::Bool(false)),
("respect-rules".into(), Value::Bool(false)),
("use-hosts".into(), Value::Bool(false)),
("use-system-hosts".into(), Value::Bool(false)),
(
"fake-ip-filter".into(),
Value::Sequence(vec![
Value::String("*.lan".into()),
Value::String("*.local".into()),
Value::String("*.arpa".into()),
Value::String("time.*.com".into()),
Value::String("ntp.*.com".into()),
Value::String("time.*.com".into()),
Value::String("+.market.xiaomi.com".into()),
Value::String("localhost.ptlogin2.qq.com".into()),
Value::String("*.msftncsi.com".into()),
Value::String("www.msftconnecttest.com".into()),
]),
),
(
"default-nameserver".into(),
Value::Sequence(vec![
Value::String("system".into()),
Value::String("223.6.6.6".into()),
Value::String("8.8.8.8".into()),
]),
),
(
"nameserver".into(),
Value::Sequence(vec![
Value::String("8.8.8.8".into()),
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
]),
),
("fallback".into(), Value::Sequence(vec![])),
(
"nameserver-policy".into(),
Value::Mapping(serde_yaml::Mapping::new()),
),
(
"proxy-server-nameserver".into(),
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()),
]),
),
("fake-ip-filter".into(), Value::Sequence(vec![
Value::String("*.lan".into()),
Value::String("*.local".into()),
Value::String("*.arpa".into()),
Value::String("time.*.com".into()),
Value::String("ntp.*.com".into()),
Value::String("time.*.com".into()),
Value::String("+.market.xiaomi.com".into()),
Value::String("localhost.ptlogin2.qq.com".into()),
Value::String("*.msftncsi.com".into()),
Value::String("www.msftconnecttest.com".into()),
])),
("default-nameserver".into(), Value::Sequence(vec![
Value::String("223.6.6.6".into()),
Value::String("8.8.8.8".into()),
])),
("nameserver".into(), Value::Sequence(vec![
Value::String("8.8.8.8".into()),
Value::String("https://doh.pub/dns-query".into()),
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()),
])),
("nameserver-policy".into(), Value::Mapping(serde_yaml::Mapping::new())),
("proxy-server-nameserver".into(), Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
])),
("direct-nameserver".into(), Value::Sequence(vec![])),
("direct-nameserver-follow-policy".into(), Value::Bool(false)),
(
"fallback-filter".into(),
Value::Mapping(serde_yaml::Mapping::from_iter([
("geoip".into(), Value::Bool(true)),
("geoip-code".into(), Value::String("CN".into())),
(
"ipcidr".into(),
Value::Sequence(vec![
Value::String("240.0.0.0/4".into()),
Value::String("0.0.0.0/32".into()),
]),
),
(
"domain".into(),
Value::Sequence(vec![
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
]),
),
("fallback-filter".into(), Value::Mapping(serde_yaml::Mapping::from_iter([
("geoip".into(), Value::Bool(true)),
("geoip-code".into(), Value::String("CN".into())),
("ipcidr".into(), Value::Sequence(vec![
Value::String("240.0.0.0/4".into()),
Value::String("0.0.0.0/32".into()),
])),
),
("domain".into(), Value::Sequence(vec![
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
])),
]))),
]);
// 检查DNS配置文件是否存在
let app_dir = dirs::app_home_dir()?;
let dns_path = app_dir.join("dns_config.yaml");
if !dns_path.exists() {
log::info!(target: "app", "Creating default DNS config file");
help::save_yaml(
&dns_path,
&default_dns_config,
Some("# Clash Verge DNS Config"),
)?;
help::save_yaml(&dns_path, &default_dns_config, Some("# Clash Verge DNS Config"))?;
}
Ok(())
}
@ -295,11 +258,15 @@ 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);
}
@ -311,6 +278,7 @@ 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| {
@ -322,6 +290,9 @@ 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;
@ -352,7 +323,8 @@ pub fn init_resources() -> Result<()> {
#[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe;
use winreg::{enums::*, RegKey};
use winreg::enums::*;
use winreg::RegKey;
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;

View File

@ -1,139 +0,0 @@
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

@ -1,9 +1,8 @@
pub mod dirs;
pub mod error;
pub mod help;
pub mod i18n;
pub mod init;
pub mod logging;
pub mod resolve;
pub mod server;
pub mod tmpl;
pub mod i18n;

View File

@ -1,13 +1,9 @@
use crate::config::IVerge;
use crate::utils::error;
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
use crate::{log_err, wrap_err};
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
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;
use percent_encoding::percent_decode_str;
@ -15,9 +11,10 @@ use serde_yaml::Mapping;
use std::net::TcpListener;
use tauri::{App, Manager};
use tauri::Url;
use url::Url;
//#[cfg(not(target_os = "linux"))]
// use window_shadows::set_shadow;
use tauri_plugin_notification::NotificationExt;
pub static VERSION: OnceCell<String> = OnceCell::new();
@ -51,98 +48,101 @@ pub async fn resolve_setup(app: &mut App) {
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
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!(init::init_config());
log_err!(init::init_resources());
log_err!(init::init_scheme());
log_err!(init::startup_script().await);
// 处理随机端口
logging_error!(Type::System, true, resolve_random_port_config());
log_err!(resolve_random_port_config());
// 启动核心
logging!(trace, Type::Config, true, "Initial config");
logging_error!(Type::Config, true, Config::init_config().await);
log::trace!(target:"app", "init config");
log_err!(Config::init_config().await);
logging!(trace, Type::Core, "Starting CoreManager");
logging_error!(Type::Core, true, CoreManager::global().init().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);
// setup a simple http server for singleton
log::trace!(target: "app", "launch embed server");
server::embed_server();
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::trace!(target: "app", "init system tray");
log_err!(tray::Tray::global().init());
log_err!(tray::Tray::global().create_systray());
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().update_sysproxy().await
);
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().init_guard_sysproxy()
);
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
create_window(!is_silent_start);
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());
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
// 初始化热键
logging!(trace, Type::System, true, "Initial hotkeys");
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
log::trace!(target: "app", "init hotkeys");
log_err!(hotkey::Hotkey::global().init());
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());
}
/// 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();
/// reset system proxy
pub fn resolve_reset() {
tauri::async_runtime::block_on(async move {
#[cfg(target_os = "macos")]
tray::Tray::global().unsubscribe_traffic();
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");
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
log_err!(CoreManager::global().stop_core().await);
#[cfg(target_os = "macos")]
restore_public_dns().await;
}
});
}
/// create main window
pub fn create_window(is_showup: bool) {
logging!(info, Type::Window, true, "Creating window");
pub fn create_window() {
println!("Starting to create window");
log::info!(target: "app", "Starting to create 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() {
logging!(
info,
Type::Window,
true,
"Found existing window, attempting to restore visibility"
);
println!("Found existing window, trying to show it");
log::info!(target: "app", "Found existing window, trying to show it");
if window.is_minimized().unwrap_or(false) {
logging!(
info,
Type::Window,
true,
"Window is minimized, restoring window state"
);
println!("Window is minimized, unminimizing");
log::info!(target: "app", "Window is minimized, unminimizing");
let _ = window.unminimize();
}
let _ = window.show();
@ -150,7 +150,8 @@ pub fn create_window(is_showup: bool) {
return;
}
logging!(info, Type::Window, true, "Creating new application window");
println!("Creating new window");
log::info!(target: "app", "Creating new window");
#[cfg(target_os = "windows")]
let window = tauri::WebviewWindowBuilder::new(
@ -196,46 +197,17 @@ pub fn create_window(is_showup: bool) {
match window {
Ok(window) => {
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();
}
println!("Window created successfully, attempting to show");
log::info!(target: "app", "Window created successfully, attempting to show");
let _ = window.show();
let _ = window.set_focus();
// 设置窗口状态监控,实时保存窗口位置和大小
// 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", ());
}
});
crate::feat::setup_window_state_monitor(&app_handle);
}
Err(e) => {
logging!(
error,
Type::Window,
true,
"Failed to create window: {:?}",
e
);
println!("Failed to create window: {:?}", e);
log::error!(target: "app", "Failed to create window: {:?}", e);
}
}
}
@ -243,6 +215,8 @@ pub fn create_window(is_showup: bool) {
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)
@ -265,32 +239,41 @@ pub async fn resolve_scheme(param: String) -> Result<()> {
.find(|(key, _)| key == "name")
.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
};
let encode_url = link_parsed
.query_pairs()
.find(|(key, _)| key == "url")
.map(|(_, value)| value.into_owned());
match url_param {
match encode_url {
Some(url) => {
log::info!(target:"app", "decoded subscription url: {}", url);
let url = percent_decode_str(url.as_ref())
.decode_utf8_lossy()
.to_string();
create_window(false);
create_window();
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();
}
}
}
@ -332,7 +315,8 @@ fn resolve_random_port_config() -> Result<()> {
#[cfg(target_os = "macos")]
pub async fn set_public_dns(dns_server: String) {
use crate::{core::handle, utils::dirs};
use crate::core::handle;
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
@ -368,7 +352,8 @@ pub async fn set_public_dns(dns_server: String) {
#[cfg(target_os = "macos")]
pub async fn restore_public_dns() {
use crate::{core::handle, utils::dirs};
use crate::core::handle;
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to unset system dns");

View File

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

View File

@ -1,14 +0,0 @@
[package]
name = "mihomo_api"
edition = "2024"
[features]
debug = []
[dependencies]
reqwest = { version = "0.12.15", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
[dev-dependencies]
tokio = { version = "1.44.1", features = ["rt", "macros"] }

View File

@ -1,5 +1,5 @@
{
"version": "2.2.3",
"version": "2.1.3-alpha",
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"bundle": {
"active": true,
@ -11,15 +11,9 @@
"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",
@ -38,11 +32,7 @@
"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"
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
],
"windows": {
"installMode": "basicUi"
@ -50,28 +40,18 @@
},
"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
}
}
}
}

View File

@ -30,5 +30,10 @@
"./sidecar/verge-mihomo",
"./sidecar/verge-mihomo-alpha"
]
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico"
}
}
}

View File

@ -30,5 +30,11 @@
}
}
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon-mono.ico",
"iconAsTemplate": true
}
}
}

View File

@ -21,6 +21,11 @@
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true,
"showMenuOnLeftClick": false
},
"windows": []
}
}

View File

@ -30,5 +30,11 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

View File

@ -30,5 +30,11 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

View File

@ -30,5 +30,11 @@
],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
}
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico",
"iconAsTemplate": true
}
}
}

View File

@ -1,13 +0,0 @@
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;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,10 +0,0 @@
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_3004_316)"/>
<path d="M23 14.6666H22.1667V13C22.1667 10.7 20.3 8.83331 18 8.83331C15.7 8.83331 13.8334 10.7 13.8334 13H15.5C15.5 11.6166 16.6167 10.5 18 10.5C19.3834 10.5 20.5 11.6166 20.5 13V14.6666H13C12.0834 14.6666 11.3334 15.4166 11.3334 16.3333V24.6666C11.3334 25.5833 12.0834 26.3333 13 26.3333H23C23.9167 26.3333 24.6667 25.5833 24.6667 24.6666V16.3333C24.6667 15.4166 23.9167 14.6666 23 14.6666ZM23 24.6666H13V16.3333H23V24.6666ZM18 22.1666C18.9167 22.1666 19.6667 21.4166 19.6667 20.5C19.6667 19.5833 18.9167 18.8333 18 18.8333C17.0834 18.8333 16.3334 19.5833 16.3334 20.5C16.3334 21.4166 17.0834 22.1666 18 22.1666Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_3004_316" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA800"/>
<stop offset="1" stop-color="#FFAC4B"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1020 B

View File

@ -1,21 +0,0 @@
import { Box, BoxProps } from "@mui/material";
import React from "react";
interface CenterProps extends BoxProps {
children: React.ReactNode;
}
export const Center: React.FC<CenterProps> = ({ children, ...props }) => {
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
{...props}
>
{children}
</Box>
);
};

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs";
import { forwardRef, useImperativeHandle, useState } from "react";
import { useLockFn } from "ahooks";
import { Box, Button, Snackbar, useTheme } from "@mui/material";
import { Box, Button, Snackbar } from "@mui/material";
import { deleteConnection } from "@/services/api";
import parseTraffic from "@/utils/parse-traffic";
import { t } from "i18next";
@ -14,7 +14,6 @@ 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) => {
@ -36,8 +35,6 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
maxWidth: "520px",
maxHeight: "480px",
overflowY: "auto",
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
},
}}
message={
@ -57,7 +54,6 @@ 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
@ -103,11 +99,11 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
const onDelete = useLockFn(async () => deleteConnection(data.id));
return (
<Box sx={{ userSelect: "text", color: theme.palette.text.secondary }}>
<Box sx={{ userSelect: "text" }}>
{information.map((each) => (
<div key={each.label}>
<b>{each.label}</b>
<span style={{ wordBreak: "break-all", color: theme.palette.text.primary }}>: {each.value}</span>
<span style={{ wordBreak: "break-all" }}>: {each.value}</span>
</div>
))}

View File

@ -1,89 +0,0 @@
import { useTranslation } from "react-i18next";
import { Typography, Stack, Divider } from "@mui/material";
import { DeveloperBoardOutlined } from "@mui/icons-material";
import { useClash } from "@/hooks/use-clash";
import { EnhancedCard } from "./enhanced-card";
import { useMemo } from "react";
import { useAppData } from "@/providers/app-data-provider";
// 将毫秒转换为时:分:秒格式的函数
const formatUptime = (uptimeMs: number) => {
const hours = Math.floor(uptimeMs / 3600000);
const minutes = Math.floor((uptimeMs % 3600000) / 60000);
const seconds = Math.floor((uptimeMs % 60000) / 1000);
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
};
export const ClashInfoCard = () => {
const { t } = useTranslation();
const { version: clashVersion } = useClash();
const { clashConfig, sysproxy, rules, uptime } = useAppData();
// 使用useMemo缓存格式化后的uptime避免频繁计算
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
// 使用备忘录组件内容,减少重新渲染
const cardContent = useMemo(() => {
if (!clashConfig) return null;
return (
<Stack spacing={1.5}>
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" color="text.secondary">
{t("Core Version")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{clashVersion || "-"}
</Typography>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" color="text.secondary">
{t("System Proxy Address")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{sysproxy?.server || "-"}
</Typography>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" color="text.secondary">
{t("Mixed Port")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{clashConfig["mixed-port"] || "-"}
</Typography>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" color="text.secondary">
{t("Uptime")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{formattedUptime}
</Typography>
</Stack>
<Divider />
<Stack direction="row" justifyContent="space-between">
<Typography variant="body2" color="text.secondary">
{t("Rules Count")}
</Typography>
<Typography variant="body2" fontWeight="medium">
{rules.length}
</Typography>
</Stack>
</Stack>
);
}, [clashConfig, clashVersion, t, formattedUptime, rules.length, sysproxy]);
return (
<EnhancedCard
title={t("Clash Info")}
icon={<DeveloperBoardOutlined />}
iconColor="warning"
action={null}
>
{cardContent}
</EnhancedCard>
);
};

View File

@ -1,156 +0,0 @@
import { useTranslation } from "react-i18next";
import { Box, Typography, Paper, Stack, Fade } from "@mui/material";
import { useLockFn } from "ahooks";
import { closeAllConnections } from "@/services/api";
import { patchClashMode } from "@/services/cmds";
import { useVerge } from "@/hooks/use-verge";
import {
LanguageRounded,
MultipleStopRounded,
DirectionsRounded,
} from "@mui/icons-material";
import { useMemo } from "react";
import { useAppData } from "@/providers/app-data-provider";
export const ClashModeCard = () => {
const { t } = useTranslation();
const { verge } = useVerge();
const { clashConfig, refreshProxy } = useAppData();
// 支持的模式列表
const modeList = useMemo(() => ["rule", "global", "direct"] as const, []);
// 直接使用API返回的模式不维护本地状态
const currentMode = clashConfig?.mode?.toLowerCase();
// 模式图标映射
const modeIcons = useMemo(() => ({
rule: <MultipleStopRounded fontSize="small" />,
global: <LanguageRounded fontSize="small" />,
direct: <DirectionsRounded fontSize="small" />
}), []);
// 切换模式的处理函数
const onChangeMode = useLockFn(async (mode: string) => {
if (mode === currentMode) return;
if (verge?.auto_close_connection) {
closeAllConnections();
}
try {
await patchClashMode(mode);
// 使用共享的刷新方法
refreshProxy();
} catch (error) {
console.error("Failed to change mode:", error);
}
});
// 按钮样式
const buttonStyles = (mode: string) => ({
cursor: "pointer",
px: 2,
py: 1.2,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 1,
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",
overflow: "visible",
"&:hover": {
transform: "translateY(-1px)",
boxShadow: 1,
},
"&:active": {
transform: "translateY(1px)",
},
"&::after": mode === currentMode
? {
content: '""',
position: "absolute",
bottom: -16,
left: "50%",
width: 2,
height: 16,
bgcolor: "primary.main",
transform: "translateX(-50%)",
}
: {},
});
// 描述样式
const descriptionStyles = {
width: "95%",
textAlign: "center",
color: "text.secondary",
p: 0.8,
borderRadius: 1,
borderColor: "primary.main",
borderWidth: 1,
borderStyle: "solid",
backgroundColor: "background.paper",
wordBreak: "break-word",
hyphens: "auto",
};
return (
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
{/* 模式选择按钮组 */}
<Stack
direction="row"
spacing={1}
sx={{
display: "flex",
justifyContent: "center",
py: 1,
position: "relative",
zIndex: 2,
}}
>
{modeList.map((mode) => (
<Paper
key={mode}
elevation={mode === currentMode ? 2 : 0}
onClick={() => onChangeMode(mode)}
sx={buttonStyles(mode)}
>
{modeIcons[mode]}
<Typography
variant="body2"
sx={{
textTransform: "capitalize",
fontWeight: mode === currentMode ? 600 : 400,
}}
>
{t(mode)}
</Typography>
</Paper>
))}
</Stack>
{/* 说明文本区域 */}
<Box
sx={{
width: "100%",
my: 1,
position: "relative",
display: "flex",
justifyContent: "center",
overflow: "visible",
}}
>
<Typography
variant="caption"
component="div"
sx={descriptionStyles}
>
{t(`${currentMode} Mode Description`)}
</Typography>
</Box>
</Box>
);
};

View File

@ -1,615 +0,0 @@
import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Chip,
Button,
alpha,
useTheme,
Select,
MenuItem,
FormControl,
InputLabel,
SelectChangeEvent,
Tooltip,
} from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react";
import {
SignalWifi4Bar as SignalStrong,
SignalWifi3Bar as SignalGood,
SignalWifi2Bar as SignalMedium,
SignalWifi1Bar as SignalWeak,
SignalWifi0Bar as SignalNone,
WifiOff as SignalError,
ChevronRight,
} from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import { EnhancedCard } from "@/components/home/enhanced-card";
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";
const STORAGE_KEY_PROXY = "clash-verge-selected-proxy";
// 代理节点信息接口
interface ProxyOption {
name: string;
}
// 将delayManager返回的颜色格式转换为MUI Chip组件需要的格式
function convertDelayColor(delayValue: number) {
const colorStr = delayManager.formatDelayColor(delayValue);
if (!colorStr) return "default";
// 从"error.main"这样的格式转为"error"
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";
}
}
// 根据延迟值获取合适的WiFi信号图标
function getSignalIcon(delay: number) {
if (delay < 0)
return { icon: <SignalNone />, text: "未测试", color: "text.secondary" };
if (delay >= 10000)
return { icon: <SignalError />, text: "超时", color: "error.main" };
if (delay >= 500)
return { icon: <SignalWeak />, text: "延迟较高", color: "error.main" };
if (delay >= 300)
return { icon: <SignalMedium />, text: "延迟中等", color: "warning.main" };
if (delay >= 200)
return { icon: <SignalGood />, text: "延迟良好", color: "info.main" };
return { icon: <SignalStrong />, text: "延迟极佳", color: "success.main" };
}
// 简单的防抖函数
function debounce(fn: Function, ms = 100) {
let timeoutId: ReturnType<typeof setTimeout>;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
}
export const CurrentProxyCard = () => {
const { t } = useTranslation();
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";
// 定义状态类型
type ProxyState = {
proxyData: {
groups: { name: string; now: string; all: string[] }[];
records: Record<string, any>;
globalProxy: string;
directProxy: any;
};
selection: {
group: string;
proxy: string;
};
displayProxy: any;
};
// 合并状态,减少状态更新次数
const [state, setState] = useState<ProxyState>({
proxyData: {
groups: [],
records: {},
globalProxy: "",
directProxy: null,
},
selection: {
group: "",
proxy: "",
},
displayProxy: null,
});
// 初始化选择的组
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) => ({
...prev,
selection: {
...prev.selection,
group: "GLOBAL",
},
}));
} else if (isDirectMode) {
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: "DIRECT",
},
}));
} else {
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: savedGroup || primaryGroupName || "",
},
}));
}
}, [isGlobalMode, isDirectMode, proxies]);
// 监听代理数据变化,更新状态
useEffect(() => {
if (!proxies) return;
// 使用函数式更新确保状态更新的原子性
setState((prev) => {
// 过滤和格式化组
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: { name: string }) => p.name),
}));
let newProxy = "";
let newDisplayProxy = null;
let newGroup = 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 = proxies.records?.[newProxy] || null;
// 保存到本地存储
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
if (newProxy) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
}
} 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 {
...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) {
connections.data.forEach((conn: any) => {
if (conn.chains.includes(previousProxy)) {
deleteConnection(conn.id);
}
});
}
// 延长刷新延迟时间
setTimeout(() => {
refreshProxy();
}, 500);
} catch (error) {
console.error("更新代理失败", error);
}
},
[
isDirectMode,
isGlobalMode,
state.proxyData.records,
state.selection,
verge?.auto_close_connection,
refreshProxy,
debouncedSetState,
connections.data,
],
);
// 导航到代理页面
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(() => {
if (isDirectMode) {
return [{ name: "DIRECT" }];
}
if (isGlobalMode && state.proxyData.records) {
// 全局模式下的选项
return Object.keys(state.proxyData.records)
.filter((name) => name !== "DIRECT" && name !== "REJECT")
.map((name) => ({ name }));
}
// 普通模式
const group = state.proxyData.groups.find(
(g: { name: string }) => g.name === state.selection.group,
);
if (group) {
return group.all.map((name) => ({ name }));
}
return [];
}, [isDirectMode, isGlobalMode, state.proxyData, state.selection.group]);
return (
<EnhancedCard
title={t("Current Node")}
icon={
<Tooltip
title={
currentProxy
? `${signalInfo.text}: ${delayManager.formatDelay(currentDelay)}`
: "无代理节点"
}
>
<Box sx={{ color: signalInfo.color }}>
{currentProxy ? signalInfo.icon : <SignalNone color="disabled" />}
</Box>
</Tooltip>
}
iconColor={currentProxy ? "primary" : undefined}
action={
<Button
variant="outlined"
size="small"
onClick={goToProxies}
sx={{ borderRadius: 1.5 }}
endIcon={<ChevronRight fontSize="small" />}
>
{t("Label-Proxies")}
</Button>
}
>
{currentProxy ? (
<Box>
{/* 代理节点信息显示 */}
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
p: 1,
mb: 2,
borderRadius: 1,
bgcolor: alpha(theme.palette.primary.main, 0.05),
border: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
}}
>
<Box>
<Typography variant="body1" fontWeight="medium">
{currentProxy.name}
</Typography>
<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 }}
/>
)}
{isDirectMode && (
<Chip
size="small"
label={t("Direct Mode")}
color="success"
sx={{ mr: 0.5 }}
/>
)}
{/* 节点特性 */}
{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>
{/* 显示延迟 */}
{currentProxy && !isDirectMode && (
<Chip
size="small"
label={delayManager.formatDelay(currentDelay)}
color={convertDelayColor(currentDelay)}
/>
)}
</Box>
{/* 代理组选择器 */}
<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"
value={state.selection.group}
onChange={handleGroupChange}
label={t("Group")}
disabled={isGlobalMode || isDirectMode}
>
{state.proxyData.groups.map((group) => (
<MenuItem key={group.name} value={group.name}>
{group.name}
</MenuItem>
))}
</Select>
</FormControl>
{/* 代理节点选择器 */}
<FormControl fullWidth variant="outlined" size="small" sx={{ mb: 0 }}>
<InputLabel id="proxy-select-label">{t("Proxy")}</InputLabel>
<Select
labelId="proxy-select-label"
value={state.selection.proxy}
onChange={handleProxyChange}
label={t("Proxy")}
disabled={isDirectMode}
renderValue={renderProxyValue}
MenuProps={{
PaperProps: {
style: {
maxHeight: 500,
},
},
}}
>
{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>
) : (
<Box sx={{ textAlign: "center", py: 4 }}>
<Typography variant="body1" color="text.secondary">
{t("No active proxy node")}
</Typography>
</Box>
)}
</EnhancedCard>
);
};

View File

@ -1,121 +0,0 @@
import { Box, Typography, alpha, useTheme } from "@mui/material";
import { ReactNode } from "react";
// 自定义卡片组件接口
export interface EnhancedCardProps {
title: ReactNode;
icon: ReactNode;
action?: ReactNode;
children: ReactNode;
iconColor?:
| "primary"
| "secondary"
| "error"
| "warning"
| "info"
| "success";
minHeight?: number | string;
noContentPadding?: boolean;
}
// 自定义卡片组件
export const EnhancedCard = ({
title,
icon,
action,
children,
iconColor = "primary",
minHeight,
noContentPadding = false,
}: EnhancedCardProps) => {
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={{
height: "100%",
display: "flex",
flexDirection: "column",
borderRadius: 2,
backgroundColor: isDark ? "#282a36" : "#ffffff",
}}
>
<Box
sx={{
px: 2,
py: 1,
display: "flex",
alignItems: "center",
justifyContent: "space-between",
borderBottom: 1,
borderColor: "divider",
}}
>
<Box sx={{
display: "flex",
alignItems: "center",
minWidth: 0,
flex: 1,
overflow: "hidden"
}}>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1.5,
width: 38,
height: 38,
mr: 1.5,
flexShrink: 0,
backgroundColor: alpha(theme.palette[iconColor].main, 0.12),
color: theme.palette[iconColor].main,
}}
>
{icon}
</Box>
<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 && <Box sx={{ ml: 2, flexShrink: 0 }}>{action}</Box>}
</Box>
<Box
sx={{
flex: 1,
display: "flex",
flexDirection: "column",
p: noContentPadding ? 0 : 2,
...(minHeight && { minHeight }),
}}
>
{children}
</Box>
</Box>
);
};

View File

@ -1,370 +0,0 @@
import {
forwardRef,
useImperativeHandle,
useState,
useEffect,
useCallback,
useMemo,
useRef,
memo,
} from "react";
import { Box, useTheme } from "@mui/material";
import parseTraffic from "@/utils/parse-traffic";
import { useTranslation } from "react-i18next";
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
AreaChart,
Area,
} from "recharts";
// 流量数据项接口
export interface ITrafficItem {
up: number;
down: number;
timestamp?: number;
}
// 组件对外暴露的方法
export interface EnhancedTrafficGraphRef {
appendData: (data: ITrafficItem) => void;
toggleStyle: () => void;
}
// 时间范围类型
type TimeRange = 1 | 5 | 10; // 分钟
// 数据点类型
type DataPoint = ITrafficItem & { name: string; timestamp: number };
/**
*
*/
export const EnhancedTrafficGraph = memo(forwardRef<EnhancedTrafficGraphRef>(
(props, ref) => {
const theme = useTheme();
const { t } = useTranslation();
// 基础状态
const [timeRange, setTimeRange] = useState<TimeRange>(10);
const [chartStyle, setChartStyle] = useState<"line" | "area">("area");
const [displayData, setDisplayData] = useState<DataPoint[]>([]);
// 数据缓冲区
const dataBufferRef = useRef<DataPoint[]>([]);
// 根据时间范围计算保留的数据点数量
const getMaxPointsByTimeRange = useCallback(
(minutes: TimeRange): number => minutes * 60,
[]
);
// 最大数据点数量
const MAX_BUFFER_SIZE = useMemo(
() => getMaxPointsByTimeRange(10),
[getMaxPointsByTimeRange]
);
// 颜色配置
const colors = useMemo(
() => ({
up: theme.palette.secondary.main,
down: theme.palette.primary.main,
grid: theme.palette.divider,
tooltip: theme.palette.background.paper,
text: theme.palette.text.primary,
}),
[theme]
);
// 切换时间范围
const handleTimeRangeClick = useCallback(() => {
setTimeRange((prevRange) => {
// 在1、5、10分钟之间循环切换
return prevRange === 1 ? 5 : prevRange === 5 ? 10 : 1;
});
}, []);
// 初始化数据缓冲区
useEffect(() => {
// 创建初始空数据
const now = Date.now();
const tenMinutesAgo = now - 10 * 60 * 1000;
const 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;
// 更新显示数据
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(initialBuffer.slice(-pointsToShow));
}, [MAX_BUFFER_SIZE, getMaxPointsByTimeRange]);
// 添加数据点方法
const appendData = useCallback((data: ITrafficItem) => {
// 安全处理数据
const safeData = {
up: typeof data.up === "number" && !isNaN(data.up) ? data.up : 0,
down: typeof data.down === "number" && !isNaN(data.down) ? data.down : 0,
};
// 使用提供的时间戳或当前时间
const timestamp = data.timestamp || Date.now();
const date = new Date(timestamp);
// 带时间标签的新数据点
const newPoint: DataPoint = {
...safeData,
name: date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}),
timestamp: timestamp,
};
// 更新缓冲区,保持原数组大小
const newBuffer = [...dataBufferRef.current.slice(1), newPoint];
dataBufferRef.current = newBuffer;
// 更新显示数据
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(newBuffer.slice(-pointsToShow));
}, [timeRange, getMaxPointsByTimeRange]);
// 监听时间范围变化,更新显示数据
useEffect(() => {
const pointsToShow = getMaxPointsByTimeRange(timeRange);
setDisplayData(dataBufferRef.current.slice(-pointsToShow));
}, [timeRange, getMaxPointsByTimeRange]);
// 切换图表样式
const toggleStyle = useCallback(() => {
setChartStyle((prev) => prev === "line" ? "area" : "line");
}, []);
// 暴露方法给父组件
useImperativeHandle(
ref,
() => ({
appendData,
toggleStyle,
}),
[appendData, toggleStyle]
);
// 格式化工具提示内容
const formatTooltip = useCallback((value: number, name: string, props: any) => {
const [num, unit] = parseTraffic(value);
return [`${num} ${unit}/s`, props?.dataKey === "up" ? t("Upload") : t("Download")];
}, [t]);
// Y轴刻度格式化
const formatYAxis = useCallback((value: number) => {
const [num, unit] = parseTraffic(value);
return `${num}${unit}`;
}, []);
// 格式化X轴标签
const formatXLabel = useCallback((value: string) => {
if (!value) return "";
const parts = value.split(":");
return `${parts[0]}:${parts[1]}`;
}, []);
// 获取当前时间范围文本
const getTimeRangeText = useCallback(() => {
return t("{{time}} Minutes", { time: timeRange });
}, [timeRange, t]);
// 共享图表配置
const chartConfig = useMemo(() => ({
data: displayData,
margin: { top: 20, right: 10, left: 0, bottom: -10 },
}), [displayData]);
// 共享的线条/区域配置
const commonLineProps = useMemo(() => ({
dot: false,
strokeWidth: 2,
connectNulls: false,
activeDot: { r: 4, strokeWidth: 1 },
isAnimationActive: false, // 禁用动画以减少CPU使用
}), []);
// 曲线类型
const curveType = "monotone";
return (
<Box
sx={{
width: "100%",
height: "100%",
position: "relative",
bgcolor: "action.hover",
borderRadius: 1,
cursor: "pointer",
}}
onClick={toggleStyle}
>
<ResponsiveContainer width="100%" height="100%">
{/* 根据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="11%"
textAnchor="start"
fill={theme.palette.text.secondary}
fontSize={11}
fontWeight="bold"
onClick={handleTimeRangeClick}
style={{ cursor: "pointer" }}
>
{getTimeRangeText()}
</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="99%"
y="19%"
textAnchor="end"
fill={colors.down}
fontSize={12}
fontWeight="bold"
onClick={toggleStyle}
style={{ cursor: "pointer" }}
>
{t("Download")}
</text>
</>
);
// 根据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>
);
},
));
// 添加显示名称以便调试
EnhancedTrafficGraph.displayName = "EnhancedTrafficGraph";

View File

@ -1,418 +0,0 @@
import { useState, useEffect, useRef, useCallback, memo, useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
Typography,
Paper,
alpha,
useTheme,
PaletteColor,
} from "@mui/material";
import Grid from "@mui/material/Grid2";
import {
ArrowUpwardRounded,
ArrowDownwardRounded,
MemoryRounded,
LinkRounded,
CloudUploadRounded,
CloudDownloadRounded,
} from "@mui/icons-material";
import {
EnhancedTrafficGraph,
EnhancedTrafficGraphRef,
ITrafficItem,
} from "./enhanced-traffic-graph";
import { useVisibility } from "@/hooks/use-visibility";
import { useClashInfo } from "@/hooks/use-clash";
import { useVerge } from "@/hooks/use-verge";
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;
oslimit?: number;
}
interface TrafficStatData {
uploadTotal: number;
downloadTotal: number;
activeConnections: number;
}
interface StatCardProps {
icon: ReactNode;
title: string;
value: string | number;
unit: string;
color: "primary" | "secondary" | "error" | "warning" | "info" | "success";
onClick?: () => void;
}
// 全局变量类型定义
declare global {
interface Window {
animationFrameId?: number;
lastTrafficData?: {
up: number;
down: number;
};
}
}
// 控制更新频率
const CONNECTIONS_UPDATE_INTERVAL = 5000; // 5秒更新一次连接数据
const THROTTLE_TRAFFIC_UPDATE = 500; // 500ms节流流量数据更新
// 统计卡片组件 - 使用memo优化
const CompactStatCard = memo(({
icon,
title,
value,
unit,
color,
onClick,
}: StatCardProps) => {
const theme = useTheme();
// 获取调色板颜色 - 使用useMemo避免重复计算
const colorValue = useMemo(() => {
const palette = theme.palette;
if (
color in palette &&
palette[color as keyof typeof palette] &&
"main" in (palette[color as keyof typeof palette] as PaletteColor)
) {
return (palette[color as keyof typeof palette] as PaletteColor).main;
}
return palette.primary.main;
}, [theme.palette, color]);
return (
<Paper
elevation={0}
sx={{
display: "flex",
alignItems: "center",
borderRadius: 2,
bgcolor: alpha(colorValue, 0.05),
border: `1px solid ${alpha(colorValue, 0.15)}`,
padding: "8px",
transition: "all 0.2s ease-in-out",
cursor: onClick ? "pointer" : "default",
"&:hover": onClick ? {
bgcolor: alpha(colorValue, 0.1),
border: `1px solid ${alpha(colorValue, 0.3)}`,
boxShadow: `0 4px 8px rgba(0,0,0,0.05)`,
} : {},
}}
onClick={onClick}
>
{/* 图标容器 */}
<Grid
component="div"
sx={{
mr: 1,
ml: "2px",
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 32,
height: 32,
borderRadius: "50%",
bgcolor: alpha(colorValue, 0.1),
color: colorValue,
}}
>
{icon}
</Grid>
{/* 文本内容 */}
<Grid component="div" sx={{ flexGrow: 1, minWidth: 0 }}>
<Typography variant="caption" color="text.secondary" noWrap>
{title}
</Typography>
<Grid component="div" sx={{ display: "flex", alignItems: "baseline" }}>
<Typography variant="body1" fontWeight="bold" noWrap sx={{ mr: 0.5 }}>
{value}
</Typography>
<Typography variant="caption" color="text.secondary">
{unit}
</Typography>
</Grid>
</Grid>
</Paper>
);
});
// 添加显示名称
CompactStatCard.displayName = "CompactStatCard";
export const EnhancedTrafficStats = () => {
const { t } = useTranslation();
const theme = useTheme();
const { clashInfo } = useClashInfo();
const { verge } = useVerge();
const trafficRef = useRef<EnhancedTrafficGraphRef>(null);
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 },
});
// 创建一个标记来追踪最后更新时间,用于节流
const lastUpdateRef = useRef({ traffic: 0 });
// 是否显示流量图表
const trafficGraph = verge?.traffic_graph ?? true;
// WebSocket引用
const socketRefs = useRef({
traffic: null as ReturnType<typeof createAuthSockette> | null,
memory: null as ReturnType<typeof createAuthSockette> | null,
});
// 检查是否支持调试
useEffect(() => {
isDebugEnabled().then((flag) => setIsDebug(flag));
}, []);
// 处理流量数据更新 - 使用节流控制更新频率
const handleTrafficUpdate = useCallback((event: MessageEvent) => {
try {
const data = JSON.parse(event.data) as ITrafficItem;
if (
data &&
typeof data.up === "number" &&
typeof data.down === "number"
) {
// 使用节流控制更新频率
const now = Date.now();
if (now - lastUpdateRef.current.traffic < THROTTLE_TRAFFIC_UPDATE) {
// 如果距离上次更新时间小于阈值,只更新图表不更新状态
if (trafficRef.current) {
trafficRef.current.appendData({
up: data.up,
down: data.down,
timestamp: now,
});
}
return;
}
// 更新最后更新时间
lastUpdateRef.current.traffic = now;
// 验证数据有效性防止NaN
const safeUp = isNaN(data.up) ? 0 : data.up;
const safeDown = isNaN(data.down) ? 0 : data.down;
// 批量更新状态
setStats(prev => ({
...prev,
traffic: { up: safeUp, down: safeDown }
}));
// 更新图表数据
if (trafficRef.current) {
trafficRef.current.appendData({
up: safeUp,
down: safeDown,
timestamp: now,
});
}
}
} catch (err) {
console.error("[Traffic] 解析数据错误:", err);
}
}, []);
// 处理内存数据更新
const handleMemoryUpdate = useCallback((event: MessageEvent) => {
try {
const data = JSON.parse(event.data) as MemoryUsage;
if (data && typeof data.inuse === "number") {
setStats(prev => ({
...prev,
memory: {
inuse: isNaN(data.inuse) ? 0 : data.inuse,
oslimit: data.oslimit,
}
}));
}
} catch (err) {
console.error("[Memory] 解析数据错误:", err);
}
}, []);
// 使用 WebSocket 连接获取数据 - 合并流量和内存连接逻辑
useEffect(() => {
if (!clashInfo || !pageVisible) return;
const { server, secret = "" } = clashInfo;
if (!server) return;
// 清理现有连接的函数
const cleanupSockets = () => {
Object.values(socketRefs.current).forEach(socket => {
if (socket) {
socket.close();
}
});
socketRefs.current = { traffic: null, memory: null };
};
// 关闭现有连接
cleanupSockets();
// 创建新连接
socketRefs.current.traffic = createAuthSockette(`${server}/traffic`, secret, {
onmessage: handleTrafficUpdate,
});
socketRefs.current.memory = createAuthSockette(`${server}/memory`, secret, {
onmessage: handleMemoryUpdate,
});
return cleanupSockets;
}, [clashInfo, pageVisible, handleTrafficUpdate, handleMemoryUpdate]);
// 执行垃圾回收
const handleGarbageCollection = useCallback(async () => {
if (isDebug) {
try {
await gc();
console.log("[Debug] 垃圾回收已执行");
} catch (err) {
console.error("[Debug] 垃圾回收失败:", err);
}
}
}, [isDebug]);
// 使用useMemo计算解析后的流量数据
const parsedData = useMemo(() => {
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(connections.uploadTotal);
const [downloadTotal, downloadTotalUnit] = parseTraffic(connections.downloadTotal);
return {
up, upUnit, down, downUnit, inuse, inuseUnit,
uploadTotal, uploadTotalUnit, downloadTotal, downloadTotalUnit
};
}, [stats, connections.uploadTotal, connections.downloadTotal]);
// 渲染流量图表 - 使用useMemo缓存渲染结果
const trafficGraphComponent = useMemo(() => {
if (!trafficGraph || !pageVisible) return null;
return (
<Paper
elevation={0}
sx={{
height: 130,
cursor: "pointer",
border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
borderRadius: 2,
overflow: "hidden",
}}
onClick={() => trafficRef.current?.toggleStyle()}
>
<div style={{ height: "100%", position: "relative" }}>
<EnhancedTrafficGraph ref={trafficRef} />
{isDebug && (
<div
style={{
position: "absolute",
top: "2px",
left: "2px",
zIndex: 10,
backgroundColor: "rgba(0,0,0,0.5)",
color: "white",
fontSize: "8px",
padding: "2px 4px",
borderRadius: "4px",
}}
>
DEBUG: {!!trafficRef.current ? "图表已初始化" : "图表未初始化"}
<br />
{new Date().toISOString().slice(11, 19)}
</div>
)}
</div>
</Paper>
);
}, [trafficGraph, pageVisible, theme.palette.divider, isDebug]);
// 使用useMemo计算统计卡片配置
const statCards = useMemo(() => [
{
icon: <ArrowUpwardRounded fontSize="small" />,
title: t("Upload Speed"),
value: parsedData.up,
unit: `${parsedData.upUnit}/s`,
color: "secondary" as const,
},
{
icon: <ArrowDownwardRounded fontSize="small" />,
title: t("Download Speed"),
value: parsedData.down,
unit: `${parsedData.downUnit}/s`,
color: "primary" as const,
},
{
icon: <LinkRounded fontSize="small" />,
title: t("Active Connections"),
value: connections.count,
unit: "",
color: "success" as const,
},
{
icon: <CloudUploadRounded fontSize="small" />,
title: t("Uploaded"),
value: parsedData.uploadTotal,
unit: parsedData.uploadTotalUnit,
color: "secondary" as const,
},
{
icon: <CloudDownloadRounded fontSize="small" />,
title: t("Downloaded"),
value: parsedData.downloadTotal,
unit: parsedData.downloadTotalUnit,
color: "primary" as const,
},
{
icon: <MemoryRounded fontSize="small" />,
title: t("Memory Usage"),
value: parsedData.inuse,
unit: parsedData.inuseUnit,
color: "error" as const,
onClick: isDebug ? handleGarbageCollection : undefined,
},
], [t, parsedData, connections.count, isDebug, handleGarbageCollection]);
return (
<Grid container spacing={1} columns={{ xs: 8, sm: 8, md: 12 }}>
{trafficGraph && (
<Grid size={12}>
{/* 流量图表区域 */}
{trafficGraphComponent}
</Grid>
)}
{/* 统计卡片区域 */}
{statCards.map((card, index) => (
<Grid key={index} size={4}>
<CompactStatCard {...card} />
</Grid>
))}
</Grid>
);
};

View File

@ -1,380 +0,0 @@
import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Button,
Stack,
LinearProgress,
alpha,
useTheme,
Link,
keyframes,
} from "@mui/material";
import { useNavigate } from "react-router-dom";
import {
CloudUploadOutlined,
StorageOutlined,
UpdateOutlined,
DnsOutlined,
SpeedOutlined,
EventOutlined,
LaunchOutlined,
} from "@mui/icons-material";
import dayjs from "dayjs";
import parseTraffic from "@/utils/parse-traffic";
import { useMemo, useCallback, useState } from "react";
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`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`;
// 辅助函数解析URL和过期时间
const parseUrl = (url?: string) => {
if (!url) return "-";
if (url.startsWith("http")) return new URL(url).host;
return "local";
};
const parseExpire = (expire?: number) => {
if (!expire) return "-";
return dayjs(expire * 1000).format("YYYY-MM-DD");
};
// 使用类型定义,而不是导入
interface ProfileExtra {
upload: number;
download: number;
total: number;
expire: number;
}
export interface ProfileItem {
uid: string;
type?: "local" | "remote" | "merge" | "script";
name?: string;
desc?: string;
file?: string;
url?: string;
updated?: number;
extra?: ProfileExtra;
home?: string;
option?: any;
}
export interface HomeProfileCardProps {
current: ProfileItem | null | undefined;
onProfileUpdated?: () => void;
}
// 添加一个通用的截断样式
const truncateStyle = {
maxWidth: "calc(100% - 28px)",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
};
// 提取独立组件减少主组件复杂度
const ProfileDetails = ({ current, onUpdateProfile, updating }: {
current: ProfileItem;
onUpdateProfile: () => void;
updating: boolean;
}) => {
const { t } = useTranslation();
const theme = useTheme();
const usedTraffic = useMemo(() => {
if (!current.extra) return 0;
return current.extra.upload + current.extra.download;
}, [current.extra]);
const trafficPercentage = useMemo(() => {
if (!current.extra || !current.extra.total) return 1;
return Math.min(
Math.round((usedTraffic * 100) / (current.extra.total + 0.01)) + 1,
100
);
}, [current.extra, usedTraffic]);
return (
<Box>
<Stack spacing={2}>
{current.url && (
<Stack direction="row" alignItems="center" spacing={1}>
<DnsOutlined fontSize="small" color="action" />
<Typography variant="body2" color="text.secondary" noWrap sx={{ display: "flex", alignItems: "center" }}>
<span style={{ flexShrink: 0 }}>{t("From")}: </span>
{current.home ? (
<Link
component="button"
fontWeight="medium"
onClick={() => current.home && openWebUrl(current.home)}
sx={{
display: "inline-flex",
alignItems: "center",
minWidth: 0,
maxWidth: "calc(100% - 40px)",
ml: 0.5
}}
title={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>
) : (
<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)}
</Typography>
)}
</Typography>
</Stack>
)}
{current.updated && (
<Stack direction="row" alignItems="center" spacing={1}>
<UpdateOutlined
fontSize="small"
color="action"
sx={{
cursor: "pointer",
animation: updating ? `${round} 1.5s linear infinite` : "none",
}}
onClick={onUpdateProfile}
/>
<Typography
variant="body2"
color="text.secondary"
sx={{ cursor: "pointer" }}
onClick={onUpdateProfile}
>
{t("Update Time")}:{" "}
<Box component="span" fontWeight="medium">
{dayjs(current.updated * 1000).format("YYYY-MM-DD HH:mm")}
</Box>
</Typography>
</Stack>
)}
{current.extra && (
<>
<Stack direction="row" alignItems="center" spacing={1}>
<SpeedOutlined fontSize="small" color="action" />
<Typography variant="body2" color="text.secondary">
{t("Used / Total")}:{" "}
<Box component="span" fontWeight="medium">
{parseTraffic(usedTraffic)} / {parseTraffic(current.extra.total)}
</Box>
</Typography>
</Stack>
{current.extra.expire > 0 && (
<Stack direction="row" alignItems="center" spacing={1}>
<EventOutlined fontSize="small" color="action" />
<Typography variant="body2" color="text.secondary">
{t("Expire Time")}:{" "}
<Box component="span" fontWeight="medium">
{parseExpire(current.extra.expire)}
</Box>
</Typography>
</Stack>
)}
<Box sx={{ mt: 1 }}>
<Typography
variant="caption"
color="text.secondary"
sx={{ mb: 0.5, display: "block" }}
>
{trafficPercentage}%
</Typography>
<LinearProgress
variant="determinate"
value={trafficPercentage}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: alpha(theme.palette.primary.main, 0.12),
}}
/>
</Box>
</>
)}
</Stack>
</Box>
);
};
// 提取空配置组件
const EmptyProfile = ({ onClick }: { onClick: () => void }) => {
const { t } = useTranslation();
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
py: 2.4,
cursor: "pointer",
"&:hover": { bgcolor: "action.hover" },
borderRadius: 2,
}}
onClick={onClick}
>
<CloudUploadOutlined
sx={{ fontSize: 60, color: "primary.main", mb: 2 }}
/>
<Typography variant="h6" gutterBottom>
{t("Import")} {t("Profiles")}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Click to import subscription")}
</Typography>
</Box>
);
};
export const HomeProfileCard = ({ current, onProfileUpdated }: HomeProfileCardProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { refreshAll } = useAppData();
// 更新当前订阅
const [updating, setUpdating] = useState(false);
const onUpdateProfile = useLockFn(async () => {
if (!current?.uid) return;
setUpdating(true);
try {
await updateProfile(current.uid);
Notice.success(t("Update subscription successfully"));
onProfileUpdated?.();
// 刷新首页数据
refreshAll();
} catch (err: any) {
Notice.error(err?.message || err.toString());
} finally {
setUpdating(false);
}
});
// 导航到订阅页面
const goToProfiles = useCallback(() => {
navigate("/profile");
}, [navigate]);
// 卡片标题
const cardTitle = useMemo(() => {
if (!current) return t("Profiles");
if (!current.home) return current.name;
return (
<Link
component="button"
variant="h6"
fontWeight="medium"
fontSize={18}
onClick={() => current.home && openWebUrl(current.home)}
sx={{
color: "inherit",
textDecoration: "none",
display: "flex",
alignItems: "center",
minWidth: 0,
maxWidth: "100%",
"& > span": {
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flex: 1
}
}}
title={current.name}
>
<span>{current.name}</span>
<LaunchOutlined
fontSize="inherit"
sx={{
ml: 0.5,
fontSize: "0.8rem",
opacity: 0.7,
flexShrink: 0
}}
/>
</Link>
);
}, [current, t]);
// 卡片操作按钮
const cardAction = useMemo(() => {
if (!current) return null;
return (
<Button
variant="outlined"
size="small"
onClick={goToProfiles}
endIcon={<StorageOutlined fontSize="small" />}
sx={{ borderRadius: 1.5 }}
>
{t("Label-Profiles")}
</Button>
);
}, [current, goToProfiles, t]);
return (
<EnhancedCard
title={cardTitle}
icon={<CloudUploadOutlined />}
iconColor="info"
action={cardAction}
>
{current ? (
<ProfileDetails
current={current}
onUpdateProfile={onUpdateProfile}
updating={updating}
/>
) : (
<EmptyProfile onClick={goToProfiles} />
)}
</EnhancedCard>
);
};

View File

@ -1,324 +0,0 @@
import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Button,
Skeleton,
IconButton,
useTheme,
} from "@mui/material";
import {
LocationOnOutlined,
RefreshOutlined,
VisibilityOutlined,
VisibilityOffOutlined,
} from "@mui/icons-material";
import { EnhancedCard } from "./enhanced-card";
import { getIpInfo } from "@/services/api";
import { useState, useEffect, useCallback, memo } from "react";
// 定义刷新时间(秒)
const IP_REFRESH_SECONDS = 300;
// 提取InfoItem子组件并使用memo优化
const InfoItem = memo(({ label, value }: { label: string; value: string }) => (
<Box sx={{ mb: 0.7, display: "flex", alignItems: "flex-start" }}>
<Typography
variant="body2"
color="text.secondary"
sx={{ minwidth: 60, mr: 0.5, flexShrink: 0, textAlign: "right" }}
>
{label}:
</Typography>
<Typography
variant="body2"
sx={{
ml: 0.5,
overflow: "hidden",
textOverflow: "ellipsis",
wordBreak: "break-word",
whiteSpace: "normal",
flexGrow: 1,
}}
>
{value || "Unknown"}
</Typography>
</Box>
));
// 获取国旗表情
const getCountryFlag = (countryCode: string) => {
if (!countryCode) return "";
const codePoints = countryCode
.toUpperCase()
.split("")
.map((char) => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
};
// IP信息卡片组件
export const IpInfoCard = () => {
const { t } = useTranslation();
const [ipInfo, setIpInfo] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [showIp, setShowIp] = useState(false);
const [countdown, setCountdown] = useState(IP_REFRESH_SECONDS);
// 获取IP信息
const fetchIpInfo = useCallback(async () => {
try {
setLoading(true);
setError("");
const data = await getIpInfo();
setIpInfo(data);
setCountdown(IP_REFRESH_SECONDS);
} catch (err: any) {
setError(err.message || t("Failed to get IP info"));
} finally {
setLoading(false);
}
}, [t]);
// 组件加载时获取IP信息
useEffect(() => {
fetchIpInfo();
// 倒计时实现优化,减少不必要的重渲染
let timer: number | null = null;
let currentCount = IP_REFRESH_SECONDS;
// 只在必要时更新状态,减少重渲染次数
const startCountdown = () => {
timer = window.setInterval(() => {
currentCount -= 1;
if (currentCount <= 0) {
fetchIpInfo();
currentCount = IP_REFRESH_SECONDS;
}
// 每5秒或倒计时结束时才更新UI
if (currentCount % 5 === 0 || currentCount <= 0) {
setCountdown(currentCount);
}
}, 1000);
};
startCountdown();
return () => {
if (timer) clearInterval(timer);
};
}, [fetchIpInfo]);
const toggleShowIp = useCallback(() => {
setShowIp(prev => !prev);
}, []);
// 渲染加载状态
if (loading) {
return (
<EnhancedCard
title={t("IP Information")}
icon={<LocationOnOutlined />}
iconColor="info"
action={
<IconButton size="small" onClick={fetchIpInfo} disabled={true}>
<RefreshOutlined />
</IconButton>
}
>
<Box sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
<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} />
</Box>
</EnhancedCard>
);
}
// 渲染错误状态
if (error) {
return (
<EnhancedCard
title={t("IP Information")}
icon={<LocationOnOutlined />}
iconColor="info"
action={
<IconButton size="small" onClick={fetchIpInfo}>
<RefreshOutlined />
</IconButton>
}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
height: "100%",
color: "error.main",
}}
>
<Typography variant="body1" color="error">
{error}
</Typography>
<Button onClick={fetchIpInfo} sx={{ mt: 2 }}>
{t("Retry")}
</Button>
</Box>
</EnhancedCard>
);
}
// 渲染正常数据
return (
<EnhancedCard
title={t("IP Information")}
icon={<LocationOnOutlined />}
iconColor="info"
action={
<IconButton size="small" onClick={fetchIpInfo}>
<RefreshOutlined />
</IconButton>
}
>
<Box sx={{ height: "100%", display: "flex", flexDirection: "column" }}>
<Box
sx={{
display: "flex",
flexDirection: "row",
flex: 1,
overflow: "hidden",
}}
>
{/* 左侧国家和IP地址 */}
<Box sx={{ width: "40%", overflow: "hidden" }}>
<Box
sx={{
display: "flex",
alignItems: "center",
mb: 1,
overflow: "hidden",
}}
>
<Box
component="span"
sx={{
fontSize: "1.5rem",
mr: 1,
display: "inline-block",
width: 28,
textAlign: "center",
flexShrink: 0,
fontFamily: '"twemoji mozilla", sans-serif',
}}
>
{getCountryFlag(ipInfo?.country_code)}
</Box>
<Typography
variant="subtitle1"
sx={{
fontWeight: "medium",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: "100%",
}}
>
{ipInfo?.country || t("Unknown")}
</Typography>
</Box>
<Box sx={{ display: "flex", alignItems: "center", mb: 1 }}>
<Typography
variant="body2"
color="text.secondary"
sx={{ flexShrink: 0 }}
>
{t("IP")}:
</Typography>
<Box
sx={{
display: "flex",
alignItems: "center",
ml: 1,
overflow: "hidden",
maxWidth: "calc(100% - 30px)",
}}
>
<Typography
variant="body2"
sx={{
fontFamily: "monospace",
fontSize: "0.75rem",
overflow: "hidden",
textOverflow: "ellipsis",
wordBreak: "break-all",
}}
>
{showIp ? ipInfo?.ip : "••••••••••"}
</Typography>
<IconButton size="small" onClick={toggleShowIp}>
{showIp ? (
<VisibilityOffOutlined fontSize="small" />
) : (
<VisibilityOutlined fontSize="small" />
)}
</IconButton>
</Box>
</Box>
<InfoItem
label={t("ASN")}
value={ipInfo?.asn ? `AS${ipInfo.asn}` : "N/A"}
/>
</Box>
{/* 右侧组织、ISP和位置信息 */}
<Box sx={{ width: "60%", overflow: "auto" }}>
<InfoItem label={t("ISP")} value={ipInfo?.isp} />
<InfoItem label={t("ORG")} value={ipInfo?.asn_organization} />
<InfoItem
label={t("Location")}
value={[ipInfo?.city, ipInfo?.region]
.filter(Boolean)
.join(", ")}
/>
<InfoItem label={t("Timezone")} value={ipInfo?.timezone} />
</Box>
</Box>
<Box
sx={{
mt: "auto",
pt: 0.5,
borderTop: 1,
borderColor: "divider",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
opacity: 0.7,
fontSize: "0.7rem",
}}
>
<Typography variant="caption">
{t("Auto refresh")}: {countdown}s
</Typography>
<Typography
variant="caption"
sx={{
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap",
}}
>
{ipInfo?.country_code}, {ipInfo?.longitude?.toFixed(2)},{" "}
{ipInfo?.latitude?.toFixed(2)}
</Typography>
</Box>
</Box>
</EnhancedCard>
);
};

View File

@ -1,246 +0,0 @@
import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Stack,
Paper,
Tooltip,
alpha,
useTheme,
Fade,
} from "@mui/material";
import { useState, useMemo, memo, FC } from "react";
import ProxyControlSwitches from "@/components/shared/ProxyControlSwitches";
import { Notice } from "@/components/base";
import {
ComputerRounded,
TroubleshootRounded,
HelpOutlineRounded,
SvgIconComponent,
} from "@mui/icons-material";
import { useVerge } from "@/hooks/use-verge";
import { useSystemState } from "@/hooks/use-system-state";
const LOCAL_STORAGE_TAB_KEY = "clash-verge-proxy-active-tab";
interface TabButtonProps {
isActive: boolean;
onClick: () => void;
icon: SvgIconComponent;
label: string;
hasIndicator?: boolean;
}
// 抽取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 }}>
{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;
tooltipTitle: string;
}
// 抽取描述文本组件
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 { verge } = useVerge();
const { isSidecarMode, isAdminMode } = useSystemState();
// 从verge配置中获取开关状态
const { enable_system_proxy, enable_tun_mode } = verge ?? {};
// 判断Tun模式是否可用 - 当处于服务模式或管理员模式时可用
const isTunAvailable = !isSidecarMode || isAdminMode;
// 处理错误
const handleError = (err: Error) => {
Notice.error(err.message || err.toString(), 3000);
};
// 处理标签切换并保存到localStorage
const handleTabChange = (tab: string) => {
setActiveTab(tab);
localStorage.setItem(LOCAL_STORAGE_TAB_KEY, tab);
};
// 用户提示文本 - 使用useMemo避免重复计算
const tabDescription = useMemo(() => {
if (activeTab === "system") {
return {
text: enable_system_proxy
? t("System Proxy Enabled")
: t("System Proxy Disabled"),
tooltip: t("System Proxy Info"),
};
} else {
return {
text: !isTunAvailable
? t("TUN Mode Service Required")
: enable_tun_mode
? t("TUN Mode Enabled")
: t("TUN Mode Disabled"),
tooltip: t("TUN Mode Intercept Info"),
};
}
}, [activeTab, enable_system_proxy, enable_tun_mode, isTunAvailable, t]);
return (
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
{/* 选项卡 */}
<Stack
direction="row"
spacing={1}
sx={{
display: "flex",
justifyContent: "center",
position: "relative",
zIndex: 2,
}}
>
<TabButton
isActive={activeTab === "system"}
onClick={() => handleTabChange("system")}
icon={ComputerRounded}
label={t("System Proxy")}
hasIndicator={enable_system_proxy}
/>
<TabButton
isActive={activeTab === "tun"}
onClick={() => handleTabChange("tun")}
icon={TroubleshootRounded}
label={t("Tun Mode")}
hasIndicator={enable_tun_mode && isTunAvailable}
/>
</Stack>
{/* 说明文本区域 */}
<Box
sx={{
width: "100%",
my: 1,
position: "relative",
display: "flex",
justifyContent: "center",
overflow: "visible",
}}
>
<TabDescription
description={tabDescription.text}
tooltipTitle={tabDescription.tooltip}
/>
</Box>
{/* 控制开关部分 */}
<Box
sx={{
mt: 0,
p: 1,
bgcolor: alpha(theme.palette.primary.main, 0.04),
borderRadius: 2,
}}
>
<ProxyControlSwitches
onError={handleError}
label={activeTab === "system" ? t("System Proxy") : t("Tun Mode")}
/>
</Box>
</Box>
);
};

Some files were not shown because too many files have changed in this diff Show More