Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3271c96531 | ||
|
|
05fa6b9aba | ||
|
|
7edd7f27cb | ||
|
|
4a6b12eda1 | ||
|
|
a354e1a0eb | ||
|
|
c2c419522a | ||
|
|
7a3cc7d242 | ||
|
|
bd6f02f6af | ||
|
|
bd33728fd7 | ||
|
|
8400ee8a3d | ||
|
|
80ee7aef8e | ||
|
|
41762be3a5 | ||
|
|
605dda7b76 | ||
|
|
8d6e3b5a58 | ||
|
|
53920fc368 | ||
|
|
96d10423d9 | ||
|
|
42be9b5b51 | ||
|
|
0add041516 | ||
|
|
dee76cac8d | ||
|
|
9646fab22c | ||
|
|
a54e9cb244 | ||
|
|
f8339c7a9a | ||
|
|
9e2c864117 | ||
|
|
e1ccd71455 | ||
|
|
0e82426ea1 | ||
|
|
6c7afd168a | ||
|
|
e014fdf3da | ||
|
|
2074da05c8 | ||
|
|
84cd87b70a | ||
|
|
75f87044f6 | ||
|
|
f71b51e64a | ||
|
|
9d741cdd63 | ||
|
|
56be17000a | ||
|
|
f6aacbc31d | ||
|
|
ad4b57c327 | ||
|
|
3794b2f0de | ||
|
|
e0e8412728 | ||
|
|
05b8175aad | ||
|
|
339c89dd1c | ||
|
|
402299e9c8 | ||
|
|
d0e8f450fd | ||
|
|
e97b1ccd7a | ||
|
|
68691b91ef | ||
|
|
213d417481 | ||
|
|
2ec841eb61 | ||
|
|
60b5c54be1 | ||
|
|
448412a191 | ||
|
|
e06327936e | ||
|
|
5f044e1aed | ||
|
|
d90fb6fc9b | ||
|
|
1c583d2ea9 | ||
|
|
ddab131102 | ||
|
|
3b06cf8a2a | ||
|
|
c5d7c29f3d | ||
|
|
26af860eac | ||
|
|
ec9852eb98 | ||
|
|
f2b8c6d0ca | ||
|
|
2cf7a048cf | ||
|
|
3dbb71a076 | ||
|
|
ddf6760543 | ||
|
|
a6fa323f33 | ||
|
|
2df8f1acdb | ||
|
|
74a3290abc | ||
|
|
ab306d21f3 | ||
|
|
1bd7795851 | ||
|
|
302c142346 | ||
|
|
7c254c9b45 | ||
|
|
b1c458359d | ||
|
|
2d925c62f5 | ||
|
|
ef4711987f | ||
|
|
8910dfaecf | ||
|
|
e8b2e79c7d | ||
|
|
333ee1e1f7 | ||
|
|
ba013bbe96 | ||
|
|
636e66fe7a | ||
|
|
372a45e667 | ||
|
|
ab6b796ce2 | ||
|
|
71e6c02897 | ||
|
|
3edeaa7038 | ||
|
|
f2f75d4015 | ||
|
|
69adade738 | ||
|
|
1bd88828e4 | ||
|
|
977fcbe6cd | ||
|
|
aa76a5c436 | ||
|
|
ea5fad428d | ||
|
|
0e1e27b35a | ||
|
|
d41f67a156 | ||
|
|
6f10dec808 | ||
|
|
9b79da2cdf | ||
|
|
c11b8519f1 | ||
|
|
b9e23a0b59 | ||
|
|
75d41b6fe5 | ||
|
|
4256590735 | ||
|
|
462fb05ea8 | ||
|
|
812dbfd836 | ||
|
|
cfca837777 | ||
|
|
ac5fb1948a | ||
|
|
22d8b73625 | ||
|
|
edde40c298 | ||
|
|
50ade92238 | ||
|
|
b598d00aef | ||
|
|
97bee17e4e | ||
|
|
6aa5c79332 | ||
|
|
7c5ce756f9 | ||
|
|
43d53a9ffa | ||
|
|
e6589bee5b | ||
|
|
9f94cad615 | ||
|
|
7778d9f773 | ||
|
|
564bd55147 | ||
|
|
a7ff806522 | ||
|
|
ac3b074d66 | ||
|
|
da949c2604 | ||
|
|
20a9775dc6 | ||
|
|
8ec19e058f | ||
|
|
a5c4549722 | ||
|
|
c7d302aa16 | ||
|
|
77ac565ff3 | ||
|
|
2e6f834a50 | ||
|
|
5b044cca9e | ||
|
|
2ab5623809 | ||
|
|
2d07f0d77c | ||
|
|
8ba7f9f702 | ||
|
|
460ac7a86b | ||
|
|
f93a1ab3eb | ||
|
|
6b02696aa8 | ||
|
|
25f20d6a85 | ||
|
|
8387964bae | ||
|
|
926278617f | ||
|
|
1933737a0c | ||
|
|
b3e5288bde | ||
|
|
ed6e966b2f | ||
|
|
9315fe36b6 | ||
|
|
41fbf5ba36 | ||
|
|
40db481453 | ||
|
|
1b5e295744 | ||
|
|
b42a52f7f6 | ||
|
|
8268df84d0 | ||
|
|
eb6fa5f1f1 | ||
|
|
025c8856ed |
@@ -1,23 +1,41 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
name: 问题反馈 / Bug report
|
||||
title: "[BUG] "
|
||||
description: 反馈你遇到的问题 / Report the issue you are experiencing
|
||||
labels: ["bug"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 以及 [常见问题](https://clash-verge-rev.github.io/faq/install/)
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保问题依然存在
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## 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.html) and [FAQ](https://clash-verge-rev.github.io/faq/install/)
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the problem still exists
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 / Describe the bug
|
||||
description: 详细清晰地描述你遇到的问题 / A clear and concise description of what the bug is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
label: 复现步骤 / To Reproduce
|
||||
description: 请提供复现问题的步骤 / Steps to reproduce the behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: Platform
|
||||
label: 操作系统 / OS
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
@@ -26,20 +44,13 @@ body:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: System Version
|
||||
placeholder: "e.g. macOS 10.15.7"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Software Version
|
||||
placeholder: "e.g. 1.4.3"
|
||||
label: 操作系统版本 / OS Version
|
||||
description: 请提供你的操作系统版本,Linux请额外提供桌面环境及窗口系统 / Please provide your OS version, for Linux, please also provide the desktop environment and window system
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log
|
||||
description: "Log file content or screenshot"
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional Information
|
||||
label: 日志 / Log
|
||||
description: 请提供完整或相关部分的Debug日志 / Please provide the complete or relevant part of the Debug log
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contact_links:
|
||||
- name: 讨论交流 / Communication
|
||||
url: https://t.me/clash_verge_rev
|
||||
about: 在 Telegram 群组中与其他用户讨论交流 / Communicate with other users in the Telegram group
|
||||
@@ -1,27 +1,35 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature]"
|
||||
name: 功能请求 / Feature request
|
||||
title: "[Feature] "
|
||||
description: 提出你的功能请求 / Propose your feature request
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
- type: markdown
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
value: |
|
||||
## 在提交问题之前,请确认以下事项:
|
||||
1. 请 **确保** 您已经查阅了 [Clash Verge Rev 官方文档](https://clash-verge-rev.github.io/guide.html) 确认软件不存在类似的功能
|
||||
2. 请 **确保** [已有的问题](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue) 中没有人提交过相似issue,否则请在已有的issue下进行讨论
|
||||
3. 请 **务必** 给issue填写一个简洁明了的标题,以便他人快速检索
|
||||
4. 请 **务必** 先下载 [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) 版本测试,确保该功能还未实现
|
||||
5. 请 **务必** 按照模板规范详细描述问题,否则issue将会被关闭
|
||||
## 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.html) to confirm that the software does not have similar functions
|
||||
2. Please make sure there is no similar issue in the [existing issues](https://github.com/clash-verge-rev/clash-verge-rev/issues?q=is%3Aissue), otherwise please discuss under the existing issue
|
||||
3. Please be sure to fill in a concise and clear title for the issue so that others can quickly search
|
||||
4. Please be sure to download the [Alpha](https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/alpha) version for testing to ensure that the function has not been implemented
|
||||
5. Please describe the problem in detail according to the template specification, otherwise the issue will be closed
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 / Feature description
|
||||
description: 详细清晰地描述你的功能请求 / A clear and concise description of what the feature is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
label: 使用场景 / Use case
|
||||
description: 请描述你的功能请求的使用场景 / Please describe the use case of your feature request
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternatives you've considered
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
|
||||
@@ -14,7 +14,7 @@ elif [ "$INPUT_TARGET" = "aarch64-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture arm64
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
||||
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libssl-dev:arm64 libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
apt-get install -y --no-install-recommends g++-aarch64-linux-gnu libc6-dev-arm64-cross libwebkit2gtk-4.0-dev:arm64 libgtk-3-dev:arm64 patchelf:arm64 librsvg2-dev:arm64 libayatana-appindicator3-dev:arm64
|
||||
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
|
||||
export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc
|
||||
export CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++
|
||||
@@ -24,12 +24,22 @@ elif [ "$INPUT_TARGET" = "armv7-unknown-linux-gnueabihf" ]; then
|
||||
dpkg --add-architecture armhf
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
||||
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libssl-dev:armhf libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
apt-get install -y --no-install-recommends g++-arm-linux-gnueabihf libc6-dev-armhf-cross libwebkit2gtk-4.0-dev:armhf libgtk-3-dev:armhf patchelf:armhf librsvg2-dev:armhf libayatana-appindicator3-dev:armhf
|
||||
export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc
|
||||
export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc
|
||||
export CXX_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
elif [ "$INPUT_TARGET" = "riscv64gc-unknown-linux-gnu" ]; then
|
||||
dpkg --add-architecture riscv64
|
||||
apt-get update
|
||||
apt-get install -y libncurses6:riscv64 libtinfo6:riscv64 linux-libc-dev:riscv64 libncursesw6:riscv64 libssl3:riscv64 libcups2:riscv64
|
||||
apt-get install -y --no-install-recommends g++-riscv64-linux-gnu libc6-dev-riscv64-cross libwebkit2gtk-4.0-dev:riscv64 libgtk-3-dev:riscv64 patchelf:riscv64 librsvg2-dev:riscv64 libayatana-appindicator3-dev:riscv64
|
||||
export CARGO_TARGET_RISCV64_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
|
||||
export CC_riscv64_unknown_linux_gnu=riscv64-linux-gnu-gcc
|
||||
export CXX_riscv64_unknown_linux_gnu=riscv64-linux-gnu-g++
|
||||
export PKG_CONFIG_PATH=/usr/lib/riscv64-linux-gnu/pkgconfig
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
else
|
||||
echo "Unknown target: $INPUT_TARGET" && exit 1
|
||||
fi
|
||||
|
||||
@@ -30,17 +30,6 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Apply Patch
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: |
|
||||
git config --global user.email "clash-verge-rev@github.io"
|
||||
git config --global user.name "clash-verge-rev"
|
||||
git am patches/support-windows-aarch64.patch
|
||||
|
||||
- name: Init Submodule
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -57,10 +46,10 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -100,6 +89,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -112,30 +103,56 @@ jobs:
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Get Version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
|
||||
- run: |
|
||||
cat > release.txt << 'EOF'
|
||||
### 我应该下载哪个版本?
|
||||
|
||||
- Windows x86_64架构: x64-setup.exe (不支持win7)
|
||||
- Windows arm64架构: arm64-setup.exe
|
||||
- MacOS intel芯片: x64.dmg
|
||||
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
|
||||
- Linux x64架构: amd64.AppImage/amd64.deb
|
||||
- Linux arm64架构: arm64.deb
|
||||
- Linux armv7架构: armhf.deb
|
||||
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
|
||||
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
|
||||
|
||||
### FAQ
|
||||
|
||||
- [FAQ](https://clash-verge-rev.github.io/faq/install/)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
- name: Upload Release
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body: "More new features are now supported."
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: alpha
|
||||
name: "Clash Verge Rev Alpha"
|
||||
body: "More new features are now supported."
|
||||
body_path: release.txt
|
||||
prerelease: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
|
||||
|
||||
update_tag:
|
||||
name: Update tag
|
||||
runs-on: ubuntu-latest
|
||||
@@ -143,21 +160,40 @@ jobs:
|
||||
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'
|
||||
## Clash Verge Rev Alpha
|
||||
### 我应该下载哪个版本?
|
||||
|
||||
- Windows x86_64架构: x64-setup.exe (不支持win7)
|
||||
- Windows arm64架构: arm64-setup.exe
|
||||
- MacOS intel芯片: x64.dmg
|
||||
- MacOS apple M芯片: aarch64.dmg (提示文件损坏看下面FAQ)
|
||||
- Linux x64架构: amd64.AppImage/amd64.deb
|
||||
- Linux arm64架构: arm64.deb
|
||||
- Linux armv7架构: armhf.deb
|
||||
- Windows 便携板 x86_64架构: x64_portable.zip (不推荐使用,无法自动更新)
|
||||
- Windows 便携板 arm64架构: arm64_portable.zip (不推荐使用,无法自动更新)
|
||||
|
||||
### FAQ
|
||||
|
||||
- [FAQ](https://clash-verge-rev.github.io/faq/install/)
|
||||
|
||||
Created at ${{ env.BUILDTIME }}.
|
||||
EOF
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -27,17 +27,6 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Apply Patch
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: |
|
||||
git config --global user.email "clash-verge-rev@github.io"
|
||||
git config --global user.name "clash-verge-rev"
|
||||
git am patches/support-windows-aarch64.patch
|
||||
|
||||
- name: Init Submodule
|
||||
if: matrix.target == 'aarch64-pc-windows-msvc'
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
@@ -54,10 +43,10 @@ jobs:
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: pnpm/action-setup@v3
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
version: 9
|
||||
run_install: false
|
||||
|
||||
- name: Pnpm install and check
|
||||
@@ -97,6 +86,8 @@ jobs:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
- os: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
@@ -109,11 +100,13 @@ jobs:
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Get Version
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release
|
||||
if: startsWith(matrix.target, 'x86_64')
|
||||
uses: softprops/action-gh-release@v1
|
||||
@@ -123,6 +116,7 @@ jobs:
|
||||
body: "More new features are now supported."
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage*
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
node_modules
|
||||
.pnpm-store
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
@@ -6,3 +7,4 @@ dist-ssr
|
||||
update.json
|
||||
scripts/_env.sh
|
||||
.vscode
|
||||
.tool-versions
|
||||
@@ -0,0 +1 @@
|
||||
nodejs 21.7.1
|
||||
@@ -9,33 +9,21 @@
|
||||
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a>.
|
||||
</h3>
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
Click on the corresponding link below to download the installation package. Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
请到发布页面下载对应的安装包:[Release page](https://github.com/clash-verge-rev/clash-verge-rev/releases)<br>
|
||||
Go to the [release page](https://github.com/clash-verge-rev/clash-verge-rev/releases) to download the corresponding installation package<br>
|
||||
Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
|
||||
|
||||
[[Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/Clash.Verge_1.5.5_x64-setup.exe)]
|
||||
[[Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/Clash.Verge_1.5.5_arm64-setup.exe)]
|
||||
### 安装说明和常见问题,请到[文档页](https://clash-verge-rev.github.io/)查看:[Doc](https://clash-verge-rev.github.io/)
|
||||
|
||||
[[macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/Clash.Verge_1.5.5_x64.dmg)]
|
||||
[[macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/Clash.Verge_1.5.5_aarch64.dmg)]
|
||||
---
|
||||
|
||||
[[Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/clash-verge_1.5.5_amd64.AppImage)]
|
||||
[[Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/clash-verge_1.5.5_amd64.deb)]
|
||||
[[Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.5.5/clash-verge_1.5.5_arm64.deb)]
|
||||
|
||||
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
||||
|
||||
Notes: If you could not start the app on Windows, please check that you have [Webview2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) installed.
|
||||
|
||||
## Features
|
||||
|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||
- Simple UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
#### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
### TG Group: [@clash_verge_rev](https://t.me/clash_verge_rev)
|
||||
|
||||
## Promotion
|
||||
|
||||
@@ -51,15 +39,17 @@ Notes: If you could not start the app on Windows, please check that you have [We
|
||||
- 解锁流媒体及 ChatGPT
|
||||
- 官网:https://狗狗加速.com
|
||||
|
||||
## Preview
|
||||
## Features
|
||||
|
||||

|
||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
||||
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
|
||||
- Improved UI and supports custom theme color.
|
||||
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
|
||||
- System proxy setting and guard.
|
||||
|
||||
### FAQ
|
||||
|
||||
#### 1. **macOS** "Clash Verge" is damaged and can't be opened
|
||||
|
||||
open the terminal and run `sudo xattr -r -d com.apple.quarantine /Applications/Clash\ Verge.app`
|
||||
Refer to [Doc FAQ Page](https://clash-verge-rev.github.io/faq/install/)
|
||||
|
||||
## Development
|
||||
|
||||
@@ -73,14 +63,6 @@ pnpm run check
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Todos
|
||||
|
||||
> This keng is a little big...
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This is a learning project for Rust practice.
|
||||
|
||||
## Contributions
|
||||
|
||||
Issue and PR welcome!
|
||||
|
||||
@@ -1,12 +1,142 @@
|
||||
## v1.5.5
|
||||
## v1.6.1
|
||||
|
||||
### Features
|
||||
|
||||
- 全新 UI 界面 (by @Amnesiash)
|
||||
- 鼠标悬浮显示当前订阅的名称 [#938](https://github.com/clash-verge-rev/clash-verge-rev/pull/938)
|
||||
- 日志过滤支持正则表达式 [#959](https://github.com/clash-verge-rev/clash-verge-rev/pull/959)
|
||||
- 更新 Clash 内核到 1.18.4
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
||||
- 窗口最大化图标调整 [#924](https://github.com/clash-verge-rev/clash-verge-rev/pull/924)
|
||||
- 修改 MacOS 托盘点击行为(左键菜单,右键点击事件)
|
||||
- 修复 MacOS 服务模式安装失败的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.6.0
|
||||
|
||||
### Features
|
||||
|
||||
- Meta(mihomo)内核回退 1.18.1(当前新版内核 hy2 协议有 bug,等修复后更新)
|
||||
- 多处界面细节调整 [#724](https://github.com/clash-verge-rev/clash-verge-rev/pull/724) [#799](https://github.com/clash-verge-rev/clash-verge-rev/pull/799) [#900](https://github.com/clash-verge-rev/clash-verge-rev/pull/900) [#901](https://github.com/clash-verge-rev/clash-verge-rev/pull/901)
|
||||
- Linux 下新增服务模式
|
||||
- 新增订阅卡片右键可以打开机场首页
|
||||
- url-test 支持手动选择、节点组 fixed 节点使用角标展示 [#840](https://github.com/clash-verge-rev/clash-verge-rev/pull/840)
|
||||
- Clash 配置、Merge 配置提供 JSON Schema 语法支持、连接界面调整 [#887](https://github.com/clash-verge-rev/clash-verge-rev/pull/887)
|
||||
- 修改 Merge 配置文件默认内容 [#889](https://github.com/clash-verge-rev/clash-verge-rev/pull/889)
|
||||
- 修改 tun 模式默认 mtu 为 1500,老版本升级,需在 tun 模式设置下“重置为默认值”。
|
||||
- 使用 npm 安装 meta-json-schema [#895](https://github.com/clash-verge-rev/clash-verge-rev/pull/895)
|
||||
- 更新部分翻译 [#904](https://github.com/clash-verge-rev/clash-verge-rev/pull/904)
|
||||
- 支持 ico 格式的任务栏图标
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 Linux KDE 环境下系统代理无法开启的问题
|
||||
- 修复延迟检测动画问题
|
||||
- 窗口最大化图标调整 [#816](https://github.com/clash-verge-rev/clash-verge-rev/pull/816)
|
||||
- 修复 Windows 某些情况下无法安装服务模式 [#822](https://github.com/clash-verge-rev/clash-verge-rev/pull/822)
|
||||
- UI 细节修复 [#821](https://github.com/clash-verge-rev/clash-verge-rev/pull/821)
|
||||
- 修复使用默认编辑器打开配置文件
|
||||
- 修复内核文件在特定目录也可以更新的问题 [#857](https://github.com/clash-verge-rev/clash-verge-rev/pull/857)
|
||||
- 修复服务模式的安装目录问题
|
||||
- 修复删除配置文件的“更新间隔”出现的问题 [#907](https://github.com/clash-verge-rev/clash-verge-rev/issues/907)
|
||||
|
||||
### 已知问题(历史遗留问题,暂未找到有效解决方案)
|
||||
|
||||
- MacOS M 芯片下服务模式无法安装;临时解决方案:在内核 ⚙️ 下,手动授权,再打开 tun 模式。
|
||||
- MacOS 下如果删除过网络配置,会导致无法正常打开系统代理;临时解决方案:使用浏览器代理插件或手动配置系统代理。
|
||||
- Window 拨号连接下无法正确识别并打开系统代理;临时解决方案:使用浏览器代理插件或使用 tun 模式。
|
||||
|
||||
---
|
||||
|
||||
## v1.5.11
|
||||
|
||||
### Features
|
||||
|
||||
- Meta(mihomo)内核更新 1.18.2
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 升级图标无法点击的问题
|
||||
- 卸载时检查安装目录是否为空
|
||||
- 代理界面图标重合的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.10
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 Linux 托盘菜单显示
|
||||
- 添加透明代理端口设置
|
||||
- 删除订阅前确认
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 删除 MacOS 程序坞图标
|
||||
- Windows 下 service 日志没有清理
|
||||
- MacOS 无法开启系统代理
|
||||
|
||||
---
|
||||
|
||||
## v1.5.9
|
||||
|
||||
### Features
|
||||
|
||||
- 缓存代理组图标
|
||||
- 使用`boa_engine` 代替 `rquickjs`
|
||||
- 支持 Linux armv7
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- Windows 首次安装无法点击
|
||||
- Windows 触摸屏无法拖动
|
||||
- 规则列表 `REJECT-DROP` 颜色
|
||||
- MacOS Dock 栏不显示图标
|
||||
- MacOS 自定义字体无效
|
||||
- 避免使用空 UA 拉取订阅
|
||||
|
||||
---
|
||||
|
||||
## v1.5.8
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 UI 细节
|
||||
- Linux 绘制窗口圆角
|
||||
- 开放 DevTools
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
- 修复 MacOS 下开启 Tun 内核崩溃的问题
|
||||
|
||||
---
|
||||
|
||||
## v1.5.7
|
||||
|
||||
### Features
|
||||
|
||||
- 优化 UI 各种细节
|
||||
- 提供菜单栏图标样式切换选项(单色/彩色/禁用)
|
||||
- 添加自动检查更新开关
|
||||
- MacOS 开启 Tun 模式自动修改 DNS
|
||||
- 调整可拖动区域(尝试修复触摸屏无法拖动的问题)
|
||||
|
||||
---
|
||||
|
||||
## v1.5.6
|
||||
|
||||
### Features
|
||||
|
||||
- 全新专属 Verge rev UI 界面 (by @Amnesiash) 及细节调整
|
||||
- 提供允许无效证书的开关
|
||||
- 删除不必要的快捷键
|
||||
- Provider 更新添加动画
|
||||
- Merge 支持 Provider
|
||||
- 更换订阅框的粘贴按钮,删除默认的"Remote File" Profile 名称
|
||||
- 链接菜单添加节点显示
|
||||
|
||||
### Bugs Fixes
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 6.7 MiB |
|
After Width: | Height: | Size: 576 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.5.5",
|
||||
"version": "1.6.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
@@ -19,45 +19,48 @@
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
"@dnd-kit/sortable": "^8.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@mui/icons-material": "^5.15.5",
|
||||
"@mui/icons-material": "^5.15.15",
|
||||
"@mui/lab": "5.0.0-alpha.149",
|
||||
"@mui/material": "^5.15.5",
|
||||
"@mui/x-data-grid": "^6.18.7",
|
||||
"@tauri-apps/api": "^1.5.3",
|
||||
"ahooks": "^3.7.8",
|
||||
"axios": "^1.6.5",
|
||||
"@mui/material": "^5.15.15",
|
||||
"@mui/x-data-grid": "^6.19.11",
|
||||
"@tauri-apps/api": "^1.5.4",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"ahooks": "^3.7.11",
|
||||
"axios": "^1.6.8",
|
||||
"dayjs": "1.11.5",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next": "^23.11.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"nanoid": "^5.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"meta-json-schema": "1.18.4-beta2",
|
||||
"monaco-editor": "^0.47.0",
|
||||
"monaco-yaml": "^5.1.1",
|
||||
"nanoid": "^5.0.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-hook-form": "^7.51.3",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-router-dom": "^6.21.2",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-router-dom": "^6.23.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-virtuoso": "^4.6.2",
|
||||
"react-virtuoso": "^4.7.10",
|
||||
"recoil": "^0.7.7",
|
||||
"snarkdown": "^2.0.0",
|
||||
"swr": "^1.3.0",
|
||||
"tar": "^6.2.0"
|
||||
"tar": "^6.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/github": "^5.1.1",
|
||||
"@tauri-apps/cli": "^1.5.9",
|
||||
"@tauri-apps/cli": "^1.5.12",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"adm-zip": "^0.5.10",
|
||||
"adm-zip": "^0.5.12",
|
||||
"cross-env": "^7.0.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
@@ -65,9 +68,9 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.3.1",
|
||||
"sass": "^1.70.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.11",
|
||||
"sass": "^1.75.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.10",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
},
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
From 871c9a6d1ed014c93da2436a437df03734e9f76c Mon Sep 17 00:00:00 2001
|
||||
From: MystiPanda <mystipanda@proton.me>
|
||||
Date: Sun, 10 Dec 2023 19:47:45 +0800
|
||||
Subject: [PATCH] feat: Support windows aarch64
|
||||
|
||||
---
|
||||
.gitmodules | 3 +
|
||||
src-tauri/Cargo.toml | 2 +-
|
||||
src-tauri/quick-rs | 1 +
|
||||
src-tauri/src/enhance/script.rs | 130 +++++++++++++++++++-------------
|
||||
4 files changed, 81 insertions(+), 55 deletions(-)
|
||||
create mode 100644 .gitmodules
|
||||
create mode 160000 src-tauri/quick-rs
|
||||
|
||||
diff --git a/.gitmodules b/.gitmodules
|
||||
new file mode 100644
|
||||
index 0000000..2eda7e4
|
||||
--- /dev/null
|
||||
+++ b/.gitmodules
|
||||
@@ -0,0 +1,3 @@
|
||||
+[submodule "src-tauri/quick-rs"]
|
||||
+ path = src-tauri/quick-rs
|
||||
+ url = https://github.com/clash-verge-rev/quick-rs.git
|
||||
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
|
||||
index 2f1a3be..d67f6ed 100644
|
||||
--- a/src-tauri/Cargo.toml
|
||||
+++ b/src-tauri/Cargo.toml
|
||||
@@ -25,7 +25,6 @@ log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
once_cell = "1.18"
|
||||
@@ -33,6 +32,7 @@ port_scanner = "0.1.5"
|
||||
delay_timer = "0.11.5"
|
||||
parking_lot = "0.12"
|
||||
percent-encoding = "2.3.1"
|
||||
+quick-rs = { path = "quick-rs" }
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
diff --git a/src-tauri/quick-rs b/src-tauri/quick-rs
|
||||
new file mode 160000
|
||||
index 0000000..78277c4
|
||||
--- /dev/null
|
||||
+++ b/src-tauri/quick-rs
|
||||
@@ -0,0 +1 @@
|
||||
+Subproject commit 78277c4509c64f18c0fc5c9f2b84671de7c83343
|
||||
diff --git a/src-tauri/src/enhance/script.rs b/src-tauri/src/enhance/script.rs
|
||||
index 30a922f..d47dc33 100644
|
||||
--- a/src-tauri/src/enhance/script.rs
|
||||
+++ b/src-tauri/src/enhance/script.rs
|
||||
@@ -3,61 +3,83 @@ use anyhow::Result;
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
- use rquickjs::{function::Func, Context, Runtime};
|
||||
- use std::sync::{Arc, Mutex};
|
||||
-
|
||||
- let runtime = Runtime::new().unwrap();
|
||||
- let context = Context::full(&runtime).unwrap();
|
||||
- let outputs = Arc::new(Mutex::new(vec![]));
|
||||
-
|
||||
- let copy_outputs = outputs.clone();
|
||||
- let result = context.with(|ctx| -> Result<Mapping> {
|
||||
- ctx.globals().set(
|
||||
- "__verge_log__",
|
||||
- Func::from(move |level: String, data: String| {
|
||||
- let mut out = copy_outputs.lock().unwrap();
|
||||
- out.push((level, data));
|
||||
- }),
|
||||
- )?;
|
||||
-
|
||||
- ctx.eval(
|
||||
- r#"var console = Object.freeze({
|
||||
- log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
- info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
- error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
- debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
- });"#,
|
||||
- )?;
|
||||
-
|
||||
- let config = use_lowercase(config.clone());
|
||||
- let config_str = serde_json::to_string(&config)?;
|
||||
-
|
||||
- let code = format!(
|
||||
- r#"try{{
|
||||
+ use quick_rs::{context::Context, function::Function, module::Module, runtime::Runtime};
|
||||
+
|
||||
+ let config = use_lowercase(config.clone());
|
||||
+ let config_str = serde_json::to_string(&config)?;
|
||||
+
|
||||
+ let runtime = Runtime::new();
|
||||
+ let context = Context::from(&runtime);
|
||||
+
|
||||
+ let code = format!(
|
||||
+ r#"
|
||||
+ let output = [];
|
||||
+
|
||||
+ function __verge_log__(type, data) {{
|
||||
+ output.push([type, data]);
|
||||
+ }}
|
||||
+
|
||||
+ var console = Object.freeze({{
|
||||
+ log(data) {{ __verge_log__("log", JSON.stringify(data)) }},
|
||||
+ info(data) {{ __verge_log__("info", JSON.stringify(data)) }},
|
||||
+ error(data) {{ __verge_log__("error", JSON.stringify(data)) }},
|
||||
+ debug(data) {{ __verge_log__("debug", JSON.stringify(data)) }},
|
||||
+ }});
|
||||
+
|
||||
{script};
|
||||
- JSON.stringify(main({config_str})||'')
|
||||
- }} catch(err) {{
|
||||
- `__error_flag__ ${{err.toString()}}`
|
||||
- }}"#
|
||||
- );
|
||||
- let result: String = ctx.eval(code.as_str())?;
|
||||
- if result.starts_with("__error_flag__") {
|
||||
- anyhow::bail!(result[15..].to_owned());
|
||||
- }
|
||||
- if result == "\"\"" {
|
||||
- anyhow::bail!("main function should return object");
|
||||
- }
|
||||
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
- });
|
||||
-
|
||||
- let mut out = outputs.lock().unwrap();
|
||||
- match result {
|
||||
- Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
- Err(err) => {
|
||||
- out.push(("exception".into(), err.to_string()));
|
||||
- Ok((config, out.to_vec()))
|
||||
- }
|
||||
- }
|
||||
+
|
||||
+ export function _main(){{
|
||||
+ try{{
|
||||
+ let result = JSON.stringify(main({config_str})||"");
|
||||
+ return JSON.stringify({{result, output}});
|
||||
+ }} catch(err) {{
|
||||
+ output.push(["exception", err.toString()]);
|
||||
+ return JSON.stringify({{result: "__error__", output}});
|
||||
+ }}
|
||||
+ }}
|
||||
+ "#
|
||||
+ );
|
||||
+ let value = context.eval_module(&code, "_main")?;
|
||||
+ let module = Module::new(value)?;
|
||||
+ let value = module.get("_main")?;
|
||||
+ let function = Function::new(value)?;
|
||||
+ let value = function.call(vec![])?;
|
||||
+ let result = serde_json::from_str::<serde_json::Value>(&value.to_string()?)?;
|
||||
+ result
|
||||
+ .as_object()
|
||||
+ .map(|obj| {
|
||||
+ let result = obj.get("result").unwrap().as_str().unwrap();
|
||||
+ let output = obj.get("output").unwrap();
|
||||
+
|
||||
+ let mut out = output
|
||||
+ .as_array()
|
||||
+ .unwrap()
|
||||
+ .iter()
|
||||
+ .map(|item| {
|
||||
+ let item = item.as_array().unwrap();
|
||||
+ (
|
||||
+ item[0].as_str().unwrap().into(),
|
||||
+ item[1].as_str().unwrap().into(),
|
||||
+ )
|
||||
+ })
|
||||
+ .collect::<Vec<_>>();
|
||||
+ if result.is_empty() {
|
||||
+ anyhow::bail!("main function should return object");
|
||||
+ }
|
||||
+ if result == "__error__" {
|
||||
+ return Ok((config, out.to_vec()));
|
||||
+ }
|
||||
+ let result = serde_json::from_str::<Mapping>(result);
|
||||
+
|
||||
+ match result {
|
||||
+ Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
+ Err(err) => {
|
||||
+ out.push(("exception".into(), err.to_string()));
|
||||
+ Ok((config, out.to_vec()))
|
||||
+ }
|
||||
+ }
|
||||
+ })
|
||||
+ .unwrap_or_else(|| anyhow::bail!("Unknown result"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -21,6 +21,7 @@ const PLATFORM_MAP = {
|
||||
"i686-unknown-linux-gnu": "linux",
|
||||
"aarch64-unknown-linux-gnu": "linux",
|
||||
"armv7-unknown-linux-gnueabihf": "linux",
|
||||
"riscv64gc-unknown-linux-gnu": "linux",
|
||||
"loongarch64-unknown-linux-gnu": "linux",
|
||||
};
|
||||
const ARCH_MAP = {
|
||||
@@ -33,6 +34,7 @@ const ARCH_MAP = {
|
||||
"i686-unknown-linux-gnu": "ia32",
|
||||
"aarch64-unknown-linux-gnu": "arm64",
|
||||
"armv7-unknown-linux-gnueabihf": "arm",
|
||||
"riscv64gc-unknown-linux-gnu": "riscv64",
|
||||
"loongarch64-unknown-linux-gnu": "loong64",
|
||||
};
|
||||
|
||||
@@ -65,6 +67,7 @@ const META_ALPHA_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -111,6 +114,7 @@ const META_MAP = {
|
||||
"linux-ia32": "mihomo-linux-386",
|
||||
"linux-arm64": "mihomo-linux-arm64",
|
||||
"linux-arm": "mihomo-linux-armv7",
|
||||
"linux-riscv64": "mihomo-linux-riscv64",
|
||||
"linux-loong64": "mihomo-linux-loong64",
|
||||
};
|
||||
|
||||
@@ -349,26 +353,62 @@ const resolvePlugin = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// service chmod
|
||||
const resolveServicePermission = async () => {
|
||||
const serviceExecutables = [
|
||||
"clash-verge-service",
|
||||
"install-service",
|
||||
"uninstall-service",
|
||||
];
|
||||
const resDir = path.join(cwd, "src-tauri/resources");
|
||||
for (let f of serviceExecutables) {
|
||||
const targetPath = path.join(resDir, f);
|
||||
if (await fs.pathExists(targetPath)) {
|
||||
execSync(`chmod 755 ${targetPath}`);
|
||||
console.log(`[INFO]: "${targetPath}" chmod finished`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* main
|
||||
*/
|
||||
|
||||
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
|
||||
|
||||
const resolveService = () =>
|
||||
const resolveService = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "clash-verge-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service.exe`,
|
||||
file: "clash-verge-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`,
|
||||
});
|
||||
const resolveInstall = () =>
|
||||
};
|
||||
|
||||
const resolveInstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "install-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/install-service.exe`,
|
||||
file: "install-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/install-service${ext}`,
|
||||
});
|
||||
const resolveUninstall = () =>
|
||||
};
|
||||
|
||||
const resolveUninstall = () => {
|
||||
let ext = platform === "win32" ? ".exe" : "";
|
||||
resolveResource({
|
||||
file: "uninstall-service.exe",
|
||||
downloadURL: `${SERVICE_URL}/uninstall-service.exe`,
|
||||
file: "uninstall-service" + ext,
|
||||
downloadURL: `${SERVICE_URL}/uninstall-service${ext}`,
|
||||
});
|
||||
};
|
||||
|
||||
const resolveSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "set_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/set_dns.sh`,
|
||||
});
|
||||
const resolveUnSetDnsScript = () =>
|
||||
resolveResource({
|
||||
file: "unset_dns.sh",
|
||||
downloadURL: `https://github.com/clash-verge-rev/set-dns-script/releases/download/script/unset_dns.sh`,
|
||||
});
|
||||
const resolveMmdb = () =>
|
||||
resolveResource({
|
||||
@@ -406,9 +446,11 @@ const tasks = [
|
||||
retry: 5,
|
||||
},
|
||||
{ name: "plugin", func: resolvePlugin, retry: 5, winOnly: true },
|
||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },
|
||||
{ name: "service", func: resolveService, retry: 5 },
|
||||
{ name: "install", func: resolveInstall, retry: 5 },
|
||||
{ name: "uninstall", func: resolveUninstall, retry: 5 },
|
||||
{ name: "set_dns_script", func: resolveSetDnsScript, retry: 5 },
|
||||
{ name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 },
|
||||
{ name: "mmdb", func: resolveMmdb, retry: 5 },
|
||||
{ name: "geosite", func: resolveGeosite, retry: 5 },
|
||||
{ name: "geoip", func: resolveGeoIP, retry: 5 },
|
||||
@@ -418,12 +460,20 @@ const tasks = [
|
||||
retry: 5,
|
||||
winOnly: true,
|
||||
},
|
||||
{
|
||||
name: "service_chmod",
|
||||
func: resolveServicePermission,
|
||||
retry: 1,
|
||||
unixOnly: true,
|
||||
},
|
||||
];
|
||||
|
||||
async function runTask() {
|
||||
const task = tasks.shift();
|
||||
if (!task) return;
|
||||
if (task.winOnly && process.platform !== "win32") return runTask();
|
||||
if (task.winOnly && platform !== "win32") return runTask();
|
||||
if (task.linuxOnly && platform !== "linux") return runTask();
|
||||
if (task.unixOnly && platform === "win32") return runTask();
|
||||
|
||||
for (let i = 0; i < task.retry; i++) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "1.5.5"
|
||||
version = "1.6.1"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
@@ -14,18 +14,16 @@ tauri-build = { version = "1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
warp = "0.3"
|
||||
which = "6.0.0"
|
||||
anyhow = "1.0"
|
||||
dirs = "5.0"
|
||||
open = "5.0"
|
||||
log = "0.4"
|
||||
ctrlc = "3.4"
|
||||
dunce = "1.0"
|
||||
log4rs = "1"
|
||||
nanoid = "0.4"
|
||||
chrono = "0.4"
|
||||
sysinfo = "0.30"
|
||||
rquickjs = "0.3" # 高版本不支持 Linux aarch64
|
||||
boa_engine = "0.18"
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
once_cell = "1.18"
|
||||
@@ -36,17 +34,18 @@ percent-encoding = "2.3.1"
|
||||
window-shadows = { version = "0.2" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||
auto-launch = { git="https://github.com/zzzgydi/auto-launch", branch = "main" }
|
||||
tauri = { version = "1.5", features = [ "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||
tauri = { version = "1.6", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
runas = "=1.0.0" # 高版本会返回错误 Status
|
||||
runas = "=1.2.0"
|
||||
deelevate = "0.2.0"
|
||||
winreg = "0.52.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
users = "0.11.0"
|
||||
#openssl
|
||||
|
||||
[features]
|
||||
|
||||
@@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
||||
use serde_yaml::Mapping;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::api;
|
||||
use tauri::{api, Manager};
|
||||
type CmdResult<T = ()> = Result<T, String>;
|
||||
|
||||
#[tauri::command]
|
||||
@@ -65,6 +65,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
let _ = handle::Handle::update_systray_part();
|
||||
Config::profiles().apply();
|
||||
wrap_err!(Config::profiles().data().save_file())?;
|
||||
Ok(())
|
||||
@@ -275,6 +276,23 @@ pub fn get_app_dir() -> CmdResult<String> {
|
||||
Ok(app_home_dir)
|
||||
}
|
||||
|
||||
#[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_cache_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_cache_dir);
|
||||
}
|
||||
if !icon_path.exists() {
|
||||
let response = wrap_err!(reqwest::get(url).await)?;
|
||||
|
||||
let mut file = wrap_err!(std::fs::File::create(&icon_path))?;
|
||||
|
||||
let content = wrap_err!(response.bytes().await)?;
|
||||
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
|
||||
}
|
||||
Ok(icon_path.to_string_lossy().to_string())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
let file_path = std::path::Path::new(&path);
|
||||
@@ -282,9 +300,17 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
if !icon_dir.exists() {
|
||||
let _ = std::fs::create_dir_all(&icon_dir);
|
||||
}
|
||||
let dest_path = icon_dir.join(name);
|
||||
let ext = match file_path.extension() {
|
||||
Some(e) => e.to_string_lossy().to_string(),
|
||||
None => "ico".to_string(),
|
||||
};
|
||||
|
||||
let png_dest_path = icon_dir.join(format!("{name}.png"));
|
||||
let ico_dest_path = icon_dir.join(format!("{name}.ico"));
|
||||
let dest_path = icon_dir.join(format!("{name}.{ext}"));
|
||||
if file_path.exists() {
|
||||
std::fs::remove_file(png_dest_path).unwrap_or_default();
|
||||
std::fs::remove_file(ico_dest_path).unwrap_or_default();
|
||||
match std::fs::copy(file_path, &dest_path) {
|
||||
Ok(_) => Ok(dest_path.to_string_lossy().to_string()),
|
||||
Err(err) => Err(err.to_string()),
|
||||
@@ -294,6 +320,17 @@ pub fn copy_icon_file(path: String, name: String) -> CmdResult<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_devtools(app_handle: tauri::AppHandle) {
|
||||
if let Some(window) = app_handle.get_window("main") {
|
||||
if !window.is_devtools_open() {
|
||||
window.open_devtools();
|
||||
} else {
|
||||
window.close_devtools();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
let _ = resolve::save_window_size_position(&app_handle, true);
|
||||
@@ -303,42 +340,23 @@ pub fn exit_app(app_handle: tauri::AppHandle) {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
use crate::core::win_service;
|
||||
use crate::core::service;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult<win_service::JsonResponse> {
|
||||
wrap_err!(win_service::check_service().await)
|
||||
pub async fn check_service() -> CmdResult<service::JsonResponse> {
|
||||
wrap_err!(service::check_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult {
|
||||
wrap_err!(win_service::install_service().await)
|
||||
wrap_err!(service::install_service().await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
wrap_err!(win_service::uninstall_service().await)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub mod service {
|
||||
use super::*;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn install_service() -> CmdResult {
|
||||
Ok(())
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn uninstall_service() -> CmdResult {
|
||||
Ok(())
|
||||
wrap_err!(service::uninstall_service().await)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,11 @@ impl IClashTemp {
|
||||
tun.insert("strict-route".into(), false.into());
|
||||
tun.insert("auto-detect-interface".into(), true.into());
|
||||
tun.insert("dns-hijack".into(), vec!["any:53"].into());
|
||||
tun.insert("mtu".into(), 9000.into());
|
||||
|
||||
tun.insert("mtu".into(), 1500.into());
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
map.insert("redir-port".into(), 7895.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
map.insert("tproxy-port".into(), 7896.into());
|
||||
map.insert("mixed-port".into(), 7897.into());
|
||||
map.insert("socks-port".into(), 7898.into());
|
||||
map.insert("port".into(), 7899.into());
|
||||
@@ -54,11 +57,18 @@ impl IClashTemp {
|
||||
}
|
||||
|
||||
fn guard(mut config: Mapping) -> Mapping {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let redir_port = Self::guard_redir_port(&config);
|
||||
#[cfg(target_os = "linux")]
|
||||
let tproxy_port = Self::guard_tproxy_port(&config);
|
||||
let mixed_port = Self::guard_mixed_port(&config);
|
||||
let socks_port = Self::guard_socks_port(&config);
|
||||
let port = Self::guard_port(&config);
|
||||
let ctrl = Self::guard_server_ctrl(&config);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
config.insert("redir-port".into(), redir_port.into());
|
||||
#[cfg(target_os = "linux")]
|
||||
config.insert("tproxy-port".into(), tproxy_port.into());
|
||||
config.insert("mixed-port".into(), mixed_port.into());
|
||||
config.insert("socks-port".into(), socks_port.into());
|
||||
config.insert("port".into(), port.into());
|
||||
@@ -110,6 +120,37 @@ impl IClashTemp {
|
||||
}),
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("redir-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7895);
|
||||
if port == 0 {
|
||||
port = 7895;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("tproxy-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7896);
|
||||
if port == 0 {
|
||||
port = 7896;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
|
||||
@@ -46,6 +46,10 @@ pub struct PrfItem {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub option: Option<PrfOption>,
|
||||
|
||||
/// profile web page url
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub home: Option<String>,
|
||||
|
||||
/// the file data
|
||||
#[serde(skip)]
|
||||
pub file_data: Option<String>,
|
||||
@@ -161,6 +165,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
home: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(file_data.unwrap_or(tmpl::ITEM_LOCAL.into())),
|
||||
})
|
||||
@@ -291,6 +296,14 @@ impl PrfItem {
|
||||
},
|
||||
};
|
||||
|
||||
let home = match header.get("profile-web-page-url") {
|
||||
Some(value) => {
|
||||
let str_value = value.to_str().unwrap_or("");
|
||||
Some(str_value.to_string())
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let uid = help::get_uid("r");
|
||||
let file = format!("{uid}.yaml");
|
||||
let name = name.unwrap_or(filename.unwrap_or("Remote File".into()));
|
||||
@@ -317,6 +330,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra,
|
||||
option,
|
||||
home,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(data.into()),
|
||||
})
|
||||
@@ -338,6 +352,7 @@ impl PrfItem {
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
home: None,
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_MERGE.into()),
|
||||
})
|
||||
@@ -356,6 +371,7 @@ impl PrfItem {
|
||||
desc: Some(desc),
|
||||
file: Some(file),
|
||||
url: None,
|
||||
home: None,
|
||||
selected: None,
|
||||
extra: None,
|
||||
option: None,
|
||||
|
||||
@@ -211,7 +211,7 @@ impl IProfiles {
|
||||
if each.uid == some_uid {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
|
||||
each.home = item.home;
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
|
||||
@@ -42,6 +42,9 @@ pub struct IVerge {
|
||||
/// common tray icon
|
||||
pub common_tray_icon: Option<bool>,
|
||||
|
||||
/// menu icon
|
||||
pub menu_icon: Option<String>,
|
||||
|
||||
/// sysproxy tray icon
|
||||
pub sysproxy_tray_icon: Option<bool>,
|
||||
|
||||
@@ -90,6 +93,9 @@ pub struct IVerge {
|
||||
/// 切换代理时自动关闭连接
|
||||
pub auto_close_connection: Option<bool>,
|
||||
|
||||
/// 是否自动检查更新
|
||||
pub auto_check_update: Option<bool>,
|
||||
|
||||
/// 默认的延迟测试连接
|
||||
pub default_latency_test: Option<String>,
|
||||
|
||||
@@ -120,7 +126,13 @@ pub struct IVerge {
|
||||
/// 是否启用随机端口
|
||||
pub enable_random_port: Option<bool>,
|
||||
|
||||
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||
/// verge 的各种 port 用于覆盖 clash 的各种 port
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub verge_redir_port: Option<u16>,
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub verge_tproxy_port: Option<u16>,
|
||||
|
||||
pub verge_mixed_port: Option<u16>,
|
||||
|
||||
pub verge_socks_port: Option<u16>,
|
||||
@@ -176,6 +188,7 @@ impl IVerge {
|
||||
traffic_graph: Some(true),
|
||||
enable_memory_usage: Some(true),
|
||||
enable_group_icon: Some(true),
|
||||
menu_icon: Some("monochrome".into()),
|
||||
common_tray_icon: Some(false),
|
||||
sysproxy_tray_icon: Some(false),
|
||||
tun_tray_icon: Some(false),
|
||||
@@ -183,12 +196,17 @@ impl IVerge {
|
||||
enable_silent_start: Some(false),
|
||||
enable_system_proxy: Some(false),
|
||||
enable_random_port: Some(false),
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
verge_redir_port: Some(7895),
|
||||
#[cfg(target_os = "linux")]
|
||||
verge_tproxy_port: Some(7896),
|
||||
verge_mixed_port: Some(7897),
|
||||
verge_socks_port: Some(7898),
|
||||
verge_port: Some(7899),
|
||||
enable_proxy_guard: Some(false),
|
||||
proxy_guard_duration: Some(30),
|
||||
auto_close_connection: Some(true),
|
||||
auto_check_update: Some(true),
|
||||
enable_builtin_enhanced: Some(true),
|
||||
auto_log_clean: Some(3),
|
||||
..Self::default()
|
||||
@@ -221,6 +239,7 @@ impl IVerge {
|
||||
patch!(traffic_graph);
|
||||
patch!(enable_memory_usage);
|
||||
patch!(enable_group_icon);
|
||||
patch!(menu_icon);
|
||||
patch!(common_tray_icon);
|
||||
patch!(sysproxy_tray_icon);
|
||||
patch!(tun_tray_icon);
|
||||
@@ -230,6 +249,10 @@ impl IVerge {
|
||||
patch!(enable_auto_launch);
|
||||
patch!(enable_silent_start);
|
||||
patch!(enable_random_port);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
patch!(verge_redir_port);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch!(verge_tproxy_port);
|
||||
patch!(verge_mixed_port);
|
||||
patch!(verge_socks_port);
|
||||
patch!(verge_port);
|
||||
@@ -244,6 +267,7 @@ impl IVerge {
|
||||
patch!(hotkeys);
|
||||
|
||||
patch!(auto_close_connection);
|
||||
patch!(auto_check_update);
|
||||
patch!(default_latency_test);
|
||||
patch!(default_latency_timeout);
|
||||
patch!(enable_builtin_enhanced);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::service;
|
||||
use super::{clash_api, logger::Logger};
|
||||
use crate::log_err;
|
||||
use crate::{config::*, utils::dirs};
|
||||
@@ -93,10 +94,9 @@ impl CoreManager {
|
||||
None => false,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
log::debug!(target: "app", "stop the core by service");
|
||||
log_err!(super::win_service::stop_core_by_service().await);
|
||||
log_err!(service::stop_core_by_service().await);
|
||||
should_kill = true;
|
||||
}
|
||||
|
||||
@@ -105,32 +105,27 @@ impl CoreManager {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use super::win_service;
|
||||
// 服务模式
|
||||
let enable = { Config::verge().latest().enable_service_mode };
|
||||
let enable = enable.unwrap_or(false);
|
||||
|
||||
// 服务模式
|
||||
let enable = { Config::verge().latest().enable_service_mode };
|
||||
let enable = enable.unwrap_or(false);
|
||||
*self.use_service_mode.lock() = enable;
|
||||
|
||||
*self.use_service_mode.lock() = enable;
|
||||
if enable {
|
||||
// 服务模式启动失败就直接运行sidecar
|
||||
log::debug!(target: "app", "try to run core in service mode");
|
||||
|
||||
if enable {
|
||||
// 服务模式启动失败就直接运行sidecar
|
||||
log::debug!(target: "app", "try to run core in service mode");
|
||||
|
||||
match (|| async {
|
||||
win_service::check_service().await?;
|
||||
win_service::run_core_by_service(&config_path).await
|
||||
})()
|
||||
.await
|
||||
{
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
// 修改这个值,免得stop出错
|
||||
*self.use_service_mode.lock() = false;
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
match (|| async {
|
||||
service::check_service().await?;
|
||||
service::run_core_by_service(&config_path).await
|
||||
})()
|
||||
.await
|
||||
{
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
// 修改这个值,免得stop出错
|
||||
*self.use_service_mode.lock() = false;
|
||||
log::error!(target: "app", "{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +200,6 @@ impl CoreManager {
|
||||
/// 重启内核
|
||||
pub fn recover_core(&'static self) -> Result<()> {
|
||||
// 服务模式不管
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -238,11 +232,10 @@ impl CoreManager {
|
||||
|
||||
/// 停止核心运行
|
||||
pub fn stop_core(&self) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
if *self.use_service_mode.lock() {
|
||||
log::debug!(target: "app", "stop the core by service");
|
||||
tauri::async_runtime::block_on(async move {
|
||||
log_err!(super::win_service::stop_core_by_service().await);
|
||||
log_err!(service::stop_core_by_service().await);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub fn grant_permission(core: String) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let output = {
|
||||
let path = path.replace(' ', "\\ "); // 避免路径中有空格
|
||||
let shell = format!("setcap cap_net_bind_service,cap_net_admin=+ep {path}");
|
||||
let shell = format!("setcap cap_net_bind_service,cap_net_admin,cap_dac_override=+ep {path}");
|
||||
|
||||
let sudo = match Command::new("which").arg("pkexec").output() {
|
||||
Ok(output) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ pub mod manager;
|
||||
pub mod sysopt;
|
||||
pub mod timer;
|
||||
pub mod tray;
|
||||
pub mod win_service;
|
||||
pub mod service;
|
||||
pub mod win_uwp;
|
||||
|
||||
pub use self::core::*;
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::utils::dirs;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use std::{env::current_exe, process::Command as StdCommand};
|
||||
use tokio::time::sleep;
|
||||
|
||||
// Windows only
|
||||
|
||||
const SERVICE_URL: &str = "http://127.0.0.1:33211";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
@@ -32,7 +29,13 @@ pub struct JsonResponse {
|
||||
|
||||
/// Install the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
///
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let install_path = binary_path.with_file_name("install-service.exe");
|
||||
|
||||
@@ -60,9 +63,69 @@ pub async fn install_service() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let installer_path = binary_path.with_file_name("install-service");
|
||||
|
||||
if !installer_path.exists() {
|
||||
bail!("installer not found");
|
||||
}
|
||||
|
||||
let elevator = crate::utils::unix_helper::linux_elevator();
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new(installer_path).status()?,
|
||||
_ => StdCommand::new(elevator)
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(installer_path)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn install_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path()?;
|
||||
let installer_path = binary_path.with_file_name("install-service");
|
||||
|
||||
if !installer_path.exists() {
|
||||
bail!("installer not found");
|
||||
}
|
||||
let shell = installer_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
|
||||
|
||||
let status = StdCommand::new("osascript")
|
||||
.args(vec!["-e", &command])
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Uninstall the Clash Verge Service
|
||||
/// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
|
||||
#[cfg(target_os = "windows")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
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");
|
||||
|
||||
@@ -90,6 +153,63 @@ pub async fn uninstall_service() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
use users::get_effective_uid;
|
||||
|
||||
let binary_path = dirs::service_path()?;
|
||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
||||
|
||||
if !uninstaller_path.exists() {
|
||||
bail!("uninstaller not found");
|
||||
}
|
||||
|
||||
let elevator = crate::utils::unix_helper::linux_elevator();
|
||||
let status = match get_effective_uid() {
|
||||
0 => StdCommand::new(uninstaller_path).status()?,
|
||||
_ => StdCommand::new(elevator)
|
||||
.arg("sh")
|
||||
.arg("-c")
|
||||
.arg(uninstaller_path)
|
||||
.status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn uninstall_service() -> Result<()> {
|
||||
let binary_path = dirs::service_path()?;
|
||||
let uninstaller_path = binary_path.with_file_name("uninstall-service");
|
||||
|
||||
if !uninstaller_path.exists() {
|
||||
bail!("uninstaller not found");
|
||||
}
|
||||
|
||||
let shell = uninstaller_path.to_string_lossy().replace(" ", "\\\\ ");
|
||||
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
|
||||
|
||||
let status = StdCommand::new("osascript")
|
||||
.args(vec!["-e", &command])
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to install service with status {}",
|
||||
status.code().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// check the windows service status
|
||||
pub async fn check_service() -> Result<JsonResponse> {
|
||||
let url = format!("{SERVICE_URL}/get_clash");
|
||||
@@ -119,7 +239,8 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
||||
let clash_core = { Config::verge().latest().clash_core.clone() };
|
||||
let clash_core = clash_core.unwrap_or("clash".into());
|
||||
|
||||
let clash_bin = format!("{clash_core}.exe");
|
||||
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
|
||||
let clash_bin = format!("{clash_core}{bin_ext}");
|
||||
let bin_path = current_exe()?.with_file_name(clash_bin);
|
||||
let bin_path = dirs::path_to_str(&bin_path)?;
|
||||
|
||||
@@ -3,9 +3,10 @@ use anyhow::{anyhow, Result};
|
||||
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use std::env::current_exe;
|
||||
use std::sync::Arc;
|
||||
use sysproxy::Sysproxy;
|
||||
use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe};
|
||||
use tauri::async_runtime::Mutex as TokioMutex;
|
||||
|
||||
pub struct Sysopt {
|
||||
/// current system proxy setting
|
||||
@@ -149,7 +150,7 @@ impl Sysopt {
|
||||
/// init the auto launch
|
||||
pub fn init_launch(&self) -> Result<()> {
|
||||
let app_exe = current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?;
|
||||
// let app_exe = dunce::canonicalize(app_exe)?;
|
||||
let app_name = app_exe
|
||||
.file_stem()
|
||||
.and_then(|f| f.to_str())
|
||||
|
||||
@@ -130,6 +130,44 @@ impl Tray {
|
||||
let _ = tray.get_item("global_mode").set_selected(mode == "global");
|
||||
let _ = tray.get_item("direct_mode").set_selected(mode == "direct");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
match mode.as_str() {
|
||||
"rule" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode ✔", "规则模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"global" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode ✔", "全局模式 ✔"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode", "直连模式"));
|
||||
}
|
||||
"direct" => {
|
||||
let _ = tray
|
||||
.get_item("rule_mode")
|
||||
.set_title(t!("Rule Mode", "规则模式"));
|
||||
let _ = tray
|
||||
.get_item("global_mode")
|
||||
.set_title(t!("Global Mode", "全局模式"));
|
||||
let _ = tray
|
||||
.get_item("direct_mode")
|
||||
.set_title(t!("Direct Mode ✔", "直连模式 ✔"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let verge = Config::verge();
|
||||
let verge = verge.latest();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
@@ -144,9 +182,13 @@ impl Tray {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon-sys.png").to_vec();
|
||||
if *sysproxy_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("sysproxy.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||
let png_path = icon_dir_path.join("sysproxy.png");
|
||||
let ico_path = icon_dir_path.join("sysproxy.ico");
|
||||
if ico_path.exists() {
|
||||
icon = std::fs::read(ico_path).unwrap();
|
||||
} else if png_path.exists() {
|
||||
icon = std::fs::read(png_path).unwrap();
|
||||
}
|
||||
}
|
||||
icon
|
||||
@@ -156,9 +198,13 @@ impl Tray {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
|
||||
if *common_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("common.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
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
|
||||
@@ -170,9 +216,13 @@ impl Tray {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = include_bytes!("../../icons/mac-tray-icon-tun.png").to_vec();
|
||||
if *tun_tray_icon {
|
||||
let path = dirs::app_home_dir()?.join("icons").join("tun.png");
|
||||
if path.exists() {
|
||||
icon = std::fs::read(path).unwrap();
|
||||
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();
|
||||
}
|
||||
}
|
||||
indication_icon = icon
|
||||
@@ -182,6 +232,27 @@ impl Tray {
|
||||
|
||||
let _ = tray.get_item("system_proxy").set_selected(*system_proxy);
|
||||
let _ = tray.get_item("tun_mode").set_selected(*tun_mode);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if *system_proxy {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy ✔", "系统代理 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("system_proxy")
|
||||
.set_title(t!("System Proxy", "系统代理"));
|
||||
}
|
||||
if *tun_mode {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode ✔", "Tun 模式 ✔"));
|
||||
} else {
|
||||
let _ = tray
|
||||
.get_item("tun_mode")
|
||||
.set_title(t!("TUN Mode", "Tun 模式"));
|
||||
}
|
||||
}
|
||||
|
||||
let switch_map = {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
@@ -190,18 +261,30 @@ impl Tray {
|
||||
map
|
||||
};
|
||||
|
||||
let mut current_profile_name = "None".to_string();
|
||||
let profiles = Config::profiles();
|
||||
let profiles = profiles.latest();
|
||||
if let Some(current_profile_uid) = profiles.get_current() {
|
||||
let current_profile = profiles.get_item(¤t_profile_uid);
|
||||
current_profile_name = match ¤t_profile.unwrap().name {
|
||||
Some(profile_name) => profile_name.to_string(),
|
||||
None => current_profile_name,
|
||||
};
|
||||
};
|
||||
let _ = tray.set_tooltip(&format!(
|
||||
"Clash Verge {version}\n{}: {}\n{}: {}",
|
||||
"Clash Verge {version}\n{}: {}\n{}: {}\n{}: {}",
|
||||
t!("System Proxy", "系统代理"),
|
||||
switch_map[system_proxy],
|
||||
t!("TUN Mode", "Tun 模式"),
|
||||
switch_map[tun_mode]
|
||||
switch_map[tun_mode],
|
||||
t!("Curent Profile", "当前订阅"),
|
||||
current_profile_name
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn on_left_click(app_handle: &AppHandle) {
|
||||
pub fn on_click(app_handle: &AppHandle) {
|
||||
let tray_event = { Config::verge().latest().tray_event.clone() };
|
||||
let tray_event = tray_event.unwrap_or("main_window".into());
|
||||
match tray_event.as_str() {
|
||||
@@ -214,7 +297,10 @@ impl Tray {
|
||||
|
||||
pub fn on_system_tray_event(app_handle: &AppHandle, event: SystemTrayEvent) {
|
||||
match event {
|
||||
SystemTrayEvent::LeftClick { .. } => Tray::on_left_click(app_handle),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
SystemTrayEvent::LeftClick { .. } => Tray::on_click(app_handle),
|
||||
#[cfg(target_os = "macos")]
|
||||
SystemTrayEvent::RightClick { .. } => Tray::on_click(app_handle),
|
||||
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
|
||||
mode @ ("rule_mode" | "global_mode" | "direct_mode") => {
|
||||
let mode = &mode[0..mode.len() - 5];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function main(params) {
|
||||
if (params.mode === "script") {
|
||||
params.mode = "rule";
|
||||
function main(config) {
|
||||
if (config.mode === "script") {
|
||||
config.mode = "rule";
|
||||
}
|
||||
return params;
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
function main(params) {
|
||||
if (Array.isArray(params.proxies)) {
|
||||
params.proxies.forEach((p, i) => {
|
||||
function main(config) {
|
||||
if (Array.isArray(config.proxies)) {
|
||||
config.proxies.forEach((p, i) => {
|
||||
if (p.type === "hysteria" && typeof p.alpn === "string") {
|
||||
params.proxies[i].alpn = [p.alpn];
|
||||
config.proxies[i].alpn = [p.alpn];
|
||||
}
|
||||
});
|
||||
}
|
||||
return params;
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub const HANDLE_FIELDS: [&str; 9] = [
|
||||
pub const HANDLE_FIELDS: [&str; 11] = [
|
||||
"mode",
|
||||
"redir-port",
|
||||
"tproxy-port",
|
||||
"mixed-port",
|
||||
"socks-port",
|
||||
"port",
|
||||
|
||||
@@ -1,62 +1,75 @@
|
||||
use super::use_lowercase;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Error, Result};
|
||||
use serde_yaml::Mapping;
|
||||
|
||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||
use rquickjs::{function::Func, Context, Runtime};
|
||||
use boa_engine::{native_function::NativeFunction, Context, JsValue, Source};
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut context = Context::default();
|
||||
|
||||
let runtime = Runtime::new().unwrap();
|
||||
let context = Context::full(&runtime).unwrap();
|
||||
let outputs = Arc::new(Mutex::new(vec![]));
|
||||
|
||||
let copy_outputs = outputs.clone();
|
||||
let result = context.with(|ctx| -> Result<Mapping> {
|
||||
ctx.globals().set(
|
||||
"__verge_log__",
|
||||
Func::from(move |level: String, data: String| {
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
}),
|
||||
)?;
|
||||
|
||||
ctx.eval(
|
||||
r#"var console = Object.freeze({
|
||||
unsafe {
|
||||
let _ = context.register_global_builtin_callable(
|
||||
"__verge_log__".into(),
|
||||
2,
|
||||
NativeFunction::from_closure(
|
||||
move |_: &JsValue, args: &[JsValue], context: &mut Context| {
|
||||
let level = args.get(0).unwrap().to_string(context)?;
|
||||
let level = level.to_std_string().unwrap();
|
||||
let data = args.get(1).unwrap().to_string(context)?;
|
||||
let data = data.to_std_string().unwrap();
|
||||
let mut out = copy_outputs.lock().unwrap();
|
||||
out.push((level, data));
|
||||
Ok(JsValue::undefined())
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
let _ = context.eval(Source::from_bytes(
|
||||
r#"var console = Object.freeze({
|
||||
log(data){__verge_log__("log",JSON.stringify(data))},
|
||||
info(data){__verge_log__("info",JSON.stringify(data))},
|
||||
error(data){__verge_log__("error",JSON.stringify(data))},
|
||||
debug(data){__verge_log__("debug",JSON.stringify(data))},
|
||||
});"#,
|
||||
)?;
|
||||
));
|
||||
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
let config = use_lowercase(config.clone());
|
||||
let config_str = serde_json::to_string(&config)?;
|
||||
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
let code = format!(
|
||||
r#"try{{
|
||||
{script};
|
||||
JSON.stringify(main({config_str})||'')
|
||||
}} catch(err) {{
|
||||
`__error_flag__ ${{err.toString()}}`
|
||||
}}"#
|
||||
);
|
||||
let result: String = ctx.eval(code.as_str())?;
|
||||
);
|
||||
if let Ok(result) = context.eval(Source::from_bytes(code.as_str())) {
|
||||
if !result.is_string() {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
let result = result.to_string(&mut context).unwrap();
|
||||
let result = result.to_std_string().unwrap();
|
||||
if result.starts_with("__error_flag__") {
|
||||
anyhow::bail!(result[15..].to_owned());
|
||||
}
|
||||
if result == "\"\"" {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
Ok(serde_json::from_str::<Mapping>(result.as_str())?)
|
||||
});
|
||||
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match result {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
let res: Result<Mapping, Error> = Ok(serde_json::from_str::<Mapping>(result.as_str())?);
|
||||
let mut out = outputs.lock().unwrap();
|
||||
match res {
|
||||
Ok(config) => Ok((use_lowercase(config), out.to_vec())),
|
||||
Err(err) => {
|
||||
out.push(("exception".into(), err.to_string()));
|
||||
Ok((config, out.to_vec()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!("main function should return object");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,60 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
revise!(config, "tun", tun_val);
|
||||
|
||||
if enable {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to set system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("set_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash")
|
||||
.args([script])
|
||||
.current_dir(resource_dir)
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
log::info!(target: "app", "set system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
log::error!(target: "app", "set system dns failed: {code}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "set system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
use_dns_for_tun(config)
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use crate::utils::dirs;
|
||||
use tauri::api::process::Command;
|
||||
log::info!(target: "app", "try to unset system dns");
|
||||
let resource_dir = dirs::app_resources_dir().unwrap();
|
||||
let script = resource_dir.join("unset_dns.sh");
|
||||
let script = script.to_string_lossy();
|
||||
match Command::new("bash")
|
||||
.args([script])
|
||||
.current_dir(resource_dir)
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
if status.success() {
|
||||
log::info!(target: "app", "unset system dns successfully");
|
||||
} else {
|
||||
let code = status.code().unwrap_or(-1);
|
||||
log::error!(target: "app", "unset system dns failed: {code}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(target: "app", "unset system dns failed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,8 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
Config::clash().draft().patch_config(patch.clone());
|
||||
|
||||
match {
|
||||
let redir_port = patch.get("redir-port");
|
||||
let tproxy_port = patch.get("tproxy-port");
|
||||
let mixed_port = patch.get("mixed-port");
|
||||
let socks_port = patch.get("socks-port");
|
||||
let port = patch.get("port");
|
||||
@@ -129,7 +131,9 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
};
|
||||
|
||||
// 激活订阅
|
||||
if mixed_port.is_some()
|
||||
if redir_port.is_some()
|
||||
|| tproxy_port.is_some()
|
||||
|| mixed_port.is_some()
|
||||
|| socks_port.is_some()
|
||||
|| port.is_some()
|
||||
|| patch.get("secret").is_some()
|
||||
@@ -181,22 +185,14 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
let tun_tray_icon = patch.tun_tray_icon;
|
||||
|
||||
match {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let service_mode = patch.enable_service_mode;
|
||||
let service_mode = patch.enable_service_mode;
|
||||
|
||||
if service_mode.is_some() {
|
||||
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
|
||||
if service_mode.is_some() {
|
||||
log::debug!(target: "app", "change service mode to {}", service_mode.unwrap());
|
||||
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
} else if tun_mode.is_some() {
|
||||
update_core_config().await?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if tun_mode.is_some() {
|
||||
Config::generate()?;
|
||||
CoreManager::global().run_core().await?;
|
||||
} else if tun_mode.is_some() {
|
||||
update_core_config().await?;
|
||||
}
|
||||
|
||||
@@ -303,7 +299,7 @@ pub fn copy_clash_env(app_handle: &AppHandle) {
|
||||
|
||||
let sh =
|
||||
format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
|
||||
let cmd: String = format!("set http_proxy={http_proxy} \n set https_proxy={http_proxy}");
|
||||
let cmd: String = format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}");
|
||||
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"");
|
||||
|
||||
let mut cliboard = app_handle.clipboard_manager();
|
||||
|
||||
@@ -20,6 +20,9 @@ fn main() -> std::io::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||||
|
||||
crate::log_err!(init::init_config());
|
||||
|
||||
#[allow(unused_mut)]
|
||||
@@ -57,6 +60,8 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::test_delay,
|
||||
cmds::get_app_dir,
|
||||
cmds::copy_icon_file,
|
||||
cmds::download_icon_cache,
|
||||
cmds::open_devtools,
|
||||
cmds::exit_app,
|
||||
// cmds::update_hotkeys,
|
||||
// profile
|
||||
|
||||
@@ -92,12 +92,16 @@ pub fn clash_pid_path() -> Result<PathBuf> {
|
||||
Ok(app_home_dir()?.join("clash.pid"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn service_path() -> Result<PathBuf> {
|
||||
Ok(app_resources_dir()?.join("clash-verge-service"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_path() -> Result<PathBuf> {
|
||||
Ok(app_resources_dir()?.join("clash-verge-service.exe"))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn service_log_file() -> Result<PathBuf> {
|
||||
use chrono::Local;
|
||||
|
||||
|
||||
@@ -135,6 +135,12 @@ pub fn delete_log() -> Result<()> {
|
||||
for file in fs::read_dir(&log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
|
||||
let service_log_dir = log_dir.join("service");
|
||||
for file in fs::read_dir(&service_log_dir)?.flatten() {
|
||||
let _ = process_file(file);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -298,7 +304,7 @@ pub fn startup_script() -> Result<()> {
|
||||
shell = "powershell";
|
||||
}
|
||||
if path.ends_with(".bat") {
|
||||
shell = "cmd";
|
||||
shell = "powershell";
|
||||
}
|
||||
if shell.is_empty() {
|
||||
return Err(anyhow::anyhow!("unsupported script: {path}"));
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod init;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod tmpl;
|
||||
pub mod unix_helper;
|
||||
|
||||
@@ -141,10 +141,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
_ => {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
builder = builder
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions")
|
||||
.inner_size(800.0, 636.0)
|
||||
.center();
|
||||
builder = builder.inner_size(800.0, 636.0).center();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -161,6 +158,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
#[cfg(target_os = "windows")]
|
||||
let window = builder
|
||||
.decorations(false)
|
||||
.additional_browser_args("--enable-features=msWebView2EnableDraggableRegions --disable-features=OverscrollHistoryNavigation,msExperimentalScrolling")
|
||||
.transparent(true)
|
||||
.visible(false)
|
||||
.build();
|
||||
@@ -171,7 +169,7 @@ pub fn create_window(app_handle: &AppHandle) {
|
||||
.title_bar_style(tauri::TitleBarStyle::Overlay)
|
||||
.build();
|
||||
#[cfg(target_os = "linux")]
|
||||
let window = builder.decorations(true).transparent(false).build();
|
||||
let window = builder.decorations(false).transparent(true).build();
|
||||
|
||||
match window {
|
||||
Ok(win) => {
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
//! Some config file template
|
||||
|
||||
/// template for new a profile item
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge
|
||||
pub const ITEM_LOCAL: &str = "# Profile Template for Clash Verge
|
||||
|
||||
proxies:
|
||||
proxies: []
|
||||
|
||||
proxy-groups:
|
||||
proxy-groups: []
|
||||
|
||||
rules:
|
||||
rules: []
|
||||
";
|
||||
|
||||
/// enhanced profile
|
||||
pub const ITEM_MERGE: &str = "# Merge Template for clash verge
|
||||
# The `Merge` format used to enhance profile
|
||||
pub const ITEM_MERGE: &str = "# Profile Enhancement Merge Template for Clash Verge
|
||||
|
||||
prepend-rules:
|
||||
prepend-rules: []
|
||||
|
||||
prepend-rule-providers:
|
||||
prepend-rule-providers: {}
|
||||
|
||||
prepend-proxies:
|
||||
prepend-proxies: []
|
||||
|
||||
prepend-proxy-providers:
|
||||
prepend-proxy-providers: {}
|
||||
|
||||
prepend-proxy-groups:
|
||||
prepend-proxy-groups: []
|
||||
|
||||
append-rules:
|
||||
append-rules: []
|
||||
|
||||
append-rule-providers:
|
||||
append-rule-providers: {}
|
||||
|
||||
append-proxies:
|
||||
append-proxies: []
|
||||
|
||||
append-proxy-providers:
|
||||
append-proxy-providers: {}
|
||||
|
||||
append-proxy-groups:
|
||||
append-proxy-groups: []
|
||||
";
|
||||
|
||||
/// enhanced profile
|
||||
pub const ITEM_SCRIPT: &str = "// Define the `main` function
|
||||
pub const ITEM_SCRIPT: &str = "// Define main function (script entry)
|
||||
|
||||
function main(params) {
|
||||
return params;
|
||||
function main(config) {
|
||||
return config;
|
||||
}
|
||||
";
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn linux_elevator() -> &'static str {
|
||||
use std::process::Command;
|
||||
match Command::new("which").arg("pkexec").output() {
|
||||
Ok(output) => {
|
||||
if output.stdout.is_empty() {
|
||||
"sudo"
|
||||
} else {
|
||||
"pkexec"
|
||||
}
|
||||
}
|
||||
Err(_) => "sudo",
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"package": {
|
||||
"productName": "Clash Verge",
|
||||
"version": "1.5.5"
|
||||
"version": "1.6.1"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -65,6 +66,11 @@
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"fs": {
|
||||
"exists": true,
|
||||
"readFile": true,
|
||||
"scope": ["$APPDATA/**", "$RESOURCE/../**", "**"]
|
||||
}
|
||||
},
|
||||
"windows": [],
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.png"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["deb", "appimage", "updater"],
|
||||
"deb": {
|
||||
"depends": ["openssl"],
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/mac-tray-icon.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["app", "dmg", "updater"],
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray-icon.png"
|
||||
},
|
||||
"bundle": {
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"targets": ["nsis", "updater"],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
|
||||
@@ -743,6 +743,30 @@ Function un.onInit
|
||||
|
||||
!insertmacro MUI_UNGETLANGUAGE
|
||||
FunctionEnd
|
||||
|
||||
Function un.isDirectoryEmpty
|
||||
Exch $0
|
||||
Push $1
|
||||
Push $2
|
||||
StrCpy $2 0
|
||||
FindFirst $1 $2 "$0\*.*"
|
||||
loop:
|
||||
StrCmp $2 "" done
|
||||
StrCmp $2 "." next
|
||||
StrCmp $2 ".." next
|
||||
StrCpy $0 0
|
||||
goto done
|
||||
next:
|
||||
FindNext $1 $2
|
||||
goto loop
|
||||
done:
|
||||
FindClose $1
|
||||
StrCmp $2 "" 0 +2
|
||||
StrCpy $0 1
|
||||
Pop $2
|
||||
Pop $1
|
||||
Exch $0
|
||||
FunctionEnd
|
||||
|
||||
Section Uninstall
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
@@ -756,7 +780,7 @@ Section Uninstall
|
||||
{{#each resources}}
|
||||
Delete "$INSTDIR\\{{this.[1]}}"
|
||||
{{/each}}
|
||||
|
||||
Delete "$INSTDIR\resources"
|
||||
; Delete external binaries
|
||||
{{#each binaries}}
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
@@ -765,13 +789,14 @@ Section Uninstall
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
${If} $DeleteAppDataCheckboxState == 1
|
||||
; Remove InstallDir
|
||||
Push "$INSTDIR"
|
||||
Call un.isDirectoryEmpty
|
||||
Pop $0
|
||||
${If} $0 == 1
|
||||
RMDir /R /REBOOTOK "$INSTDIR"
|
||||
${Else}
|
||||
{{#each resources_ancestors}}
|
||||
RMDir /REBOOTOK "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
RMDir "$INSTDIR"
|
||||
MessageBox MB_OK "Install Directory is not Empty, Please remove it manually."
|
||||
${EndIf}
|
||||
|
||||
; Remove start menu shortcut
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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_290_83)"/>
|
||||
<path d="M26.1791 19.6042C25.8714 21.1676 25.1224 22.6103 24.0208 23.7615C22.9191 24.9128 21.5109 25.7246 19.9625 26.1008C20.3875 25.5992 20.7558 24.9492 21.0616 24.2117C21.3658 23.4783 21.62 22.6275 21.8083 21.695C23.2058 21.335 24.4716 20.7817 25.5333 20.0742C25.7566 19.925 25.9725 19.7683 26.1791 19.6042ZM9.82082 19.6042C10.0275 19.7683 10.2433 19.925 10.4666 20.0742C11.5275 20.7817 12.7933 21.335 14.1916 21.695C14.3791 22.6275 14.6333 23.4783 14.9383 24.2117C15.2433 24.9483 15.6116 25.5992 16.0366 26.1008C14.4884 25.7244 13.0803 24.9126 11.9788 23.7613C10.8773 22.6101 10.1285 21.1675 9.82082 19.6042ZM20.0225 22.0375C19.8927 22.5611 19.7254 23.0747 19.5216 23.5742C19.2325 24.2725 18.9141 24.7867 18.6125 25.1125C18.3041 25.4458 18.0925 25.5 18 25.5C17.9075 25.5 17.6966 25.4458 17.3875 25.1125C17.0858 24.7875 16.7675 24.2725 16.4783 23.5742C16.2746 23.0747 16.1072 22.5611 15.9775 22.0375C16.6341 22.1225 17.3108 22.1667 18 22.1667C18.6762 22.1671 19.3518 22.1239 20.0225 22.0375ZM20.4691 16.9408C20.5344 18.0629 20.4947 19.1886 20.3508 20.3033C19.6033 20.4317 18.815 20.5 18 20.5C17.2119 20.5009 16.4253 20.4351 15.6483 20.3033C15.5023 19.1888 15.463 18.0629 15.5308 16.9408C16.3191 17.0883 17.1475 17.1667 18 17.1667C18.8282 17.1676 19.6548 17.092 20.4691 16.9408ZM25.555 14.4792C25.9383 15.3008 26.1925 16.1958 26.2883 17.1367C25.8708 17.6975 25.3075 18.2217 24.6083 18.6867C23.8833 19.17 23.0283 19.5783 22.0758 19.8842C22.1845 18.7637 22.1962 17.6359 22.1108 16.5133C22.8976 16.2518 23.6486 15.8932 24.3466 15.4458C24.7812 15.1654 25.186 14.8416 25.555 14.4792ZM10.445 14.4792C10.8033 14.835 11.2116 15.1583 11.6533 15.4458C12.3512 15.8934 13.1023 16.252 13.8891 16.5133C13.804 17.6359 13.8154 18.7636 13.9233 19.8842C12.9708 19.5783 12.1158 19.1708 11.3908 18.6875C10.6925 18.2217 10.1291 17.6975 9.71082 17.1367C9.80748 16.195 10.0608 15.3008 10.4441 14.4783L10.445 14.4792ZM18 10.5C18.0916 10.5 18.3033 10.555 18.6125 10.8883C18.9141 11.2133 19.2325 11.7283 19.5216 12.4267C19.8466 13.2125 20.1166 14.1833 20.29 15.2775C19.5716 15.4217 18.8025 15.5 18 15.5C17.2309 15.5015 16.4636 15.4269 15.7091 15.2775C15.8825 14.1833 16.1525 13.2125 16.4783 12.4275C16.7675 11.7283 17.0858 11.2142 17.3866 10.8883C17.6966 10.555 17.9075 10.5 18 10.5ZM19.9625 9.89917C21.8406 10.3553 23.5019 11.4496 24.6625 12.995C24.3075 13.4034 23.8951 13.7582 23.4383 14.0483C22.9554 14.3584 22.4408 14.6162 21.9033 14.8175C21.7108 13.6833 21.42 12.655 21.0616 11.7892C20.7558 11.0517 20.3883 10.4008 19.9625 9.89917ZM16.0375 9.89917C15.6125 10.4008 15.2441 11.0517 14.9383 11.7892C14.58 12.6558 14.2883 13.6833 14.0966 14.8175C13.5592 14.6162 13.0446 14.3584 12.5616 14.0483C12.1046 13.7583 11.6919 13.4035 11.3366 12.995C12.4974 11.4494 14.1591 10.3551 16.0375 9.89917Z" fill="white"/>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_118)"/>
|
||||
<path d="M17.9917 9.66675C13.3917 9.66675 9.66669 13.4001 9.66669 18.0001C9.66669 22.6001 13.3917 26.3334 17.9917 26.3334C22.6 26.3334 26.3334 22.6001 26.3334 18.0001C26.3334 13.4001 22.6 9.66675 17.9917 9.66675ZM23.7667 14.6667H21.3084C21.0417 13.6251 20.6584 12.6251 20.1584 11.7001C21.6917 12.2251 22.9667 13.2917 23.7667 14.6667ZM18 11.3667C18.6917 12.3667 19.2334 13.4751 19.5917 14.6667H16.4084C16.7667 13.4751 17.3084 12.3667 18 11.3667ZM11.55 19.6667C11.4167 19.1334 11.3334 18.5751 11.3334 18.0001C11.3334 17.4251 11.4167 16.8667 11.55 16.3334H14.3667C14.3 16.8834 14.25 17.4334 14.25 18.0001C14.25 18.5667 14.3 19.1167 14.3667 19.6667H11.55ZM12.2334 21.3334H14.6917C14.9584 22.3751 15.3417 23.3751 15.8417 24.3001C14.3084 23.7751 13.0334 22.7167 12.2334 21.3334ZM14.6917 14.6667H12.2334C13.0334 13.2834 14.3084 12.2251 15.8417 11.7001C15.3417 12.6251 14.9584 13.6251 14.6917 14.6667ZM18 24.6334C17.3084 23.6334 16.7667 22.5251 16.4084 21.3334H19.5917C19.2334 22.5251 18.6917 23.6334 18 24.6334ZM19.95 19.6667H16.05C15.975 19.1167 15.9167 18.5667 15.9167 18.0001C15.9167 17.4334 15.975 16.8751 16.05 16.3334H19.95C20.025 16.8751 20.0834 17.4334 20.0834 18.0001C20.0834 18.5667 20.025 19.1167 19.95 19.6667ZM20.1584 24.3001C20.6584 23.3751 21.0417 22.3751 21.3084 21.3334H23.7667C22.9667 22.7084 21.6917 23.7751 20.1584 24.3001ZM21.6334 19.6667C21.7 19.1167 21.75 18.5667 21.75 18.0001C21.75 17.4334 21.7 16.8834 21.6334 16.3334H24.45C24.5834 16.8667 24.6667 17.4251 24.6667 18.0001C24.6667 18.5751 24.5834 19.1334 24.45 19.6667H21.6334Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_290_83" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_118" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#009038"/>
|
||||
<stop offset="1" stop-color="#1CA350"/>
|
||||
</linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,19 +1,8 @@
|
||||
<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_289_53)"/>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint1_linear_289_53)"/>
|
||||
<mask id="mask0_289_53" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="8" y="8" width="20" height="20">
|
||||
<path d="M28 8V28H8V8H28ZM18.4942 27.3817L18.485 27.3833L18.4258 27.4125L18.4092 27.4158L18.3975 27.4125L18.3383 27.3833C18.33 27.38 18.3225 27.3825 18.3183 27.3875L18.315 27.3958L18.3008 27.7525L18.305 27.7692L18.3133 27.78L18.4 27.8417L18.4125 27.845L18.4225 27.8417L18.5092 27.78L18.5192 27.7667L18.5225 27.7525L18.5083 27.3967C18.5067 27.3883 18.5008 27.3825 18.4942 27.3817ZM18.715 27.2875L18.7042 27.2892L18.55 27.3667L18.5417 27.375L18.5392 27.3842L18.5542 27.7425L18.5583 27.7525L18.565 27.7583L18.7325 27.8358C18.7425 27.8392 18.7517 27.8358 18.7567 27.8292L18.76 27.8175L18.7317 27.3058C18.7292 27.2958 18.7233 27.2892 18.715 27.2875ZM18.1192 27.2892C18.1154 27.2874 18.1111 27.287 18.1071 27.2879C18.103 27.2888 18.0994 27.291 18.0967 27.2942L18.0917 27.3058L18.0633 27.8175C18.0633 27.8275 18.0692 27.8342 18.0775 27.8375L18.09 27.8358L18.2575 27.7583L18.2658 27.7517L18.2692 27.7425L18.2833 27.3842L18.2808 27.3742L18.2725 27.3658L18.1192 27.2892Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_289_53)">
|
||||
<path d="M15.9167 9.66669C15.5852 9.66669 15.2672 9.79838 15.0328 10.0328C14.7984 10.2672 14.6667 10.5852 14.6667 10.9167V11.75C14.6667 12.0815 14.7984 12.3995 15.0328 12.6339C15.2672 12.8683 15.5852 13 15.9167 13H20.0834C20.4149 13 20.7328 12.8683 20.9672 12.6339C21.2017 12.3995 21.3334 12.0815 21.3334 11.75V10.9167C21.3334 10.5852 21.2017 10.2672 20.9672 10.0328C20.7328 9.79838 20.4149 9.66669 20.0834 9.66669H15.9167Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4167 11.3642C12.3683 11.4225 11.7067 11.5892 11.2325 12.0642C10.5 12.7967 10.5 13.975 10.5 16.3317V21.3317C10.5 23.6892 10.5 24.8675 11.2325 25.6C11.9642 26.3317 13.1433 26.3317 15.5 26.3317H20.5C22.8567 26.3317 24.0358 26.3317 24.7675 25.6C25.5 24.8667 25.5 23.6892 25.5 21.3317V16.3317C25.5 13.975 25.5 12.7967 24.7675 12.0642C24.2933 11.5892 23.6317 11.4225 22.5833 11.3642V11.75C22.5833 12.4131 22.3199 13.049 21.8511 13.5178C21.3823 13.9866 20.7464 14.25 20.0833 14.25H15.9167C15.2536 14.25 14.6177 13.9866 14.1489 13.5178C13.6801 13.049 13.4167 12.4131 13.4167 11.75V11.3642ZM13.8333 16.125C13.6676 16.125 13.5086 16.1909 13.3914 16.3081C13.2742 16.4253 13.2083 16.5843 13.2083 16.75C13.2083 16.9158 13.2742 17.0748 13.3914 17.192C13.5086 17.3092 13.6676 17.375 13.8333 17.375H14.25C14.4158 17.375 14.5747 17.3092 14.6919 17.192C14.8092 17.0748 14.875 16.9158 14.875 16.75C14.875 16.5843 14.8092 16.4253 14.6919 16.3081C14.5747 16.1909 14.4158 16.125 14.25 16.125H13.8333ZM16.75 16.125C16.5842 16.125 16.4253 16.1909 16.3081 16.3081C16.1908 16.4253 16.125 16.5843 16.125 16.75C16.125 16.9158 16.1908 17.0748 16.3081 17.192C16.4253 17.3092 16.5842 17.375 16.75 17.375H22.1667C22.3324 17.375 22.4914 17.3092 22.6086 17.192C22.7258 17.0748 22.7917 16.9158 22.7917 16.75C22.7917 16.5843 22.7258 16.4253 22.6086 16.3081C22.4914 16.1909 22.3324 16.125 22.1667 16.125H16.75ZM13.8333 19.0417C13.6676 19.0417 13.5086 19.1075 13.3914 19.2248C13.2742 19.342 13.2083 19.5009 13.2083 19.6667C13.2083 19.8325 13.2742 19.9914 13.3914 20.1086C13.5086 20.2258 13.6676 20.2917 13.8333 20.2917H14.25C14.4158 20.2917 14.5747 20.2258 14.6919 20.1086C14.8092 19.9914 14.875 19.8325 14.875 19.6667C14.875 19.5009 14.8092 19.342 14.6919 19.2248C14.5747 19.1075 14.4158 19.0417 14.25 19.0417H13.8333ZM16.75 19.0417C16.5842 19.0417 16.4253 19.1075 16.3081 19.2248C16.1908 19.342 16.125 19.5009 16.125 19.6667C16.125 19.8325 16.1908 19.9914 16.3081 20.1086C16.4253 20.2258 16.5842 20.2917 16.75 20.2917H22.1667C22.3324 20.2917 22.4914 20.2258 22.6086 20.1086C22.7258 19.9914 22.7917 19.8325 22.7917 19.6667C22.7917 19.5009 22.7258 19.342 22.6086 19.2248C22.4914 19.1075 22.3324 19.0417 22.1667 19.0417H16.75ZM13.8333 21.9584C13.6676 21.9584 13.5086 22.0242 13.3914 22.1414C13.2742 22.2586 13.2083 22.4176 13.2083 22.5834C13.2083 22.7491 13.2742 22.9081 13.3914 23.0253C13.5086 23.1425 13.6676 23.2084 13.8333 23.2084H14.25C14.4158 23.2084 14.5747 23.1425 14.6919 23.0253C14.8092 22.9081 14.875 22.7491 14.875 22.5834C14.875 22.4176 14.8092 22.2586 14.6919 22.1414C14.5747 22.0242 14.4158 21.9584 14.25 21.9584H13.8333ZM16.75 21.9584C16.5842 21.9584 16.4253 22.0242 16.3081 22.1414C16.1908 22.2586 16.125 22.4176 16.125 22.5834C16.125 22.7491 16.1908 22.9081 16.3081 23.0253C16.4253 23.1425 16.5842 23.2084 16.75 23.2084H22.1667C22.3324 23.2084 22.4914 23.1425 22.6086 23.0253C22.7258 22.9081 22.7917 22.7491 22.7917 22.5834C22.7917 22.4176 22.7258 22.2586 22.6086 22.1414C22.4914 22.0242 22.3324 21.9584 22.1667 21.9584H16.75Z" fill="white"/>
|
||||
</g>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_127)"/>
|
||||
<path d="M18.8334 22.1667H12.1667C11.7084 22.1667 11.3334 22.5417 11.3334 23.0001C11.3334 23.4584 11.7084 23.8334 12.1667 23.8334H18.8334C19.2917 23.8334 19.6667 23.4584 19.6667 23.0001C19.6667 22.5417 19.2917 22.1667 18.8334 22.1667ZM23.8334 15.5001H12.1667C11.7084 15.5001 11.3334 15.8751 11.3334 16.3334C11.3334 16.7917 11.7084 17.1667 12.1667 17.1667H23.8334C24.2917 17.1667 24.6667 16.7917 24.6667 16.3334C24.6667 15.8751 24.2917 15.5001 23.8334 15.5001ZM12.1667 20.5001H23.8334C24.2917 20.5001 24.6667 20.1251 24.6667 19.6667C24.6667 19.2084 24.2917 18.8334 23.8334 18.8334H12.1667C11.7084 18.8334 11.3334 19.2084 11.3334 19.6667C11.3334 20.1251 11.7084 20.5001 12.1667 20.5001ZM11.3334 13.0001C11.3334 13.4584 11.7084 13.8334 12.1667 13.8334H23.8334C24.2917 13.8334 24.6667 13.4584 24.6667 13.0001C24.6667 12.5417 24.2917 12.1667 23.8334 12.1667H12.1667C11.7084 12.1667 11.3334 12.5417 11.3334 13.0001Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_289_53" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#56718E"/>
|
||||
<stop offset="1" stop-color="#4B6683"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_289_53" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_127" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E96038"/>
|
||||
<stop offset="1" stop-color="#E1451D"/>
|
||||
</linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,15 +1,10 @@
|
||||
<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_293_142)"/>
|
||||
<g clip-path="url(#clip0_293_142)">
|
||||
<path d="M24.5167 13C24.7587 13.0001 24.9978 13.0529 25.2173 13.1547C25.4369 13.2566 25.6316 13.405 25.788 13.5897C25.9444 13.7744 26.0587 13.9909 26.1229 14.2243C26.1872 14.4576 26.1998 14.7021 26.16 14.9408L24.7717 23.2742C24.7068 23.6633 24.5059 24.0168 24.2049 24.2718C23.9038 24.5268 23.5221 24.6667 23.1275 24.6667H12.8725C12.478 24.6667 12.0962 24.5268 11.7952 24.2718C11.4941 24.0168 11.2933 23.6633 11.2284 23.2742L9.84003 14.9408C9.80022 14.7021 9.81287 14.4576 9.87711 14.2243C9.94135 13.9909 10.0556 13.7744 10.212 13.5897C10.3684 13.405 10.5632 13.2566 10.7827 13.1547C11.0023 13.0529 11.2413 13.0001 11.4834 13H24.5167ZM23 10.5C23.221 10.5 23.433 10.5878 23.5893 10.7441C23.7456 10.9004 23.8334 11.1123 23.8334 11.3333C23.8334 11.5543 23.7456 11.7663 23.5893 11.9226C23.433 12.0789 23.221 12.1667 23 12.1667H13C12.779 12.1667 12.5671 12.0789 12.4108 11.9226C12.2545 11.7663 12.1667 11.5543 12.1667 11.3333C12.1667 11.1123 12.2545 10.9004 12.4108 10.7441C12.5671 10.5878 12.779 10.5 13 10.5H23Z" fill="white"/>
|
||||
</g>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_113)"/>
|
||||
<path d="M23.8333 18.8333H12.1667C11.25 18.8333 10.5 19.5833 10.5 20.5V23.8333C10.5 24.75 11.25 25.5 12.1667 25.5H23.8333C24.75 25.5 25.5 24.75 25.5 23.8333V20.5C25.5 19.5833 24.75 18.8333 23.8333 18.8333ZM13.8333 23.8333C12.9167 23.8333 12.1667 23.0833 12.1667 22.1667C12.1667 21.25 12.9167 20.5 13.8333 20.5C14.75 20.5 15.5 21.25 15.5 22.1667C15.5 23.0833 14.75 23.8333 13.8333 23.8333ZM23.8333 10.5H12.1667C11.25 10.5 10.5 11.25 10.5 12.1667V15.5C10.5 16.4167 11.25 17.1667 12.1667 17.1667H23.8333C24.75 17.1667 25.5 16.4167 25.5 15.5V12.1667C25.5 11.25 24.75 10.5 23.8333 10.5ZM13.8333 15.5C12.9167 15.5 12.1667 14.75 12.1667 13.8333C12.1667 12.9167 12.9167 12.1667 13.8333 12.1667C14.75 12.1667 15.5 12.9167 15.5 13.8333C15.5 14.75 14.75 15.5 13.8333 15.5Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_293_142" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_113" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6038CB"/>
|
||||
<stop offset="1" stop-color="#704ADC"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_293_142">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,15 +1,10 @@
|
||||
<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_294_169)"/>
|
||||
<g clip-path="url(#clip0_294_169)">
|
||||
<path d="M18 22.1667C18.221 22.1667 18.433 22.2545 18.5893 22.4107C18.7455 22.567 18.8333 22.779 18.8333 23C18.8333 23.221 18.7455 23.433 18.5893 23.5893C18.433 23.7455 18.221 23.8333 18 23.8333C17.779 23.8333 17.567 23.7455 17.4107 23.5893C17.2545 23.433 17.1667 23.221 17.1667 23C17.1667 22.779 17.2545 22.567 17.4107 22.4107C17.567 22.2545 17.779 22.1667 18 22.1667ZM18 18.8333C19.15 18.8333 20.1933 19.3 20.9467 20.0533C21.103 20.2097 21.1909 20.4218 21.1909 20.6429C21.1909 20.8641 21.103 21.0761 20.9467 21.2325C20.7903 21.3889 20.5782 21.4767 20.3571 21.4767C20.136 21.4767 19.9239 21.3889 19.7675 21.2325C19.5357 20.9999 19.2603 20.8154 18.9569 20.6897C18.6536 20.564 18.3284 20.4995 18 20.5C17.3092 20.5 16.6858 20.7792 16.2325 21.2325C16.0761 21.3889 15.8641 21.4767 15.6429 21.4767C15.4218 21.4767 15.2097 21.3889 15.0533 21.2325C14.897 21.0761 14.8091 20.8641 14.8091 20.6429C14.8091 20.4218 14.897 20.2097 15.0533 20.0533C15.44 19.666 15.8993 19.3588 16.405 19.1494C16.9107 18.9401 17.4527 18.8326 18 18.8333ZM18 15.5C18.9851 15.4989 19.9607 15.6923 20.8708 16.0693C21.7809 16.4463 22.6076 16.9993 23.3033 17.6967C23.4551 17.8538 23.5391 18.0643 23.5372 18.2828C23.5353 18.5013 23.4477 18.7103 23.2932 18.8648C23.1387 19.0194 22.9297 19.107 22.7112 19.1089C22.4927 19.1108 22.2822 19.0268 22.125 18.875C21.584 18.3324 20.941 17.9022 20.2331 17.609C19.5251 17.3158 18.7662 17.1655 18 17.1667C17.2338 17.1655 16.4749 17.3158 15.7669 17.609C15.059 17.9022 14.416 18.3324 13.875 18.875C13.7178 19.0268 13.5073 19.1108 13.2888 19.1089C13.0703 19.107 12.8613 19.0194 12.7068 18.8648C12.5523 18.7103 12.4647 18.5013 12.4628 18.2828C12.4609 18.0643 12.5449 17.8538 12.6967 17.6967C13.3924 16.9993 14.2191 16.4463 15.1292 16.0693C16.0393 15.6923 17.0149 15.4989 18 15.5ZM18 12.1667C20.9917 12.1667 23.7 13.38 25.66 15.34C25.8118 15.4972 25.8958 15.7077 25.8939 15.9262C25.892 16.1447 25.8044 16.3537 25.6499 16.5082C25.4953 16.6627 25.2863 16.7503 25.0678 16.7522C24.8493 16.7541 24.6388 16.6701 24.4817 16.5183C23.6315 15.6657 22.6212 14.9896 21.5089 14.5288C20.3965 14.068 19.204 13.8316 18 13.8333C16.796 13.8315 15.6035 14.0679 14.4911 14.5286C13.3787 14.9894 12.3684 15.6656 11.5183 16.5183C11.3612 16.6701 11.1507 16.7541 10.9322 16.7522C10.7137 16.7503 10.5047 16.6627 10.3502 16.5082C10.1957 16.3537 10.108 16.1447 10.1061 15.9262C10.1042 15.7077 10.1882 15.4972 10.34 15.34C11.3449 14.3326 12.5389 13.5337 13.8534 12.9891C15.1679 12.4445 16.5771 12.165 18 12.1667Z" fill="white"/>
|
||||
</g>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_108)"/>
|
||||
<path d="M9.7167 16.3834C10.1417 16.8084 10.8167 16.85 11.275 16.4667C15.1667 13.2667 20.8167 13.2667 24.7167 16.4583C25.1834 16.8417 25.8667 16.8084 26.2917 16.3834C26.7834 15.8917 26.75 15.075 26.2084 14.6333C21.45 10.7417 14.5667 10.7417 9.80003 14.6333C9.25836 15.0667 9.2167 15.8834 9.7167 16.3834ZM16.1834 22.85L17.4084 24.075C17.7334 24.4 18.2584 24.4 18.5834 24.075L19.8084 22.85C20.2 22.4583 20.1167 21.7833 19.6167 21.525C18.6 21 17.3834 21 16.3584 21.525C15.8834 21.7833 15.7917 22.4583 16.1834 22.85ZM13.075 19.7417C13.4834 20.15 14.125 20.1917 14.6 19.85C16.6334 18.4084 19.3667 18.4084 21.4 19.85C21.875 20.1834 22.5167 20.15 22.925 19.7417L22.9334 19.7333C23.4334 19.2333 23.4 18.3834 22.825 17.975C19.9584 15.9 16.05 15.9 13.175 17.975C12.6 18.3917 12.5667 19.2333 13.075 19.7417Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_294_169" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_108" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#21B2CB"/>
|
||||
<stop offset="1" stop-color="#3EC5D2"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_294_169">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,15 +1,10 @@
|
||||
<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_294_182)"/>
|
||||
<g clip-path="url(#clip0_294_182)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.7759 10.6333C24.307 10.1647 23.6713 9.90137 23.0084 9.90137C22.3454 9.90137 21.7097 10.1647 21.2409 10.6333L20.6517 11.2233L24.7767 15.3483L25.365 14.7592C25.5972 14.527 25.7814 14.2514 25.9071 13.9481C26.0327 13.6447 26.0974 13.3196 26.0974 12.9913C26.0974 12.6629 26.0327 12.3378 25.9071 12.0344C25.7814 11.7311 25.5972 11.4555 25.365 11.2233L24.7759 10.6333ZM23.5975 16.5267L19.4725 12.4017L11.8975 19.9775C11.7318 20.1433 11.616 20.3524 11.5634 20.5808L10.7059 24.2933C10.6739 24.4312 10.6776 24.5748 10.7165 24.7109C10.7554 24.8469 10.8284 24.9707 10.9284 25.0708C11.0284 25.1708 11.1523 25.2437 11.2883 25.2827C11.4243 25.3216 11.568 25.3253 11.7059 25.2933L15.4192 24.4367C15.6474 24.3839 15.8561 24.2681 16.0217 24.1025L23.5975 16.5267Z" fill="white"/>
|
||||
</g>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_122)"/>
|
||||
<path d="M15.5 24.6666C15.5 25.125 15.875 25.5 16.3333 25.5C16.7917 25.5 17.1667 25.125 17.1667 24.6666V22.1666C17.775 20.0166 19.725 19.275 21.475 19.6666L20.7416 20.4C20.4166 20.725 20.4166 21.25 20.7416 21.575C21.0666 21.9 21.5917 21.9 21.9167 21.575L24.075 19.4166C24.4 19.0916 24.4 18.5666 24.075 18.2416L21.9167 16.0833C21.8396 16.006 21.748 15.9447 21.6472 15.9029C21.5464 15.8611 21.4383 15.8396 21.3291 15.8396C21.22 15.8396 21.1119 15.8611 21.0111 15.9029C20.9103 15.9447 20.8187 16.006 20.7416 16.0833C20.4166 16.4083 20.4166 16.9333 20.7416 17.2583L21.475 18C20.2166 17.725 18.3667 18.0666 17.1667 19.1333V13.6916L17.9 14.425C18.225 14.75 18.75 14.75 19.075 14.425C19.4 14.1 19.4 13.575 19.075 13.25L16.9167 11.0916C16.8396 11.0144 16.748 10.9531 16.6472 10.9113C16.5464 10.8694 16.4383 10.8479 16.3292 10.8479C16.22 10.8479 16.1119 10.8694 16.0111 10.9113C15.9103 10.9531 15.8187 11.0144 15.7417 11.0916L13.5917 13.2416C13.2667 13.5666 13.2667 14.0916 13.5917 14.4166C13.9167 14.7416 14.4417 14.7416 14.7667 14.4166L15.5 13.6916V24.6666Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_294_182" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_122" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FB4293"/>
|
||||
<stop offset="1" stop-color="#F957A1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_294_182">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,13 +1,8 @@
|
||||
<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_289_60)"/>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint1_linear_289_60)"/>
|
||||
<path d="M20.0083 9.91083C20.8155 10.1108 21.5881 10.431 22.3 10.8608C22.4427 10.947 22.5563 11.0739 22.6262 11.2253C22.696 11.3767 22.7188 11.5455 22.6916 11.71C22.5975 12.2842 22.74 12.7233 23.0083 12.9917C23.2766 13.26 23.7166 13.4025 24.29 13.3083C24.4545 13.2812 24.6233 13.304 24.7747 13.3738C24.9261 13.4437 25.053 13.5573 25.1391 13.7C25.569 14.4119 25.8893 15.1844 26.0891 15.9917C26.1294 16.1536 26.1201 16.3239 26.0624 16.4805C26.0048 16.6371 25.9015 16.7728 25.7658 16.87C25.2933 17.2092 25.0825 17.62 25.0825 18C25.0825 18.38 25.2933 18.7917 25.7658 19.1308C25.9013 19.228 26.0045 19.3635 26.0622 19.52C26.1198 19.6764 26.1292 19.8465 26.0891 20.0083C25.8892 20.8156 25.569 21.5881 25.1391 22.3C25.053 22.4427 24.9261 22.5564 24.7747 22.6262C24.6233 22.696 24.4545 22.7188 24.29 22.6917C23.7158 22.5975 23.2766 22.7408 23.0083 23.0083C22.74 23.2767 22.5975 23.7167 22.6916 24.29C22.719 24.4546 22.6962 24.6236 22.6264 24.7752C22.5566 24.9267 22.4429 25.0538 22.3 25.14C21.5881 25.5698 20.8155 25.8901 20.0083 26.09C19.8464 26.1302 19.6761 26.1209 19.5195 26.0633C19.3629 26.0056 19.2272 25.9023 19.13 25.7667C18.7908 25.2933 18.38 25.0833 18 25.0833C17.62 25.0833 17.2083 25.2933 16.8691 25.7667C16.772 25.9022 16.6365 26.0054 16.48 26.063C16.3236 26.1207 16.1535 26.1301 15.9916 26.09C15.1844 25.8901 14.4119 25.5698 13.7 25.14C13.5571 25.0538 13.4434 24.9267 13.3736 24.7752C13.3037 24.6236 13.281 24.4546 13.3083 24.29C13.4025 23.7167 13.2591 23.2775 12.9916 23.0092C12.7225 22.7408 12.2833 22.5975 11.71 22.6925C11.5454 22.7198 11.3763 22.6971 11.2248 22.6273C11.0733 22.5574 10.9462 22.4437 10.86 22.3008C10.43 21.5884 10.1098 20.8153 9.90998 20.0075C9.86992 19.8457 9.87931 19.6756 9.93695 19.5191C9.99459 19.3627 10.0978 19.2271 10.2333 19.13C10.7066 18.7917 10.9166 18.38 10.9166 18C10.9166 17.6208 10.7066 17.2083 10.2333 16.87C10.0978 16.7729 9.99459 16.6373 9.93695 16.4809C9.87931 16.3245 9.86992 16.1543 9.90998 15.9925C10.1098 15.185 10.4301 14.4122 10.86 13.7C10.9462 13.5571 11.0733 13.4434 11.2248 13.3736C11.3763 13.3037 11.5454 13.281 11.71 13.3083C12.2833 13.4025 12.7225 13.26 12.9908 12.9917C13.2591 12.7233 13.4025 12.2833 13.3075 11.71C13.2803 11.5455 13.3032 11.3767 13.373 11.2253C13.4428 11.0739 13.5564 10.947 13.6991 10.8608C14.4113 10.4309 15.1841 10.1107 15.9916 9.91083C16.1535 9.87077 16.3236 9.88017 16.48 9.93781C16.6365 9.99545 16.772 10.0987 16.8691 10.2342C17.2083 10.7067 17.62 10.9175 18 10.9175C18.3791 10.9175 18.7916 10.7067 19.13 10.2342C19.2271 10.0987 19.3627 9.99545 19.5191 9.93781C19.6755 9.88017 19.8465 9.87077 20.0083 9.91083ZM18 13.8333C16.8949 13.8333 15.8351 14.2723 15.0537 15.0537C14.2723 15.8351 13.8333 16.8949 13.8333 18C13.8333 19.1051 14.2723 20.1649 15.0537 20.9463C15.8351 21.7277 16.8949 22.1667 18 22.1667C19.105 22.1667 20.1649 21.7277 20.9463 20.9463C21.7277 20.1649 22.1666 19.1051 22.1666 18C22.1666 16.8949 21.7277 15.8351 20.9463 15.0537C20.1649 14.2723 19.105 13.8333 18 13.8333ZM18 15.5C18.663 15.5 19.2989 15.7634 19.7677 16.2322C20.2366 16.7011 20.5 17.337 20.5 18C20.5 18.663 20.2366 19.2989 19.7677 19.7678C19.2989 20.2366 18.663 20.5 18 20.5C17.3369 20.5 16.7011 20.2366 16.2322 19.7678C15.7634 19.2989 15.5 18.663 15.5 18C15.5 17.337 15.7634 16.7011 16.2322 16.2322C16.7011 15.7634 17.3369 15.5 18 15.5Z" fill="white"/>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_137)"/>
|
||||
<path d="M24.25 18.0001C24.25 17.8084 24.2416 17.6251 24.225 17.4334L25.775 16.2584C26.1083 16.0084 26.2 15.5417 25.9916 15.1751L24.4333 12.4834C24.225 12.1167 23.775 11.9667 23.3916 12.1334L21.6 12.8917C21.2916 12.6751 20.9666 12.4834 20.625 12.3251L20.3833 10.4001C20.3333 9.98341 19.975 9.66675 19.5583 9.66675H16.45C16.025 9.66675 15.6666 9.98341 15.6166 10.4001L15.375 12.3251C15.0333 12.4834 14.7083 12.6751 14.4 12.8917L12.6083 12.1334C12.225 11.9667 11.775 12.1167 11.5666 12.4834L10.0083 15.1834C9.79997 15.5501 9.89163 16.0084 10.225 16.2667L11.775 17.4417C11.7583 17.6251 11.75 17.8084 11.75 18.0001C11.75 18.1917 11.7583 18.3751 11.775 18.5667L10.225 19.7417C9.89163 19.9917 9.79997 20.4584 10.0083 20.8251L11.5666 23.5167C11.775 23.8834 12.225 24.0334 12.6083 23.8667L14.4 23.1084C14.7083 23.3251 15.0333 23.5167 15.375 23.6751L15.6166 25.6001C15.6666 26.0167 16.025 26.3334 16.4416 26.3334H19.55C19.9666 26.3334 20.325 26.0167 20.375 25.6001L20.6166 23.6751C20.9583 23.5167 21.2833 23.3251 21.5916 23.1084L23.3833 23.8667C23.7666 24.0334 24.2166 23.8834 24.425 23.5167L25.9833 20.8251C26.1916 20.4584 26.1 20.0001 25.7666 19.7417L24.2166 18.5667C24.2416 18.3751 24.25 18.1917 24.25 18.0001ZM18.0333 20.9167C16.425 20.9167 15.1166 19.6084 15.1166 18.0001C15.1166 16.3917 16.425 15.0834 18.0333 15.0834C19.6416 15.0834 20.95 16.3917 20.95 18.0001C20.95 19.6084 19.6416 20.9167 18.0333 20.9167Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_289_60" x1="0" y1="0" x2="36" y2="36" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E95E36"/>
|
||||
<stop offset="1" stop-color="#E0421D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_289_60" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_137" x1="6" y1="6.5" x2="29.5" y2="30.5" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#56718E"/>
|
||||
<stop offset="1" stop-color="#4B6683"/>
|
||||
</linearGradient>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,15 +1,10 @@
|
||||
<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_293_156)"/>
|
||||
<g clip-path="url(#clip0_293_156)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 11.3334C16.4977 11.3327 15.0392 11.8395 13.861 12.7715C12.6828 13.7036 11.854 15.0063 11.5088 16.4684C11.1637 17.9305 11.3225 19.4663 11.9595 20.8269C12.5965 22.1874 13.6744 23.2929 15.0184 23.9642C15.2161 24.0632 15.3663 24.2367 15.4361 24.4465C15.5059 24.6563 15.4895 24.8852 15.3904 25.0829C15.2914 25.2806 15.1179 25.4309 14.9081 25.5007C14.6983 25.5705 14.4694 25.554 14.2717 25.455C12.8875 24.7626 11.7236 23.6984 10.9102 22.3816C10.0969 21.0649 9.66628 19.5477 9.66669 18C9.66669 13.3975 13.3975 9.66669 18 9.66669C22.6025 9.66669 26.3334 13.3975 26.3334 18C26.3338 19.5477 25.9032 21.0649 25.0898 22.3816C24.2765 23.6984 23.1125 24.7626 21.7284 25.455C21.6305 25.504 21.5239 25.5333 21.4147 25.5411C21.3055 25.549 21.1958 25.5352 21.0919 25.5007C20.988 25.4661 20.892 25.4115 20.8092 25.3398C20.7265 25.2681 20.6586 25.1808 20.6096 25.0829C20.5606 24.985 20.5313 24.8785 20.5235 24.7693C20.5156 24.6601 20.5294 24.5504 20.5639 24.4465C20.5985 24.3426 20.6532 24.2466 20.7248 24.1638C20.7965 24.081 20.8838 24.0132 20.9817 23.9642C22.3257 23.2929 23.4036 22.1874 24.0406 20.8269C24.6776 19.4663 24.8364 17.9305 24.4912 16.4684C24.1461 15.0063 23.3172 13.7036 22.139 12.7715C20.9608 11.8395 19.5023 11.3327 18 11.3334ZM18 14.6667C17.2487 14.6662 16.5193 14.9195 15.93 15.3855C15.3407 15.8516 14.9262 16.503 14.7536 17.2342C14.581 17.9654 14.6604 18.7335 14.9791 19.4139C15.2978 20.0943 15.837 20.647 16.5092 20.9825C16.7017 21.0844 16.8466 21.2575 16.9131 21.4649C16.9795 21.6723 16.9621 21.8974 16.8647 22.0921C16.7672 22.2869 16.5974 22.4357 16.3915 22.5068C16.1857 22.5779 15.9602 22.5656 15.7634 22.4725C14.7561 21.9686 13.9485 21.1394 13.4714 20.1192C12.9942 19.0991 12.8754 17.9477 13.1343 16.8516C13.3932 15.7555 14.0146 14.7789 14.8977 14.08C15.7809 13.3811 16.8742 13.0009 18.0004 13.0009C19.1267 13.0009 20.22 13.3811 21.1031 14.08C21.9863 14.7789 22.6077 15.7555 22.8666 16.8516C23.1254 17.9477 23.0067 19.0991 22.5295 20.1192C22.0524 21.1394 21.2448 21.9686 20.2375 22.4725C20.0402 22.5695 19.8125 22.5846 19.6041 22.5144C19.3957 22.4443 19.2235 22.2946 19.1249 22.098C19.0264 21.9014 19.0096 21.6738 19.0781 21.4649C19.1467 21.2559 19.295 21.0826 19.4909 20.9825C20.1631 20.647 20.7023 20.0943 21.0209 19.4139C21.3396 18.7335 21.4191 17.9654 21.2465 17.2342C21.0739 16.503 20.6593 15.8516 20.07 15.3855C19.4808 14.9195 18.7513 14.6662 18 14.6667ZM16.3334 18C16.3334 17.558 16.5089 17.1341 16.8215 16.8215C17.1341 16.5089 17.558 16.3334 18 16.3334C18.442 16.3334 18.866 16.5089 19.1785 16.8215C19.4911 17.1341 19.6667 17.558 19.6667 18C19.6667 18.442 19.4911 18.866 19.1785 19.1785C18.866 19.4911 18.442 19.6667 18 19.6667C17.558 19.6667 17.1341 19.4911 16.8215 19.1785C16.5089 18.866 16.3334 18.442 16.3334 18Z" fill="white"/>
|
||||
</g>
|
||||
<rect width="36" height="36" rx="18" fill="url(#paint0_linear_971_132)"/>
|
||||
<path d="M18 17.1668C17.0834 17.1668 16.3334 17.9168 16.3334 18.8335C16.3334 19.7502 17.0834 20.5002 18 20.5002C18.9167 20.5002 19.6667 19.7502 19.6667 18.8335C19.6667 17.9168 18.9167 17.1668 18 17.1668ZM23 18.8335C23 15.8668 20.4084 13.5002 17.375 13.8752C15.1917 14.1418 13.3917 15.8835 13.0584 18.0585C12.7834 19.8502 13.4667 21.4835 14.6667 22.5585C15.0667 22.9168 15.6917 22.8335 15.9667 22.3668L15.975 22.3585C16.175 22.0085 16.0917 21.5835 15.7917 21.3085C14.9334 20.5335 14.4667 19.3335 14.775 18.0252C15.05 16.8418 16.0084 15.8835 17.1917 15.6002C19.375 15.0752 21.3334 16.7252 21.3334 18.8335C21.3334 19.8168 20.9 20.6918 20.225 21.3002C19.925 21.5668 19.8334 22.0002 20.0334 22.3502L20.0417 22.3585C20.3 22.8002 20.9 22.9335 21.2917 22.5918C22.3334 21.6752 23 20.3335 23 18.8335ZM17.025 10.5585C13.175 10.9918 10.0667 14.1668 9.70838 18.0252C9.41671 21.1085 10.8084 23.8752 13.0584 25.5335C13.4584 25.8252 14.025 25.7002 14.275 25.2752C14.4834 24.9168 14.3917 24.4502 14.0584 24.2002C12.1584 22.7918 11.0167 20.4085 11.425 17.7835C11.875 14.8668 14.3084 12.5418 17.2417 12.2168C21.2584 11.7585 24.6667 14.9002 24.6667 18.8335C24.6667 21.0418 23.5917 22.9835 21.9417 24.2002C21.6084 24.4502 21.5167 24.9085 21.725 25.2752C21.975 25.7085 22.5417 25.8252 22.9417 25.5335C25 24.0168 26.3334 21.5835 26.3334 18.8335C26.3334 13.9085 22.0584 9.98349 17.025 10.5585Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_293_156" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<linearGradient id="paint0_linear_971_132" x1="31" y1="27.5" x2="6.5" y2="7" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFA800"/>
|
||||
<stop offset="1" stop-color="#FFAC4B"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_293_156">
|
||||
<rect width="20" height="20" fill="white" transform="translate(8 8)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -35,6 +35,9 @@ body {
|
||||
border-radius: 6px;
|
||||
background-color: var(--scroller-color);
|
||||
}
|
||||
*::-webkit-scrollbar-corner {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
@@ -44,11 +47,11 @@ body {
|
||||
@import "./page.scss";
|
||||
@import "./font.scss";
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
background-color: rgba(18, 18, 18, 1);
|
||||
}
|
||||
}
|
||||
// @media (prefers-color-scheme: dark) {
|
||||
// :root {
|
||||
// background-color: rgba(18, 18, 18, 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
.user-none {
|
||||
user-select: none;
|
||||
@@ -56,7 +59,3 @@ body {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
[data-windrag] {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
overflow: hidden;
|
||||
|
||||
&__left {
|
||||
flex: 1 0 228px;
|
||||
flex: 1 0 200px;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
// max-width: 225px;
|
||||
// min-width: 225px;
|
||||
padding: 16px 0 8px;
|
||||
// padding: 16px 0 8px;
|
||||
padding: 0px 0px 8px;
|
||||
// position: relative;
|
||||
flex-direction: column;
|
||||
align-self: stretch;
|
||||
@@ -37,7 +38,7 @@
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
// border-bottom: 1px solid var(--divider-color);
|
||||
// max-width: $maxLogo + 32px;
|
||||
// max-height: $maxLogo;
|
||||
// margin: 0 auto;
|
||||
@@ -60,7 +61,7 @@
|
||||
.the-newbtn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0px;
|
||||
top: 15px;
|
||||
border-radius: 8px;
|
||||
padding: 2px 4px;
|
||||
transform: scale(0.8);
|
||||
@@ -71,6 +72,7 @@
|
||||
flex: 1 1 80%;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 0px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.the-traffic {
|
||||
@@ -78,7 +80,7 @@
|
||||
|
||||
> div {
|
||||
margin: 0 auto;
|
||||
// padding: 0 20px;
|
||||
padding: 0px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,14 +92,19 @@
|
||||
// background-color: var(--background-color-alpha);
|
||||
|
||||
.the-bar {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
height: 24px;
|
||||
// position: absolute;
|
||||
// top: 0px;
|
||||
// right: 0px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// align-items: center;
|
||||
justify-content: end;
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
.the-dragbar {
|
||||
margin-top: 5px;
|
||||
app-region: drag;
|
||||
}
|
||||
}
|
||||
|
||||
.the-content {
|
||||
@@ -114,12 +121,18 @@
|
||||
.windows,
|
||||
.unknown {
|
||||
&.layout {
|
||||
.layout__left {
|
||||
padding-top: 24px;
|
||||
}
|
||||
//.layout__left {
|
||||
// padding-top: 24px;
|
||||
//}
|
||||
|
||||
.layout__left .the-logo {
|
||||
flex: 1 0 58px;
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
padding-top: 30px;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.layout__right .the-content {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
.base-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
// border-radius: 10px;
|
||||
// border-top-left-radius: var(--border-radius);
|
||||
|
||||
> section {
|
||||
@@ -27,11 +28,11 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 16px 0;
|
||||
padding: 10px 0;
|
||||
box-sizing: border-box;
|
||||
scrollbar-gutter: stable;
|
||||
.base-content {
|
||||
width: calc(100% - 16px * 2);
|
||||
width: calc(100% - 10px * 2);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { ReactNode, useState, useEffect } from "react";
|
||||
import { Box, IconButton, Slide, Snackbar, Typography } from "@mui/material";
|
||||
import { Close, CheckCircleRounded, ErrorRounded } from "@mui/icons-material";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
interface InnerProps {
|
||||
type: string;
|
||||
duration?: number;
|
||||
message: ReactNode;
|
||||
isDark?: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const NoticeInner = (props: InnerProps) => {
|
||||
const { type, message, duration = 1500, onClose } = props;
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
const [isDark, setIsDark] = useState(false);
|
||||
const { verge } = useVerge();
|
||||
const { theme_mode } = verge ?? {};
|
||||
const onBtnClose = () => {
|
||||
setVisible(false);
|
||||
onClose();
|
||||
@@ -22,6 +26,26 @@ const NoticeInner = (props: InnerProps) => {
|
||||
if (reason !== "clickaway") onBtnClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const themeMode = ["light", "dark", "system"].includes(theme_mode!)
|
||||
? theme_mode!
|
||||
: "light";
|
||||
|
||||
if (themeMode !== "system") {
|
||||
setIsDark(themeMode === "dark");
|
||||
return;
|
||||
}
|
||||
|
||||
appWindow.theme().then((m) => m && setIsDark(m === "dark"));
|
||||
const unlisten = appWindow.onThemeChanged((e) =>
|
||||
setIsDark(e.payload === "dark")
|
||||
);
|
||||
|
||||
return () => {
|
||||
unlisten.then((fn) => fn());
|
||||
};
|
||||
}, [theme_mode]);
|
||||
|
||||
const msgElement =
|
||||
type === "info" ? (
|
||||
message
|
||||
@@ -46,7 +70,13 @@ const NoticeInner = (props: InnerProps) => {
|
||||
autoHideDuration={duration}
|
||||
onClose={onAutoClose}
|
||||
message={msgElement}
|
||||
sx={{ maxWidth: 360 }}
|
||||
sx={{
|
||||
maxWidth: 360,
|
||||
".MuiSnackbarContent-root": {
|
||||
bgcolor: isDark ? "#50515C" : "#ffffff",
|
||||
color: isDark ? "#ffffff" : "#000000",
|
||||
},
|
||||
}}
|
||||
TransitionComponent={(p) => <Slide {...p} direction="left" />}
|
||||
transitionDuration={200}
|
||||
action={
|
||||
@@ -61,9 +91,9 @@ const NoticeInner = (props: InnerProps) => {
|
||||
interface NoticeInstance {
|
||||
(props: Omit<InnerProps, "onClose">): void;
|
||||
|
||||
info(message: ReactNode, duration?: number): void;
|
||||
error(message: ReactNode, duration?: number): void;
|
||||
success(message: ReactNode, duration?: number): void;
|
||||
info(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
error(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
success(message: ReactNode, duration?: number, isDark?: boolean): void;
|
||||
}
|
||||
|
||||
let parent: HTMLDivElement = null!;
|
||||
|
||||
@@ -20,10 +20,10 @@ export const BasePage: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<BaseErrorBoundary>
|
||||
<div className="base-page">
|
||||
<header data-windrag style={{ userSelect: "none" }}>
|
||||
<header data-tauri-drag-region="true" style={{ userSelect: "none" }}>
|
||||
<Typography
|
||||
sx={{ fontSize: "20px", fontWeight: "700 " }}
|
||||
data-windrag
|
||||
data-tauri-drag-region="true"
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { TextField, type TextFieldProps, styled } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const BaseStyledTextField = styled((props: TextFieldProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<TextField
|
||||
hiddenLabel
|
||||
fullWidth
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
variant="outlined"
|
||||
spellCheck="false"
|
||||
placeholder={t("Filter conditions")}
|
||||
sx={{ input: { py: 0.65, px: 1.25 } }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
})(({ theme }) => ({
|
||||
"& .MuiInputBase-root": {
|
||||
background: theme.palette.mode === "light" ? "#fff" : undefined,
|
||||
},
|
||||
}));
|
||||
@@ -3,8 +3,8 @@ import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { Box, Button, Snackbar } from "@mui/material";
|
||||
import { deleteConnection } from "@/services/api";
|
||||
import { truncateStr } from "@/utils/truncate-str";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { t } from "i18next";
|
||||
|
||||
export interface ConnectionDetailRef {
|
||||
open: (detail: IConnectionsItem) => void;
|
||||
@@ -69,7 +69,9 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
|
||||
{ label: "Rule", value: rule },
|
||||
{
|
||||
label: "Process",
|
||||
value: truncateStr(metadata.process || metadata.processPath),
|
||||
value: `${metadata.process}${
|
||||
metadata.processPath ? `(${metadata.processPath})` : ""
|
||||
}`,
|
||||
},
|
||||
{ label: "Time", value: dayjs(data.start).fromNow() },
|
||||
{ label: "Source", value: `${metadata.sourceIP}:${metadata.sourcePort}` },
|
||||
@@ -96,7 +98,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
Close
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -38,6 +38,7 @@ export const ConnectionItem = (props: Props) => {
|
||||
return (
|
||||
<ListItem
|
||||
dense
|
||||
sx={{ borderBottom: "1px solid var(--divider-color)" }}
|
||||
secondaryAction={
|
||||
<IconButton edge="end" color="inherit" onClick={onDelete}>
|
||||
<CloseRounded />
|
||||
@@ -58,7 +59,9 @@ export const ConnectionItem = (props: Props) => {
|
||||
|
||||
{!!metadata.process && <Tag>{metadata.process}</Tag>}
|
||||
|
||||
{chains?.length > 0 && <Tag>{chains[value.chains.length - 1]}</Tag>}
|
||||
{chains?.length > 0 && (
|
||||
<Tag>{[...chains].reverse().join(" / ")}</Tag>
|
||||
)}
|
||||
|
||||
<Tag>{dayjs(start).fromNow()}</Tag>
|
||||
|
||||
|
||||
@@ -8,16 +8,29 @@ import {
|
||||
PushPinOutlined,
|
||||
PushPinRounded,
|
||||
} from "@mui/icons-material";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const LayoutControl = () => {
|
||||
const minWidth = 40;
|
||||
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
const [isPined, setIsPined] = useState(false);
|
||||
appWindow.isMaximized().then((isMaximized) => {
|
||||
setIsMaximized(() => isMaximized);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const unlistenResized = appWindow.onResized(() => {
|
||||
appWindow.isMaximized().then((maximized) => {
|
||||
setIsMaximized(() => maximized);
|
||||
});
|
||||
});
|
||||
|
||||
appWindow.isMaximized().then((maximized) => {
|
||||
setIsMaximized(() => maximized);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlistenResized.then((fn) => fn());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
|
||||
@@ -3,20 +3,19 @@ import {
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
Avatar,
|
||||
ListItemIcon,
|
||||
} from "@mui/material";
|
||||
import { useMatch, useResolvedPath, useNavigate } from "react-router-dom";
|
||||
import type { LinkProps } from "react-router-dom";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
interface Props {
|
||||
to: string;
|
||||
children: string;
|
||||
img: string;
|
||||
icon: React.ReactNode[];
|
||||
}
|
||||
export const LayoutItem = (props: Props) => {
|
||||
const { to, children, img } = props;
|
||||
|
||||
const { to, children, icon } = props;
|
||||
const { verge } = useVerge();
|
||||
const { menu_icon } = verge ?? {};
|
||||
const resolved = useResolvedPath(to);
|
||||
const match = useMatch({ path: resolved.pathname, end: true });
|
||||
const navigate = useNavigate();
|
||||
@@ -28,11 +27,10 @@ export const LayoutItem = (props: Props) => {
|
||||
sx={[
|
||||
{
|
||||
borderRadius: 2,
|
||||
marginLeft: 1.5,
|
||||
marginLeft: 1.25,
|
||||
paddingLeft: 1,
|
||||
paddingRight: 1,
|
||||
marginRight: 1.5,
|
||||
textAlign: "left",
|
||||
marginRight: 1.25,
|
||||
"& .MuiListItemText-primary": {
|
||||
color: "text.primary",
|
||||
fontWeight: "700",
|
||||
@@ -54,10 +52,19 @@ export const LayoutItem = (props: Props) => {
|
||||
]}
|
||||
onClick={() => navigate(to)}
|
||||
>
|
||||
<ListItemAvatar sx={{ marginRight: -0.5 }}>
|
||||
<Avatar src={img}></Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText primary={children} />
|
||||
{(menu_icon === "monochrome" || !menu_icon) && (
|
||||
<ListItemIcon sx={{ color: "text.primary", marginLeft: "6px" }}>
|
||||
{icon[0]}
|
||||
</ListItemIcon>
|
||||
)}
|
||||
{menu_icon === "colorful" && <ListItemIcon>{icon[1]}</ListItemIcon>}
|
||||
<ListItemText
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
marginLeft: menu_icon === "disable" ? "" : "-35px",
|
||||
}}
|
||||
primary={children}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@@ -29,21 +29,23 @@ export const LayoutTraffic = () => {
|
||||
// setup log ws during layout
|
||||
useLogSetup();
|
||||
|
||||
const { connect, disconnect } = useWebsocket((event) => {
|
||||
const data = JSON.parse(event.data) as ITrafficItem;
|
||||
trafficRef.current?.appendData(data);
|
||||
setTraffic(data);
|
||||
});
|
||||
const trafficWs = useWebsocket(
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data) as ITrafficItem;
|
||||
trafficRef.current?.appendData(data);
|
||||
setTraffic(data);
|
||||
},
|
||||
{ onError: () => setTraffic({ up: 0, down: 0 }), errorCount: 10 }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clashInfo || !pageVisible) return;
|
||||
|
||||
const { server = "", secret = "" } = clashInfo;
|
||||
connect(`ws://${server}/traffic?token=${encodeURIComponent(secret)}`);
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
trafficWs.connect(
|
||||
`ws://${server}/traffic?token=${encodeURIComponent(secret)}`
|
||||
);
|
||||
return () => trafficWs.disconnect();
|
||||
}, [clashInfo, pageVisible]);
|
||||
|
||||
/* --------- meta memory information --------- */
|
||||
@@ -54,7 +56,7 @@ export const LayoutTraffic = () => {
|
||||
(event) => {
|
||||
setMemory(JSON.parse(event.data));
|
||||
},
|
||||
{ onError: () => setMemory({ inuse: 0 }) }
|
||||
{ onError: () => setMemory({ inuse: 0 }), errorCount: 10 }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,7 +77,7 @@ export const LayoutTraffic = () => {
|
||||
};
|
||||
const valStyle: any = {
|
||||
component: "span",
|
||||
color: "primary",
|
||||
// color: "primary",
|
||||
textAlign: "center",
|
||||
sx: { flex: "1 1 56px", userSelect: "none" },
|
||||
};
|
||||
@@ -88,11 +90,7 @@ export const LayoutTraffic = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
width="188px"
|
||||
position="relative"
|
||||
onClick={trafficRef.current?.toggleStyle}
|
||||
>
|
||||
<Box position="relative" onClick={trafficRef.current?.toggleStyle}>
|
||||
{trafficGraph && pageVisible && (
|
||||
<div style={{ width: "100%", height: 60, marginBottom: 6 }}>
|
||||
<TrafficGraph ref={trafficRef} />
|
||||
@@ -103,9 +101,11 @@ export const LayoutTraffic = () => {
|
||||
<Box display="flex" alignItems="center" whiteSpace="nowrap">
|
||||
<ArrowUpward
|
||||
{...iconStyle}
|
||||
color={+up > 0 ? "primary" : "disabled"}
|
||||
color={+up > 0 ? "secondary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle}>{up}</Typography>
|
||||
<Typography {...valStyle} color="secondary">
|
||||
{up}
|
||||
</Typography>
|
||||
<Typography {...unitStyle}>{upUnit}/s</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -114,7 +114,9 @@ export const LayoutTraffic = () => {
|
||||
{...iconStyle}
|
||||
color={+down > 0 ? "primary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle}>{down}</Typography>
|
||||
<Typography {...valStyle} color="primary">
|
||||
{down}
|
||||
</Typography>
|
||||
<Typography {...unitStyle}>{downUnit}/s</Typography>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from "@mui/material";
|
||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||
import { UpdateViewer } from "../setting/mods/update-viewer";
|
||||
import { DialogRef } from "../base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -11,14 +12,20 @@ interface Props {
|
||||
|
||||
export const UpdateButton = (props: Props) => {
|
||||
const { className } = props;
|
||||
const { verge } = useVerge();
|
||||
const { auto_check_update } = verge || {};
|
||||
|
||||
const viewerRef = useRef<DialogRef>(null);
|
||||
|
||||
const { data: updateInfo } = useSWR("checkUpdate", checkUpdate, {
|
||||
errorRetryCount: 2,
|
||||
revalidateIfStale: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
});
|
||||
const { data: updateInfo } = useSWR(
|
||||
auto_check_update || auto_check_update === null ? "checkUpdate" : null,
|
||||
checkUpdate,
|
||||
{
|
||||
errorRetryCount: 2,
|
||||
revalidateIfStale: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
}
|
||||
);
|
||||
|
||||
if (!updateInfo?.shouldUpdate) return null;
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
title: string;
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmViewer = (props: Props) => {
|
||||
const { open, title, message, onClose, onConfirm } = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>{t(title)}</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pb: 1, userSelect: "text" }}>
|
||||
{t(message)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
{t("Cancel")}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant="contained">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -12,23 +12,45 @@ import {
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
import { readProfileFile, saveProfileFile } from "@/services/cmds";
|
||||
import { Notice } from "@/components/base";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js";
|
||||
import "monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js";
|
||||
import "monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js";
|
||||
import * as monaco from "monaco-editor";
|
||||
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
import { configureMonacoYaml } from "monaco-yaml";
|
||||
|
||||
import { type JSONSchema7 } from "json-schema";
|
||||
import metaSchema from "meta-json-schema/schemas/meta-json-schema.json";
|
||||
import mergeSchema from "meta-json-schema/schemas/clash-verge-merge-json-schema.json";
|
||||
|
||||
interface Props {
|
||||
uid: string;
|
||||
open: boolean;
|
||||
mode: "yaml" | "javascript";
|
||||
language: "yaml" | "javascript";
|
||||
schema?: "clash" | "merge";
|
||||
onClose: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
export const EditorViewer = (props: Props) => {
|
||||
const { uid, open, mode, onClose, onChange } = props;
|
||||
// yaml worker
|
||||
configureMonacoYaml(monaco, {
|
||||
validate: true,
|
||||
enableSchemaRequest: true,
|
||||
schemas: [
|
||||
{
|
||||
uri: "http://example.com/meta-json-schema.json",
|
||||
fileMatch: ["**/*.clash.yaml"],
|
||||
schema: metaSchema as JSONSchema7,
|
||||
},
|
||||
{
|
||||
uri: "http://example.com/clash-verge-merge-json-schema.json",
|
||||
fileMatch: ["**/*.merge.yaml"],
|
||||
schema: mergeSchema as JSONSchema7,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const EditorViewer = (props: Props) => {
|
||||
const { uid, open, language, schema, onClose, onChange } = props;
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef<any>();
|
||||
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
|
||||
@@ -41,13 +63,23 @@ export const EditorViewer = (props: Props) => {
|
||||
const dom = editorRef.current;
|
||||
|
||||
if (!dom) return;
|
||||
|
||||
if (instanceRef.current) instanceRef.current.dispose();
|
||||
|
||||
const uri = monaco.Uri.parse(`${nanoid()}.${schema}.${language}`);
|
||||
const model = monaco.editor.createModel(data, language, uri);
|
||||
instanceRef.current = editor.create(editorRef.current, {
|
||||
value: data,
|
||||
language: mode,
|
||||
model: model,
|
||||
language: language,
|
||||
tabSize: ["yaml", "javascript"].includes(language) ? 2 : 4, // 根据语言类型设置缩进
|
||||
theme: themeMode === "light" ? "vs" : "vs-dark",
|
||||
minimap: { enabled: false },
|
||||
minimap: { enabled: dom.clientWidth >= 1000 }, // 超过一定宽度显示minimap滚动条
|
||||
mouseWheelZoom: true, // Ctrl+滚轮调节缩放
|
||||
quickSuggestions: {
|
||||
strings: true, // 字符串类型的建议
|
||||
comments: true, // 注释类型的建议
|
||||
other: true, // 其他类型的建议
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,8 +109,10 @@ export const EditorViewer = (props: Props) => {
|
||||
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
|
||||
<DialogTitle>{t("Edit File")}</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ width: "95%", pb: 1, userSelect: "text" }}>
|
||||
<div style={{ width: "100%", height: "500px" }} ref={editorRef} />
|
||||
<DialogContent
|
||||
sx={{ width: "94%", height: "100vh", pb: 1, userSelect: "text" }}
|
||||
>
|
||||
<div style={{ width: "100%", height: "100%" }} ref={editorRef} />
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
|
||||
@@ -21,23 +21,34 @@ export const ProfileBox = styled(Box)(
|
||||
"dark-false": text.primary,
|
||||
}[key]!;
|
||||
|
||||
const borderLeft = {
|
||||
"light-true": `3px solid ${primary.main}`,
|
||||
"light-false": "none",
|
||||
"dark-true": `3px solid ${primary.main}`,
|
||||
"dark-false": "none",
|
||||
const borderSelect = {
|
||||
"light-true": {
|
||||
borderLeft: `3px solid ${primary.main}`,
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
},
|
||||
"light-false": {
|
||||
width: "100%",
|
||||
},
|
||||
"dark-true": {
|
||||
borderLeft: `3px solid ${primary.main}`,
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
},
|
||||
"dark-false": {
|
||||
width: "100%",
|
||||
},
|
||||
}[key];
|
||||
|
||||
return {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
display: "block",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
padding: "8px 16px",
|
||||
boxSizing: "border-box",
|
||||
backgroundColor,
|
||||
borderLeft,
|
||||
...borderSelect,
|
||||
borderRadius: "8px",
|
||||
color,
|
||||
"& h2": { color: h2color },
|
||||
|
||||
@@ -23,7 +23,8 @@ import { Notice } from "@/components/base";
|
||||
import { EditorViewer } from "./editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
import { ConfirmViewer } from "./confirm-viewer";
|
||||
import { open } from "@tauri-apps/api/shell";
|
||||
const round = keyframes`
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
@@ -54,9 +55,11 @@ export const ProfileItem = (props: Props) => {
|
||||
// remote file mode
|
||||
const hasUrl = !!itemData.url;
|
||||
const hasExtra = !!extra; // only subscription url has extra info
|
||||
const hasHome = !!itemData.home; // only subscription url has home page
|
||||
|
||||
const { upload = 0, download = 0, total = 0 } = extra ?? {};
|
||||
const from = parseUrl(itemData.url);
|
||||
const description = itemData.desc;
|
||||
const expire = parseExpire(extra?.expire);
|
||||
const progress = Math.round(((download + upload) * 100) / (total + 0.1));
|
||||
|
||||
@@ -91,6 +94,12 @@ export const ProfileItem = (props: Props) => {
|
||||
}, [hasUrl, updated]);
|
||||
|
||||
const [fileOpen, setFileOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
|
||||
const onOpenHome = () => {
|
||||
setAnchorEl(null);
|
||||
open(itemData.home ?? "");
|
||||
};
|
||||
|
||||
const onEditInfo = () => {
|
||||
setAnchorEl(null);
|
||||
@@ -163,21 +172,35 @@ export const ProfileItem = (props: Props) => {
|
||||
}
|
||||
});
|
||||
|
||||
const urlModeMenu = [
|
||||
const urlModeMenu = (
|
||||
hasHome ? [{ label: "Home", handler: onOpenHome }] : []
|
||||
).concat([
|
||||
{ label: "Select", handler: onForceSelect },
|
||||
{ label: "Edit Info", handler: onEditInfo },
|
||||
{ label: "Edit File", handler: onEditFile },
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Update", handler: () => onUpdate(0) },
|
||||
{ label: "Update(Proxy)", handler: () => onUpdate(2) },
|
||||
{ label: "Delete", handler: onDelete },
|
||||
];
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
]);
|
||||
const fileModeMenu = [
|
||||
{ label: "Select", handler: onForceSelect },
|
||||
{ label: "Edit Info", handler: onEditInfo },
|
||||
{ label: "Edit File", handler: onEditFile },
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Delete", handler: onDelete },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const boxStyle = {
|
||||
@@ -242,6 +265,7 @@ export const ProfileItem = (props: Props) => {
|
||||
|
||||
<Typography
|
||||
width="calc(100% - 36px)"
|
||||
sx={{ fontSize: "18px", fontWeight: "600", lineHeight: "26px" }}
|
||||
variant="h6"
|
||||
component="h2"
|
||||
noWrap
|
||||
@@ -275,27 +299,36 @@ export const ProfileItem = (props: Props) => {
|
||||
</Box>
|
||||
{/* the second line show url's info or description */}
|
||||
<Box sx={boxStyle}>
|
||||
{hasUrl ? (
|
||||
{
|
||||
<>
|
||||
<Typography noWrap title={`From: ${from}`}>
|
||||
{from}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
noWrap
|
||||
flex="1 0 auto"
|
||||
fontSize={14}
|
||||
textAlign="right"
|
||||
title={`Updated Time: ${parseExpire(updated)}`}
|
||||
>
|
||||
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
||||
</Typography>
|
||||
{description ? (
|
||||
<Typography
|
||||
noWrap
|
||||
title={description}
|
||||
sx={{ fontSize: "14px" }}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
) : (
|
||||
hasUrl && (
|
||||
<Typography noWrap title={`From ${from}`}>
|
||||
{from}
|
||||
</Typography>
|
||||
)
|
||||
)}
|
||||
{hasUrl && (
|
||||
<Typography
|
||||
noWrap
|
||||
flex="1 0 auto"
|
||||
fontSize={14}
|
||||
textAlign="right"
|
||||
title={`Updated Time: ${parseExpire(updated)}`}
|
||||
>
|
||||
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Typography noWrap title={itemData.desc}>
|
||||
{itemData.desc}
|
||||
</Typography>
|
||||
)}
|
||||
}
|
||||
</Box>
|
||||
{/* the third line show extra info or last updated time */}
|
||||
{hasExtra ? (
|
||||
@@ -306,7 +339,7 @@ export const ProfileItem = (props: Props) => {
|
||||
<span title="Expire Time">{expire}</span>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ ...boxStyle, fontSize: 14, justifyContent: "flex-end" }}>
|
||||
<Box sx={{ ...boxStyle, fontSize: 12, justifyContent: "flex-end" }}>
|
||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
||||
</Box>
|
||||
)}
|
||||
@@ -330,7 +363,19 @@ export const ProfileItem = (props: Props) => {
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
sx={{ minWidth: 120 }}
|
||||
sx={[
|
||||
{
|
||||
minWidth: 120,
|
||||
},
|
||||
(theme) => {
|
||||
return {
|
||||
color:
|
||||
item.label === "Delete"
|
||||
? theme.palette.error.main
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
]}
|
||||
dense
|
||||
>
|
||||
{t(item.label)}
|
||||
@@ -341,9 +386,20 @@ export const ProfileItem = (props: Props) => {
|
||||
<EditorViewer
|
||||
uid={uid}
|
||||
open={fileOpen}
|
||||
mode="yaml"
|
||||
language="yaml"
|
||||
schema="clash"
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
onDelete();
|
||||
setConfirmOpen(false);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Notice } from "@/components/base";
|
||||
import { EditorViewer } from "./editor-viewer";
|
||||
import { ProfileBox } from "./profile-box";
|
||||
import { LogViewer } from "./log-viewer";
|
||||
import { ConfirmViewer } from "./confirm-viewer";
|
||||
|
||||
interface Props {
|
||||
selected: boolean;
|
||||
@@ -51,6 +52,7 @@ export const ProfileMore = (props: Props) => {
|
||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [fileOpen, setFileOpen] = useState(false);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [logOpen, setLogOpen] = useState(false);
|
||||
|
||||
const onEditInfo = () => {
|
||||
@@ -87,7 +89,13 @@ export const ProfileMore = (props: Props) => {
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "To Top", show: showMove, handler: fnWrapper(onMoveTop) },
|
||||
{ label: "To End", show: showMove, handler: fnWrapper(onMoveEnd) },
|
||||
{ label: "Delete", handler: fnWrapper(onDelete) },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const disableMenu = [
|
||||
@@ -95,7 +103,13 @@ export const ProfileMore = (props: Props) => {
|
||||
{ label: "Edit Info", handler: onEditInfo },
|
||||
{ label: "Edit File", handler: onEditFile },
|
||||
{ label: "Open File", handler: onOpenFile },
|
||||
{ label: "Delete", handler: fnWrapper(onDelete) },
|
||||
{
|
||||
label: "Delete",
|
||||
handler: () => {
|
||||
setAnchorEl(null);
|
||||
setConfirmOpen(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const boxStyle = {
|
||||
@@ -178,17 +192,6 @@ export const ProfileMore = (props: Props) => {
|
||||
{itemData.desc}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
noWrap
|
||||
component="span"
|
||||
title={`Updated Time: ${parseExpire(itemData.updated)}`}
|
||||
style={{ fontSize: 14 }}
|
||||
>
|
||||
{!!itemData.updated
|
||||
? dayjs(itemData.updated! * 1000).fromNow()
|
||||
: ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
</ProfileBox>
|
||||
|
||||
@@ -211,7 +214,17 @@ export const ProfileMore = (props: Props) => {
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={item.handler}
|
||||
sx={{ minWidth: 120 }}
|
||||
sx={[
|
||||
{ minWidth: 120 },
|
||||
(theme) => {
|
||||
return {
|
||||
color:
|
||||
item.label === "Delete"
|
||||
? theme.palette.error.main
|
||||
: undefined,
|
||||
};
|
||||
},
|
||||
]}
|
||||
dense
|
||||
>
|
||||
{t(item.label)}
|
||||
@@ -222,10 +235,20 @@ export const ProfileMore = (props: Props) => {
|
||||
<EditorViewer
|
||||
uid={uid}
|
||||
open={fileOpen}
|
||||
mode={type === "merge" ? "yaml" : "javascript"}
|
||||
language={type === "merge" ? "yaml" : "javascript"}
|
||||
schema={type === "merge" ? "merge" : undefined}
|
||||
onClose={() => setFileOpen(false)}
|
||||
/>
|
||||
|
||||
<ConfirmViewer
|
||||
title="Confirm deletion"
|
||||
message="This operation is not reversible"
|
||||
open={confirmOpen}
|
||||
onClose={() => setConfirmOpen(false)}
|
||||
onConfirm={() => {
|
||||
onDelete();
|
||||
setConfirmOpen(false);
|
||||
}}
|
||||
/>
|
||||
{selected && (
|
||||
<LogViewer
|
||||
open={logOpen}
|
||||
|
||||
@@ -51,7 +51,6 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
desc: "",
|
||||
url: "",
|
||||
option: {
|
||||
// user_agent: "",
|
||||
with_proxy: false,
|
||||
self_proxy: false,
|
||||
},
|
||||
@@ -98,6 +97,11 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
|
||||
}
|
||||
if (form.option?.update_interval) {
|
||||
form.option.update_interval = +form.option.update_interval;
|
||||
} else {
|
||||
delete form.option?.update_interval;
|
||||
}
|
||||
if (form.option?.user_agent === "") {
|
||||
delete form.option.user_agent;
|
||||
}
|
||||
const name = form.name || `${form.type} file`;
|
||||
const item = { ...form, name };
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
providerHealthCheck,
|
||||
updateProxy,
|
||||
deleteConnection,
|
||||
getGroupProxyDelays,
|
||||
} from "@/services/api";
|
||||
import { useProfiles } from "@/hooks/use-profiles";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
@@ -32,7 +33,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
// 切换分组的节点代理
|
||||
const handleChangeProxy = useLockFn(
|
||||
async (group: IProxyGroupItem, proxy: IProxyItem) => {
|
||||
if (group.type !== "Selector" && group.type !== "Fallback") return;
|
||||
if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return;
|
||||
|
||||
const { name, now } = group;
|
||||
await updateProxy(name, proxy.name);
|
||||
@@ -84,7 +85,11 @@ export const ProxyGroups = (props: Props) => {
|
||||
}
|
||||
|
||||
const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
|
||||
await delayManager.checkListDelay(names, groupName, timeout);
|
||||
|
||||
await Promise.race([
|
||||
delayManager.checkListDelay(names, groupName, timeout),
|
||||
getGroupProxyDelays(groupName, delayManager.getUrl(groupName), timeout), // 查询group delays 将清除fixed(不关注调用结果)
|
||||
]);
|
||||
|
||||
onProxies();
|
||||
});
|
||||
@@ -117,7 +122,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
return (
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ height: "100%" }}
|
||||
style={{ height: "calc(100% - 12px)" }}
|
||||
totalCount={renderList.length}
|
||||
increaseViewportBy={256}
|
||||
itemContent={(index) => (
|
||||
|
||||
@@ -109,7 +109,7 @@ export const ProxyHead = (props: Props) => {
|
||||
<IconButton
|
||||
size="small"
|
||||
color="inherit"
|
||||
title={t("Proxy detail")}
|
||||
title={showType ? t("Proxy basic") : t("Proxy detail")}
|
||||
onClick={() => onHeadState({ showType: !showType })}
|
||||
>
|
||||
{showType ? <VisibilityRounded /> : <VisibilityOffRounded />}
|
||||
|
||||
@@ -7,7 +7,7 @@ import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
group: IProxyGroupItem;
|
||||
proxy: IProxyItem;
|
||||
selected: boolean;
|
||||
showType?: boolean;
|
||||
@@ -16,7 +16,7 @@ interface Props {
|
||||
|
||||
// 多列布局
|
||||
export const ProxyItemMini = (props: Props) => {
|
||||
const { groupName, proxy, selected, showType = true, onClick } = props;
|
||||
const { group, proxy, selected, showType = true, onClick } = props;
|
||||
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
@@ -25,21 +25,21 @@ export const ProxyItemMini = (props: Props) => {
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
||||
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, groupName);
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, groupName]);
|
||||
}, [proxy.name, group.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, groupName));
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -58,7 +58,6 @@ export const ProxyItemMini = (props: Props) => {
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||
const color = mode === "light" ? primary.main : primary.light;
|
||||
const showDelay = delay > 0;
|
||||
const selectColor = mode === "light" ? primary.main : primary.light;
|
||||
|
||||
@@ -66,25 +65,31 @@ export const ProxyItemMini = (props: Props) => {
|
||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
||||
"&:hover .the-icon": { display: "none" },
|
||||
"&:hover ": {
|
||||
"& .the-pin, & .the-unpin": {
|
||||
position: "absolute",
|
||||
fontSize: "12px",
|
||||
top: "-5px",
|
||||
right: "-5px",
|
||||
},
|
||||
"& .the-unpin": { filter: "grayscale(1)" },
|
||||
"&.Mui-selected": {
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor:
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35),
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor,
|
||||
},
|
||||
// "&.Mui-selected .MuiListItemText-secondary": { color },
|
||||
backgroundColor: bgcolor,
|
||||
};
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Box title={proxy.name} sx={{ overflow: "hidden" }}>
|
||||
<Box
|
||||
title={`${proxy.name}\n${proxy.now ?? ""}`}
|
||||
sx={{ overflow: "hidden" }}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
component="div"
|
||||
@@ -95,8 +100,6 @@ export const ProxyItemMini = (props: Props) => {
|
||||
wordBreak: "break-all",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "13px",
|
||||
fontWeight: "700",
|
||||
}}
|
||||
>
|
||||
{proxy.name}
|
||||
@@ -122,8 +125,6 @@ export const ProxyItemMini = (props: Props) => {
|
||||
wordBreak: "break-all",
|
||||
overflow: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
fontSize: "11px",
|
||||
fontWeight: "700",
|
||||
marginRight: "8px",
|
||||
}}
|
||||
>
|
||||
@@ -131,23 +132,37 @@ export const ProxyItemMini = (props: Props) => {
|
||||
</Typography>
|
||||
)}
|
||||
{!!proxy.provider && (
|
||||
<TypeBox component="span">{proxy.provider}</TypeBox>
|
||||
<TypeBox color="text.secondary" component="span">
|
||||
{proxy.provider}
|
||||
</TypeBox>
|
||||
)}
|
||||
<TypeBox color="text.secondary" component="span">
|
||||
{proxy.type}
|
||||
</TypeBox>
|
||||
{proxy.udp && (
|
||||
<TypeBox color="text.secondary" component="span">
|
||||
UDP
|
||||
</TypeBox>
|
||||
)}
|
||||
{proxy.xudp && (
|
||||
<TypeBox color="text.secondary" component="span">
|
||||
XUDP
|
||||
</TypeBox>
|
||||
)}
|
||||
{proxy.tfo && (
|
||||
<TypeBox color="text.secondary" component="span">
|
||||
TFO
|
||||
</TypeBox>
|
||||
)}
|
||||
<TypeBox component="span">{proxy.type}</TypeBox>
|
||||
{proxy.udp && <TypeBox component="span">UDP</TypeBox>}
|
||||
{proxy.xudp && <TypeBox component="span">XUDP</TypeBox>}
|
||||
{proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ ml: 0.5, color: "primary.main" }}>
|
||||
{delay === -2 && (
|
||||
<Widget>
|
||||
<BaseLoading />
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{!proxy.provider && delay !== -2 && (
|
||||
// provider的节点不支持检测
|
||||
<Widget
|
||||
@@ -186,7 +201,6 @@ export const ProxyItemMini = (props: Props) => {
|
||||
{delayManager.formatDelay(delay, timeout)}
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{delay !== -2 && delay <= 0 && selected && (
|
||||
// 展示已选择的icon
|
||||
<CheckCircleOutlineRounded
|
||||
@@ -195,13 +209,20 @@ export const ProxyItemMini = (props: Props) => {
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{group.fixed && group.fixed === proxy.name && (
|
||||
// 展示fixed状态
|
||||
<span className={proxy.name === group.now ? "the-pin" : "the-unpin"}>
|
||||
📌
|
||||
</span>
|
||||
)}
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
const Widget = styled(Box)(({ theme: { typography } }) => ({
|
||||
padding: "2px 4px",
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
fontFamily: typography.fontFamily,
|
||||
borderRadius: "4px",
|
||||
}));
|
||||
@@ -209,8 +230,8 @@ const Widget = styled(Box)(({ theme: { typography } }) => ({
|
||||
const TypeBox = styled(Box)(({ theme: { palette, typography } }) => ({
|
||||
display: "inline-block",
|
||||
border: "1px solid #ccc",
|
||||
borderColor: palette.mode === "light" ? "#d9d9d9" : "#424242",
|
||||
color: palette.mode === "light" ? "#8c8c8c" : "#ffffff",
|
||||
borderColor: "text.secondary",
|
||||
color: "text.secondary",
|
||||
borderRadius: 4,
|
||||
fontSize: 10,
|
||||
fontFamily: typography.fontFamily,
|
||||
@@ -219,12 +240,3 @@ const TypeBox = styled(Box)(({ theme: { palette, typography } }) => ({
|
||||
padding: "0 4px",
|
||||
lineHeight: 1.5,
|
||||
}));
|
||||
|
||||
const TypeTypo = styled(Box)(({ theme: { palette, typography } }) => ({
|
||||
display: "inline-block",
|
||||
fontSize: 10,
|
||||
fontFamily: typography.fontFamily,
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
}));
|
||||
|
||||
@@ -11,14 +11,13 @@ import {
|
||||
styled,
|
||||
SxProps,
|
||||
Theme,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { BaseLoading } from "@/components/base";
|
||||
import delayManager from "@/services/delay";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
|
||||
interface Props {
|
||||
groupName: string;
|
||||
group: IProxyGroupItem;
|
||||
proxy: IProxyItem;
|
||||
selected: boolean;
|
||||
showType?: boolean;
|
||||
@@ -45,7 +44,7 @@ const TypeBox = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
export const ProxyItem = (props: Props) => {
|
||||
const { groupName, proxy, selected, showType = true, sx, onClick } = props;
|
||||
const { group, proxy, selected, showType = true, sx, onClick } = props;
|
||||
|
||||
// -1/<=0 为 不显示
|
||||
// -2 为 loading
|
||||
@@ -53,21 +52,21 @@ export const ProxyItem = (props: Props) => {
|
||||
const { verge } = useVerge();
|
||||
const timeout = verge?.default_latency_timeout || 10000;
|
||||
useEffect(() => {
|
||||
delayManager.setListener(proxy.name, groupName, setDelay);
|
||||
delayManager.setListener(proxy.name, group.name, setDelay);
|
||||
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, groupName);
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, groupName]);
|
||||
}, [proxy.name, group.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, groupName));
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
setDelay(await delayManager.checkDelay(proxy.name, groupName, timeout));
|
||||
setDelay(await delayManager.checkDelay(proxy.name, group.name, timeout));
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -87,17 +86,15 @@ export const ProxyItem = (props: Props) => {
|
||||
"&:hover .the-check": { display: !showDelay ? "block" : "none" },
|
||||
"&:hover .the-delay": { display: showDelay ? "block" : "none" },
|
||||
"&:hover .the-icon": { display: "none" },
|
||||
"&:hover ": {
|
||||
"&.Mui-selected": {
|
||||
width: `calc(100% + 3px)`,
|
||||
marginLeft: `-3px`,
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor:
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.15)
|
||||
: alpha(primary.main, 0.35),
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
borderLeft: `3px solid ${selectColor}`,
|
||||
bgcolor,
|
||||
},
|
||||
// "&.Mui-selected .MuiListItemText-secondary": { bgcolor },
|
||||
backgroundColor: bgcolor,
|
||||
marginBottom: "8px",
|
||||
height: "40px",
|
||||
@@ -109,17 +106,17 @@ export const ProxyItem = (props: Props) => {
|
||||
title={proxy.name}
|
||||
secondary={
|
||||
<>
|
||||
<span
|
||||
style={{
|
||||
<Box
|
||||
sx={{
|
||||
display: "inline-block",
|
||||
marginRight: "8px",
|
||||
fontSize: "13px",
|
||||
fontSize: "14px",
|
||||
color: "text.primary",
|
||||
fontWeight: "700",
|
||||
}}
|
||||
>
|
||||
{proxy.name}
|
||||
{showType && proxy.now && ` - ${proxy.now}`}
|
||||
</span>
|
||||
</Box>
|
||||
{showType && !!proxy.provider && (
|
||||
<TypeBox component="span">{proxy.provider}</TypeBox>
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,9 @@ import type { IRenderItem } from "./use-render-list";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
import { useEffect, useState } from "react";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
import { downloadIconCache } from "@/services/cmds";
|
||||
|
||||
interface RenderProps {
|
||||
item: IRenderItem;
|
||||
@@ -36,9 +39,26 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
const { verge } = useVerge();
|
||||
const enable_group_icon = verge?.enable_group_icon ?? true;
|
||||
const [mode] = useRecoilState(atomThemeMode);
|
||||
console.log(mode);
|
||||
const isDark = mode === "light" ? false : true;
|
||||
const itembackgroundcolor = isDark ? "#282A36" : "#ffffff";
|
||||
const [iconCachePath, setIconCachePath] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initIconCachePath();
|
||||
}, [group]);
|
||||
|
||||
async function initIconCachePath() {
|
||||
if (group.icon && group.icon.trim().startsWith("http")) {
|
||||
const fileName =
|
||||
group.name.replaceAll(" ", "") + "-" + getFileName(group.icon);
|
||||
const iconPath = await downloadIconCache(group.icon, fileName);
|
||||
setIconCachePath(convertFileSrc(iconPath));
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(url: string) {
|
||||
return url.substring(url.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
if (type === 0 && !group.hidden) {
|
||||
return (
|
||||
@@ -46,8 +66,8 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
dense
|
||||
style={{
|
||||
background: itembackgroundcolor,
|
||||
height: "64px",
|
||||
margin: "8px 16px",
|
||||
height: "100%",
|
||||
margin: "8px 8px",
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
onClick={() => onHeadState(group.name, { open: !headState?.open })}
|
||||
@@ -56,8 +76,8 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon &&
|
||||
group.icon.trim().startsWith("http") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
src={iconCachePath === "" ? group.icon : iconCachePath}
|
||||
width="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
@@ -66,7 +86,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon.trim().startsWith("data") && (
|
||||
<img
|
||||
src={group.icon}
|
||||
height="32px"
|
||||
width="32px"
|
||||
style={{ marginRight: "12px", borderRadius: "6px" }}
|
||||
/>
|
||||
)}
|
||||
@@ -75,14 +95,13 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
group.icon.trim().startsWith("<svg") && (
|
||||
<img
|
||||
src={`data:image/svg+xml;base64,${btoa(group.icon)}`}
|
||||
height="32px"
|
||||
width="32px"
|
||||
/>
|
||||
)}
|
||||
<ListItemText
|
||||
primary={<StyledPrimary>{group.name}</StyledPrimary>}
|
||||
secondary={
|
||||
<ListItemTextChild
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
@@ -90,15 +109,12 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
pt: "2px",
|
||||
}}
|
||||
>
|
||||
<StyledTypeBox>{group.type}</StyledTypeBox>
|
||||
<StyledSubtitle
|
||||
sx={{
|
||||
color: isDark ? "#ffffff" : "#8c8c8c",
|
||||
fontWeight: "600",
|
||||
}}
|
||||
>
|
||||
{group.now}
|
||||
</StyledSubtitle>
|
||||
<Box sx={{ marginTop: "2px" }}>
|
||||
<StyledTypeBox>{group.type}</StyledTypeBox>
|
||||
<StyledSubtitle sx={{ color: "text.secondary" }}>
|
||||
{group.now}
|
||||
</StyledSubtitle>
|
||||
</Box>
|
||||
</ListItemTextChild>
|
||||
}
|
||||
secondaryTypographyProps={{
|
||||
@@ -113,7 +129,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
if (type === 1 && !group.hidden) {
|
||||
return (
|
||||
<ProxyHead
|
||||
sx={{ pl: indent ? 4.5 : 2.5, pr: 3, mt: indent ? 1 : 0.5, mb: 1 }}
|
||||
sx={{ pl: 2, pr: 3, mt: indent ? 1 : 0.5, mb: 1 }}
|
||||
groupName={group.name}
|
||||
headState={headState!}
|
||||
onLocation={() => onLocation(group)}
|
||||
@@ -126,11 +142,11 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
if (type === 2 && !group.hidden) {
|
||||
return (
|
||||
<ProxyItem
|
||||
groupName={group.name}
|
||||
group={group}
|
||||
proxy={proxy!}
|
||||
selected={group.now === proxy?.name}
|
||||
showType={headState?.showType}
|
||||
sx={{ py: 0, pl: indent ? 4 : 2 }}
|
||||
sx={{ py: 0, pl: 2 }}
|
||||
onClick={() => onChangeProxy(group, proxy!)}
|
||||
/>
|
||||
);
|
||||
@@ -141,7 +157,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
<Box
|
||||
sx={{
|
||||
py: 2,
|
||||
pl: indent ? 4.5 : 0,
|
||||
pl: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
@@ -161,7 +177,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
height: 56,
|
||||
display: "grid",
|
||||
gap: 1,
|
||||
pl: indent ? 4 : 2,
|
||||
pl: 2,
|
||||
pr: 2,
|
||||
pb: 1,
|
||||
gridTemplateColumns: `repeat(${item.col! || 2}, 1fr)`,
|
||||
@@ -170,7 +186,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
{proxyCol?.map((proxy) => (
|
||||
<ProxyItemMini
|
||||
key={item.key + proxy.name}
|
||||
groupName={group.name}
|
||||
group={group}
|
||||
proxy={proxy!}
|
||||
selected={group.now === proxy.name}
|
||||
showType={headState?.showType}
|
||||
@@ -185,7 +201,7 @@ export const ProxyRender = (props: RenderProps) => {
|
||||
};
|
||||
|
||||
const StyledPrimary = styled("span")`
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
@@ -193,8 +209,9 @@ const StyledPrimary = styled("span")`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
const StyledSubtitle = styled("span")`
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
color: text.secondary;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const parseColor = (text: string) => {
|
||||
if (text === "REJECT") return "error.main";
|
||||
if (text === "REJECT" || text === "REJECT-DROP") return "error.main";
|
||||
if (text === "DIRECT") return "text.primary";
|
||||
|
||||
let sum = 0;
|
||||
@@ -34,7 +34,7 @@ const RuleItem = (props: Props) => {
|
||||
const { index, value } = props;
|
||||
|
||||
return (
|
||||
<Item>
|
||||
<Item sx={{ borderBottom: "1px solid var(--divider-color)" }}>
|
||||
<Typography
|
||||
color="text.secondary"
|
||||
variant="body2"
|
||||
|
||||
@@ -5,6 +5,8 @@ import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import getSystem from "@/utils/get-system";
|
||||
const OS = getSystem();
|
||||
|
||||
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -13,6 +15,12 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { verge, patchVerge } = useVerge();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [redirPort, setRedirPort] = useState(
|
||||
verge?.verge_redir_port ?? clashInfo?.redir_port ?? 7895
|
||||
);
|
||||
const [tproxyPort, setTproxyPort] = useState(
|
||||
verge?.verge_tproxy_port ?? clashInfo?.tproxy_port ?? 7896
|
||||
);
|
||||
const [mixedPort, setMixedPort] = useState(
|
||||
verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897
|
||||
);
|
||||
@@ -25,6 +33,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
if (verge?.verge_redir_port) setRedirPort(verge?.verge_redir_port);
|
||||
if (verge?.verge_tproxy_port) setTproxyPort(verge?.verge_tproxy_port);
|
||||
if (verge?.verge_mixed_port) setMixedPort(verge?.verge_mixed_port);
|
||||
if (verge?.verge_socks_port) setSocksPort(verge?.verge_socks_port);
|
||||
if (verge?.verge_port) setPort(verge?.verge_port);
|
||||
@@ -35,6 +45,8 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
if (
|
||||
redirPort === verge?.verge_redir_port &&
|
||||
tproxyPort === verge?.verge_tproxy_port &&
|
||||
mixedPort === verge?.verge_mixed_port &&
|
||||
socksPort === verge?.verge_socks_port &&
|
||||
port === verge?.verge_port
|
||||
@@ -42,7 +54,34 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
OS === "linux" &&
|
||||
new Set([redirPort, tproxyPort, mixedPort, socksPort, port]).size !== 5
|
||||
) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
OS === "macos" &&
|
||||
new Set([redirPort, mixedPort, socksPort, port]).size !== 4
|
||||
) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
if (OS === "windows" && new Set([mixedPort, socksPort, port]).size !== 3) {
|
||||
Notice.error("Port conflict!", 4000);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (OS !== "windows") {
|
||||
await patchInfo({ "redir-port": redirPort });
|
||||
await patchVerge({ verge_redir_port: redirPort });
|
||||
}
|
||||
if (OS === "linux") {
|
||||
await patchInfo({ "tproxy-port": tproxyPort });
|
||||
await patchVerge({ verge_tproxy_port: tproxyPort });
|
||||
}
|
||||
await patchInfo({ "mixed-port": mixedPort });
|
||||
await patchInfo({ "socks-port": socksPort });
|
||||
await patchInfo({ port });
|
||||
@@ -68,6 +107,35 @@ export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onOk={onSave}
|
||||
>
|
||||
<List>
|
||||
{OS !== "windows" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Redir Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={redirPort}
|
||||
onChange={(e) =>
|
||||
setRedirPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{OS === "linux" && (
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Tproxy Port" />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 135 }}
|
||||
value={tproxyPort}
|
||||
onChange={(e) =>
|
||||
setTproxyPort(+e.target.value?.replace(/\D+/, "").slice(0, 5))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Mixed Port" />
|
||||
<TextField
|
||||
|
||||
@@ -7,10 +7,17 @@ import {
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useRecoilValue } from "recoil";
|
||||
import { Chip } from "@mui/material";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Chip,
|
||||
} from "@mui/material";
|
||||
import { atomThemeMode } from "@/services/states";
|
||||
import { getRuntimeYaml } from "@/services/cmds";
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { DialogRef } from "@/components/base";
|
||||
import { editor } from "monaco-editor/esm/vs/editor/editor.api";
|
||||
|
||||
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js";
|
||||
@@ -47,9 +54,12 @@ export const ConfigViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
instanceRef.current = editor.create(editorRef.current, {
|
||||
value: data ?? "# Error\n",
|
||||
language: "yaml",
|
||||
tabSize: 2,
|
||||
theme: themeMode === "light" ? "vs" : "vs-dark",
|
||||
minimap: { enabled: false },
|
||||
readOnly: true,
|
||||
minimap: { enabled: dom.clientWidth >= 1000 }, // 超过一定宽度显示minimap滚动条
|
||||
mouseWheelZoom: true, // Ctrl+滚轮调节缩放
|
||||
readOnly: true, // 只读
|
||||
readOnlyMessage: { value: t("ReadOnlyMessage") },
|
||||
});
|
||||
});
|
||||
},
|
||||
@@ -57,20 +67,22 @@ export const ConfigViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
open={open}
|
||||
title={
|
||||
<>
|
||||
{t("Runtime Config")} <Chip label={t("ReadOnly")} size="small" />
|
||||
</>
|
||||
}
|
||||
contentSx={{ width: 520, pb: 1, userSelect: "text" }}
|
||||
cancelBtn={t("Back")}
|
||||
disableOk
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
<div style={{ width: "100%", height: "420px" }} ref={editorRef} />
|
||||
</BaseDialog>
|
||||
<Dialog open={open} onClose={() => setOpen(false)} maxWidth="xl" fullWidth>
|
||||
<DialogTitle>
|
||||
{t("Runtime Config")} <Chip label={t("ReadOnly")} size="small" />
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent
|
||||
sx={{ width: "94%", height: "100vh", pb: 1, userSelect: "text" }}
|
||||
>
|
||||
<div style={{ width: "100%", height: "100%" }} ref={editorRef} />
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpen(false)} variant="outlined">
|
||||
{t("Back")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
>
|
||||
<List>
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="External Controller" />
|
||||
<ListItemText primary={t("External Controller")} />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
@@ -58,13 +58,13 @@ export const ControllerViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary="Core Secret" />
|
||||
<ListItemText primary={t("Core Secret")} />
|
||||
<TextField
|
||||
size="small"
|
||||
autoComplete="off"
|
||||
sx={{ width: 175 }}
|
||||
value={secret}
|
||||
placeholder="Recommended"
|
||||
placeholder={t("Recommended")}
|
||||
onChange={(e) => setSecret(e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { List, Button } from "@mui/material";
|
||||
import { List, Button, Select, MenuItem } from "@mui/material";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
import { SettingItem } from "./setting-comp";
|
||||
@@ -9,6 +9,7 @@ import { open as openDialog } from "@tauri-apps/api/dialog";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
import { copyIconFile, getAppDir } from "@/services/cmds";
|
||||
import { join } from "@tauri-apps/api/path";
|
||||
import { exists } from "@tauri-apps/api/fs";
|
||||
|
||||
export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -26,12 +27,27 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
async function initIconPath() {
|
||||
const appDir = await getAppDir();
|
||||
const icon_dir = await join(appDir, "icons");
|
||||
const common_icon = await join(icon_dir, "common.png");
|
||||
const sysproxy_icon = await join(icon_dir, "sysproxy.png");
|
||||
const tun_icon = await join(icon_dir, "tun.png");
|
||||
setCommonIcon(common_icon);
|
||||
setSysproxyIcon(sysproxy_icon);
|
||||
setTunIcon(tun_icon);
|
||||
const common_icon_png = await join(icon_dir, "common.png");
|
||||
const common_icon_ico = await join(icon_dir, "common.ico");
|
||||
const sysproxy_icon_png = await join(icon_dir, "sysproxy.png");
|
||||
const sysproxy_icon_ico = await join(icon_dir, "sysproxy.ico");
|
||||
const tun_icon_png = await join(icon_dir, "tun.png");
|
||||
const tun_icon_ico = await join(icon_dir, "tun.ico");
|
||||
if (await exists(common_icon_ico)) {
|
||||
setCommonIcon(common_icon_ico);
|
||||
} else {
|
||||
setCommonIcon(common_icon_png);
|
||||
}
|
||||
if (await exists(sysproxy_icon_ico)) {
|
||||
setSysproxyIcon(sysproxy_icon_ico);
|
||||
} else {
|
||||
setSysproxyIcon(sysproxy_icon_png);
|
||||
}
|
||||
if (await exists(tun_icon_ico)) {
|
||||
setTunIcon(tun_icon_ico);
|
||||
} else {
|
||||
setTunIcon(tun_icon_png);
|
||||
}
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -97,6 +113,22 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Menu Icon")}>
|
||||
<GuardState
|
||||
value={verge?.menu_icon ?? "monochrome"}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ menu_icon: e })}
|
||||
onGuard={(e) => patchVerge({ menu_icon: e })}
|
||||
>
|
||||
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
||||
<MenuItem value="monochrome">{t("Monochrome")}</MenuItem>
|
||||
<MenuItem value="colorful">{t("Colorful")}</MenuItem>
|
||||
<MenuItem value="disable">{t("Disable")}</MenuItem>
|
||||
</Select>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Common Tray Icon")}>
|
||||
<GuardState
|
||||
value={verge?.common_tray_icon}
|
||||
@@ -124,12 +156,13 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
extensions: ["png", "ico"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "common.png");
|
||||
await copyIconFile(`${path}`, "common");
|
||||
await initIconPath();
|
||||
onChangeData({ common_tray_icon: true });
|
||||
patchVerge({ common_tray_icon: true });
|
||||
}
|
||||
@@ -168,12 +201,13 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
extensions: ["png", "ico"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "sysproxy.png");
|
||||
await copyIconFile(`${path}`, "sysproxy");
|
||||
await initIconPath();
|
||||
onChangeData({ sysproxy_tray_icon: true });
|
||||
patchVerge({ sysproxy_tray_icon: true });
|
||||
}
|
||||
@@ -210,12 +244,13 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
filters: [
|
||||
{
|
||||
name: "Tray Icon Image",
|
||||
extensions: ["png"],
|
||||
extensions: ["png", "ico"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (path?.length) {
|
||||
await copyIconFile(`${path}`, "tun.png");
|
||||
await copyIconFile(`${path}`, "tun");
|
||||
await initIconPath();
|
||||
onChangeData({ tun_tray_icon: true });
|
||||
patchVerge({ tun_tray_icon: true });
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const [values, setValues] = useState({
|
||||
appLogLevel: "info",
|
||||
autoCloseConnection: true,
|
||||
autoCheckUpdate: true,
|
||||
enableBuiltinEnhanced: true,
|
||||
proxyLayoutColumn: 6,
|
||||
defaultLatencyTest: "",
|
||||
@@ -33,6 +34,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setValues({
|
||||
appLogLevel: verge?.app_log_level ?? "info",
|
||||
autoCloseConnection: verge?.auto_close_connection ?? true,
|
||||
autoCheckUpdate: verge?.auto_check_update ?? true,
|
||||
enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true,
|
||||
proxyLayoutColumn: verge?.proxy_layout_column || 6,
|
||||
defaultLatencyTest: verge?.default_latency_test || "",
|
||||
@@ -48,6 +50,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
await patchVerge({
|
||||
app_log_level: values.appLogLevel,
|
||||
auto_close_connection: values.autoCloseConnection,
|
||||
auto_check_update: values.autoCheckUpdate,
|
||||
enable_builtin_enhanced: values.enableBuiltinEnhanced,
|
||||
proxy_layout_column: values.proxyLayoutColumn,
|
||||
default_latency_test: values.defaultLatencyTest,
|
||||
@@ -104,6 +107,17 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Auto Check Update")} />
|
||||
<Switch
|
||||
edge="end"
|
||||
checked={values.autoCheckUpdate}
|
||||
onChange={(_, c) =>
|
||||
setValues((v) => ({ ...v, autoCheckUpdate: c }))
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem sx={{ padding: "5px 2px" }}>
|
||||
<ListItemText primary={t("Enable Builtin Enhanced")} />
|
||||
<Switch
|
||||
|
||||
@@ -86,12 +86,15 @@ export const ServiceViewer = forwardRef<DialogRef, Props>((props, ref) => {
|
||||
disableFooter
|
||||
onClose={() => setOpen(false)}
|
||||
>
|
||||
<Typography>Current State: {state}</Typography>
|
||||
<Typography>
|
||||
{t("Current State")}: {t(state)}
|
||||
</Typography>
|
||||
|
||||
{(state === "unknown" || state === "uninstall") && (
|
||||
<Typography>
|
||||
Information: Please make sure that the Clash Verge Service is
|
||||
installed and enabled
|
||||
{t(
|
||||
"Information: Please make sure that the Clash Verge Service is installed and enabled"
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -102,19 +105,19 @@ export const ServiceViewer = forwardRef<DialogRef, Props>((props, ref) => {
|
||||
>
|
||||
{state === "uninstall" && enable && (
|
||||
<Button variant="contained" onClick={onDisable}>
|
||||
Disable Service Mode
|
||||
{t("Disable Service Mode")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{state === "uninstall" && (
|
||||
<Button variant="contained" onClick={onInstall}>
|
||||
Install
|
||||
{t("Install")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{(state === "active" || state === "installed") && (
|
||||
<Button variant="outlined" onClick={onUninstall}>
|
||||
Uninstall
|
||||
{t("Uninstall")}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -128,6 +128,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
rows={3}
|
||||
sx={{ width: 280 }}
|
||||
value={value.bypass}
|
||||
placeholder={sysproxy?.bypass || `-`}
|
||||
onChange={(e) =>
|
||||
setValue((v) => ({ ...v, bypass: e.target.value }))
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
|
||||
import { StackModeSwitch } from "./stack-mode-switch";
|
||||
import { enhanceProfiles } from "@/services/cmds";
|
||||
|
||||
export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -27,7 +28,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
mtu: 9000,
|
||||
mtu: 1500,
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -40,7 +41,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
|
||||
dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53"],
|
||||
strictRoute: clash?.tun["strict-route"] ?? false,
|
||||
mtu: clash?.tun.mtu ?? 9000,
|
||||
mtu: clash?.tun.mtu ?? 1500,
|
||||
});
|
||||
},
|
||||
close: () => setOpen(false),
|
||||
@@ -50,12 +51,12 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
try {
|
||||
let tun = {
|
||||
stack: values.stack,
|
||||
device: values.device,
|
||||
device: values.device === "" ? "Meta" : values.device,
|
||||
"auto-route": values.autoRoute,
|
||||
"auto-detect-interface": values.autoDetectInterface,
|
||||
"dns-hijack": values.dnsHijack,
|
||||
"dns-hijack": values.dnsHijack[0] === "" ? [] : values.dnsHijack,
|
||||
"strict-route": values.strictRoute,
|
||||
mtu: values.mtu,
|
||||
mtu: values.mtu ?? 1500,
|
||||
};
|
||||
await patchClash({ tun });
|
||||
await mutateClash(
|
||||
@@ -65,6 +66,12 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
}),
|
||||
false
|
||||
);
|
||||
try {
|
||||
await enhanceProfiles();
|
||||
Notice.success("Refresh clash config", 1000);
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString(), 3000);
|
||||
}
|
||||
setOpen(false);
|
||||
} catch (err: any) {
|
||||
Notice.error(err.message || err.toString());
|
||||
@@ -88,7 +95,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
"auto-detect-interface": true,
|
||||
"dns-hijack": ["any:53"],
|
||||
"strict-route": false,
|
||||
mtu: 9000,
|
||||
mtu: 1500,
|
||||
};
|
||||
setValues({
|
||||
stack: "gvisor",
|
||||
@@ -97,7 +104,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
autoDetectInterface: true,
|
||||
dnsHijack: ["any:53"],
|
||||
strictRoute: false,
|
||||
mtu: 9000,
|
||||
mtu: 1500,
|
||||
});
|
||||
await patchClash({ tun });
|
||||
await mutateClash(
|
||||
@@ -208,7 +215,7 @@ export const TunViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
spellCheck="false"
|
||||
sx={{ width: 250 }}
|
||||
value={values.mtu}
|
||||
placeholder="9000"
|
||||
placeholder="1500"
|
||||
onChange={(e) =>
|
||||
setValues((v) => ({
|
||||
...v,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import useSWR from "swr";
|
||||
import snarkdown from "snarkdown";
|
||||
import { forwardRef, useImperativeHandle, useState, useMemo } from "react";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { Box, LinearProgress, styled } from "@mui/material";
|
||||
import { Box, LinearProgress } from "@mui/material";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { relaunch } from "@tauri-apps/api/process";
|
||||
@@ -11,10 +10,8 @@ import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||
import { atomUpdateState } from "@/services/states";
|
||||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { portableFlag } from "@/pages/_layout";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
const UpdateLog = styled(Box)(() => ({
|
||||
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
|
||||
}));
|
||||
let eventListener: UnlistenFn | null = null;
|
||||
|
||||
export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
@@ -38,12 +35,11 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
// markdown parser
|
||||
const parseContent = useMemo(() => {
|
||||
const markdownContent = useMemo(() => {
|
||||
if (!updateInfo?.manifest?.body) {
|
||||
return "New Version is available";
|
||||
}
|
||||
return snarkdown(updateInfo?.manifest?.body);
|
||||
return updateInfo?.manifest?.body;
|
||||
}, [updateInfo]);
|
||||
|
||||
const onUpdate = useLockFn(async () => {
|
||||
@@ -87,10 +83,22 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onCancel={() => setOpen(false)}
|
||||
onOk={onUpdate}
|
||||
>
|
||||
<UpdateLog
|
||||
dangerouslySetInnerHTML={{ __html: parseContent }}
|
||||
sx={{ height: "calc(100% - 10px)", overflow: "auto" }}
|
||||
/>
|
||||
<Box sx={{ height: "calc(100% - 10px)", overflow: "auto" }}>
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
a: ({ node, ...props }) => {
|
||||
const { children } = props;
|
||||
return (
|
||||
<a {...props} target="_blank">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{markdownContent}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
{updateState && (
|
||||
<LinearProgress
|
||||
variant="buffer"
|
||||
|
||||
@@ -90,6 +90,8 @@ export const WebUIItem = (props: Props) => {
|
||||
title={value}
|
||||
color={value ? "text.primary" : "text.secondary"}
|
||||
sx={({ palette }) => ({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
"> span": {
|
||||
color: palette.primary.main,
|
||||
},
|
||||
|
||||
@@ -22,10 +22,13 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
close: () => setOpen(false),
|
||||
}));
|
||||
|
||||
const webUIList = verge?.web_ui_list || [];
|
||||
const webUIList = verge?.web_ui_list || [
|
||||
"https://d.metacubex.one/#?hostname=%host&port=%port&secret=%secret",
|
||||
"https://yacd.metacubex.one/?host=%host&port=%port&secret=%secret",
|
||||
];
|
||||
|
||||
const handleAdd = useLockFn(async (value: string) => {
|
||||
const newList = [value, ...webUIList];
|
||||
const newList = [...webUIList, value];
|
||||
mutateVerge((old) => (old ? { ...old, web_ui_list: newList } : old), false);
|
||||
await patchVerge({ web_ui_list: newList });
|
||||
});
|
||||
@@ -100,18 +103,6 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onClose={() => setOpen(false)}
|
||||
onCancel={() => setOpen(false)}
|
||||
>
|
||||
{editing && (
|
||||
<WebUIItem
|
||||
value=""
|
||||
onlyEdit
|
||||
onChange={(v) => {
|
||||
setEditing(false);
|
||||
handleAdd(v || "");
|
||||
}}
|
||||
onCancel={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!editing && webUIList.length === 0 && (
|
||||
<BaseEmpty
|
||||
text="Empty List"
|
||||
@@ -132,6 +123,17 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
onOpenUrl={handleOpenUrl}
|
||||
/>
|
||||
))}
|
||||
{editing && (
|
||||
<WebUIItem
|
||||
value=""
|
||||
onlyEdit
|
||||
onChange={(v) => {
|
||||
setEditing(false);
|
||||
handleAdd(v || "");
|
||||
}}
|
||||
onCancel={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,23 +17,17 @@ interface Props {
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
|
||||
const isWIN = getSystem() === "windows";
|
||||
|
||||
const SettingSystem = ({ onError }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||
|
||||
// service mode
|
||||
const { data: serviceStatus } = useSWR(
|
||||
isWIN ? "checkService" : null,
|
||||
checkService,
|
||||
{
|
||||
revalidateIfStale: false,
|
||||
shouldRetryOnError: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
}
|
||||
);
|
||||
const { data: serviceStatus } = useSWR("checkService", checkService, {
|
||||
revalidateIfStale: false,
|
||||
shouldRetryOnError: false,
|
||||
focusThrottleInterval: 36e5, // 1 hour
|
||||
});
|
||||
|
||||
const serviceRef = useRef<DialogRef>(null);
|
||||
const sysproxyRef = useRef<DialogRef>(null);
|
||||
@@ -56,20 +50,13 @@ const SettingSystem = ({ onError }: Props) => {
|
||||
<SettingList title={t("System Setting")}>
|
||||
<SysproxyViewer ref={sysproxyRef} />
|
||||
<TunViewer ref={tunRef} />
|
||||
{isWIN && (
|
||||
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
||||
)}
|
||||
<ServiceViewer ref={serviceRef} enable={!!enable_service_mode} />
|
||||
|
||||
<SettingItem
|
||||
label={t("Tun Mode")}
|
||||
extra={
|
||||
<>
|
||||
<Tooltip
|
||||
title={
|
||||
isWIN ? t("Tun Mode Info Windows") : t("Tun Mode Info Unix")
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Tooltip title={t("Tun Mode Info")} placement="top">
|
||||
<IconButton color="inherit" size="small">
|
||||
<InfoRounded
|
||||
fontSize="inherit"
|
||||
@@ -102,39 +89,37 @@ const SettingSystem = ({ onError }: Props) => {
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
{isWIN && (
|
||||
<SettingItem
|
||||
label={t("Service Mode")}
|
||||
extra={
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => serviceRef.current?.open()}
|
||||
>
|
||||
<PrivacyTipRounded
|
||||
fontSize="inherit"
|
||||
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||
/>
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<GuardState
|
||||
value={enable_service_mode ?? false}
|
||||
valueProps="checked"
|
||||
onCatch={onError}
|
||||
onFormat={onSwitchFormat}
|
||||
onChange={(e) => onChangeData({ enable_service_mode: e })}
|
||||
onGuard={(e) => patchVerge({ enable_service_mode: e })}
|
||||
<SettingItem
|
||||
label={t("Service Mode")}
|
||||
extra={
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => serviceRef.current?.open()}
|
||||
>
|
||||
<Switch
|
||||
edge="end"
|
||||
disabled={
|
||||
serviceStatus !== "active" && serviceStatus !== "installed"
|
||||
}
|
||||
<PrivacyTipRounded
|
||||
fontSize="inherit"
|
||||
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||
/>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
)}
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<GuardState
|
||||
value={enable_service_mode ?? false}
|
||||
valueProps="checked"
|
||||
onCatch={onError}
|
||||
onFormat={onSwitchFormat}
|
||||
onChange={(e) => onChangeData({ enable_service_mode: e })}
|
||||
onGuard={(e) => patchVerge({ enable_service_mode: e })}
|
||||
>
|
||||
<Switch
|
||||
edge="end"
|
||||
disabled={
|
||||
serviceStatus !== "active" && serviceStatus !== "installed"
|
||||
}
|
||||
/>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
label={t("System Proxy")}
|
||||
|
||||
@@ -10,7 +10,13 @@ import {
|
||||
Input,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { exitApp, openAppDir, openCoreDir, openLogsDir } from "@/services/cmds";
|
||||
import {
|
||||
exitApp,
|
||||
openAppDir,
|
||||
openCoreDir,
|
||||
openLogsDir,
|
||||
openDevTools,
|
||||
} from "@/services/cmds";
|
||||
import { ArrowForward } from "@mui/icons-material";
|
||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
@@ -27,6 +33,7 @@ import { LayoutViewer } from "./mods/layout-viewer";
|
||||
import { UpdateViewer } from "./mods/update-viewer";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { routers } from "@/pages/_routers";
|
||||
|
||||
interface Props {
|
||||
onError?: (err: Error) => void;
|
||||
}
|
||||
@@ -90,6 +97,7 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
<MenuItem value="zh">中文</MenuItem>
|
||||
<MenuItem value="en">English</MenuItem>
|
||||
<MenuItem value="ru">Русский</MenuItem>
|
||||
<MenuItem value="fa">فارسی</MenuItem>
|
||||
</Select>
|
||||
</GuardState>
|
||||
</SettingItem>
|
||||
@@ -149,8 +157,8 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
onGuard={(e) => patchVerge({ start_page: e })}
|
||||
>
|
||||
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
|
||||
{routers.map((page: { label: string; link: string }) => {
|
||||
return <MenuItem value={page.link}>{t(page.label)}</MenuItem>;
|
||||
{routers.map((page: { label: string; path: string }) => {
|
||||
return <MenuItem value={page.path}>{t(page.label)}</MenuItem>;
|
||||
})}
|
||||
</Select>
|
||||
</GuardState>
|
||||
@@ -167,6 +175,7 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
<Input
|
||||
value={startup_script}
|
||||
disabled
|
||||
sx={{ width: 230 }}
|
||||
endAdornment={
|
||||
<>
|
||||
<Button
|
||||
@@ -303,6 +312,17 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Open Dev Tools")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ my: "2px" }}
|
||||
onClick={openDevTools}
|
||||
>
|
||||
<ArrowForward />
|
||||
</IconButton>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t("Exit")}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
|
||||
@@ -26,7 +26,7 @@ export const TestBox = styled(Box)(({ theme, "aria-selected": selected }) => {
|
||||
display: "block",
|
||||
cursor: "pointer",
|
||||
textAlign: "left",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderRadius: 8,
|
||||
boxShadow: theme.shadows[2],
|
||||
padding: "8px 16px",
|
||||
boxSizing: "border-box",
|
||||
|
||||
@@ -17,8 +17,9 @@ import { LanguageTwoTone } from "@mui/icons-material";
|
||||
import { Notice } from "@/components/base";
|
||||
import { TestBox } from "./test-box";
|
||||
import delayManager from "@/services/delay";
|
||||
import { cmdTestDelay } from "@/services/cmds";
|
||||
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { cmdTestDelay, downloadIconCache } from "@/services/cmds";
|
||||
import { listen, UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
@@ -39,6 +40,23 @@ export const TestItem = (props: Props) => {
|
||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||
const [delay, setDelay] = useState(-1);
|
||||
const { uid, name, icon, url } = itemData;
|
||||
const [iconCachePath, setIconCachePath] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
initIconCachePath();
|
||||
}, [icon]);
|
||||
|
||||
async function initIconCachePath() {
|
||||
if (icon && icon.trim().startsWith("http")) {
|
||||
const fileName = uid + "-" + getFileName(icon);
|
||||
const iconPath = await downloadIconCache(icon, fileName);
|
||||
setIconCachePath(convertFileSrc(iconPath));
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(url: string) {
|
||||
return url.substring(url.lastIndexOf("/") + 1);
|
||||
}
|
||||
|
||||
const onDelay = async () => {
|
||||
setDelay(-2);
|
||||
@@ -86,7 +104,6 @@ export const TestItem = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
<TestBox
|
||||
onClick={onEditTest}
|
||||
onContextMenu={(event) => {
|
||||
const { clientX, clientY } = event;
|
||||
setPosition({ top: clientY, left: clientX });
|
||||
@@ -104,10 +121,13 @@ export const TestItem = (props: Props) => {
|
||||
{icon && icon.trim() !== "" ? (
|
||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||
{icon.trim().startsWith("http") && (
|
||||
<img src={icon} height="40px" style={{ marginRight: "8px" }} />
|
||||
<img
|
||||
src={iconCachePath === "" ? icon : iconCachePath}
|
||||
height="40px"
|
||||
/>
|
||||
)}
|
||||
{icon.trim().startsWith("data") && (
|
||||
<img src={icon} height="40px" style={{ marginRight: "8px" }} />
|
||||
<img src={icon} height="40px" />
|
||||
)}
|
||||
{icon.trim().startsWith("<svg") && (
|
||||
<img
|
||||
|
||||
@@ -49,11 +49,19 @@ export const useClashInfo = () => {
|
||||
patch: Partial<
|
||||
Pick<
|
||||
IConfigData,
|
||||
"port" | "socks-port" | "mixed-port" | "external-controller" | "secret"
|
||||
| "port"
|
||||
| "socks-port"
|
||||
| "mixed-port"
|
||||
| "redir-port"
|
||||
| "tproxy-port"
|
||||
| "external-controller"
|
||||
| "secret"
|
||||
>
|
||||
>
|
||||
) => {
|
||||
const hasInfo =
|
||||
patch["redir-port"] != null ||
|
||||
patch["tproxy-port"] != null ||
|
||||
patch["mixed-port"] != null ||
|
||||
patch["socks-port"] != null ||
|
||||
patch["port"] != null ||
|
||||
@@ -62,6 +70,26 @@ export const useClashInfo = () => {
|
||||
|
||||
if (!hasInfo) return;
|
||||
|
||||
if (patch["redir-port"]) {
|
||||
const port = patch["redir-port"];
|
||||
if (port < 1000) {
|
||||
throw new Error("The port should not < 1000");
|
||||
}
|
||||
if (port > 65536) {
|
||||
throw new Error("The port should not > 65536");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch["tproxy-port"]) {
|
||||
const port = patch["tproxy-port"];
|
||||
if (port < 1000) {
|
||||
throw new Error("The port should not < 1000");
|
||||
}
|
||||
if (port > 65536) {
|
||||
throw new Error("The port should not > 65536");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch["mixed-port"]) {
|
||||
const port = patch["mixed-port"];
|
||||
if (port < 1000) {
|
||||
|
||||
@@ -5,7 +5,8 @@ export type WsMsgFn = (event: MessageEvent<any>) => void;
|
||||
export interface WsOptions {
|
||||
errorCount?: number; // default is 5
|
||||
retryInterval?: number; // default is 2500
|
||||
onError?: () => void;
|
||||
onError?: (event: Event) => void;
|
||||
onClose?: (event: CloseEvent) => void;
|
||||
}
|
||||
|
||||
export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
|
||||
@@ -33,17 +34,23 @@ export const useWebsocket = (onMessage: WsMsgFn, options?: WsOptions) => {
|
||||
const ws = new WebSocket(url);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.addEventListener("message", onMessage);
|
||||
ws.addEventListener("error", () => {
|
||||
ws.addEventListener("message", (event) => {
|
||||
errorCount = 0; // reset counter
|
||||
onMessage(event);
|
||||
});
|
||||
ws.addEventListener("error", (event) => {
|
||||
errorCount -= 1;
|
||||
|
||||
if (errorCount >= 0) {
|
||||
timerRef.current = setTimeout(connectHelper, 2500);
|
||||
} else {
|
||||
disconnect();
|
||||
options?.onError?.();
|
||||
options?.onError?.(event);
|
||||
}
|
||||
});
|
||||
ws.addEventListener("close", (event) => {
|
||||
options?.onClose?.(event);
|
||||
});
|
||||
};
|
||||
|
||||
connectHelper();
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"direct": "direct",
|
||||
"script": "script",
|
||||
|
||||
"Create Test": "Create Test",
|
||||
"Edit Test": "Edit Test",
|
||||
"Edit": "Edit",
|
||||
"Icon": "Icon",
|
||||
"Test URL": "Test URL",
|
||||
@@ -29,7 +31,9 @@
|
||||
"New": "New",
|
||||
"Create Profile": "Create Profile",
|
||||
"Choose File": "Choose File",
|
||||
"Close": "Close",
|
||||
"Close All": "Close All",
|
||||
"Home": "Home",
|
||||
"Select": "Select",
|
||||
"Edit Info": "Edit Info",
|
||||
"Edit File": "Edit File",
|
||||
@@ -45,6 +49,8 @@
|
||||
"Update All Profiles": "Update All Profiles",
|
||||
"View Runtime Config": "View Runtime Config",
|
||||
"Reactivate Profiles": "Reactivate Profiles",
|
||||
"Confirm deletion": "Confirm deletion",
|
||||
"This operation is not reversible": "This operation is not reversible",
|
||||
|
||||
"Location": "Location",
|
||||
"Delay check": "Delay check",
|
||||
@@ -52,6 +58,7 @@
|
||||
"Sort by delay": "Sort by delay",
|
||||
"Sort by name": "Sort by name",
|
||||
"Delay check URL": "Delay check URL",
|
||||
"Proxy basic": "Proxy basic",
|
||||
"Proxy detail": "Proxy detail",
|
||||
"Filter": "Filter",
|
||||
"Filter conditions": "Filter conditions",
|
||||
@@ -80,6 +87,9 @@
|
||||
"Random Port": "Random Port",
|
||||
"After restart to take effect": "After restart to take effect",
|
||||
"External": "External",
|
||||
"External Controller": "External Controller",
|
||||
"Core Secret": "Core Secret",
|
||||
"Recommended": "Recommended",
|
||||
"Clash Core": "Clash Core",
|
||||
"Grant": "Grant",
|
||||
"Tun mode requires": "Tun mode requires",
|
||||
@@ -94,10 +104,10 @@
|
||||
"Proxy Guard": "Proxy Guard",
|
||||
"Guard Duration": "Guard Duration",
|
||||
"Proxy Bypass": "Proxy Bypass",
|
||||
"Current System Proxy": "Current System Proxy",
|
||||
"Enable status": "Enable status",
|
||||
"Server Addr": "Server Addr",
|
||||
"Bypass": "Bypass",
|
||||
"Current System Proxy": "Current System Proxy",
|
||||
"Theme Mode": "Theme Mode",
|
||||
"Tray Click Event": "Tray Click Event",
|
||||
"Copy Env Type": "Copy Env Type",
|
||||
@@ -112,6 +122,9 @@
|
||||
"Traffic Graph": "Traffic Graph",
|
||||
"Memory Usage": "Memory Usage",
|
||||
"Proxy Group Icon": "Proxy Group Icon",
|
||||
"Menu Icon": "Menu Icon",
|
||||
"Monochrome": "Monochrome",
|
||||
"Colorful": "Colorful",
|
||||
"Common Tray Icon": "Common Tray Icon",
|
||||
"System Proxy Tray Icon": "System Proxy Tray Icon",
|
||||
"Tun Tray Icon": "Tun Tray Icon",
|
||||
@@ -120,6 +133,7 @@
|
||||
"Open Core Dir": "Open Core Dir",
|
||||
"Open Logs Dir": "Open Logs Dir",
|
||||
"Check for Updates": "Check for Updates",
|
||||
"Open Dev Tools": "Open Dev Tools",
|
||||
"Verge Version": "Verge Version",
|
||||
"theme.light": "Light",
|
||||
"theme.dark": "Dark",
|
||||
@@ -127,6 +141,7 @@
|
||||
"Clash Field": "Clash Field",
|
||||
"Runtime Config": "Runtime Config",
|
||||
"ReadOnly": "ReadOnly",
|
||||
"ReadOnlyMessage": "Cannot edit in read-only editor",
|
||||
"Restart": "Restart",
|
||||
"Upgrade": "Upgrade",
|
||||
|
||||
@@ -134,6 +149,7 @@
|
||||
"Save": "Save",
|
||||
"Cancel": "Cancel",
|
||||
"Exit": "Exit",
|
||||
"Confirm": "Confirm",
|
||||
|
||||
"Default": "Default",
|
||||
"Download Speed": "Download Speed",
|
||||
@@ -148,11 +164,11 @@
|
||||
|
||||
"App Log Level": "App Log Level",
|
||||
"Auto Close Connections": "Auto Close Connections",
|
||||
"Enable Clash Fields Filter": "Enable Clash Fields Filter",
|
||||
"Auto Check Update": "Auto Check Update",
|
||||
"Enable Builtin Enhanced": "Enable Builtin Enhanced",
|
||||
"Proxy Layout Column": "Proxy Layout Column",
|
||||
"Default Latency Test": "Default Latency Test",
|
||||
"Defaule Latency Timeout": "Defaule Latency Timeout",
|
||||
"Default Latency Timeout": "Default Latency Timeout",
|
||||
|
||||
"Auto Log Clean": "Auto Log Clean",
|
||||
"Never Clean": "Never Clean",
|
||||
@@ -167,9 +183,22 @@
|
||||
"Auto Detect Interface": "Auto Detect Interface",
|
||||
"DNS Hijack": "DNS Hijack",
|
||||
"MTU": "Max Transmission Unit",
|
||||
"Reset to Default": "Reset to Default",
|
||||
|
||||
"Current State": "Current State",
|
||||
"pending": "pending",
|
||||
"installed": "installed",
|
||||
"uninstall": "uninstalled",
|
||||
"active": "active",
|
||||
"unknown": "unknown",
|
||||
"Disable Service Mode": "Disable Service Mode",
|
||||
"Install": "Install",
|
||||
"Uninstall": "Uninstall",
|
||||
|
||||
"Portable Updater Error": "The portable version does not support in-app updates. Please manually download and replace it",
|
||||
"Tun Mode Info Windows": "The Tun mode requires granting core-related permissions. Please enable service mode before using it",
|
||||
"Tun Mode Info Unix": "The Tun mode requires granting core-related permissions. Before using it, please authorize the core in the core settings",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System and Mixed Can Only be Used in Service Mode"
|
||||
"Tun Mode Info": "The Tun mode requires granting core-related permissions. Please enable service mode before using it",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System and Mixed Can Only be Used in Service Mode",
|
||||
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Information: Please make sure that the Clash Verge Service is installed and enabled",
|
||||
|
||||
"Use Regular Expression": "Use Regular Expression"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -11,13 +11,15 @@
|
||||
"Logs": "Логи",
|
||||
"Clear": "Очистить",
|
||||
"Proxies": "Прокси",
|
||||
"Test": "Тест",
|
||||
"Proxy Groups": "Группы прокси",
|
||||
"Test": "Тест",
|
||||
"rule": "правила",
|
||||
"global": "глобальный",
|
||||
"direct": "прямой",
|
||||
"script": "скриптовый",
|
||||
|
||||
"Create Test": "Создать тест",
|
||||
"Edit Test": "Редактировать тест",
|
||||
"Edit": "Редактировать",
|
||||
"Icon": "Икона",
|
||||
"Test URL": "Тестовый URL",
|
||||
@@ -29,7 +31,9 @@
|
||||
"New": "Новый",
|
||||
"Create Profile": "Создать профиль",
|
||||
"Choose File": "Выбрать файл",
|
||||
"Close": "Закрыть",
|
||||
"Close All": "Закрыть всё",
|
||||
"Home": "Главная",
|
||||
"Select": "Выбрать",
|
||||
"Edit Info": "Изменить информацию",
|
||||
"Edit File": "Изменить файл",
|
||||
@@ -45,6 +49,8 @@
|
||||
"Update All Profiles": "Обновить все профили",
|
||||
"View Runtime Config": "Просмотреть используемый конфиг",
|
||||
"Reactivate Profiles": "Реактивировать профили",
|
||||
"Confirm deletion": "Подтвердите удаление",
|
||||
"This operation is not reversible": "Эта операция необратима",
|
||||
|
||||
"Location": "Местоположение",
|
||||
"Delay check": "Проверка задержки",
|
||||
@@ -52,6 +58,7 @@
|
||||
"Sort by delay": "Сортировать по задержке",
|
||||
"Sort by name": "Сортировать по названию",
|
||||
"Delay check URL": "URL проверки задержки",
|
||||
"Proxy basic": "Резюме о прокси",
|
||||
"Proxy detail": "Подробности о прокси",
|
||||
"Filter": "Фильтр",
|
||||
"Filter conditions": "Условия фильтрации",
|
||||
@@ -79,7 +86,13 @@
|
||||
"Port Config": "Настройка порта",
|
||||
"Random Port": "Случайный порт",
|
||||
"After restart to take effect": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
||||
"External": "Внешний",
|
||||
"External Controller": "Адрес прослушивания внешнего контроллера",
|
||||
"Core Secret": "Секрет",
|
||||
"Recommended": "Рекомендуется",
|
||||
"Clash Core": "Ядро Clash",
|
||||
"Grant": "Предоставить",
|
||||
"Tun mode requires": "Требуется Режим туннеля",
|
||||
"Tun Mode": "Режим туннеля",
|
||||
"Service Mode": "Режим сервиса",
|
||||
"Auto Launch": "Автозапуск",
|
||||
@@ -92,20 +105,35 @@
|
||||
"Guard Duration": "Период защиты",
|
||||
"Proxy Bypass": "Игнорирование прокси",
|
||||
"Current System Proxy": "Текущий системный прокси",
|
||||
"Enable status": "Статус включения",
|
||||
"Server Addr": "Адрес сервера",
|
||||
"Bypass": "Игнорирование",
|
||||
"Theme Mode": "Режим темы",
|
||||
"Tray Click Event": "Событие щелчка в лотке",
|
||||
"Start Page": "Главная страница",
|
||||
"Copy Env Type": "Скопировать тип Env",
|
||||
"Start Page": "Главная страница",
|
||||
"Startup Script": "Скрипт запуска",
|
||||
"Browse": "Просмотреть",
|
||||
"Show Main Window": "Показать главное окно",
|
||||
"Theme Setting": "Настройка темы",
|
||||
"Layout Setting": "Настройка раскладки",
|
||||
"Miscellaneous": "Настройка различные",
|
||||
"Hotkey Setting": "Настройка клавиатурных сокращений",
|
||||
"Traffic Graph": "График трафика",
|
||||
"Memory Usage": "Использование памяти",
|
||||
"Proxy Group Icon": "Иконка Группы прокси",
|
||||
"Menu Icon": "Иконка меню",
|
||||
"Monochrome": "Монохромный",
|
||||
"Colorful": "Полноцветный",
|
||||
"Common Tray Icon": "Общий значок в лотке",
|
||||
"System Proxy Tray Icon": "Значок системного прокси в лотке",
|
||||
"Tun Tray Icon": "Значок туннеля в лотке",
|
||||
"Language": "Язык",
|
||||
"Open App Dir": "Открыть папку приложения",
|
||||
"Open Core Dir": "Открыть папку ядра",
|
||||
"Open Logs Dir": "Открыть папку логов",
|
||||
"Check for Updates": "Проверить обновления",
|
||||
"Open Dev Tools": "Открыть инструменты разработчика",
|
||||
"Verge Version": "Версия Verge",
|
||||
"theme.light": "Светлая",
|
||||
"theme.dark": "Тёмная",
|
||||
@@ -113,6 +141,7 @@
|
||||
"Clash Field": "Используемые настройки Clash",
|
||||
"Runtime Config": "Используемый конфиг",
|
||||
"ReadOnly": "Только для чтения",
|
||||
"ReadOnlyMessage": "Невозможно редактировать в режиме только для чтения",
|
||||
"Restart": "Перезапуск",
|
||||
"Upgrade": "Обновлять",
|
||||
|
||||
@@ -120,6 +149,11 @@
|
||||
"Save": "Сохранить",
|
||||
"Cancel": "Отмена",
|
||||
"Exit": "Выход",
|
||||
"Confirm": "Подтвердить",
|
||||
|
||||
"Default": "По умолчанию",
|
||||
"Download Speed": "Скорость загрузки",
|
||||
"Upload Speed": "Скорость загрузки",
|
||||
|
||||
"open_or_close_dashboard": "Open/Close Dashboard",
|
||||
"clash_mode_rule": "Режим правил",
|
||||
@@ -128,5 +162,43 @@
|
||||
"toggle_system_proxy": "Включить/Отключить системный прокси",
|
||||
"toggle_tun_mode": "Включить/Отключить режим туннеля",
|
||||
|
||||
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную"
|
||||
"App Log Level": "Уровень журнала приложения",
|
||||
"Auto Close Connections": "Автоматическое закрытие соединений",
|
||||
"Auto Check Update": "Автоматическая проверка обновлений",
|
||||
"Enable Builtin Enhanced": "Включить встроенные улучшения",
|
||||
"Proxy Layout Column": "Количество столбцов в макете прокси",
|
||||
"Default Latency Test": "Ссылка на тестирование задержки по умолчанию",
|
||||
"Default Latency Timeout": "Таймаут задержки по умолчанию",
|
||||
|
||||
"Auto Log Clean": "Автоматическая очистка журналов",
|
||||
"Never Clean": "Никогда не очищать",
|
||||
"Retain 7 Days": "Сохранять 7 дней",
|
||||
"Retain 30 Days": "Сохранять 30 дней",
|
||||
"Retain 90 Days": "Сохранять 90 дней",
|
||||
|
||||
"Stack": "Стек",
|
||||
"Device": "Имя устройства",
|
||||
"Auto Route": "Автоматическая маршрутизация",
|
||||
"Strict Route": "Строгий маршрут",
|
||||
"Auto Detect Interface": "Автоопределение интерфейса",
|
||||
"DNS Hijack": "DNS-перехват",
|
||||
"MTU": "Максимальная единица передачи",
|
||||
"Reset to Default": "Сбросить настройки по умолчанию",
|
||||
|
||||
"Current State": "Текущее состояние",
|
||||
"pending": "Ожидающий",
|
||||
"installed": "Установленный",
|
||||
"uninstall": "Не установленный",
|
||||
"active": "Активированный",
|
||||
"unknown": "неизвестный",
|
||||
"Disable Service Mode": "Отключить режим обслуживания",
|
||||
"Install": "Установить",
|
||||
"Uninstall": "Удалить",
|
||||
|
||||
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную",
|
||||
"Tun Mode Info": "Режим туннеля требует предоставления разрешений, связанных с ядром. Пожалуйста, включите сервисный режим перед его использованием",
|
||||
"System and Mixed Can Only be Used in Service Mode": "Система и смешанные могут использоваться только в сервисном режиме",
|
||||
"Information: Please make sure that the Clash Verge Service is installed and enabled": "Информация: Пожалуйста, убедитесь, что сервис Clash Verge Service установлен и включен",
|
||||
|
||||
"Use Regular Expression": "Использование регулярных выражений"
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"direct": "直连",
|
||||
"script": "脚本",
|
||||
|
||||
"Create Test": "新建测试",
|
||||
"Edit Test": "编辑测试",
|
||||
"Edit": "编辑",
|
||||
"Icon": "图标",
|
||||
"Test URL": "测试地址",
|
||||
@@ -29,7 +31,9 @@
|
||||
"New": "新建",
|
||||
"Create Profile": "新建订阅",
|
||||
"Choose File": "选择文件",
|
||||
"Close": "关闭",
|
||||
"Close All": "关闭全部",
|
||||
"Home": "首页",
|
||||
"Select": "使用",
|
||||
"Edit Info": "编辑信息",
|
||||
"Edit File": "编辑文件",
|
||||
@@ -45,6 +49,8 @@
|
||||
"Update All Profiles": "更新所有订阅",
|
||||
"View Runtime Config": "查看运行时订阅",
|
||||
"Reactivate Profiles": "重新激活订阅",
|
||||
"Confirm deletion": "确认删除",
|
||||
"This operation is not reversible": "此操作不可逆",
|
||||
|
||||
"Location": "当前节点",
|
||||
"Delay check": "延迟测试",
|
||||
@@ -52,6 +58,7 @@
|
||||
"Sort by delay": "按延迟排序",
|
||||
"Sort by name": "按名称排序",
|
||||
"Delay check URL": "延迟测试链接",
|
||||
"Proxy basic": "隐藏节点细节",
|
||||
"Proxy detail": "展示节点细节",
|
||||
"Filter": "过滤节点",
|
||||
"Filter conditions": "过滤条件",
|
||||
@@ -80,6 +87,9 @@
|
||||
"Random Port": "随机端口",
|
||||
"After restart to take effect": "重启后生效",
|
||||
"External": "外部控制",
|
||||
"External Controller": "外部控制器监听地址",
|
||||
"Core Secret": "API访问密钥",
|
||||
"Recommended": "建议设置",
|
||||
"Clash Core": "Clash 内核",
|
||||
"Grant": "授权",
|
||||
"Tun mode requires": "如需启用TUN模式需要授权",
|
||||
@@ -112,6 +122,9 @@
|
||||
"Traffic Graph": "流量图显",
|
||||
"Memory Usage": "内存使用",
|
||||
"Proxy Group Icon": "代理组图标",
|
||||
"Menu Icon": "菜单图标",
|
||||
"Monochrome": "单色图标",
|
||||
"Colorful": "彩色图标",
|
||||
"Common Tray Icon": "常规托盘图标",
|
||||
"System Proxy Tray Icon": "系统代理托盘图标",
|
||||
"Tun Tray Icon": "Tun模式托盘图标",
|
||||
@@ -120,6 +133,7 @@
|
||||
"Open Core Dir": "内核目录",
|
||||
"Open Logs Dir": "日志目录",
|
||||
"Check for Updates": "检查更新",
|
||||
"Open Dev Tools": "打开开发者工具",
|
||||
"Verge Version": "Verge 版本",
|
||||
"theme.light": "浅色",
|
||||
"theme.dark": "深色",
|
||||
@@ -127,6 +141,7 @@
|
||||
"Clash Field": "Clash 字段",
|
||||
"Runtime Config": "当前配置",
|
||||
"ReadOnly": "只读",
|
||||
"ReadOnlyMessage": "无法在只读模式下编辑",
|
||||
"Restart": "重启内核",
|
||||
"Upgrade": "升级内核",
|
||||
|
||||
@@ -134,6 +149,7 @@
|
||||
"Save": "保存",
|
||||
"Cancel": "取消",
|
||||
"Exit": "退出",
|
||||
"Confirm": "确认",
|
||||
|
||||
"Default": "默认",
|
||||
"Download Speed": "下载速度",
|
||||
@@ -148,7 +164,7 @@
|
||||
|
||||
"App Log Level": "App日志等级",
|
||||
"Auto Close Connections": "自动关闭连接",
|
||||
"Enable Clash Fields Filter": "开启Clash字段过滤",
|
||||
"Auto Check Update": "自动检查更新",
|
||||
"Enable Builtin Enhanced": "开启内建增强功能",
|
||||
"Proxy Layout Column": "代理页布局列数",
|
||||
"Default Latency Test": "默认测试链接",
|
||||
@@ -169,8 +185,20 @@
|
||||
"MTU": "最大传输单元",
|
||||
"Reset to Default": "重置为默认值",
|
||||
|
||||
"Current State": "当前状态",
|
||||
"pending": "等待中",
|
||||
"installed": "已安装",
|
||||
"uninstall": "未安装",
|
||||
"active": "已激活",
|
||||
"unknown": "未知",
|
||||
"Disable Service Mode": "禁用服务模式",
|
||||
"Install": "安装",
|
||||
"Uninstall": "卸载",
|
||||
|
||||
"Portable Updater Error": "便携版不支持应用内更新,请手动下载替换",
|
||||
"Tun Mode Info Windows": "Tun模式需要授予内核相关权限,使用前请先开启服务模式",
|
||||
"Tun Mode Info Unix": "Tun模式需要授予内核相关权限,使用前请先在内核设置中给内核授权",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System和Mixed只能在服务模式下使用"
|
||||
"Tun Mode Info": "Tun模式需要授予内核相关权限,使用前请先开启服务模式",
|
||||
"System and Mixed Can Only be Used in Service Mode": "System和Mixed只能在服务模式下使用",
|
||||
"Information: Please make sure that the Clash Verge Service is installed and enabled": "提示信息: 请确保Clash Verge Service已安装并启用",
|
||||
|
||||
"Use Regular Expression": "使用正则表达式"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,19 @@ if (!container) {
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
// Disable WebView keyboard shortcuts
|
||||
if (["F5", "F7"].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
["F", "H", "P", "R", "U"].includes(event.key.toUpperCase())
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
createRoot(container).render(
|
||||
<React.StrictMode>
|
||||
<RecoilRoot>
|
||||
|
||||