Compare commits
18 Commits
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -57,6 +57,6 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: 日志 / Log
|
||||
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到trace,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
|
||||
description: 请提供完整或相关部分的Debug日志(请在“软件左侧菜单”->“设置”->“日志等级”调整到debug,Verge错误请把“杂项设置”->“app日志等级”调整到debug/trace,并重启Verge生效。日志文件在“软件左侧菜单”->“设置”->“日志目录”下) / Please provide a complete or relevant part of the Debug log (please adjust the "Log level" to debug in "Software left menu" -> "Settings" -> "Log level". If there is a Verge error, please adjust "Miscellaneous settings" -> "app log level" to trace, and restart Verge to take effect. The log file is under "Software left menu" -> "Settings" -> "Log directory")
|
||||
validations:
|
||||
required: true
|
||||
|
||||
18
.github/workflows/alpha.yml
vendored
18
.github/workflows/alpha.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
workspaces: src-tauri
|
||||
cache-all-crates: true
|
||||
cache-on-failure: true
|
||||
|
||||
|
||||
- name: Install dependencies (ubuntu only)
|
||||
if: matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: "20"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -141,10 +141,10 @@ jobs:
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: 'Setup for linux'
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
|
||||
|
||||
cat > /tmp/sources.list << EOF
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
||||
@@ -170,20 +170,20 @@ jobs:
|
||||
patchelf:${{ matrix.arch }} \
|
||||
librsvg2-dev:${{ matrix.arch }}
|
||||
|
||||
- name: 'Install aarch64 tools'
|
||||
- name: "Install aarch64 tools"
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-aarch64-linux-gnu \
|
||||
g++-aarch64-linux-gnu
|
||||
|
||||
- name: 'Install armv7 tools'
|
||||
- name: "Install armv7 tools"
|
||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -196,7 +196,7 @@ jobs:
|
||||
fi
|
||||
pnpm build --target ${{ matrix.target }}
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -67,8 +67,8 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
|
||||
- name: Add Rust Target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
node-version: "22"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -131,10 +131,10 @@ jobs:
|
||||
pnpm i
|
||||
pnpm check ${{ matrix.target }}
|
||||
|
||||
- name: 'Setup for linux'
|
||||
- name: "Setup for linux"
|
||||
run: |-
|
||||
sudo ls -lR /etc/apt/
|
||||
|
||||
|
||||
cat > /tmp/sources.list << EOF
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
|
||||
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
|
||||
@@ -160,20 +160,20 @@ jobs:
|
||||
patchelf:${{ matrix.arch }} \
|
||||
librsvg2-dev:${{ matrix.arch }}
|
||||
|
||||
- name: 'Install aarch64 tools'
|
||||
- name: "Install aarch64 tools"
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-aarch64-linux-gnu \
|
||||
g++-aarch64-linux-gnu
|
||||
|
||||
- name: 'Install armv7 tools'
|
||||
- name: "Install armv7 tools"
|
||||
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
|
||||
run: |
|
||||
sudo apt install -y \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
g++-arm-linux-gnueabihf
|
||||
|
||||
|
||||
- name: Build for Linux
|
||||
run: |
|
||||
export PKG_CONFIG_ALLOW_CROSS=1
|
||||
@@ -186,7 +186,7 @@ jobs:
|
||||
fi
|
||||
pnpm build --target ${{ matrix.target }}
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=4096'
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
@@ -195,6 +195,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install jq
|
||||
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
|
||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
@@ -237,7 +238,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -262,8 +263,8 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
tauriScript: pnpm
|
||||
args: --target ${{ matrix.target }}
|
||||
@@ -298,7 +299,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -323,7 +324,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
4
.github/workflows/updater.yml
vendored
4
.github/workflows/updater.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
node-version: "22"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
|
||||
26
UPDATELOG.md
26
UPDATELOG.md
@@ -1,16 +1,30 @@
|
||||
## v2.0.0
|
||||
## v2.0.1
|
||||
|
||||
### Notice
|
||||
|
||||
- 强烈建议完全删除 1.x 老版本再安装此版本!
|
||||
- 历时3个月的紧密开发与严格测试稳定版2.0.0终于发布了:巨量改进与性能、稳定性提升,目前Clash Verge Rev已经有了比肩cfw的健壮性;而且更强大易用!
|
||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入 2 遍系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
||||
- 因 Tauri 2.0 底层 bug,关闭窗口暂时修改为最小化功能
|
||||
- 由于更改了服务安装逻辑,Mac/Linux 首次安装需要输入系统密码卸载和安装服务,以后可以丝滑使用 tun(虚拟网卡)模式
|
||||
- 因 Tauri 2.0 底层 bug,关闭窗口后保留webview进程,优点是再次打开面板更快,缺点是内存使用略有增加
|
||||
|
||||
### 2.0.1相对于2.0.0改进了:
|
||||
|
||||
- 无法从 2.0rc和2.0.0 升级的问题(已经安装了2.0版本的需手动下载安装)
|
||||
- MacOS 系统下少有的无法安装服务,无法启动的问题,目前更健壮了
|
||||
- 当系统中没有 yaml 编辑器的情况下,打开文件程序崩溃的问题
|
||||
- Windows 应用内升级和覆盖安装不会删除老执行文件的问题
|
||||
- 修改优化了 mac 下 fakeip 段和 dns
|
||||
- 测试菜单 svg 图标格式检查
|
||||
- 应用内升级重复安装 vs runtime 的问题
|
||||
- 修复外部控制下密码有特殊字符认证出错的问题
|
||||
- 修复恢复 Webdav 备份设置后, Webdav 设置丢失的问题
|
||||
- 代理页面增加快速回到顶部的按钮
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- 重大框架升级:使用 Tauri 2.0(巨量改进与性能提升)
|
||||
- 强烈建议完全删除 1.x 老版本再安装此版本
|
||||
- 出现 bug 到 issues 中提出;以后不再接受1.x版本的bug反馈。
|
||||
- 强烈建议完全删除 1.x 老版本再安装此版本
|
||||
|
||||
### Features
|
||||
|
||||
@@ -30,7 +44,7 @@
|
||||
- 添加统一延迟的设置开关
|
||||
- 添加 Windows 下自动检测并下载 vc runtime 的功能
|
||||
- 支持显示 mux 和 mptcp 的节点标识
|
||||
- 延迟测试连接更换 https 的 cp.cloudflare.com/generate_204 以防止机场劫持(关闭统一延迟的情况下延迟测试结果会有所增加)。
|
||||
- 延迟测试连接更换 http 的 cp.cloudflare.com/generate_204 (关闭统一延迟的情况下延迟测试结果会有所增加)
|
||||
- 重构日志记录逻辑,可以收集和筛选所有日志类型了(之前无法记录debug的日志类型)
|
||||
|
||||
### Performance
|
||||
@@ -58,7 +72,7 @@
|
||||
- 修复快捷键设置的相关 bug
|
||||
- 修复 Win 下点左键菜单闪现的问题(Mac 下的操作逻辑相反,默认情况下不管点左/右键均会打开菜单,闪现不属于 bug)
|
||||
|
||||
### Know issues
|
||||
### Known issues
|
||||
|
||||
- Windows 下窗口大小无法记忆(等待上游修复)
|
||||
- Webdav 备份因为安全性和兼容性问题,暂不支持同步 Webdav 服务器地址和登录信息;跨平台配置同步
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
"dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
||||
|
||||
@@ -153,13 +153,13 @@ async function getLatestReleaseVersion() {
|
||||
*/
|
||||
if (!META_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||
`clash meta alpha unsupported platform "${platform}-${arch}"`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ const resolvePlugin = async () => {
|
||||
const tempDir = path.join(TEMP_DIR, "SimpleSC");
|
||||
const tempZip = path.join(
|
||||
tempDir,
|
||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip"
|
||||
"NSIS_Simple_Service_Plugin_Unicode_1.30.zip",
|
||||
);
|
||||
const tempDll = path.join(tempDir, "SimpleSC.dll");
|
||||
const pluginDir = path.join(process.env.APPDATA, "Local/NSIS");
|
||||
|
||||
@@ -49,9 +49,9 @@ async function resolvePortable() {
|
||||
zip.addLocalFolder(
|
||||
path.join(
|
||||
releaseDir,
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
),
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`
|
||||
`Microsoft.WebView2.FixedVersionRuntime.109.0.1518.78.${arch}`,
|
||||
);
|
||||
zip.addLocalFolder(configDir, ".config");
|
||||
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
@@ -988,7 +988,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clash-verge"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "clash-verge"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
description = "clash verge"
|
||||
authors = ["zzzgydi", "wonfen", "MystiPanda"]
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
@@ -697,25 +697,62 @@ Var VC_REDIST_URL
|
||||
Var VC_REDIST_EXE
|
||||
|
||||
Section CheckAndInstallVSRuntime
|
||||
; 检查是否已安装 Visual C++ Redistributable
|
||||
${If} ${IsNativeARM64}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.arm64.exe"
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
|
||||
${ElseIf} ${RunningX64}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.x64.exe"
|
||||
IfFileExists "$WINDIR\SysWOW64\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
|
||||
${Else}
|
||||
StrCpy $VC_REDIST_URL "https://aka.ms/vs/17/release/vc_redist.x86.exe"
|
||||
StrCpy $VC_REDIST_EXE "vc_redist.x86.exe"
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done
|
||||
|
||||
; 检查关键DLL
|
||||
IfFileExists "$SYSDIR\vcruntime140.dll" 0 checkInstall
|
||||
IfFileExists "$SYSDIR\msvcp140.dll" Done checkInstall
|
||||
${EndIf}
|
||||
|
||||
; 下载并安装VC运行库
|
||||
checkInstall:
|
||||
; 检查注册表
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\${ARCH}" "Installed"
|
||||
${If} $R0 == "1"
|
||||
Goto Done
|
||||
${EndIf}
|
||||
${Else}
|
||||
ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" "Installed"
|
||||
${If} $R0 == "1"
|
||||
Goto Done
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
; 如果没有安装,则下载并安装
|
||||
DetailPrint "正在下载 Visual C++ Redistributable..."
|
||||
nsisdl::download "$VC_REDIST_URL" "$TEMP\$VC_REDIST_EXE"
|
||||
Pop $0
|
||||
${If} $0 == "success"
|
||||
nsExec::Exec '"$TEMP\$VC_REDIST_EXE" /quiet /norestart'
|
||||
DetailPrint "正在安装 Visual C++ Redistributable..."
|
||||
ExecWait '"$TEMP\$VC_REDIST_EXE" /quiet /norestart' $0
|
||||
${If} $0 == 0
|
||||
DetailPrint "Visual C++ Redistributable 安装成功"
|
||||
${Else}
|
||||
DetailPrint "Visual C++ Redistributable 安装失败"
|
||||
${EndIf}
|
||||
Delete "$TEMP\$VC_REDIST_EXE"
|
||||
${Else}
|
||||
DetailPrint "Visual C++ Redistributable 下载失败"
|
||||
${EndIf}
|
||||
|
||||
Done:
|
||||
@@ -728,6 +765,12 @@ Section Install
|
||||
nsExec::Exec 'netsh int tcp res'
|
||||
!insertmacro CheckIfAppIsRunning
|
||||
!insertmacro CheckAllVergeProcesses
|
||||
|
||||
; Delete old files before installation
|
||||
; Delete clash-verge.desktop
|
||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
||||
Delete "$INSTDIR\Clash Verge.exe"
|
||||
|
||||
; Copy main executable
|
||||
File "${MAINBINARYSRCPATH}"
|
||||
|
||||
@@ -862,6 +905,10 @@ Section Uninstall
|
||||
Delete "$INSTDIR\\{{this}}"
|
||||
{{/each}}
|
||||
|
||||
; Delete clash-verge.desktop
|
||||
IfFileExists "$INSTDIR\Clash Verge.exe" 0 +2
|
||||
Delete "$INSTDIR\Clash Verge.exe"
|
||||
|
||||
; Delete uninstaller
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
revise!(dns_val, "enable", true);
|
||||
revise!(dns_val, "ipv6", true);
|
||||
revise!(dns_val, "enhanced-mode", "fake-ip");
|
||||
revise!(dns_val, "fake-ip-range", "10.96.0.0/16");
|
||||
revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
crate::utils::resolve::restore_public_dns().await;
|
||||
crate::utils::resolve::set_public_dns("10.96.0.2".to_string()).await;
|
||||
crate::utils::resolve::set_public_dns("8.8.8.8".to_string()).await;
|
||||
}
|
||||
} else {
|
||||
revise!(dns_val, "enhanced-mode", "redir-host");
|
||||
|
||||
@@ -464,6 +464,12 @@ pub async fn delete_webdav_backup(filename: String) -> Result<()> {
|
||||
}
|
||||
|
||||
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let verge = Config::verge();
|
||||
let verge_data = verge.data().clone();
|
||||
let webdav_url = verge_data.webdav_url.clone();
|
||||
let webdav_username = verge_data.webdav_username.clone();
|
||||
let webdav_password = verge_data.webdav_password.clone();
|
||||
|
||||
let backup_storage_path = app_home_dir().unwrap().join(&filename);
|
||||
backup::WebDavClient::global()
|
||||
.download(filename, backup_storage_path.clone())
|
||||
@@ -477,6 +483,15 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let mut zip = zip::ZipArchive::new(fs::File::open(backup_storage_path.clone())?)?;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
|
||||
log_err!(
|
||||
patch_verge(IVerge {
|
||||
webdav_url: webdav_url,
|
||||
webdav_username: webdav_username,
|
||||
webdav_password: webdav_password,
|
||||
..IVerge::default()
|
||||
})
|
||||
.await
|
||||
);
|
||||
// 最后删除临时文件
|
||||
fs::remove_file(backup_storage_path)?;
|
||||
Ok(())
|
||||
|
||||
@@ -99,9 +99,41 @@ pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
/// open file
|
||||
/// use vscode by default
|
||||
/// try to use vscode first, if not found then use system default app
|
||||
pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
|
||||
app.shell().open(path.to_string_lossy(), None).unwrap();
|
||||
#[cfg(target_os = "macos")]
|
||||
let code = "Visual Studio Code";
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let code = "code";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let vscode_exists = {
|
||||
use std::process::Command;
|
||||
Command::new("where").arg("code").output().is_ok()
|
||||
};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let vscode_exists = {
|
||||
use std::process::Command;
|
||||
Command::new("which").arg("code").output().is_ok()
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let vscode_exists = {
|
||||
use std::process::Command;
|
||||
Command::new("which").arg("code").output().is_ok()
|
||||
};
|
||||
|
||||
// 如果 VS Code 存在就用它打开,否则用系统默认程序
|
||||
if vscode_exists {
|
||||
if let Err(err) = open::with(&path.as_os_str(), code) {
|
||||
log::error!(target: "app", "Failed to open with VS Code: {}", err);
|
||||
app.shell().open(path.to_string_lossy(), None)?;
|
||||
}
|
||||
} else {
|
||||
app.shell().open(path.to_string_lossy(), None)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"devUrl": "http://localhost:3000/"
|
||||
},
|
||||
"productName": "Clash Verge",
|
||||
"version": "2.0.0",
|
||||
"version": "2.0.1",
|
||||
"identifier": "io.github.clash-verge-rev.clash-verge-rev",
|
||||
"plugins": {
|
||||
"updater": {
|
||||
|
||||
@@ -38,7 +38,7 @@ export const ConnectionDetail = forwardRef<ConnectionDetailRef>(
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
interface InnerProps {
|
||||
|
||||
@@ -59,7 +59,7 @@ export const LayoutTraffic = () => {
|
||||
this.close();
|
||||
next(event, { up: 0, down: 0 });
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -69,7 +69,7 @@ export const LayoutTraffic = () => {
|
||||
{
|
||||
fallbackData: { up: 0, down: 0 },
|
||||
keepPreviousData: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/* --------- meta memory information --------- */
|
||||
@@ -96,7 +96,7 @@ export const LayoutTraffic = () => {
|
||||
this.close();
|
||||
next(event, { inuse: 0 });
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -106,7 +106,7 @@ export const LayoutTraffic = () => {
|
||||
{
|
||||
fallbackData: { inuse: 0 },
|
||||
keepPreviousData: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const [up, upUnit] = parseTraffic(traffic.up);
|
||||
|
||||
@@ -104,7 +104,7 @@ export const useCustomTheme = () => {
|
||||
rootEle.style.setProperty("--primary-main", theme.palette.primary.main);
|
||||
rootEle.style.setProperty(
|
||||
"--background-color-alpha",
|
||||
alpha(theme.palette.primary.main, 0.1)
|
||||
alpha(theme.palette.primary.main, 0.1),
|
||||
);
|
||||
|
||||
// inject css
|
||||
|
||||
@@ -34,7 +34,7 @@ export const ProviderButton = () => {
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
Object.keys(data || {}).map(() => false),
|
||||
);
|
||||
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
@@ -107,7 +107,7 @@ export const ProviderButton = () => {
|
||||
const expire = sub?.Expire || 0;
|
||||
const progress = Math.min(
|
||||
Math.round(((download + upload) * 100) / (total + 0.01)) + 1,
|
||||
100
|
||||
100,
|
||||
);
|
||||
return (
|
||||
<>
|
||||
@@ -213,7 +213,7 @@ const StyledTypeBox = styled(Box)<{ component?: React.ElementType }>(
|
||||
marginRight: "4px",
|
||||
padding: "0 2px",
|
||||
lineHeight: 1.25,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const boxStyle = {
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useRenderList = (mode: string) => {
|
||||
const { data: proxiesData, mutate: mutateProxies } = useSWR(
|
||||
"getProxies",
|
||||
getProxies,
|
||||
{ refreshInterval: 45000 }
|
||||
{ refreshInterval: 45000 },
|
||||
);
|
||||
|
||||
const { verge } = useVerge();
|
||||
@@ -78,7 +78,7 @@ export const useRenderList = (mode: string) => {
|
||||
group.all,
|
||||
group.name,
|
||||
headState.filterText,
|
||||
headState.sortType
|
||||
headState.sortType,
|
||||
);
|
||||
|
||||
ret.push({ type: 1, key: `head-${group.name}`, group, headState });
|
||||
@@ -97,7 +97,7 @@ export const useRenderList = (mode: string) => {
|
||||
headState,
|
||||
col,
|
||||
proxyCol,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export const useRenderList = (mode: string) => {
|
||||
group,
|
||||
proxy,
|
||||
headState,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -32,7 +32,7 @@ export const ProviderButton = () => {
|
||||
|
||||
const hasProvider = Object.keys(data || {}).length > 0;
|
||||
const [updating, setUpdating] = useState(
|
||||
Object.keys(data || {}).map(() => false)
|
||||
Object.keys(data || {}).map(() => false),
|
||||
);
|
||||
|
||||
const setUpdatingAt = (status: boolean, index: number) => {
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface BackupTableViewerProps {
|
||||
page: number;
|
||||
onPageChange: (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
page: number
|
||||
page: number,
|
||||
) => void;
|
||||
total: number;
|
||||
onRefresh: () => Promise<void>;
|
||||
@@ -109,7 +109,7 @@ export const BackupTableViewer = memo(
|
||||
onClick={async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const confirmed = await window.confirm(
|
||||
t("Confirm to delete this backup file?")
|
||||
t("Confirm to delete this backup file?"),
|
||||
);
|
||||
if (confirmed) {
|
||||
await handleDelete(file.filename);
|
||||
@@ -132,7 +132,7 @@ export const BackupTableViewer = memo(
|
||||
onClick={async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const confirmed = await window.confirm(
|
||||
t("Confirm to restore this backup file?")
|
||||
t("Confirm to restore this backup file?"),
|
||||
);
|
||||
if (confirmed) {
|
||||
await handleRestore(file.filename);
|
||||
@@ -181,7 +181,7 @@ export const BackupTableViewer = memo(
|
||||
/>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function LinuxIcon(props: SVGProps<SVGSVGElement>) {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
(_: React.MouseEvent<HTMLButtonElement> | null, page: number) => {
|
||||
setPage(page);
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchAndSetBackupFiles = async () => {
|
||||
@@ -95,8 +95,8 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setDataSource(
|
||||
backupFiles.slice(
|
||||
page * DEFAULT_ROWS_PER_PAGE,
|
||||
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE
|
||||
)
|
||||
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE,
|
||||
),
|
||||
);
|
||||
}, [page, backupFiles]);
|
||||
|
||||
|
||||
@@ -76,10 +76,10 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
setDownloaded((a) => {
|
||||
return a + e.payload.chunkLength;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
try {
|
||||
await updateInfo.install();
|
||||
await updateInfo.downloadAndInstall();
|
||||
await relaunch();
|
||||
} catch (err: any) {
|
||||
Notice.error(err?.message || err.toString());
|
||||
@@ -100,7 +100,7 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
size="small"
|
||||
onClick={() => {
|
||||
openUrl(
|
||||
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`
|
||||
`https://github.com/clash-verge-rev/clash-verge-rev/releases/tag/v${updateInfo?.version}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -139,7 +139,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
>
|
||||
<GuardState
|
||||
// clash premium 2022.08.26 值为warn
|
||||
value={logLevel === "warn" ? "warning" : logLevel ?? "info"}
|
||||
value={logLevel === "warn" ? "warning" : (logLevel ?? "info")}
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ "log-level": e })}
|
||||
@@ -165,7 +165,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
onClick={() => {
|
||||
Notice.success(
|
||||
t("Restart Application to Apply Modifications"),
|
||||
1000
|
||||
1000,
|
||||
);
|
||||
onChangeVerge({ enable_random_port: !enable_random_port });
|
||||
patchVerge({ enable_random_port: !enable_random_port });
|
||||
|
||||
@@ -64,13 +64,18 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
try {
|
||||
if (!form.name) throw new Error("`Name` should not be null");
|
||||
if (!form.url) throw new Error("`Url` should not be null");
|
||||
|
||||
let newList;
|
||||
let uid;
|
||||
|
||||
if (form.icon && form.icon.startsWith("<svg")) {
|
||||
// 移除 icon 中的注释
|
||||
if (form.icon) {
|
||||
form.icon = form.icon.replace(/<!--[\s\S]*?-->/g, "");
|
||||
}
|
||||
const doc = new DOMParser().parseFromString(
|
||||
form.icon,
|
||||
"image/svg+xml"
|
||||
"image/svg+xml",
|
||||
);
|
||||
if (doc.querySelector("parsererror")) {
|
||||
throw new Error("`Icon`svg format error");
|
||||
@@ -97,7 +102,7 @@ export const TestViewer = forwardRef<TestViewerRef, Props>((props, ref) => {
|
||||
Notice.error(err.message || err.toString());
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
export const useClash = () => {
|
||||
const { data: clash, mutate: mutateClash } = useSWR(
|
||||
"getRuntimeConfig",
|
||||
getRuntimeConfig
|
||||
getRuntimeConfig,
|
||||
);
|
||||
|
||||
const { data: versionData, mutate: mutateVersion } = useSWR(
|
||||
"getVersion",
|
||||
getVersion
|
||||
getVersion,
|
||||
);
|
||||
|
||||
const patchClash = useLockFn(async (patch: Partial<IConfigData>) => {
|
||||
@@ -26,8 +26,8 @@ export const useClash = () => {
|
||||
const version = versionData?.premium
|
||||
? `${versionData.version} Premium`
|
||||
: versionData?.meta
|
||||
? `${versionData.version} Mihomo`
|
||||
: versionData?.version || "-";
|
||||
? `${versionData.version} Mihomo`
|
||||
: versionData?.version || "-";
|
||||
|
||||
return {
|
||||
clash,
|
||||
@@ -41,7 +41,7 @@ export const useClash = () => {
|
||||
export const useClashInfo = () => {
|
||||
const { data: clashInfo, mutate: mutateInfo } = useSWR(
|
||||
"getClashInfo",
|
||||
getClashInfo
|
||||
getClashInfo,
|
||||
);
|
||||
|
||||
const patchInfo = async (
|
||||
@@ -56,7 +56,7 @@ export const useClashInfo = () => {
|
||||
| "external-controller"
|
||||
| "secret"
|
||||
>
|
||||
>
|
||||
>,
|
||||
) => {
|
||||
const hasInfo =
|
||||
patch["redir-port"] != null ||
|
||||
|
||||
@@ -7,7 +7,7 @@ export const useListen = () => {
|
||||
|
||||
const addListener = async <T>(
|
||||
eventName: string,
|
||||
handler: EventCallback<T>
|
||||
handler: EventCallback<T>,
|
||||
) => {
|
||||
const unlisten = await listen(eventName, handler);
|
||||
unlistenFns.current.push(unlisten);
|
||||
|
||||
@@ -21,7 +21,7 @@ const buildWSUrl = (server: string, secret: string, logLevel: LogLevel) => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (secret) {
|
||||
params.append("token", encodeURIComponent(secret));
|
||||
params.append("token", secret);
|
||||
}
|
||||
if (logLevel === "all") {
|
||||
params.append("level", "debug");
|
||||
|
||||
@@ -47,7 +47,7 @@ const ConnectionsPage = () => {
|
||||
list.sort(
|
||||
(a, b) =>
|
||||
new Date(b.start || "0").getTime()! -
|
||||
new Date(a.start || "0").getTime()!
|
||||
new Date(a.start || "0").getTime()!,
|
||||
),
|
||||
"Upload Speed": (list) => list.sort((a, b) => b.curUpload! - a.curUpload!),
|
||||
"Download Speed": (list) =>
|
||||
@@ -103,7 +103,7 @@ const ConnectionsPage = () => {
|
||||
next(event);
|
||||
},
|
||||
},
|
||||
3
|
||||
3,
|
||||
);
|
||||
|
||||
return () => {
|
||||
@@ -114,7 +114,7 @@ const ConnectionsPage = () => {
|
||||
const [filterConn, download, upload] = useMemo(() => {
|
||||
const orderFunc = orderOpts[curOrderOpt];
|
||||
let connections = connData.connections.filter((conn) =>
|
||||
match(conn.metadata.host || conn.metadata.destinationIP || "")
|
||||
match(conn.metadata.host || conn.metadata.destinationIP || ""),
|
||||
);
|
||||
|
||||
if (orderFunc) connections = orderFunc(connections);
|
||||
@@ -152,7 +152,7 @@ const ConnectionsPage = () => {
|
||||
setSetting((o) =>
|
||||
o?.layout !== "table"
|
||||
? { ...o, layout: "table" }
|
||||
: { ...o, layout: "list" }
|
||||
: { ...o, layout: "list" },
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -64,7 +64,7 @@ const ProfilePage = () => {
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
}),
|
||||
);
|
||||
const { current } = location.state || {};
|
||||
|
||||
@@ -105,7 +105,7 @@ const ProfilePage = () => {
|
||||
|
||||
const { data: chainLogs = {}, mutate: mutateLogs } = useSWR(
|
||||
"getRuntimeLogs",
|
||||
getRuntimeLogs
|
||||
getRuntimeLogs,
|
||||
);
|
||||
|
||||
const viewerRef = useRef<ProfileViewerRef>(null);
|
||||
@@ -240,7 +240,7 @@ const ProfilePage = () => {
|
||||
setLoadingCache((cache) => {
|
||||
// 获取没有正在更新的订阅
|
||||
const items = profileItems.filter(
|
||||
(e) => e.type === "remote" && !cache[e.uid]
|
||||
(e) => e.type === "remote" && !cache[e.uid],
|
||||
);
|
||||
const change = Object.fromEntries(items.map((e) => [e.uid, true]));
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const ProxyPage = () => {
|
||||
|
||||
const { data: clashConfig, mutate: mutateClash } = useSWR(
|
||||
"getClashConfig",
|
||||
getClashConfig
|
||||
getClashConfig,
|
||||
);
|
||||
|
||||
const { verge } = useVerge();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { Box, Button, Grid } from "@mui/material";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import Grid2 from "@mui/material/Grid2";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -34,7 +35,7 @@ const TestPage = () => {
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
}),
|
||||
);
|
||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||
|
||||
@@ -68,7 +69,7 @@ const TestPage = () => {
|
||||
|
||||
const onTestListItemChange = (
|
||||
uid: string,
|
||||
patch?: Partial<IVergeTestItem>
|
||||
patch?: Partial<IVergeTestItem>,
|
||||
) => {
|
||||
if (patch) {
|
||||
const newList = testList.map((x) => {
|
||||
@@ -157,24 +158,28 @@ const TestPage = () => {
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
<Box sx={{ mb: 4.5 }}>
|
||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||
<Grid2 container spacing={{ xs: 1, lg: 1 }}>
|
||||
<SortableContext
|
||||
items={testList.map((x) => {
|
||||
return x.uid;
|
||||
})}
|
||||
>
|
||||
{testList.map((item) => (
|
||||
<Grid item xs={6} sm={4} md={3} lg={2} key={item.uid}>
|
||||
<Grid2
|
||||
component={"div"}
|
||||
size={{ xs: 6, lg: 2, sm: 4, md: 3 }}
|
||||
key={item.uid}
|
||||
>
|
||||
<TestItem
|
||||
id={item.uid}
|
||||
itemData={item}
|
||||
onEdit={() => viewerRef.current?.edit(item)}
|
||||
onDelete={onDeleteTestListItem}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
))}
|
||||
</SortableContext>
|
||||
</Grid>
|
||||
</Grid2>
|
||||
</Box>
|
||||
</DndContext>
|
||||
</Box>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const useConnectionSetting = () =>
|
||||
{
|
||||
serializer: JSON.stringify,
|
||||
deserializer: JSON.parse,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// save the state of each profile item loading
|
||||
|
||||
@@ -6,7 +6,7 @@ import Sockette, { type SocketteOptions } from "sockette";
|
||||
export const createSockette = (
|
||||
url: string,
|
||||
opt: SocketteOptions,
|
||||
maxError = 10
|
||||
maxError = 10,
|
||||
) => {
|
||||
let remainRetryCount = maxError;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import monacoEditorPlugin, {
|
||||
type IMonacoEditorOpts,
|
||||
} from "vite-plugin-monaco-editor";
|
||||
const monacoEditorPluginDefault = (monacoEditorPlugin as any).default as (
|
||||
options: IMonacoEditorOpts
|
||||
options: IMonacoEditorOpts,
|
||||
) => any;
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
Reference in New Issue
Block a user