Compare commits
23 Commits
1
.github/workflows/compatible.yml
vendored
1
.github/workflows/compatible.yml
vendored
@@ -24,7 +24,6 @@ jobs:
|
|||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
|
|
||||||
runs-on: ${{ matrix.targets.os }}
|
runs-on: ${{ matrix.targets.os }}
|
||||||
if: startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
1
.github/workflows/dev.yaml
vendored
1
.github/workflows/dev.yaml
vendored
@@ -17,7 +17,6 @@ jobs:
|
|||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
if: startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
1
.github/workflows/meta.yml
vendored
1
.github/workflows/meta.yml
vendored
@@ -17,7 +17,6 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
if: startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
108
.github/workflows/release.yml
vendored
108
.github/workflows/release.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v**
|
- v**
|
||||||
|
permissions: write-all
|
||||||
env:
|
env:
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
@@ -15,17 +15,36 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
- os: windows-latest
|
||||||
|
target: i686-pc-windows-msvc
|
||||||
|
# - os: windows-latest
|
||||||
|
# target: aarch64-pc-windows-msvc
|
||||||
|
- os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
- os: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
- os: ubuntu-latest
|
||||||
|
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 }}
|
runs-on: ${{ matrix.os }}
|
||||||
if: startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: install Rust stable
|
- name: Install Rust Stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Add Rust Target
|
||||||
|
run: |
|
||||||
|
rustup target add ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
@@ -43,17 +62,42 @@ jobs:
|
|||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Install Dependencies (Ubuntu Only)
|
- name: Install Dependencies (Ubuntu Only)
|
||||||
if: startsWith(matrix.os, 'ubuntu-')
|
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.target,'x86_64')
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf openssl
|
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libayatana-appindicator3-dev librsvg2-dev patchelf openssl
|
||||||
|
|
||||||
|
- name: Install Dependencies (Ubuntu Only)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.target,'aarch64')
|
||||||
|
run: |
|
||||||
|
sed 's/mirror+file:\/etc\/apt\/apt-mirrors.txt/[arch-=amd64] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/ports.list
|
||||||
|
sudo sed -i 's/mirror+file/[arch=amd64] mirror+file/g' /etc/apt/sources.list
|
||||||
|
cat /etc/apt/sources.list
|
||||||
|
cat /etc/apt/sources.list.d/ports.list
|
||||||
|
sudo dpkg --add-architecture arm64
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libncurses6:arm64 libtinfo6:arm64 linux-libc-dev:arm64 libncursesw6:arm64 libssl3:arm64 libcups2:arm64
|
||||||
|
sudo 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
|
||||||
|
|
||||||
|
- name: Install Dependencies (Ubuntu Only)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.target,'armv7')
|
||||||
|
run: |
|
||||||
|
sed 's/mirror+file:\/etc\/apt\/apt-mirrors.txt/[arch-=amd64] http:\/\/ports.ubuntu.com\/ubuntu-ports\//g' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/ports.list
|
||||||
|
sudo sed -i 's/mirror+file/[arch=amd64] mirror+file/g' /etc/apt/sources.list
|
||||||
|
cat /etc/apt/sources.list
|
||||||
|
cat /etc/apt/sources.list.d/ports.list
|
||||||
|
sudo dpkg --add-architecture armhf
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libncurses6:armhf libtinfo6:armhf linux-libc-dev:armhf libncursesw6:armhf libssl3:armhf libcups2:armhf
|
||||||
|
sudo 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
|
||||||
|
|
||||||
- name: Pnpm install and check
|
- name: Pnpm install and check
|
||||||
run: |
|
run: |
|
||||||
pnpm i
|
pnpm i
|
||||||
pnpm check
|
pnpm check ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Tauri build
|
- name: Tauri build
|
||||||
|
if: startsWith(matrix.os, 'windows') || startsWith(matrix.os,'macos') || startsWith(matrix.target,'x86_64')
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -66,7 +110,49 @@ jobs:
|
|||||||
releaseDraft: false
|
releaseDraft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
tauriScript: pnpm
|
tauriScript: pnpm
|
||||||
args: -f default-meta
|
args: -f default-meta --target ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Tauri build (Ubuntu Arm64)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.target,'aarch64')
|
||||||
|
uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||||
|
CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc
|
||||||
|
CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++
|
||||||
|
PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig
|
||||||
|
PKG_CONFIG_ALLOW_CROSS: 1
|
||||||
|
with:
|
||||||
|
tagName: v__VERSION__
|
||||||
|
releaseName: "Clash Verge v__VERSION__"
|
||||||
|
releaseBody: "More new features are now supported."
|
||||||
|
releaseDraft: false
|
||||||
|
prerelease: false
|
||||||
|
tauriScript: pnpm
|
||||||
|
args: -f default-meta --target ${{ matrix.target }} -b deb,updater
|
||||||
|
|
||||||
|
- name: Tauri build (Ubuntu Armv7)
|
||||||
|
if: startsWith(matrix.os, 'ubuntu') && startsWith(matrix.target,'armv7')
|
||||||
|
uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
|
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
|
CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
|
||||||
|
CC_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
|
||||||
|
CXX_armv7_unknown_linux_gnueabihf: arm-linux-gnueabihf-g++
|
||||||
|
PKG_CONFIG_PATH: /usr/lib/arm-linux-gnueabihf/pkgconfig
|
||||||
|
PKG_CONFIG_ALLOW_CROSS: 1
|
||||||
|
with:
|
||||||
|
tagName: v__VERSION__
|
||||||
|
releaseName: "Clash Verge v__VERSION__"
|
||||||
|
releaseBody: "More new features are now supported."
|
||||||
|
releaseDraft: false
|
||||||
|
prerelease: false
|
||||||
|
tauriScript: pnpm
|
||||||
|
args: -f default-meta --target ${{ matrix.target }} -b deb,updater
|
||||||
|
|
||||||
- name: Portable Bundle
|
- name: Portable Bundle
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
@@ -81,9 +167,7 @@ jobs:
|
|||||||
release-update:
|
release-update:
|
||||||
needs: [release]
|
needs: [release]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
startsWith(github.repository, 'wonfen') &&
|
|
||||||
startsWith(github.ref, 'refs/tags/v')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -28,7 +28,6 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ${{ github.event.inputs.os }}
|
runs-on: ${{ github.event.inputs.os }}
|
||||||
if: startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: System Version
|
- name: System Version
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
4
.github/workflows/updater.yml
vendored
4
.github/workflows/updater.yml
vendored
@@ -1,12 +1,10 @@
|
|||||||
name: Updater CI
|
name: Updater CI
|
||||||
|
|
||||||
on: workflow_dispatch
|
on: workflow_dispatch
|
||||||
|
permissions: write-all
|
||||||
jobs:
|
jobs:
|
||||||
release-update:
|
release-update:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: |
|
|
||||||
startsWith(github.repository, 'wonfen')
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,18 +1,18 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
|
<img src="./src/assets/image/logo.png" alt="Clash" width="128" />
|
||||||
<br>
|
<br>
|
||||||
Clash Verge
|
Continuation of Clash Verge
|
||||||
<br>
|
<br>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h3 align="center">
|
<h3 align="center">
|
||||||
A 接盘 Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">tauri</a>.
|
A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">tauri</a>.
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
|
- 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://github.com/wonfen/clash-verge-rev/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
|
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://github.com/clash-verge-rev/clash-verge-rev/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
|
||||||
- Simple UI and supports custom theme color.
|
- Simple UI and supports custom theme color.
|
||||||
- Built-in support [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) core.
|
- Built-in support [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta) core.
|
||||||
- System proxy setting and guard.
|
- System proxy setting and guard.
|
||||||
@@ -39,13 +39,13 @@ A 接盘 Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">t
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Download from [release](https://github.com/wonfen/clash-verge-rev/releases). Supports Windows x64, Linux x86_64 and macOS 11+
|
Download from [release](https://github.com/clash-verge-rev/clash-verge-rev/releases). Supports Windows x64, Linux x86_64 and macOS 11+
|
||||||
|
|
||||||
- [Windows x64](https://github.com/wonfen/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_x64_zh-CN.msi)
|
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_x64_zh-CN.msi)
|
||||||
- [macOS intel](https://github.com/wonfen/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_x64.dmg)
|
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_x64.dmg)
|
||||||
- [macOS arm](https://github.com/wonfen/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_aarch64.dmg)
|
- [macOS arm](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.0/Clash.Verge_1.4.0_aarch64.dmg)
|
||||||
- [Linux AppImage](https://github.com/wonfen/clash-verge-rev/releases/download/v1.4.0/clash-verge_1.4.0_amd64.AppImage)
|
- [Linux AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.0/clash-verge_1.4.0_amd64.AppImage)
|
||||||
- [Linux deb](https://github.com/wonfen/clash-verge-rev/releases/download/v1.4.0/clash-verge_1.4.0_amd64.deb)
|
- [Linux deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.0/clash-verge_1.4.0_amd64.deb)
|
||||||
|
|
||||||
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
|
||||||
|
|
||||||
|
|||||||
18
UPDATELOG.md
18
UPDATELOG.md
@@ -1,3 +1,21 @@
|
|||||||
|
## v1.4.2
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- update clash meta core to mihomo 1.17.0
|
||||||
|
- fixed the problem of not being able to set the system proxy when there is a dial-up link on windows system [#833](https://github.com/zzzgydi/clash-verge/issues/833)
|
||||||
|
- support new clash field
|
||||||
|
- support random mixed port
|
||||||
|
- add windows x86 and linux armv7 support
|
||||||
|
- support disable tray click event
|
||||||
|
- add download progress for updater
|
||||||
|
- support drag to reorder the profile
|
||||||
|
- embed emoji fonts
|
||||||
|
- update depends
|
||||||
|
- improve UI style
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v1.4.1
|
## v1.4.1
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
BIN
docs/preview.gif
BIN
docs/preview.gif
Binary file not shown.
|
Before Width: | Height: | Size: 6.3 MiB After Width: | Height: | Size: 6.9 MiB |
65
package.json
65
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clash-verge",
|
"name": "clash-verge",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tauri dev -f default-meta",
|
"dev": "tauri dev -f default-meta",
|
||||||
@@ -18,57 +18,60 @@
|
|||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.1.0",
|
||||||
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@juggle/resize-observer": "^3.4.0",
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
"@mui/icons-material": "^5.14.14",
|
"@mui/icons-material": "^5.14.19",
|
||||||
"@mui/lab": "5.0.0-alpha.149",
|
"@mui/lab": "5.0.0-alpha.149",
|
||||||
"@mui/material": "^5.14.14",
|
"@mui/material": "^5.14.19",
|
||||||
"@mui/x-data-grid": "^6.16.3",
|
"@mui/x-data-grid": "^6.18.2",
|
||||||
"@tauri-apps/api": "^1.3.0",
|
"@tauri-apps/api": "^1.5.1",
|
||||||
"ahooks": "^3.7.2",
|
"ahooks": "^3.7.8",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.6.2",
|
||||||
"dayjs": "1.11.5",
|
"dayjs": "1.11.5",
|
||||||
"i18next": "^22.0.4",
|
"i18next": "^23.7.7",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"monaco-editor": "^0.34.1",
|
"monaco-editor": "^0.34.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-error-boundary": "^3.1.4",
|
"react-error-boundary": "^3.1.4",
|
||||||
"react-hook-form": "^7.39.5",
|
"react-hook-form": "^7.48.2",
|
||||||
"react-i18next": "^12.0.0",
|
"react-i18next": "^13.5.0",
|
||||||
"react-router-dom": "^6.4.3",
|
"react-router-dom": "^6.20.0",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-virtuoso": "^3.1.3",
|
"react-virtuoso": "^4.6.2",
|
||||||
"recoil": "^0.7.6",
|
"recoil": "^0.7.7",
|
||||||
"snarkdown": "^2.0.0",
|
"snarkdown": "^2.0.0",
|
||||||
"tar": "^6.2.0",
|
"swr": "^1.3.0",
|
||||||
"swr": "^1.3.0"
|
"tar": "^6.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/github": "^5.0.3",
|
"@actions/github": "^5.1.1",
|
||||||
"@tauri-apps/cli": "^1.3.1",
|
"@tauri-apps/cli": "^1.5.6",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^9.0.13",
|
||||||
"@types/js-cookie": "^3.0.2",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/lodash": "^4.14.180",
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/lodash-es": "^4.17.7",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/react": "^18.2.37",
|
"@types/react": "^18.2.39",
|
||||||
"@types/react-dom": "^18.0.11",
|
"@types/react-dom": "^18.2.17",
|
||||||
"@types/react-transition-group": "^4.4.9",
|
"@types/react-transition-group": "^4.4.9",
|
||||||
"@vitejs/plugin-react": "^4.1.0",
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"adm-zip": "^0.5.9",
|
"adm-zip": "^0.5.10",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^11.2.0",
|
||||||
"https-proxy-agent": "^5.0.1",
|
"https-proxy-agent": "^5.0.1",
|
||||||
"husky": "^7.0.0",
|
"husky": "^7.0.4",
|
||||||
"node-fetch": "^3.2.6",
|
"node-fetch": "^3.3.2",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.8.8",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
"sass": "^1.54.0",
|
"sass": "^1.69.5",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.3.2",
|
||||||
"vite": "^4.5.0",
|
"vite": "^4.5.0",
|
||||||
"vite-plugin-monaco-editor": "^1.1.0",
|
"vite-plugin-monaco-editor": "^1.1.0",
|
||||||
"vite-plugin-svgr": "^4.1.0"
|
"vite-plugin-svgr": "^4.2.0"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
|
|||||||
1163
pnpm-lock.yaml
generated
1163
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -11,122 +11,127 @@ const cwd = process.cwd();
|
|||||||
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
const TEMP_DIR = path.join(cwd, "node_modules/.verge");
|
||||||
const FORCE = process.argv.includes("--force");
|
const FORCE = process.argv.includes("--force");
|
||||||
|
|
||||||
const SIDECAR_HOST = execSync("rustc -vV")
|
const PLATFORM_MAP = {
|
||||||
.toString()
|
"x86_64-pc-windows-msvc": "win32",
|
||||||
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
"i686-pc-windows-msvc": "win32",
|
||||||
|
"aarch64-pc-windows-msvc": "win32",
|
||||||
/* ======= clash =======
|
"x86_64-apple-darwin": "darwin",
|
||||||
const CLASH_STORAGE_PREFIX = "https://release.dreamacro.workers.dev/";
|
"aarch64-apple-darwin": "darwin",
|
||||||
const CLASH_URL_PREFIX =
|
"x86_64-unknown-linux-gnu": "linux",
|
||||||
"https://github.com/Dreamacro/clash/releases/download/premium/";
|
"aarch64-unknown-linux-gnu": "linux",
|
||||||
const CLASH_LATEST_DATE = "latest";
|
"armv7-unknown-linux-gnueabihf": "linux",
|
||||||
|
|
||||||
const CLASH_BACKUP_URL_PREFIX =
|
|
||||||
"https://github.com/zhongfly/Clash-premium-backup/releases/download/";
|
|
||||||
const CLASH_BACKUP_LATEST_DATE = "2023-09-05-gdcc8d87";
|
|
||||||
|
|
||||||
//https://github.com/zhongfly/Clash-premium-backup/releases/download/2023-09-05-gdcc8d87/clash-windows-amd64-2023-09-05-gdcc8d87.zip
|
|
||||||
//https://github.com/zhongfly/Clash-premium-backup/releases/download/2023-09-05-gdcc8d87/clash-windows-amd64-n2023-09-05-gdcc8d87.zip
|
|
||||||
|
|
||||||
const CLASH_MAP = {
|
|
||||||
"win32-x64": "clash-windows-amd64",
|
|
||||||
"darwin-x64": "clash-darwin-amd64",
|
|
||||||
"darwin-arm64": "clash-darwin-arm64",
|
|
||||||
"linux-x64": "clash-linux-amd64",
|
|
||||||
"linux-arm64": "clash-linux-arm64",
|
|
||||||
};
|
};
|
||||||
*/
|
const ARCH_MAP = {
|
||||||
/* ======= clash meta ======= */
|
"x86_64-pc-windows-msvc": "x64",
|
||||||
const META_URL_PREFIX = `https://github.com/wonfen/Clash.Meta/releases/download/latest`;
|
"i686-pc-windows-msvc": "ia32",
|
||||||
// const META_VERSION = "2023.11.23";
|
"aarch64-pc-windows-msvc": "arm64",
|
||||||
|
"x86_64-apple-darwin": "x64",
|
||||||
|
"aarch64-apple-darwin": "arm64",
|
||||||
|
"x86_64-unknown-linux-gnu": "x64",
|
||||||
|
"aarch64-unknown-linux-gnu": "arm64",
|
||||||
|
"armv7-unknown-linux-gnueabihf": "arm",
|
||||||
|
};
|
||||||
|
|
||||||
|
const arg1 = process.argv.slice(2)[0];
|
||||||
|
const arg2 = process.argv.slice(2)[1];
|
||||||
|
const target = arg1 === "--force" ? arg2 : arg1;
|
||||||
|
const { platform, arch } = target
|
||||||
|
? { platform: PLATFORM_MAP[target], arch: ARCH_MAP[target] }
|
||||||
|
: process;
|
||||||
|
|
||||||
|
const SIDECAR_HOST = target
|
||||||
|
? target
|
||||||
|
: execSync("rustc -vV")
|
||||||
|
.toString()
|
||||||
|
.match(/(?<=host: ).+(?=\s*)/g)[0];
|
||||||
|
|
||||||
|
/* ======= clash meta alpha======= */
|
||||||
|
const VERSION_URL =
|
||||||
|
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
|
||||||
|
const META_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`;
|
||||||
|
let META_ALPHA_VERSION;
|
||||||
|
|
||||||
|
const META_ALPHA_MAP = {
|
||||||
|
"win32-x64": "mihomo-windows-amd64",
|
||||||
|
"win32-ia32": "mihomo-windows-386",
|
||||||
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
|
"linux-x64": "mihomo-linux-amd64",
|
||||||
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch the latest release version from the version.txt file
|
||||||
|
async function getLatestVersion() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(VERSION_URL, { method: "GET" });
|
||||||
|
let v = await response.text();
|
||||||
|
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
|
||||||
|
console.log(`Latest release version: ${META_ALPHA_VERSION}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching latest release version:", error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======= clash meta stable ======= */
|
||||||
|
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
|
||||||
|
let META_VERSION = "v1.17.0";
|
||||||
|
|
||||||
const META_MAP = {
|
const META_MAP = {
|
||||||
"win32-x64": "clash.meta-win-amd64",
|
"win32-x64": "mihomo-windows-amd64",
|
||||||
"darwin-x64": "clash.meta-darwin-amd64",
|
"win32-ia32": "mihomo-windows-386",
|
||||||
"darwin-arm64": "clash.meta-darwin-arm64",
|
"win32-arm64": "mihomo-windows-arm64",
|
||||||
"linux-x64": "clash.meta-linux-amd64",
|
"darwin-x64": "mihomo-darwin-amd64",
|
||||||
"linux-arm64": "clash.meta-linux-arm64",
|
"darwin-arm64": "mihomo-darwin-arm64",
|
||||||
|
"linux-x64": "mihomo-linux-amd64",
|
||||||
|
"linux-arm64": "mihomo-linux-arm64",
|
||||||
|
"linux-arm": "mihomo-linux-armv7",
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check available
|
* check available
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { platform, arch } = process;
|
|
||||||
/*
|
|
||||||
if (!CLASH_MAP[`${platform}-${arch}`]) {
|
|
||||||
throw new Error(`clash unsupported platform "${platform}-${arch}"`);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (!META_MAP[`${platform}-${arch}`]) {
|
if (!META_MAP[`${platform}-${arch}`]) {
|
||||||
throw new Error(`clash meta unsupported platform "${platform}-${arch}"`);
|
throw new Error(
|
||||||
|
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
function clash() {
|
|
||||||
const name = CLASH_MAP[`${platform}-${arch}`];
|
|
||||||
|
|
||||||
|
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||||
|
throw new Error(
|
||||||
|
`clash meta alpha unsupported platform "${platform}-${arch}"`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* core info
|
||||||
|
*/
|
||||||
|
function clashMetaAlpha() {
|
||||||
|
const name = META_ALPHA_MAP[`${platform}-${arch}`];
|
||||||
const isWin = platform === "win32";
|
const isWin = platform === "win32";
|
||||||
const urlExt = isWin ? "zip" : "gz";
|
const urlExt = isWin ? "zip" : "gz";
|
||||||
const downloadURL = `${CLASH_URL_PREFIX}${name}-${CLASH_LATEST_DATE}.${urlExt}`;
|
const downloadURL = `${META_ALPHA_URL_PREFIX}/${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
||||||
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
||||||
const zipFile = `${name}.${urlExt}`;
|
const zipFile = `${name}-${META_ALPHA_VERSION}.${urlExt}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "clash",
|
name: "clash-meta-alpha",
|
||||||
targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
targetFile: `clash-meta-alpha-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
||||||
exeFile,
|
exeFile,
|
||||||
zipFile,
|
zipFile,
|
||||||
downloadURL,
|
downloadURL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function clashBackup() {
|
|
||||||
const name = CLASH_MAP[`${platform}-${arch}`];
|
|
||||||
|
|
||||||
const isWin = platform === "win32";
|
|
||||||
const urlExt = isWin ? "zip" : "gz";
|
|
||||||
const downloadURL = `${CLASH_BACKUP_URL_PREFIX}${CLASH_BACKUP_LATEST_DATE}/${name}-n${CLASH_BACKUP_LATEST_DATE}.${urlExt}`;
|
|
||||||
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
|
||||||
const zipFile = `${name}.${urlExt}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "clash",
|
|
||||||
targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
|
||||||
exeFile,
|
|
||||||
zipFile,
|
|
||||||
downloadURL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function clashS3() {
|
|
||||||
const name = CLASH_MAP[`${platform}-${arch}`];
|
|
||||||
|
|
||||||
const isWin = platform === "win32";
|
|
||||||
const urlExt = isWin ? "zip" : "gz";
|
|
||||||
const downloadURL = `${CLASH_STORAGE_PREFIX}${CLASH_LATEST_DATE}/${name}-${CLASH_LATEST_DATE}.${urlExt}`;
|
|
||||||
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
|
||||||
const zipFile = `${name}.${urlExt}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "clash",
|
|
||||||
targetFile: `clash-${SIDECAR_HOST}${isWin ? ".exe" : ""}`,
|
|
||||||
exeFile,
|
|
||||||
zipFile,
|
|
||||||
downloadURL,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
function clashMeta() {
|
function clashMeta() {
|
||||||
const name = META_MAP[`${platform}-${arch}`];
|
const name = META_MAP[`${platform}-${arch}`];
|
||||||
const isWin = platform === "win32";
|
const isWin = platform === "win32";
|
||||||
/* const urlExt = isWin ? "zip" : "gz";
|
const urlExt = isWin ? "zip" : "gz";
|
||||||
const downloadURL = `${META_URL_PREFIX}${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
|
const downloadURL = `${META_URL_PREFIX}/${META_VERSION}/${name}-${META_VERSION}.${urlExt}`;
|
||||||
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
const exeFile = `${name}${isWin ? ".exe" : ""}`;
|
||||||
const zipFile = `${name}-${META_VERSION}.${urlExt}`; */
|
const zipFile = `${name}-${META_VERSION}.${urlExt}`;
|
||||||
const urlExt = isWin ? "zip" : "tgz";
|
|
||||||
const downloadURL = `${META_URL_PREFIX}/${name}.${urlExt}`;
|
|
||||||
const exeFile = isWin ? "虚空终端-win-amd64.exe" : name;
|
|
||||||
const zipFile = `${name}.${urlExt}`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "clash-meta",
|
name: "clash-meta",
|
||||||
@@ -136,7 +141,6 @@ function clashMeta() {
|
|||||||
downloadURL,
|
downloadURL,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* download sidecar and rename
|
* download sidecar and rename
|
||||||
*/
|
*/
|
||||||
@@ -218,58 +222,6 @@ async function resolveSidecar(binInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* prepare clash core
|
|
||||||
* if the core version is not updated in time, use S3 storage as a backup.
|
|
||||||
*/
|
|
||||||
async function resolveClash() {
|
|
||||||
try {
|
|
||||||
return await resolveSidecar(clash());
|
|
||||||
} catch {
|
|
||||||
console.log(`[WARN]: clash core needs to be updated`);
|
|
||||||
return await resolveSidecar(clashS3());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* only Windows
|
|
||||||
* get the wintun.dll (not required)
|
|
||||||
|
|
||||||
async function resolveWintun() {
|
|
||||||
const { platform } = process;
|
|
||||||
|
|
||||||
if (platform !== "win32") return;
|
|
||||||
|
|
||||||
const url = "https://www.wintun.net/builds/wintun-0.14.1.zip";
|
|
||||||
|
|
||||||
const tempDir = path.join(TEMP_DIR, "wintun");
|
|
||||||
const tempZip = path.join(tempDir, "wintun.zip");
|
|
||||||
|
|
||||||
const wintunPath = path.join(tempDir, "wintun/bin/amd64/wintun.dll");
|
|
||||||
const targetPath = path.join(cwd, "src-tauri/resources", "wintun.dll");
|
|
||||||
|
|
||||||
if (!FORCE && (await fs.pathExists(targetPath))) return;
|
|
||||||
|
|
||||||
await fs.mkdirp(tempDir);
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(tempZip))) {
|
|
||||||
await downloadFile(url, tempZip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unzip
|
|
||||||
const zip = new AdmZip(tempZip);
|
|
||||||
zip.extractAllTo(tempDir, true);
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(wintunPath))) {
|
|
||||||
throw new Error(`path not found "${wintunPath}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.rename(wintunPath, targetPath);
|
|
||||||
await fs.remove(tempDir);
|
|
||||||
|
|
||||||
console.log(`[INFO]: resolve wintun.dll finished`);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* download the file to the resources dir
|
* download the file to the resources dir
|
||||||
*/
|
*/
|
||||||
@@ -358,7 +310,16 @@ const resolveEnableLoopback = () =>
|
|||||||
|
|
||||||
const tasks = [
|
const tasks = [
|
||||||
// { name: "clash", func: resolveClash, retry: 5 },
|
// { name: "clash", func: resolveClash, retry: 5 },
|
||||||
{ name: "clash-meta", func: () => resolveSidecar(clashMeta()), retry: 5 },
|
{
|
||||||
|
name: "clash-meta-alpha",
|
||||||
|
func: () => getLatestVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||||
|
retry: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clash-meta",
|
||||||
|
func: () => resolveSidecar(clashMeta()),
|
||||||
|
retry: 5,
|
||||||
|
},
|
||||||
// { name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
|
// { name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
|
||||||
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
{ name: "service", func: resolveService, retry: 5, winOnly: true },
|
||||||
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ async function resolveUpdater() {
|
|||||||
"darwin-intel": { signature: "", url: "" },
|
"darwin-intel": { signature: "", url: "" },
|
||||||
"darwin-x86_64": { signature: "", url: "" },
|
"darwin-x86_64": { signature: "", url: "" },
|
||||||
"linux-x86_64": { signature: "", url: "" },
|
"linux-x86_64": { signature: "", url: "" },
|
||||||
|
"linux-aarch64": { signature: "", url: "" },
|
||||||
"windows-x86_64": { signature: "", url: "" },
|
"windows-x86_64": { signature: "", url: "" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -53,17 +54,43 @@ async function resolveUpdater() {
|
|||||||
const { name, browser_download_url } = asset;
|
const { name, browser_download_url } = asset;
|
||||||
|
|
||||||
// win64 url
|
// win64 url
|
||||||
if (name.endsWith(".msi.zip") && name.includes("en-US")) {
|
if (
|
||||||
|
name.endsWith(".msi.zip") &&
|
||||||
|
name.includes("en-US") &&
|
||||||
|
name.includes("x64")
|
||||||
|
) {
|
||||||
updateData.platforms.win64.url = browser_download_url;
|
updateData.platforms.win64.url = browser_download_url;
|
||||||
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
updateData.platforms["windows-x86_64"].url = browser_download_url;
|
||||||
}
|
}
|
||||||
// win64 signature
|
// win64 signature
|
||||||
if (name.endsWith(".msi.zip.sig") && name.includes("en-US")) {
|
if (
|
||||||
|
name.endsWith(".msi.zip.sig") &&
|
||||||
|
name.includes("en-US") &&
|
||||||
|
name.includes("x64")
|
||||||
|
) {
|
||||||
const sig = await getSignature(browser_download_url);
|
const sig = await getSignature(browser_download_url);
|
||||||
updateData.platforms.win64.signature = sig;
|
updateData.platforms.win64.signature = sig;
|
||||||
updateData.platforms["windows-x86_64"].signature = sig;
|
updateData.platforms["windows-x86_64"].signature = sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// win32 url
|
||||||
|
if (
|
||||||
|
name.endsWith(".msi.zip") &&
|
||||||
|
name.includes("en-US") &&
|
||||||
|
name.includes("x86")
|
||||||
|
) {
|
||||||
|
updateData.platforms["windows-i686"].url = browser_download_url;
|
||||||
|
}
|
||||||
|
// win32 signature
|
||||||
|
if (
|
||||||
|
name.endsWith(".msi.zip.sig") &&
|
||||||
|
name.includes("en-US") &&
|
||||||
|
name.includes("x86")
|
||||||
|
) {
|
||||||
|
const sig = await getSignature(browser_download_url);
|
||||||
|
updateData.platforms["windows-i686"].signature = sig;
|
||||||
|
}
|
||||||
|
|
||||||
// darwin url (intel)
|
// darwin url (intel)
|
||||||
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
|
if (name.endsWith(".app.tar.gz") && !name.includes("aarch")) {
|
||||||
updateData.platforms.darwin.url = browser_download_url;
|
updateData.platforms.darwin.url = browser_download_url;
|
||||||
@@ -92,12 +119,18 @@ async function resolveUpdater() {
|
|||||||
if (name.endsWith(".AppImage.tar.gz")) {
|
if (name.endsWith(".AppImage.tar.gz")) {
|
||||||
updateData.platforms.linux.url = browser_download_url;
|
updateData.platforms.linux.url = browser_download_url;
|
||||||
updateData.platforms["linux-x86_64"].url = browser_download_url;
|
updateData.platforms["linux-x86_64"].url = browser_download_url;
|
||||||
|
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||||
|
updateData.platforms["linux-aarch64"].url = browser_download_url;
|
||||||
|
// updateData.platforms["linux-armv7"].url = browser_download_url;
|
||||||
}
|
}
|
||||||
// linux signature
|
// linux signature
|
||||||
if (name.endsWith(".AppImage.tar.gz.sig")) {
|
if (name.endsWith(".AppImage.tar.gz.sig")) {
|
||||||
const sig = await getSignature(browser_download_url);
|
const sig = await getSignature(browser_download_url);
|
||||||
updateData.platforms.linux.signature = sig;
|
updateData.platforms.linux.signature = sig;
|
||||||
updateData.platforms["linux-x86_64"].signature = sig;
|
updateData.platforms["linux-x86_64"].signature = sig;
|
||||||
|
// 暂时使用x64版本的url和sig,使得可以检查更新,但aarch64版本还不支持构建appimage
|
||||||
|
updateData.platforms["linux-aarch64"].signature = sig;
|
||||||
|
// updateData.platforms["linux-armv7"].signature = sig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
515
src-tauri/Cargo.lock
generated
515
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "clash-verge"
|
name = "clash-verge"
|
||||||
version = "1.4.1"
|
version = "1.4.2"
|
||||||
description = "clash verge"
|
description = "clash verge"
|
||||||
authors = ["zzzgydi"]
|
authors = ["zzzgydi"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
repository = "https://github.com/wonfen/clash-verge-rev.git"
|
repository = "https://github.com/clash-verge-rev/clash-verge-rev.git"
|
||||||
default-run = "clash-verge"
|
default-run = "clash-verge"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
@@ -14,40 +14,39 @@ tauri-build = { version = "1", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.3"
|
warp = "0.3"
|
||||||
which = "4.2.2"
|
which = "5.0.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
dirs = "5.0.0"
|
dirs = "5.0"
|
||||||
open = "4.0.1"
|
open = "5.0"
|
||||||
log = "0.4.14"
|
log = "0.4"
|
||||||
ctrlc = "3.2.3"
|
ctrlc = "3.4"
|
||||||
dunce = "1.0.2"
|
dunce = "1.0"
|
||||||
log4rs = "1.0.0"
|
log4rs = "1"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4"
|
||||||
sysinfo = "0.29"
|
sysinfo = "0.29"
|
||||||
sysproxy = "0.3"
|
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
|
||||||
rquickjs = "0.1.7"
|
rquickjs = "0.3"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
auto-launch = "0.5"
|
auto-launch = "0.5"
|
||||||
once_cell = "1.14.0"
|
once_cell = "1.18"
|
||||||
port_scanner = "0.1.5"
|
port_scanner = "0.1.5"
|
||||||
delay_timer = "0.11.1"
|
delay_timer = "0.11"
|
||||||
parking_lot = "0.12.0"
|
parking_lot = "0.12"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
|
||||||
tauri = { version = "1.2.4", features = ["global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
tauri = { version = "1.5", features = ["clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
|
||||||
window-vibrancy = { version = "0.3.0" }
|
window-vibrancy = { version = "0.4.3" }
|
||||||
window-shadows = { version = "0.2.0" }
|
window-shadows = { version = "0.2" }
|
||||||
wry = { version = "0.24.3" }
|
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.0.0"
|
runas = "=1.1.0"
|
||||||
deelevate = "0.2.0"
|
deelevate = "0.2.0"
|
||||||
winreg = { version = "0.50", features = ["transactions"] }
|
winreg = { version = "0.52", features = ["transactions"] }
|
||||||
windows-sys = { version = "0.48", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] }
|
windows-sys = { version = "0.52", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.tauri]
|
[target.'cfg(windows)'.dependencies.tauri]
|
||||||
features = ["global-shortcut-all", "icon-png", "process-all", "shell-all", "system-tray", "updater", "window-all"]
|
features = ["global-shortcut-all", "icon-png", "process-all", "shell-all", "system-tray", "updater", "window-all"]
|
||||||
|
|||||||
BIN
src-tauri/icons/win-tray-icon-tun.png
Normal file
BIN
src-tauri/icons/win-tray-icon-tun.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -30,6 +30,11 @@ pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult
|
|||||||
wrap_err!(Config::profiles().data().append_item(item))
|
wrap_err!(Config::profiles().data().append_item(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||||
|
wrap_err!(Config::profiles().data().reorder(active_id, over_id))
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResult {
|
||||||
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
let item = wrap_err!(PrfItem::from(item, file_data).await)?;
|
||||||
@@ -229,7 +234,6 @@ pub fn open_web_url(url: String) -> CmdResult<()> {
|
|||||||
wrap_err!(open::that(url))
|
wrap_err!(open::that(url))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub mod uwp {
|
pub mod uwp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -194,7 +194,10 @@ impl PrfItem {
|
|||||||
|
|
||||||
// 使用软件自己的代理
|
// 使用软件自己的代理
|
||||||
if self_proxy {
|
if self_proxy {
|
||||||
let port = Config::clash().data().get_mixed_port();
|
let port = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
|
|
||||||
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
let proxy_scheme = format!("http://127.0.0.1:{port}");
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,12 @@ impl IProfiles {
|
|||||||
|
|
||||||
pub fn template() -> Self {
|
pub fn template() -> Self {
|
||||||
Self {
|
Self {
|
||||||
valid: Some(vec!["dns".into(), "sub-rules".into(), "unified-delay".into(), "tcp-concurrent".into()]),
|
valid: Some(vec![
|
||||||
|
"dns".into(),
|
||||||
|
"sub-rules".into(),
|
||||||
|
"unified-delay".into(),
|
||||||
|
"tcp-concurrent".into(),
|
||||||
|
]),
|
||||||
items: Some(vec![]),
|
items: Some(vec![]),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
@@ -151,6 +156,30 @@ impl IProfiles {
|
|||||||
self.save_file()
|
self.save_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reorder items
|
||||||
|
pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
|
||||||
|
let mut items = self.items.take().unwrap_or(vec![]);
|
||||||
|
let mut old_index = None;
|
||||||
|
let mut new_index = None;
|
||||||
|
|
||||||
|
for i in 0..items.len() {
|
||||||
|
if items[i].uid == Some(active_id.clone()) {
|
||||||
|
old_index = Some(i);
|
||||||
|
}
|
||||||
|
if items[i].uid == Some(over_id.clone()) {
|
||||||
|
new_index = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if old_index.is_none() || new_index.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let item = items.remove(old_index.unwrap());
|
||||||
|
items.insert(new_index.unwrap(), item);
|
||||||
|
self.items = Some(items);
|
||||||
|
self.save_file()
|
||||||
|
}
|
||||||
|
|
||||||
/// update the item value
|
/// update the item value
|
||||||
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
|
||||||
let mut items = self.items.take().unwrap_or(vec![]);
|
let mut items = self.items.take().unwrap_or(vec![]);
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ pub struct IVerge {
|
|||||||
/// window size and position
|
/// window size and position
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub window_size_position: Option<Vec<f64>>,
|
pub window_size_position: Option<Vec<f64>>,
|
||||||
|
|
||||||
|
/// 是否启用随机端口
|
||||||
|
pub enable_random_port: Option<bool>,
|
||||||
|
|
||||||
|
/// verge mixed port 用于覆盖 clash 的 mixed port
|
||||||
|
pub verge_mixed_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -139,6 +145,8 @@ impl IVerge {
|
|||||||
enable_auto_launch: Some(false),
|
enable_auto_launch: Some(false),
|
||||||
enable_silent_start: Some(false),
|
enable_silent_start: Some(false),
|
||||||
enable_system_proxy: Some(false),
|
enable_system_proxy: Some(false),
|
||||||
|
enable_random_port: Some(false),
|
||||||
|
verge_mixed_port: Some(7890),
|
||||||
enable_proxy_guard: Some(false),
|
enable_proxy_guard: Some(false),
|
||||||
proxy_guard_duration: Some(30),
|
proxy_guard_duration: Some(30),
|
||||||
auto_close_connection: Some(true),
|
auto_close_connection: Some(true),
|
||||||
@@ -177,6 +185,8 @@ impl IVerge {
|
|||||||
patch!(enable_service_mode);
|
patch!(enable_service_mode);
|
||||||
patch!(enable_auto_launch);
|
patch!(enable_auto_launch);
|
||||||
patch!(enable_silent_start);
|
patch!(enable_silent_start);
|
||||||
|
patch!(enable_random_port);
|
||||||
|
patch!(verge_mixed_port);
|
||||||
patch!(enable_system_proxy);
|
patch!(enable_system_proxy);
|
||||||
patch!(enable_proxy_guard);
|
patch!(enable_proxy_guard);
|
||||||
patch!(system_proxy_bypass);
|
patch!(system_proxy_bypass);
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ impl CoreManager {
|
|||||||
// fix #212
|
// fix #212
|
||||||
let args = match clash_core.as_str() {
|
let args = match clash_core.as_str() {
|
||||||
"clash-meta" => vec!["-m", "-d", app_dir, "-f", config_path],
|
"clash-meta" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||||
|
"clash-meta-alpha" => vec!["-m", "-d", app_dir, "-f", config_path],
|
||||||
_ => vec!["-d", app_dir, "-f", config_path],
|
_ => vec!["-d", app_dir, "-f", config_path],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -258,8 +259,9 @@ impl CoreManager {
|
|||||||
/// 切换核心
|
/// 切换核心
|
||||||
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
pub async fn change_core(&self, clash_core: Option<String>) -> Result<()> {
|
||||||
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?;
|
||||||
|
const CLASH_CORES: [&str; 3] = ["clash", "clash-meta", "clash-meta-alpha"];
|
||||||
|
|
||||||
if &clash_core != "clash" && &clash_core != "clash-meta" {
|
if !CLASH_CORES.contains(&clash_core.as_str()) {
|
||||||
bail!("invalid clash core name \"{clash_core}\"");
|
bail!("invalid clash core name \"{clash_core}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use once_cell::sync::OnceCell;
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
use tauri::{AppHandle, GlobalShortcutManager};
|
use tauri::{AppHandle, GlobalShortcutManager};
|
||||||
use wry::application::accelerator::Accelerator;
|
|
||||||
|
|
||||||
pub struct Hotkey {
|
pub struct Hotkey {
|
||||||
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
|
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
|
||||||
@@ -35,7 +34,7 @@ impl Hotkey {
|
|||||||
|
|
||||||
match (key, func) {
|
match (key, func) {
|
||||||
(Some(key), Some(func)) => {
|
(Some(key), Some(func)) => {
|
||||||
log_err!(Self::check_key(key).and_then(|_| self.register(key, func)));
|
log_err!(self.register(key, func));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let key = key.unwrap_or("None");
|
let key = key.unwrap_or("None");
|
||||||
@@ -50,16 +49,6 @@ impl Hotkey {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查一个键是否合法
|
|
||||||
fn check_key(hotkey: &str) -> Result<()> {
|
|
||||||
// fix #287
|
|
||||||
// tauri的这几个方法全部有Result expect,会panic,先检测一遍避免挂了
|
|
||||||
if hotkey.parse::<Accelerator>().is_err() {
|
|
||||||
bail!("invalid hotkey `{hotkey}`");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
|
fn get_manager(&self) -> Result<impl GlobalShortcutManager> {
|
||||||
let app_handle = self.app_handle.lock();
|
let app_handle = self.app_handle.lock();
|
||||||
if app_handle.is_none() {
|
if app_handle.is_none() {
|
||||||
@@ -109,11 +98,6 @@ impl Hotkey {
|
|||||||
|
|
||||||
let (del, add) = Self::get_diff(old_map, new_map);
|
let (del, add) = Self::get_diff(old_map, new_map);
|
||||||
|
|
||||||
// 先检查一遍所有新的热键是不是可以用的
|
|
||||||
for (hotkey, _) in add.iter() {
|
|
||||||
Self::check_key(hotkey)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
del.iter().for_each(|key| {
|
del.iter().for_each(|key| {
|
||||||
let _ = self.unregister(key);
|
let _ = self.unregister(key);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;<local>";
|
|||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
|
static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1";
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
static DEFAULT_BYPASS: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
static DEFAULT_BYPASS: &str =
|
||||||
|
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
||||||
|
|
||||||
impl Sysopt {
|
impl Sysopt {
|
||||||
pub fn global() -> &'static Sysopt {
|
pub fn global() -> &'static Sysopt {
|
||||||
@@ -43,7 +44,10 @@ impl Sysopt {
|
|||||||
|
|
||||||
/// init the sysproxy
|
/// init the sysproxy
|
||||||
pub fn init_sysproxy(&self) -> Result<()> {
|
pub fn init_sysproxy(&self) -> Result<()> {
|
||||||
let port = { Config::clash().latest().get_mixed_port() };
|
let port = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
|
|
||||||
let (enable, bypass) = {
|
let (enable, bypass) = {
|
||||||
let verge = Config::verge();
|
let verge = Config::verge();
|
||||||
@@ -284,7 +288,12 @@ impl Sysopt {
|
|||||||
|
|
||||||
log::debug!(target: "app", "try to guard the system proxy");
|
log::debug!(target: "app", "try to guard the system proxy");
|
||||||
|
|
||||||
let port = { Config::clash().latest().get_mixed_port() };
|
let port = {
|
||||||
|
Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port())
|
||||||
|
};
|
||||||
|
|
||||||
let sysproxy = Sysproxy {
|
let sysproxy = Sysproxy {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
|||||||
@@ -145,12 +145,16 @@ impl Tray {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
let indication_icon = if *system_proxy {
|
let mut indication_icon = if *system_proxy {
|
||||||
include_bytes!("../../icons/win-tray-icon-activated.png").to_vec()
|
include_bytes!("../../icons/win-tray-icon-activated.png").to_vec()
|
||||||
} else {
|
} else {
|
||||||
include_bytes!("../../icons/win-tray-icon.png").to_vec()
|
include_bytes!("../../icons/win-tray-icon.png").to_vec()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if *tun_mode {
|
||||||
|
indication_icon = include_bytes!("../../icons/win-tray-icon-tun.png").to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
let _ = tray.set_icon(tauri::Icon::Raw(indication_icon));
|
let _ = tray.set_icon(tauri::Icon::Raw(indication_icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +186,8 @@ impl Tray {
|
|||||||
match tray_event.as_str() {
|
match tray_event.as_str() {
|
||||||
"system_proxy" => feat::toggle_system_proxy(),
|
"system_proxy" => feat::toggle_system_proxy(),
|
||||||
"tun_mode" => feat::toggle_tun_mode(),
|
"tun_mode" => feat::toggle_tun_mode(),
|
||||||
_ => resolve::create_window(app_handle),
|
"main_window" => resolve::create_window(app_handle),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,11 +204,11 @@ impl Tray {
|
|||||||
"open_window" => resolve::create_window(app_handle),
|
"open_window" => resolve::create_window(app_handle),
|
||||||
"system_proxy" => feat::toggle_system_proxy(),
|
"system_proxy" => feat::toggle_system_proxy(),
|
||||||
"tun_mode" => feat::toggle_tun_mode(),
|
"tun_mode" => feat::toggle_tun_mode(),
|
||||||
"copy_env_sh" => feat::copy_clash_env("sh"),
|
"copy_env_sh" => feat::copy_clash_env(app_handle, "sh"),
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
"copy_env_cmd" => feat::copy_clash_env("cmd"),
|
"copy_env_cmd" => feat::copy_clash_env(app_handle, "cmd"),
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
"copy_env_ps" => feat::copy_clash_env("ps"),
|
"copy_env_ps" => feat::copy_clash_env(app_handle, "ps"),
|
||||||
"open_app_dir" => crate::log_err!(cmds::open_app_dir()),
|
"open_app_dir" => crate::log_err!(cmds::open_app_dir()),
|
||||||
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
|
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
|
||||||
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ pub enum ChainType {
|
|||||||
pub enum ChainSupport {
|
pub enum ChainSupport {
|
||||||
Clash,
|
Clash,
|
||||||
ClashMeta,
|
ClashMeta,
|
||||||
|
ClashMetaAlpha,
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +61,19 @@ impl ChainItem {
|
|||||||
let hy_alpn =
|
let hy_alpn =
|
||||||
ChainItem::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
|
ChainItem::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
|
||||||
|
|
||||||
|
// meta 的一些处理
|
||||||
|
let meta_guard_alpha =
|
||||||
|
ChainItem::to_script("verge_meta_guard", include_str!("./builtin/meta_guard.js"));
|
||||||
|
|
||||||
|
// meta 1.13.2 alpn string 转 数组
|
||||||
|
let hy_alpn_alpha =
|
||||||
|
ChainItem::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
(ChainSupport::ClashMeta, hy_alpn),
|
(ChainSupport::ClashMeta, hy_alpn),
|
||||||
(ChainSupport::ClashMeta, meta_guard),
|
(ChainSupport::ClashMeta, meta_guard),
|
||||||
|
(ChainSupport::ClashMetaAlpha, hy_alpn_alpha),
|
||||||
|
(ChainSupport::ClashMetaAlpha, meta_guard_alpha),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +92,7 @@ impl ChainSupport {
|
|||||||
(ChainSupport::All, _) => true,
|
(ChainSupport::All, _) => true,
|
||||||
(ChainSupport::Clash, "clash") => true,
|
(ChainSupport::Clash, "clash") => true,
|
||||||
(ChainSupport::ClashMeta, "clash-meta") => true,
|
(ChainSupport::ClashMeta, "clash-meta") => true,
|
||||||
|
(ChainSupport::ClashMetaAlpha, "clash-meta-alpha") => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
None => true,
|
None => true,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub const DEFAULT_FIELDS: [&str; 5] = [
|
|||||||
"rule-providers",
|
"rule-providers",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const OTHERS_FIELDS: [&str; 30] = [
|
pub const OTHERS_FIELDS: [&str; 31] = [
|
||||||
"dns",
|
"dns",
|
||||||
"tun",
|
"tun",
|
||||||
"ebpf",
|
"ebpf",
|
||||||
@@ -50,6 +50,7 @@ pub const OTHERS_FIELDS: [&str; 30] = [
|
|||||||
"tcp-concurrent", // meta
|
"tcp-concurrent", // meta
|
||||||
"enable-process", // meta
|
"enable-process", // meta
|
||||||
"find-process-mode", // meta
|
"find-process-mode", // meta
|
||||||
|
"skip-auth-prefixes", // meta
|
||||||
"external-controller-tls", // meta
|
"external-controller-tls", // meta
|
||||||
"global-client-fingerprint", // meta
|
"global-client-fingerprint", // meta
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use anyhow::Result;
|
|||||||
use serde_yaml::Mapping;
|
use serde_yaml::Mapping;
|
||||||
|
|
||||||
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(String, String)>)> {
|
||||||
use rquickjs::{Context, Func, Runtime};
|
use rquickjs::{function::Func, Context, Runtime};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
let runtime = Runtime::new().unwrap();
|
let runtime = Runtime::new().unwrap();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use crate::log_err;
|
|||||||
use crate::utils::resolve;
|
use crate::utils::resolve;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use wry::application::clipboard::Clipboard;
|
use tauri::{AppHandle, ClipboardManager};
|
||||||
|
|
||||||
// 打开面板
|
// 打开面板
|
||||||
pub fn open_dashboard() {
|
pub fn open_dashboard() {
|
||||||
@@ -162,8 +162,13 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
|||||||
|
|
||||||
match {
|
match {
|
||||||
let mixed_port = patch.get("mixed-port");
|
let mixed_port = patch.get("mixed-port");
|
||||||
if mixed_port.is_some() {
|
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
||||||
let changed = mixed_port != Config::clash().data().0.get("mixed-port");
|
if mixed_port.is_some() && !enable_random_port {
|
||||||
|
let changed = mixed_port.clone().unwrap()
|
||||||
|
!= Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
// 检查端口占用
|
// 检查端口占用
|
||||||
if changed {
|
if changed {
|
||||||
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
|
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
|
||||||
@@ -332,21 +337,21 @@ async fn update_core_config() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// copy env variable
|
/// copy env variable
|
||||||
pub fn copy_clash_env(option: &str) {
|
pub fn copy_clash_env(app_handle: &AppHandle, option: &str) {
|
||||||
let port = { Config::clash().data().get_client_info().port };
|
let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7890) };
|
||||||
let http_proxy = format!("http://127.0.0.1:{}", port);
|
let http_proxy = format!("http://127.0.0.1:{}", port);
|
||||||
let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
|
let socks5_proxy = format!("socks5://127.0.0.1:{}", port);
|
||||||
|
|
||||||
let sh = format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}");
|
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} \n set https_proxy={http_proxy}");
|
||||||
let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env: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();
|
||||||
let mut cliboard = Clipboard::new();
|
|
||||||
|
|
||||||
match option {
|
match option {
|
||||||
"sh" => cliboard.write_text(sh),
|
"sh" => cliboard.write_text(sh).unwrap_or_default(),
|
||||||
"cmd" => cliboard.write_text(cmd),
|
"cmd" => cliboard.write_text(cmd).unwrap_or_default(),
|
||||||
"ps" => cliboard.write_text(ps),
|
"ps" => cliboard.write_text(ps).unwrap_or_default(),
|
||||||
_ => log::error!(target: "app", "copy_clash_env: Invalid option! {option}"),
|
_ => log::error!(target: "app", "copy_clash_env: Invalid option! {option}"),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
cmds::patch_profile,
|
cmds::patch_profile,
|
||||||
cmds::create_profile,
|
cmds::create_profile,
|
||||||
cmds::import_profile,
|
cmds::import_profile,
|
||||||
|
cmds::reorder_profile,
|
||||||
cmds::update_profile,
|
cmds::update_profile,
|
||||||
cmds::delete_profile,
|
cmds::delete_profile,
|
||||||
cmds::read_profile_file,
|
cmds::read_profile_file,
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
|
use crate::config::IVerge;
|
||||||
use crate::{config::Config, core::*, utils::init, utils::server};
|
use crate::{config::Config, core::*, utils::init, utils::server};
|
||||||
use crate::{log_err, trace_err};
|
use crate::{log_err, trace_err};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use serde_yaml::Mapping;
|
||||||
|
use std::net::TcpListener;
|
||||||
use tauri::{App, AppHandle, Manager};
|
use tauri::{App, AppHandle, Manager};
|
||||||
|
|
||||||
|
pub fn find_unused_port() -> Result<u16> {
|
||||||
|
match TcpListener::bind("127.0.0.1:0") {
|
||||||
|
Ok(listener) => {
|
||||||
|
let port = listener.local_addr()?.port();
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let port = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
|
log::warn!(target: "app", "use default port: {}", port);
|
||||||
|
Ok(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// handle something when start app
|
/// handle something when start app
|
||||||
pub fn resolve_setup(app: &mut App) {
|
pub fn resolve_setup(app: &mut App) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -12,6 +32,33 @@ pub fn resolve_setup(app: &mut App) {
|
|||||||
|
|
||||||
log_err!(init::init_resources(app.package_info()));
|
log_err!(init::init_resources(app.package_info()));
|
||||||
|
|
||||||
|
// 处理随机端口
|
||||||
|
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
|
||||||
|
|
||||||
|
let mut port = Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port());
|
||||||
|
|
||||||
|
if enable_random_port {
|
||||||
|
port = find_unused_port().unwrap_or(
|
||||||
|
Config::verge()
|
||||||
|
.latest()
|
||||||
|
.verge_mixed_port
|
||||||
|
.unwrap_or(Config::clash().data().get_mixed_port()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::verge().data().patch_config(IVerge {
|
||||||
|
verge_mixed_port: Some(port),
|
||||||
|
..IVerge::default()
|
||||||
|
});
|
||||||
|
let _ = Config::verge().data().save_file();
|
||||||
|
let mut mapping = Mapping::new();
|
||||||
|
mapping.insert("mixed-port".into(), port.into());
|
||||||
|
Config::clash().data().patch_config(mapping);
|
||||||
|
let _ = Config::clash().data().save_config();
|
||||||
|
|
||||||
// 启动核心
|
// 启动核心
|
||||||
log::trace!("init config");
|
log::trace!("init config");
|
||||||
log_err!(Config::init_config());
|
log_err!(Config::init_config());
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Clash Verge",
|
"productName": "Clash Verge",
|
||||||
"version": "1.4.1"
|
"version": "1.4.2"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../dist",
|
"distDir": "../dist",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": ["resources"],
|
"resources": ["resources"],
|
||||||
"externalBin": ["sidecar/clash-meta"],
|
"externalBin": ["sidecar/clash-meta", "sidecar/clash-meta-alpha"],
|
||||||
"copyright": "© 2022 zzzgydi All Rights Reserved",
|
"copyright": "© 2022 zzzgydi All Rights Reserved",
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
"shortDescription": "A Clash GUI based on tauri.",
|
"shortDescription": "A Clash GUI based on tauri.",
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
"updater": {
|
"updater": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
"https://mirror.ghproxy.com/https://github.com/wonfen/clash-verge-rev/releases/download/updater/update-proxy.json",
|
"https://mirror.ghproxy.com/https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update-proxy.json",
|
||||||
"https://github.com/wonfen/clash-verge-rev/releases/download/updater/update.json"
|
"https://github.com/clash-verge-rev/clash-verge-rev/releases/download/updater/update.json"
|
||||||
],
|
],
|
||||||
"dialog": false,
|
"dialog": false,
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEQyOEMyRjBCQkVGOUJEREYKUldUZnZmbStDeStNMHU5Mmo1N24xQXZwSVRYbXA2NUpzZE5oVzlqeS9Bc0t6RVV4MmtwVjBZaHgK"
|
||||||
@@ -71,6 +71,9 @@
|
|||||||
},
|
},
|
||||||
"globalShortcut": {
|
"globalShortcut": {
|
||||||
"all": true
|
"all": true
|
||||||
|
},
|
||||||
|
"clipboard": {
|
||||||
|
"all": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"windows": [],
|
"windows": [],
|
||||||
|
|||||||
BIN
src/assets/fonts/Twemoji.Mozilla.ttf
Normal file
BIN
src/assets/fonts/Twemoji.Mozilla.ttf
Normal file
Binary file not shown.
@@ -1,21 +1,21 @@
|
|||||||
.page-enter {
|
.page-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.9);
|
clip-path: inset(0 100% 0 0); /* 完全隐藏内容 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-enter-active {
|
.page-enter-active {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
clip-path: inset(0 0 0 0); /* 逐渐显示整个内容 */
|
||||||
transition: opacity 300ms, transform 300ms;
|
transition: opacity 300ms, clip-path 300ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-exit {
|
.page-exit {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(0);
|
clip-path: inset(0 0 0 0); /* 完全显示内容 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-exit-active {
|
.page-exit-active {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: scale(0.9);
|
clip-path: inset(0 100% 0 0); /* 逐渐隐藏内容 */
|
||||||
transition: opacity 300ms, transform 300ms;
|
transition: opacity 300ms, clip-path 300ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|||||||
4
src/assets/styles/font.scss
Normal file
4
src/assets/styles/font.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "twemoji mozilla";
|
||||||
|
src: url("../fonts/Twemoji.Mozilla.ttf");
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ body {
|
|||||||
--scroller-color: #90939980;
|
--scroller-color: #90939980;
|
||||||
--background-color: #ffffff;
|
--background-color: #ffffff;
|
||||||
--background-color-alpha: rgba(24, 103, 192, 0.1);
|
--background-color-alpha: rgba(24, 103, 192, 0.1);
|
||||||
--border-radius: 12px;
|
--border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
@@ -43,6 +43,7 @@ body {
|
|||||||
@import "./layout.scss";
|
@import "./layout.scss";
|
||||||
@import "./page.scss";
|
@import "./page.scss";
|
||||||
@import "./anime.scss";
|
@import "./anime.scss";
|
||||||
|
@import "./font.scss";
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
.the-menu {
|
.the-menu {
|
||||||
flex: 1 1 80%;
|
flex: 1 1 80%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.the-traffic {
|
.the-traffic {
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
padding: 5px 5px;
|
padding: 5px 5px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
background-color: var(--background-color);
|
|
||||||
|
|
||||||
.base-content {
|
.base-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { Typography } from "@mui/material";
|
import { Typography, alpha } from "@mui/material";
|
||||||
import { BaseErrorBoundary } from "./base-error-boundary";
|
import { BaseErrorBoundary } from "./base-error-boundary";
|
||||||
|
import { useCustomTheme } from "@/components/layout/use-custom-theme";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title?: React.ReactNode; // the page title
|
title?: React.ReactNode; // the page title
|
||||||
@@ -11,6 +12,9 @@ interface Props {
|
|||||||
|
|
||||||
export const BasePage: React.FC<Props> = (props) => {
|
export const BasePage: React.FC<Props> = (props) => {
|
||||||
const { title, header, contentStyle, children } = props;
|
const { title, header, contentStyle, children } = props;
|
||||||
|
const { theme } = useCustomTheme();
|
||||||
|
|
||||||
|
const isDark = theme.palette.mode === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseErrorBoundary>
|
<BaseErrorBoundary>
|
||||||
@@ -23,8 +27,17 @@ export const BasePage: React.FC<Props> = (props) => {
|
|||||||
{header}
|
{header}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="base-container">
|
<div
|
||||||
<section>
|
className="base-container"
|
||||||
|
style={{ backgroundColor: isDark ? "#090909" : "#ffffff" }}
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
style={{
|
||||||
|
backgroundColor: isDark
|
||||||
|
? alpha(theme.palette.primary.main, 0.1)
|
||||||
|
: "",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="base-content" style={contentStyle} data-windrag>
|
<div className="base-content" style={contentStyle} data-windrag>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,24 +10,22 @@ export const LayoutItem = (props: LinkProps) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem sx={{ py: 0.5, maxWidth: 250, mx: "auto", padding: "1px 0px" }}>
|
||||||
sx={{ py: 0.5, maxWidth: 250, mx: "auto", padding: "4px 0px 4px 2px" }}
|
|
||||||
>
|
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
selected={!!match}
|
selected={!!match}
|
||||||
sx={[
|
sx={[
|
||||||
{
|
{
|
||||||
borderTopLeftRadius: 18,
|
borderRadius: 3,
|
||||||
borderBottomLeftRadius: 18,
|
marginLeft: 1,
|
||||||
|
marginRight: 1,
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
"& .MuiListItemText-primary": { color: "text.secondary" },
|
"& .MuiListItemText-primary": { color: "text.secondary" },
|
||||||
},
|
},
|
||||||
({ palette: { mode, primary } }) => {
|
({ palette: { mode, primary } }) => {
|
||||||
/* const bgcolor =
|
const bgcolor =
|
||||||
mode === "light"
|
mode === "light"
|
||||||
? alpha(primary.main, 0.15)
|
? alpha(primary.main, 0.15)
|
||||||
: alpha(primary.main, 0.35); */
|
: alpha(primary.main, 0.35);
|
||||||
const bgcolor = mode === "light" ? "#ffffff" : "#0E1621";
|
|
||||||
const color = mode === "light" ? primary.main : primary.light;
|
const color = mode === "light" ? primary.main : primary.light;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ export const useCustomTheme = () => {
|
|||||||
primary: setting.primary_text || dt.primary_text,
|
primary: setting.primary_text || dt.primary_text,
|
||||||
secondary: setting.secondary_text || dt.secondary_text,
|
secondary: setting.secondary_text || dt.secondary_text,
|
||||||
},
|
},
|
||||||
|
background: {
|
||||||
|
paper: dt.background_color,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
// todo
|
// todo
|
||||||
@@ -84,7 +87,7 @@ export const useCustomTheme = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// css
|
// css
|
||||||
const backgroundColor = mode === "light" ? "#ffffff" : "#0E1621";
|
const backgroundColor = mode === "light" ? "#ffffff" : "#0B121C";
|
||||||
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
|
||||||
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
const scrollColor = mode === "light" ? "#90939980" : "#54545480";
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useSortable } from "@dnd-kit/sortable";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -14,7 +16,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { RefreshRounded } from "@mui/icons-material";
|
import { RefreshRounded, DragIndicator } from "@mui/icons-material";
|
||||||
import { atomLoadingCache } from "@/services/states";
|
import { atomLoadingCache } from "@/services/states";
|
||||||
import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds";
|
import { updateProfile, deleteProfile, viewProfile } from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
import { Notice } from "@/components/base";
|
||||||
@@ -28,6 +30,7 @@ const round = keyframes`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
id: string;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
activating: boolean;
|
activating: boolean;
|
||||||
itemData: IProfileItem;
|
itemData: IProfileItem;
|
||||||
@@ -37,6 +40,8 @@ interface Props {
|
|||||||
|
|
||||||
export const ProfileItem = (props: Props) => {
|
export const ProfileItem = (props: Props) => {
|
||||||
const { selected, activating, itemData, onSelect, onEdit } = props;
|
const { selected, activating, itemData, onSelect, onEdit } = props;
|
||||||
|
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||||
|
useSortable({ id: props.id });
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [anchorEl, setAnchorEl] = useState<any>(null);
|
const [anchorEl, setAnchorEl] = useState<any>(null);
|
||||||
@@ -183,7 +188,12 @@ export const ProfileItem = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box
|
||||||
|
sx={{
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ProfileBox
|
<ProfileBox
|
||||||
aria-selected={selected}
|
aria-selected={selected}
|
||||||
onClick={() => onSelect(false)}
|
onClick={() => onSelect(false)}
|
||||||
@@ -212,17 +222,27 @@ export const ProfileItem = (props: Props) => {
|
|||||||
<CircularProgress size={20} />
|
<CircularProgress size={20} />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box position="relative">
|
<Box position="relative">
|
||||||
<Typography
|
<Box sx={{ display: "flex", justifyContent: "start" }}>
|
||||||
width="calc(100% - 36px)"
|
<Box
|
||||||
variant="h6"
|
ref={setNodeRef}
|
||||||
component="h2"
|
sx={{ display: "flex", margin: "auto 0" }}
|
||||||
noWrap
|
{...attributes}
|
||||||
title={name}
|
{...listeners}
|
||||||
>
|
>
|
||||||
{name}
|
<DragIndicator sx={{ cursor: "grab" }} />
|
||||||
</Typography>
|
</Box>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
width="calc(100% - 36px)"
|
||||||
|
variant="h6"
|
||||||
|
component="h2"
|
||||||
|
noWrap
|
||||||
|
title={name}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* only if has url can it be updated */}
|
{/* only if has url can it be updated */}
|
||||||
{hasUrl && (
|
{hasUrl && (
|
||||||
@@ -246,7 +266,6 @@ export const ProfileItem = (props: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* the second line show url's info or description */}
|
{/* the second line show url's info or description */}
|
||||||
<Box sx={boxStyle}>
|
<Box sx={boxStyle}>
|
||||||
{hasUrl ? (
|
{hasUrl ? (
|
||||||
@@ -271,7 +290,6 @@ export const ProfileItem = (props: Props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* the third line show extra info or last updated time */}
|
{/* the third line show extra info or last updated time */}
|
||||||
{hasExtra ? (
|
{hasExtra ? (
|
||||||
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
<Box sx={{ ...boxStyle, fontSize: 14 }}>
|
||||||
@@ -285,7 +303,6 @@ export const ProfileItem = (props: Props) => {
|
|||||||
<span title="Updated Time">{parseExpire(updated)}</span>
|
<span title="Updated Time">{parseExpire(updated)}</span>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="determinate"
|
variant="determinate"
|
||||||
value={progress}
|
value={progress}
|
||||||
@@ -324,7 +341,7 @@ export const ProfileItem = (props: Props) => {
|
|||||||
mode="yaml"
|
mode="yaml"
|
||||||
onClose={() => setFileOpen(false)}
|
onClose={() => setFileOpen(false)}
|
||||||
/>
|
/>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Lock } from "@mui/icons-material";
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
Tooltip,
|
||||||
List,
|
List,
|
||||||
ListItemButton,
|
ListItemButton,
|
||||||
ListItemText,
|
ListItemText,
|
||||||
@@ -18,11 +18,10 @@ import { closeAllConnections } from "@/services/api";
|
|||||||
import { grantPermission } from "@/services/cmds";
|
import { grantPermission } from "@/services/cmds";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
|
||||||
/* const VALID_CORE = [
|
const VALID_CORE = [
|
||||||
{ name: "Clash", core: "clash" },
|
|
||||||
{ name: "Clash Meta", core: "clash-meta" },
|
{ name: "Clash Meta", core: "clash-meta" },
|
||||||
]; */
|
{ name: "Clash Meta Alpha", core: "clash-meta-alpha" },
|
||||||
const VALID_CORE = [{ name: "Clash Meta", core: "clash-meta" }];
|
];
|
||||||
|
|
||||||
const OS = getSystem();
|
const OS = getSystem();
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { clash_core = "clash" } = verge ?? {};
|
const { clash_core = "clash-meta" } = verge ?? {};
|
||||||
|
|
||||||
const onCoreChange = useLockFn(async (core: string) => {
|
const onCoreChange = useLockFn(async (core: string) => {
|
||||||
if (core === clash_core) return;
|
if (core === clash_core) return;
|
||||||
@@ -92,7 +91,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
contentSx={{
|
contentSx={{
|
||||||
pb: 0,
|
pb: 0,
|
||||||
width: 320,
|
width: 320,
|
||||||
height: 90,
|
height: 180,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
userSelect: "text",
|
userSelect: "text",
|
||||||
marginTop: "-8px",
|
marginTop: "-8px",
|
||||||
@@ -112,30 +111,19 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<ListItemText primary={each.name} secondary={`/${each.core}`} />
|
<ListItemText primary={each.name} secondary={`/${each.core}`} />
|
||||||
|
|
||||||
{(OS === "macos" || OS === "linux") && (
|
{(OS === "macos" || OS === "linux") && (
|
||||||
/* <IconButton
|
<Tooltip title={t("Tun mode requires")}>
|
||||||
color="inherit"
|
<Button
|
||||||
size="small"
|
variant="outlined"
|
||||||
edge="end"
|
size="small"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onGrant(each.core);
|
onGrant(each.core);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Lock fontSize="inherit" />
|
{t("Grant")}
|
||||||
</IconButton> */
|
</Button>
|
||||||
<Button
|
</Tooltip>
|
||||||
variant="outlined"
|
|
||||||
size="small"
|
|
||||||
title={t("Tun mode requires")}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
onGrant(each.core);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("Grant")}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -4,30 +4,35 @@ import { useLockFn } from "ahooks";
|
|||||||
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
import { List, ListItem, ListItemText, TextField } from "@mui/material";
|
||||||
import { useClashInfo } from "@/hooks/use-clash";
|
import { useClashInfo } from "@/hooks/use-clash";
|
||||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
export const ClashPortViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { clashInfo, patchInfo } = useClashInfo();
|
const { clashInfo, patchInfo } = useClashInfo();
|
||||||
|
const { verge, patchVerge } = useVerge();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [port, setPort] = useState(clashInfo?.port ?? 7890);
|
const [port, setPort] = useState(
|
||||||
|
verge?.verge_mixed_port ?? clashInfo?.port ?? 7890
|
||||||
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
if (clashInfo?.port) setPort(clashInfo?.port);
|
if (verge?.verge_mixed_port) setPort(verge?.verge_mixed_port);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
},
|
},
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onSave = useLockFn(async () => {
|
const onSave = useLockFn(async () => {
|
||||||
if (port === clashInfo?.port) {
|
if (port === verge?.verge_mixed_port) {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await patchInfo({ "mixed-port": port });
|
await patchInfo({ "mixed-port": port });
|
||||||
|
await patchVerge({ verge_mixed_port: port });
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
Notice.success("Change Clash port successfully!", 1000);
|
Notice.success("Change Clash port successfully!", 1000);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ import useSWR from "swr";
|
|||||||
import snarkdown from "snarkdown";
|
import snarkdown from "snarkdown";
|
||||||
import { forwardRef, useImperativeHandle, useState, useMemo } from "react";
|
import { forwardRef, useImperativeHandle, useState, useMemo } from "react";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Box, styled } from "@mui/material";
|
import { Box, LinearProgress, styled } from "@mui/material";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { relaunch } from "@tauri-apps/api/process";
|
import { relaunch } from "@tauri-apps/api/process";
|
||||||
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
|
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
|
||||||
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
import { BaseDialog, DialogRef, Notice } from "@/components/base";
|
||||||
import { atomUpdateState } from "@/services/states";
|
import { atomUpdateState } from "@/services/states";
|
||||||
|
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
|
||||||
|
|
||||||
const UpdateLog = styled(Box)(() => ({
|
const UpdateLog = styled(Box)(() => ({
|
||||||
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
|
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
|
||||||
}));
|
}));
|
||||||
|
let eventListener: UnlistenFn | null = null;
|
||||||
|
|
||||||
export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -26,6 +28,10 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
focusThrottleInterval: 36e5, // 1 hour
|
focusThrottleInterval: 36e5, // 1 hour
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [downloaded, setDownloaded] = useState(0);
|
||||||
|
const [buffer, setBuffer] = useState(0);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
open: () => setOpen(true),
|
open: () => setOpen(true),
|
||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
@@ -42,7 +48,19 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
const onUpdate = useLockFn(async () => {
|
const onUpdate = useLockFn(async () => {
|
||||||
if (updateState) return;
|
if (updateState) return;
|
||||||
setUpdateState(true);
|
setUpdateState(true);
|
||||||
|
if (eventListener !== null) {
|
||||||
|
eventListener();
|
||||||
|
}
|
||||||
|
eventListener = await listen(
|
||||||
|
"tauri://update-download-progress",
|
||||||
|
(e: Event<any>) => {
|
||||||
|
setTotal(e.payload.contentLength);
|
||||||
|
setBuffer(e.payload.chunkLength);
|
||||||
|
setDownloaded((a) => {
|
||||||
|
return a + e.payload.chunkLength;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await installUpdate();
|
await installUpdate();
|
||||||
await relaunch();
|
await relaunch();
|
||||||
@@ -65,6 +83,14 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
onOk={onUpdate}
|
onOk={onUpdate}
|
||||||
>
|
>
|
||||||
<UpdateLog dangerouslySetInnerHTML={{ __html: parseContent }} />
|
<UpdateLog dangerouslySetInnerHTML={{ __html: parseContent }} />
|
||||||
|
{updateState && (
|
||||||
|
<LinearProgress
|
||||||
|
variant="buffer"
|
||||||
|
value={(downloaded / total) * 100}
|
||||||
|
valueBuffer={buffer}
|
||||||
|
sx={{ marginTop: "5px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Typography,
|
Typography,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { ArrowForward, Settings } from "@mui/icons-material";
|
import { ArrowForward, Settings, Shuffle } from "@mui/icons-material";
|
||||||
import { DialogRef } from "@/components/base";
|
import { DialogRef, Notice } from "@/components/base";
|
||||||
import { useClash } from "@/hooks/use-clash";
|
import { useClash } from "@/hooks/use-clash";
|
||||||
import { GuardState } from "./mods/guard-state";
|
import { GuardState } from "./mods/guard-state";
|
||||||
import { WebUIViewer } from "./mods/web-ui-viewer";
|
import { WebUIViewer } from "./mods/web-ui-viewer";
|
||||||
@@ -20,6 +21,7 @@ import { SettingList, SettingItem } from "./mods/setting-comp";
|
|||||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||||
import { invoke_uwp_tool } from "@/services/cmds";
|
import { invoke_uwp_tool } from "@/services/cmds";
|
||||||
import getSystem from "@/utils/get-system";
|
import getSystem from "@/utils/get-system";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
const isWIN = getSystem() === "windows";
|
const isWIN = getSystem() === "windows";
|
||||||
|
|
||||||
@@ -32,12 +34,11 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
|
|
||||||
const { clash, version, mutateClash, patchClash } = useClash();
|
const { clash, version, mutateClash, patchClash } = useClash();
|
||||||
|
|
||||||
const {
|
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||||
ipv6,
|
|
||||||
"allow-lan": allowLan,
|
const { ipv6, "allow-lan": allowLan, "log-level": logLevel } = clash ?? {};
|
||||||
"log-level": logLevel,
|
|
||||||
"mixed-port": mixedPort,
|
const { enable_random_port = false, verge_mixed_port } = verge ?? {};
|
||||||
} = clash ?? {};
|
|
||||||
|
|
||||||
const webRef = useRef<DialogRef>(null);
|
const webRef = useRef<DialogRef>(null);
|
||||||
const fieldRef = useRef<DialogRef>(null);
|
const fieldRef = useRef<DialogRef>(null);
|
||||||
@@ -49,7 +50,9 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
const onChangeData = (patch: Partial<IConfigData>) => {
|
const onChangeData = (patch: Partial<IConfigData>) => {
|
||||||
mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
|
mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
|
||||||
};
|
};
|
||||||
|
const onChangeVerge = (patch: Partial<IVergeConfig>) => {
|
||||||
|
mutateVerge({ ...verge, ...patch }, false);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<SettingList title={t("Clash Setting")}>
|
<SettingList title={t("Clash Setting")}>
|
||||||
<WebUIViewer ref={webRef} />
|
<WebUIViewer ref={webRef} />
|
||||||
@@ -103,11 +106,32 @@ const SettingClash = ({ onError }: Props) => {
|
|||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem label={t("Mixed Port")}>
|
<SettingItem
|
||||||
|
label={t("Mixed Port")}
|
||||||
|
extra={
|
||||||
|
<Tooltip title={t("Random Port")}>
|
||||||
|
<IconButton
|
||||||
|
color={enable_random_port ? "success" : "inherit"}
|
||||||
|
size="medium"
|
||||||
|
onClick={() => {
|
||||||
|
Notice.success(t("After restart to take effect"), 1000);
|
||||||
|
onChangeVerge({ enable_random_port: !enable_random_port });
|
||||||
|
patchVerge({ enable_random_port: !enable_random_port });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Shuffle
|
||||||
|
fontSize="inherit"
|
||||||
|
style={{ cursor: "pointer", opacity: 0.75 }}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
<TextField
|
<TextField
|
||||||
|
disabled={enable_random_port}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
size="small"
|
size="small"
|
||||||
value={mixedPort ?? 0}
|
value={verge_mixed_port ?? 7890}
|
||||||
sx={{ width: 100, input: { py: "7.5px", cursor: "pointer" } }}
|
sx={{ width: 100, input: { py: "7.5px", cursor: "pointer" } }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
portRef.current?.open();
|
portRef.current?.open();
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ const SettingVerge = ({ onError }: Props) => {
|
|||||||
<MenuItem value="main_window">{t("Show Main Window")}</MenuItem>
|
<MenuItem value="main_window">{t("Show Main Window")}</MenuItem>
|
||||||
<MenuItem value="system_proxy">{t("System Proxy")}</MenuItem>
|
<MenuItem value="system_proxy">{t("System Proxy")}</MenuItem>
|
||||||
<MenuItem value="tun_mode">{t("Tun Mode")}</MenuItem>
|
<MenuItem value="tun_mode">{t("Tun Mode")}</MenuItem>
|
||||||
|
<MenuItem value="disable">{t("Disable")}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</GuardState>
|
</GuardState>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|||||||
@@ -67,6 +67,8 @@
|
|||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "Log Level",
|
"Log Level": "Log Level",
|
||||||
"Mixed Port": "Mixed Port",
|
"Mixed Port": "Mixed Port",
|
||||||
|
"Random Port": "Random Port",
|
||||||
|
"After restart to take effect": "After restart to take effect",
|
||||||
"External": "External",
|
"External": "External",
|
||||||
"Clash Core": "Clash Core",
|
"Clash Core": "Clash Core",
|
||||||
"Grant": "Grant",
|
"Grant": "Grant",
|
||||||
|
|||||||
@@ -64,6 +64,8 @@
|
|||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "Уровень логов",
|
"Log Level": "Уровень логов",
|
||||||
"Mixed Port": "Смешанный порт",
|
"Mixed Port": "Смешанный порт",
|
||||||
|
"Random Port": "Случайный порт",
|
||||||
|
"After restart to take effect": "Чтобы изменения вступили в силу, необходимо перезапустить приложение",
|
||||||
"Clash Core": "Ядро Clash",
|
"Clash Core": "Ядро Clash",
|
||||||
"Tun Mode": "Режим туннеля",
|
"Tun Mode": "Режим туннеля",
|
||||||
"Service Mode": "Режим сервиса",
|
"Service Mode": "Режим сервиса",
|
||||||
|
|||||||
@@ -67,6 +67,8 @@
|
|||||||
"IPv6": "IPv6",
|
"IPv6": "IPv6",
|
||||||
"Log Level": "日志等级",
|
"Log Level": "日志等级",
|
||||||
"Mixed Port": "端口设置",
|
"Mixed Port": "端口设置",
|
||||||
|
"Random Port": "随机端口",
|
||||||
|
"After restart to take effect": "重启后生效",
|
||||||
"External": "外部控制",
|
"External": "外部控制",
|
||||||
"Clash Core": "Clash 内核",
|
"Clash Core": "Clash 内核",
|
||||||
"Grant": "授权",
|
"Grant": "授权",
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import getSystem from "@/utils/get-system";
|
||||||
|
const OS = getSystem();
|
||||||
|
|
||||||
// default theme setting
|
// default theme setting
|
||||||
export const defaultTheme = {
|
export const defaultTheme = {
|
||||||
primary_color: "#1867c0",
|
primary_color: "#1867c0",
|
||||||
@@ -8,12 +11,16 @@ export const defaultTheme = {
|
|||||||
error_color: "#d32f2f",
|
error_color: "#d32f2f",
|
||||||
warning_color: "#ed6c02",
|
warning_color: "#ed6c02",
|
||||||
success_color: "#2e7d32",
|
success_color: "#2e7d32",
|
||||||
font_family: `"twemoji mozilla", "Roboto", "Helvetica", "Arial", sans-serif`,
|
background_color: "#ffffff",
|
||||||
|
font_family: `"Roboto", "Helvetica", "Arial", sans-serif, ${
|
||||||
|
OS === "windows" ? "twemoji mozilla" : ""
|
||||||
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// dark mode
|
// dark mode
|
||||||
export const defaultDarkTheme = {
|
export const defaultDarkTheme = {
|
||||||
...defaultTheme,
|
...defaultTheme,
|
||||||
primary_text: "#E8E8ED",
|
primary_text: "#E8E8ED",
|
||||||
|
background_color: "#181818",
|
||||||
secondary_text: "#bbbbbb",
|
secondary_text: "#bbbbbb",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,19 @@ import { useMemo, useRef, useState } from "react";
|
|||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useSetRecoilState } from "recoil";
|
||||||
import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material";
|
import { Box, Button, Grid, IconButton, Stack, TextField } from "@mui/material";
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
closestCenter,
|
||||||
|
KeyboardSensor,
|
||||||
|
PointerSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
DragEndEvent,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
|
import {
|
||||||
|
SortableContext,
|
||||||
|
sortableKeyboardCoordinates,
|
||||||
|
} from "@dnd-kit/sortable";
|
||||||
import { LoadingButton } from "@mui/lab";
|
import { LoadingButton } from "@mui/lab";
|
||||||
import {
|
import {
|
||||||
ClearRounded,
|
ClearRounded,
|
||||||
@@ -19,6 +32,7 @@ import {
|
|||||||
getRuntimeLogs,
|
getRuntimeLogs,
|
||||||
deleteProfile,
|
deleteProfile,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
|
reorderProfile,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { atomLoadingCache } from "@/services/states";
|
import { atomLoadingCache } from "@/services/states";
|
||||||
import { closeAllConnections } from "@/services/api";
|
import { closeAllConnections } from "@/services/api";
|
||||||
@@ -40,7 +54,12 @@ const ProfilePage = () => {
|
|||||||
const [disabled, setDisabled] = useState(false);
|
const [disabled, setDisabled] = useState(false);
|
||||||
const [activating, setActivating] = useState("");
|
const [activating, setActivating] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor),
|
||||||
|
useSensor(KeyboardSensor, {
|
||||||
|
coordinateGetter: sortableKeyboardCoordinates,
|
||||||
|
})
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
profiles = {},
|
profiles = {},
|
||||||
activateSelected,
|
activateSelected,
|
||||||
@@ -106,6 +125,16 @@ const ProfilePage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = async (event: DragEndEvent) => {
|
||||||
|
const { active, over } = event;
|
||||||
|
if (over) {
|
||||||
|
if (active.id !== over.id) {
|
||||||
|
await reorderProfile(active.id.toString(), over.id.toString());
|
||||||
|
mutateProfiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
const onSelect = useLockFn(async (current: string, force: boolean) => {
|
||||||
if (!force && current === profiles.current) return;
|
if (!force && current === profiles.current) return;
|
||||||
// 避免大多数情况下loading态闪烁
|
// 避免大多数情况下loading态闪烁
|
||||||
@@ -293,22 +322,34 @@ const ProfilePage = () => {
|
|||||||
{t("New")}
|
{t("New")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<DndContext
|
||||||
<Box sx={{ mb: 4.5 }}>
|
sensors={sensors}
|
||||||
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
collisionDetection={closestCenter}
|
||||||
{regularItems.map((item) => (
|
onDragEnd={onDragEnd}
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
>
|
||||||
<ProfileItem
|
<Box sx={{ mb: 4.5 }}>
|
||||||
selected={profiles.current === item.uid}
|
<Grid container spacing={{ xs: 1, lg: 1 }}>
|
||||||
activating={activating === item.uid}
|
<SortableContext
|
||||||
itemData={item}
|
items={regularItems.map((x) => {
|
||||||
onSelect={(f) => onSelect(item.uid, f)}
|
return x.uid;
|
||||||
onEdit={() => viewerRef.current?.edit(item)}
|
})}
|
||||||
/>
|
>
|
||||||
</Grid>
|
{regularItems.map((item) => (
|
||||||
))}
|
<Grid item xs={12} sm={6} md={4} lg={3} key={item.file}>
|
||||||
</Grid>
|
<ProfileItem
|
||||||
</Box>
|
id={item.uid}
|
||||||
|
selected={profiles.current === item.uid}
|
||||||
|
activating={activating === item.uid}
|
||||||
|
itemData={item}
|
||||||
|
onSelect={(f) => onSelect(item.uid, f)}
|
||||||
|
onEdit={() => viewerRef.current?.edit(item)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</DndContext>
|
||||||
|
|
||||||
{enhanceItems.length > 0 && (
|
{enhanceItems.length > 0 && (
|
||||||
<Grid container spacing={{ xs: 2, lg: 2 }}>
|
<Grid container spacing={{ xs: 2, lg: 2 }}>
|
||||||
@@ -330,7 +371,6 @@ const ProfilePage = () => {
|
|||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
<ProfileViewer ref={viewerRef} onChange={() => mutateProfiles()} />
|
||||||
<ConfigViewer ref={configRef} />
|
<ConfigViewer ref={configRef} />
|
||||||
</BasePage>
|
</BasePage>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const SettingPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toGithubRepo = useLockFn(() => {
|
const toGithubRepo = useLockFn(() => {
|
||||||
return openWebUrl("https://github.com/wonfen/clash-verge-rev");
|
return openWebUrl("https://github.com/clash-verge-rev/clash-verge-rev");
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +26,7 @@ const SettingPage = () => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
title="@wonfen/clash-verge-rev"
|
title="@clash-verge-rev/clash-verge-rev"
|
||||||
onClick={toGithubRepo}
|
onClick={toGithubRepo}
|
||||||
>
|
>
|
||||||
<GitHub fontSize="inherit" />
|
<GitHub fontSize="inherit" />
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ export async function importProfile(url: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function reorderProfile(activeId: string, overId: string) {
|
||||||
|
return invoke<void>("reorder_profile", {
|
||||||
|
activeId,
|
||||||
|
overId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateProfile(index: string, option?: IProfileOption) {
|
export async function updateProfile(index: string, option?: IProfileOption) {
|
||||||
return invoke<void>("update_profile", { index, option });
|
return invoke<void>("update_profile", { index, option });
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/services/types.d.ts
vendored
2
src/services/types.d.ts
vendored
@@ -166,6 +166,8 @@ interface IVergeConfig {
|
|||||||
enable_service_mode?: boolean;
|
enable_service_mode?: boolean;
|
||||||
enable_silent_start?: boolean;
|
enable_silent_start?: boolean;
|
||||||
enable_system_proxy?: boolean;
|
enable_system_proxy?: boolean;
|
||||||
|
enable_random_port?: boolean;
|
||||||
|
verge_mixed_port?: number;
|
||||||
enable_proxy_guard?: boolean;
|
enable_proxy_guard?: boolean;
|
||||||
proxy_guard_duration?: number;
|
proxy_guard_duration?: number;
|
||||||
system_proxy_bypass?: string;
|
system_proxy_bypass?: string;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export const OTHERS_FIELDS = [
|
|||||||
"tcp-concurrent", // meta
|
"tcp-concurrent", // meta
|
||||||
"enable-process", // meta
|
"enable-process", // meta
|
||||||
"find-process-mode", // meta
|
"find-process-mode", // meta
|
||||||
|
"skip-auth-prefixes", // meta
|
||||||
"external-controller-tls", // meta
|
"external-controller-tls", // meta
|
||||||
"global-client-fingerprint", // meta
|
"global-client-fingerprint", // meta
|
||||||
] as const;
|
] as const;
|
||||||
|
|||||||
Reference in New Issue
Block a user