Compare commits

...

80 Commits
v2.4.1 ... main

Author SHA1 Message Date
niuhuan
92fab5aafa feat(ohos) build har package (#1440)
Some checks are pending
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Blocked by required conditions
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Blocked by required conditions
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Blocked by required conditions
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Blocked by required conditions
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Blocked by required conditions
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Blocked by required conditions
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Blocked by required conditions
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Blocked by required conditions
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Blocked by required conditions
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Blocked by required conditions
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Blocked by required conditions
EasyTier Core / core-result (push) Blocked by required conditions
EasyTier Core / magisk_build (push) Blocked by required conditions
EasyTier GUI / pre_job (push) Waiting to run
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Blocked by required conditions
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Blocked by required conditions
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Blocked by required conditions
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Blocked by required conditions
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Blocked by required conditions
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Blocked by required conditions
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Blocked by required conditions
EasyTier GUI / gui-result (push) Blocked by required conditions
EasyTier Mobile / pre_job (push) Waiting to run
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Blocked by required conditions
EasyTier Mobile / mobile-result (push) Blocked by required conditions
EasyTier OHOS / cargo_fmt_check (push) Waiting to run
EasyTier OHOS / pre_job (push) Waiting to run
EasyTier OHOS / build-ohos (push) Blocked by required conditions
EasyTier Test / pre_job (push) Waiting to run
EasyTier Test / test (push) Blocked by required conditions
Co-authored-by: niuhuan <20847533+niuhuan@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-02 22:29:49 +08:00
Mg Pig
841d525913 refactor(rpc): Centralize RPC service and unify API (#1427)
This change introduces a major refactoring of the RPC service layer to improve modularity, unify the API, and simplify the overall architecture.

Key changes:
- Replaced per-network-instance RPC services with a single global RPC server, reducing resource usage and simplifying management.
- All clients (CLI, Web UI, etc.) now interact with EasyTier core through a unified RPC entrypoint, enabling consistent authentication and control.
- RPC implementation logic has been moved to `easytier/src/rpc_service/` and organized by functionality (e.g., `instance_manage.rs`, `peer_manage.rs`, `config.rs`) for better maintainability.
- Standardized Protobuf API definitions under `easytier/src/proto/` with an `api_` prefix (e.g., `cli.proto` → `api_instance.proto`) to provide a consistent interface.
- CLI commands now require explicit `--instance-id` or `--instance-name` when multiple network instances are running; the parameter is optional when only one instance exists.

BREAKING CHANGE:  
RPC portal configuration (`rpc_portal` and `rpc_portal_whitelist`) has been removed from per-instance configs and the Web UI. The RPC listen address must now be specified globally via the `--rpc-portal` command-line flag or the `ET_RPC_PORTAL` environment variable, as there is only one RPC service for the entire application.
2025-10-02 20:30:39 +08:00
Luna Yao
d2efbbef04 refactor: change magicdns to internal redirect (#1428)
To resolve issue #1419, DNS request packets are read directly and responses are sent back internally instead of being forwarded to the listening port.

The DNS service on fake_ip (100.100.100.101) no longer supports DNS-over-TCP.

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
2025-10-02 20:19:12 +08:00
Sijie.Sun
971ef82679 fix data not encrypted when no tun is enabled (#1435)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-10-01 11:16:24 +08:00
Mg Pig
020bf04ec4 refactor(config): unify runtime configuration management via ConfigRpc (#1397)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* refactor(config): unify runtime configuration management via ConfigRpc
* feat(tests): add config patch test and fix problem
2025-10-01 00:32:28 +08:00
韩嘉乐
4d91582fd8 Update ohos-rs (#1434) 2025-09-30 23:51:58 +08:00
Sijie.Sun
e9b4dbce6e use cargo ndk in jni build script (#1424)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-28 23:18:51 +08:00
R0S
00fd02c739 正确的hostname
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-26 21:17:14 +08:00
sijie.sun
c0d2045e52 bump version to v2.4.5
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-26 00:48:10 +08:00
ThermalEng
835cd407bf Update hotspot_iprule.sh, Support subnet forward for usb shared network (#1411)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-25 16:25:53 +08:00
Sijie.Sun
f5ba5bb146 show traffic stats chart in web/gui (#1410) 2025-09-25 13:43:11 +08:00
Sijie.Sun
7a694257d9 add test for ipv6 wireguard vpn portal (#1408)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-25 08:24:56 +08:00
Sijie.Sun
67abf4446d fix socks5 panic (#1409) 2025-09-25 08:24:50 +08:00
Sijie.Sun
7035a3fef4 fix firewall rule not specify interface (#1407)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-25 00:11:26 +08:00
Sijie.Sun
4445916ba7 fix open log dir not work on gui (#1403)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-21 23:17:31 +08:00
Sijie.Sun
a102a8bfc7 fix macos bind failed when addr is v6 (#1398) 2025-09-21 21:47:03 +08:00
Sijie.Sun
c9e8c35e77 fix log dir not work; fix stun config from file not work; (#1393)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-20 00:20:08 +08:00
Sijie.Sun
1a1be8138a bump version to v2.4.4 (#1386)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-18 19:49:10 +08:00
Sijie.Sun
e06e8a9e8a allow enable log with cli, limit log size (#1384)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* impl logger rpc
* use size based appender
* add log args
2025-09-18 16:35:12 +08:00
Sijie.Sun
56fd6e4ab6 fix wireguard listener (#1382)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* listen both v4 and v6 for wireguard portal
* fix panic when getting udp local addr

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-17 23:45:05 +08:00
Sijie.Sun
215db09925 avoid packets sending to non-exist peer causing route loop (#1378)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-17 09:52:53 +08:00
fanyang
9fff5e4fec Add config validation flag (#1376)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
Add `--check-config` CLI option to validate configuration without
starting network
2025-09-16 22:58:07 +08:00
Sijie.Sun
802d3f78d7 distinct v6 and v4 tunnel in gui and cli (#1373)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-16 07:24:31 +08:00
Sijie.Sun
3593035eb9 fix networksetup timeout on some machine (#1372)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-15 23:33:43 +08:00
Sijie.Sun
757d76c9da fix stun server list empty when config is from web (#1371) 2025-09-15 22:52:58 +08:00
fanyang
445e68ddd1 Read config from stdin (#1354)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-13 21:21:30 +08:00
Sijie.Sun
b540ec3f46 improve uptime (#1365) 2025-09-13 19:14:13 +08:00
Sijie.Sun
5c90431876 fix smoltcp attempt to subtract sequence numbers with underflow (#1360) 2025-09-13 15:03:04 +08:00
Sijie.Sun
793889c3b7 fix ospf ipv4 map error when ipv4 conflicted and changed (#1359)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-13 08:48:50 +08:00
Sijie.Sun
eb42086f9c set correct route policy for udp/icmp (#1361) 2025-09-13 08:48:37 +08:00
Sijie.Sun
d0efc40efb fix foreign network direct conn with mapped listeners (#1363) 2025-09-13 08:48:12 +08:00
fanyang
ae704d1d5f Fix jemalloc warning on macOS (#1344)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
fix:
```
-> % easytier-core
<jemalloc>: option background_thread currently supports pthread only
```

Reference: https://github.com/apache/arrow/pull/5729
2025-09-08 21:53:40 +08:00
fanyang
525dfd9fc1 cli: improve peer table display with shorter columns for small display (#1342)
- Add short column names for latency, loss rate, rx/tx bytes, tunnel protocol and NAT type
- Format loss rate as percentage with one decimal place
- Change table style from modern to markdown for better readability
2025-09-08 21:52:53 +08:00
Sijie.Sun
18bd178bbd update readme to fix script installation command (#1341)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-06 17:02:31 +08:00
fanyang
088155f6f3 core: hide default STUN servers from cli (#1334) 2025-09-06 15:53:34 +08:00
Sijie.Sun
b750faa66f add android jni (#1340) 2025-09-06 13:49:42 +08:00
Sijie.Sun
ef3309814d fix cli add port forward failed if initial forward list is empty (#1324)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-09-02 22:03:57 +08:00
fanyang
b87a05b457 refactor: update custom STUN server settings (#1310)
* refactor: update global context STUN server initialization

Modified global context initialization to use a single StunInfoCollector
instance with properly configured IPv4 and IPv6 servers instead of
creating separate instances.

feat: add IPv6 STUN server configuration support

Added interface methods and config struct fields to support both IPv4
and IPv6 STUN server configuration. Modified getter and setter methods
to handle Option<Vec<String>> type for both server types.

feat: enhance StunInfoCollector with IPv6 support

Updated StunInfoCollector to support both IPv4 and IPv6 STUN servers.
Added new constructor that accepts both server types and methods to set
them independently.

feat: add CLI argument for IPv6 STUN servers

Added command line argument support for configuring IPv6 STUN servers.
Updated configuration setup to handle both IPv4 and IPv6 STUN server
settings.

docs: add localization for STUN server configuration

Added English and Chinese localization strings for the new STUN server
configuration options, including both IPv4 and IPv6 variants.
2025-09-02 21:46:37 +08:00
Joel Stodolski
754439f03c feat(gui): add macOS dock icon visibility control (#1328) 2025-09-02 17:32:18 +08:00
Sijie.Sun
2145ef40b9 fix ospf route panic (#1304)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-27 13:22:29 +08:00
Sijie.Sun
a3806e0190 fix set ipv6 mtu may cause tun init error (#1300) 2025-08-27 09:57:32 +08:00
Sijie.Sun
0ceb58586b fix keepalive on accepted tcp proxy connection (#1302)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-26 23:30:30 +08:00
Sijie.Sun
719a1fe7cf bump version to 2.4.3 (#1296)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-26 12:22:08 +08:00
Sijie.Sun
671b8d5a0c fix quic transport (#1293) 2025-08-26 08:37:31 +08:00
fanyang
e29206aef9 tray: place the exit menu item at bottom (#1291)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-25 12:47:43 +08:00
Sijie.Sun
3299a77da3 make magic dns domain check robust (#1288)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-24 18:24:42 +08:00
Sijie.Sun
0804fd6632 retry create tun device if it closed (#1279) 2025-08-24 15:25:09 +08:00
Sijie.Sun
ea76114d50 fix kcp not work as expect (#1285) 2025-08-24 14:33:11 +08:00
Mg Pig
9304d3b227 feat(nix): refactor Flake and Migrate Android Support (#1280)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-24 00:53:42 +08:00
fanyang
78004de5e5 gui: sort peer list (#1278) 2025-08-24 00:53:32 +08:00
Sijie.Sun
5b7384fddd disable nat4 hole punch (#1277)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-22 23:33:21 +08:00
Mg Pig
08a92a53c3 feat(acl): add group-based ACL rules and related structures (#1265)
* feat(acl): add group-based ACL rules and related structures

* refactor(acl): optimize group handling with Arc and improve cache management

* refactor(acl): clippy

* feat(tests): add performance tests for generate_with_proof and verify methods

* feat: update group_trust_map to use HashMap for more secure group proofs

* refactor: refactor the logic of the trusted group getting and setting

* feat(acl): support kcp/quic use group acl

* feat(proxy): optimize group retrieval by IP in Kcp and Quic proxy handlers

* feat(tests): add group-based ACL tree node test

* always allow quic proxy traffic

---------

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
Co-authored-by: sijie.sun <sijie.sun@smartx.com>
2025-08-22 22:25:00 +08:00
fanyang
34560af141 cli: put the local IP at the front (#1256) 2025-08-22 20:40:28 +08:00
Sijie.Sun
2e7e0088dd Revise QQ group details in README_CN.md (#1274)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-22 12:52:01 +08:00
Sijie.Sun
d23366ea84 Update QQ Group contact information in README (#1275) 2025-08-22 12:51:50 +08:00
fanyang
df7eb47593 Support tokio-console (#1259)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-21 11:41:42 +08:00
Sijie.Sun
839a28a3d5 avoid panic on smoltcp socket accept (#1272)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-21 09:30:51 +08:00
Sijie.Sun
9c6d1dabdf fix dead lock in tokio smoltcp (#1270)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-21 00:16:11 +08:00
Sijie.Sun
e6ec7f405c introduce uptime monitor for easytier public nodes (#1250) 2025-08-20 22:59:44 +08:00
ThermalEng
8f37d4ef7c 增加magisk模块功能:热点局域网转发。 (#1252)
* 增加magisk模块功能:热点局域网转发。该功能由后台监控,热点打开,可自动增加转发规则。在三星fold3测试通过。

* 增加了默认tun名称的识别

1.防止配置文件没有配置dev_name的情况,按默认名称tun+在网络设备中查找;
2.一旦热点关闭,自动删除规则。

* 考虑到主程序已可通过模块开关来重新加载,将操作按钮用于转发开关。此外对状态栏信息进行了一些修饰,加入了转发状态的显示。
2025-08-20 20:49:25 +08:00
TigerBeanst
c37af8c1be feat(easytier-magisk): add support for custom command args. (#1236)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-19 00:04:54 +08:00
Glavo
489661a2ce Fix #1255: Using mimalloc for Linux LoongArch64 (#1257) 2025-08-19 00:04:11 +08:00
Sijie.Sun
fa3e208668 fix panic of std::Instant overflow (#1243)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-15 22:54:58 +08:00
21paradox
4d240efde9 add a android flake.nix for local development/test/build (#1237) 2025-08-15 16:59:11 +08:00
Sijie.Sun
d9bcbd9b31 fix proxy traffic not count into traffic (#1229)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-14 00:05:12 +08:00
fanyang
35ff9b82fc Support custom STUN servers configuration (#1212)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* Support custom STUN servers

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-13 10:35:59 +08:00
Sijie.Sun
a511abb613 fix docker file (#1219)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-11 18:09:16 +08:00
Sijie.Sun
1eec27b5ff bump version to 2.4.2 (#1218)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-11 09:03:13 +08:00
Sijie.Sun
1de7777a71 fix quic transport panic (#1216) 2025-08-11 08:30:59 +08:00
Sijie.Sun
975ca8bd9c Update docker workflow (#1217)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
1. push all supported platform
2. support unstable tag
2025-08-10 23:36:50 +08:00
Sijie.Sun
e43537939a clippy all codes (#1214)
1. clippy code
2. add fmt and clippy check in ci
2025-08-10 22:56:41 +08:00
CyiceK
0087ac3ffc feat(encrypt): Add XOR and ChaCha20 encryption with low-end device optimization and openssl support. (#1186)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
Add ChaCha20 XOR algorithm, extend AES-GCM-256 capabilities, and integrate OpenSSL support.

---------

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
2025-08-09 18:53:55 +08:00
21paradox
7de4b33dd1 add FOREGROUND_SERVICE for no_tun mode, not using vpn service (#1203)
1. add FOREGROUND_SERVICE related code, connection not to be **blocked by android system** when apps running in background
2. no_tun mode not enabling vpnservice, makeing other app to use vpnservice

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-09 18:34:45 +08:00
Sijie.Sun
8ffc2f12e4 optimize the condition of enabling kcp (#1210) 2025-08-09 16:16:09 +08:00
FuturePrayer
37b24164b6 add portforward config to gui (#1198)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* Added port forwarding to the GUI interface
* Separated port forwarding into a separate drop-down menu
2025-08-09 09:50:09 +08:00
Sijie.Sun
8cdb27d43d add stats metrics (#1207)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
support new cli command `easytier-cli stats`

It's useful to find out which components are consuming bandwidth.
2025-08-09 00:06:35 +08:00
Sijie.Sun
efa17a7c10 fix dead loop in direct connecto if disable-p2p is enabled in dst (#1206) 2025-08-08 22:30:39 +08:00
Sijie.Sun
6d14e9e441 fix jemalloc prof feature (#1201)
Some checks failed
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / pre_job (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
2025-08-08 17:54:39 +08:00
fanyang
e3e406dcde cli: sort peers by IPv4 and hostname (#1191)
Some checks failed
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-loongarch64, ubuntu-24.04, loongarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-riscv64, ubuntu-22.04, riscv64gc-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier OHOS / build-ohos (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled
* cli: sort entries by IPv4 and hostname

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-04 21:18:49 +08:00
sijie.sun
d0a6c93c2c fix ipv6 packet routing and avoid route looping
properly handle ipv6 link local address and exit node.
2025-08-03 18:10:27 +08:00
299 changed files with 27377 additions and 13828 deletions

3
.envrc
View File

@@ -1 +1,2 @@
use flake
PROFILE=$(cat .flake-profile 2>/dev/null)
use flake .#${PROFILE}

View File

@@ -8,10 +8,16 @@ WORKDIR /tmp/output
RUN ARTIFACT_ARCH=""; \
if [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
ARTIFACT_ARCH="x86_64"; \
elif [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then \
ARTIFACT_ARCH="armhf"; \
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then \
ARTIFACT_ARCH="armv7hf"; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
ARTIFACT_ARCH="aarch64"; \
elif [ "$TARGETPLATFORM" = "linux/riscv64" ]; then \
ARTIFACT_ARCH="riscv64"; \
else \
echo "Unsupported architecture: $TARGETARCH"; \
echo "Unsupported architecture: $TARGETPLATFORM"; \
exit 1; \
fi; \
cp /tmp/artifacts/easytier-linux-${ARTIFACT_ARCH}/* /tmp/output;

View File

@@ -154,14 +154,13 @@ jobs:
name: easytier-web-dashboard
path: easytier-web/frontend/dist/
- name: Cargo cache
- uses: Swatinem/rust-cache@v2
if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }}
uses: actions/cache@v4
with:
path: |
~/.cargo
./target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# The prefix cache key, this can be changed to start a new cache manually.
# default: "v0-rust"
prefix-key: ""
- name: Setup protoc
uses: arduino/setup-protoc@v3
@@ -187,12 +186,12 @@ jobs:
fi
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
cargo +nightly build -r --target $TARGET -Z build-std=std,panic_abort --package=easytier --features=jemalloc
cargo +nightly-2025-09-01 build -r --target $TARGET -Z build-std=std,panic_abort --package=easytier --features=jemalloc
else
if [[ $OS =~ ^windows.*$ ]]; then
SUFFIX=.exe
CORE_FEATURES="--features=mimalloc"
elif [[ $TARGET =~ ^riscv64.*$ ]]; then
elif [[ $TARGET =~ ^riscv64.*$ || $TARGET =~ ^loongarch64.*$ ]]; then
CORE_FEATURES="--features=mimalloc"
else
CORE_FEATURES="--features=jemalloc"
@@ -229,8 +228,8 @@ jobs:
rustup set auto-self-update disable
rustup install 1.87
rustup default 1.87
rustup install 1.89
rustup default 1.89
export CC=clang
export CXX=clang++

View File

@@ -11,13 +11,18 @@ on:
image_tag:
description: 'Tag for this image build'
type: string
default: 'v2.4.1'
default: 'v2.4.5'
required: true
mark_latest:
description: 'Mark this image as latest'
type: boolean
default: false
required: true
mark_unstable:
description: 'Mark this image as unstable'
type: boolean
default: false
required: true
jobs:
docker:
@@ -27,6 +32,13 @@ jobs:
-
name: Checkout
uses: actions/checkout@v4
-
name: Validate inputs
run: |
if [[ "${{ inputs.mark_latest }}" == "true" && "${{ inputs.mark_unstable }}" == "true" ]]; then
echo "Error: mark_latest and mark_unstable cannot both be true"
exit 1
fi
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
@@ -56,14 +68,36 @@ jobs:
- name: List files
run: |
ls -l -R .
- name: Prepare Docker tags
id: tags
run: |
# Base tags with version
DOCKERHUB_TAGS="easytier/easytier:${{ inputs.image_tag }}"
GHCR_TAGS="ghcr.io/easytier/easytier:${{ inputs.image_tag }}"
# Add latest tags if requested
if [[ "${{ inputs.mark_latest }}" == "true" ]]; then
DOCKERHUB_TAGS="${DOCKERHUB_TAGS},easytier/easytier:latest"
GHCR_TAGS="${GHCR_TAGS},ghcr.io/easytier/easytier:latest"
fi
# Add unstable tags if requested
if [[ "${{ inputs.mark_unstable }}" == "true" ]]; then
DOCKERHUB_TAGS="${DOCKERHUB_TAGS},easytier/easytier:unstable"
GHCR_TAGS="${GHCR_TAGS},ghcr.io/easytier/easytier:unstable"
fi
# Combine all tags
ALL_TAGS="${DOCKERHUB_TAGS},${GHCR_TAGS}"
echo "tags=${ALL_TAGS}" >> $GITHUB_OUTPUT
echo "Generated tags: ${ALL_TAGS}"
-
name: Build and push
uses: docker/build-push-action@v6
with:
context: ./docker_context
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/riscv64
push: true
file: .github/workflows/Dockerfile
tags: |
easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
ghcr.io/easytier/easytier:${{ inputs.image_tag }}${{ inputs.mark_latest && ',easytier/easytier:latest' || '' }},
tags: ${{ steps.tags.outputs.tags }}

View File

@@ -29,7 +29,7 @@ jobs:
concurrent_skipping: 'same_content_newer'
skip_after_successful_duplicate: 'true'
cancel_others: 'true'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml", ".github/workflows/install_rust.sh"]'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-gui/**", ".github/workflows/gui.yml", ".github/workflows/install_rust.sh", ".github/workflows/install_gui_dep.sh"]'
build-gui:
strategy:
fail-fast: false
@@ -78,20 +78,11 @@ jobs:
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
steps:
- uses: actions/checkout@v3
- name: Install GUI dependencies (x86 only)
if: ${{ matrix.TARGET == 'x86_64-unknown-linux-musl' }}
run: |
sudo apt update
sudo apt install -qq libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libgtk-3-dev \
librsvg2-dev \
libxdo-dev \
libssl-dev \
patchelf
run: bash ./.github/workflows/install_gui_dep.sh
- name: Install GUI cross compile (aarch64 only)
if: ${{ matrix.TARGET == 'aarch64-unknown-linux-musl' }}
@@ -124,12 +115,10 @@ jobs:
sudo apt install aptitude
sudo aptitude install -y libgstreamer1.0-0:arm64 gstreamer1.0-plugins-base:arm64 gstreamer1.0-plugins-good:arm64 \
libgstreamer-gl1.0-0:arm64 libgstreamer-plugins-base1.0-0:arm64 libgstreamer-plugins-good1.0-0:arm64 libwebkit2gtk-4.1-0:arm64 \
libwebkit2gtk-4.1-dev:arm64 libssl-dev:arm64 gcc-aarch64-linux-gnu
libwebkit2gtk-4.1-dev:arm64 libssl-dev:arm64 gcc-aarch64-linux-gnu libsoup-3.0-dev:arm64 libjavascriptcoregtk-4.1-dev:arm64
echo "PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/" >> "$GITHUB_ENV"
echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/" >> "$GITHUB_ENV"
- uses: actions/checkout@v3
- name: Set current ref as env variable
run: |
echo "GIT_DESC=$(git log -1 --format=%cd.%h --date=format:%Y-%m-%d_%H:%M:%S)" >> $GITHUB_ENV
@@ -162,13 +151,11 @@ jobs:
pnpm -r install
pnpm -r build
- name: Cargo cache
uses: actions/cache@v4
- uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo
./target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# The prefix cache key, this can be changed to start a new cache manually.
# default: "v0-rust"
prefix-key: ""
- name: Install rust target
run: bash ./.github/workflows/install_rust.sh

11
.github/workflows/install_gui_dep.sh vendored Normal file
View File

@@ -0,0 +1,11 @@
sudo apt update
sudo apt install -qq libwebkit2gtk-4.1-dev \
build-essential \
curl \
wget \
file \
libgtk-3-dev \
librsvg2-dev \
libxdo-dev \
libssl-dev \
patchelf

View File

@@ -31,8 +31,8 @@ fi
# see https://github.com/rust-lang/rustup/issues/3709
rustup set auto-self-update disable
rustup install 1.87
rustup default 1.87
rustup install 1.89
rustup default 1.89
# mips/mipsel cannot add target from rustup, need compile by ourselves
if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
@@ -44,8 +44,8 @@ if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then
ar x libgcc.a _ctzsi2.o _clz.o _bswapsi2.o
ar rcs libctz.a _ctzsi2.o _clz.o _bswapsi2.o
rustup toolchain install nightly-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
rustup toolchain install nightly-2025-09-01-x86_64-unknown-linux-gnu
rustup component add rust-src --toolchain nightly-2025-09-01-x86_64-unknown-linux-gnu
# https://github.com/rust-lang/rust/issues/128808
# remove it after Cargo or rustc fix this.

View File

@@ -98,13 +98,11 @@ jobs:
pnpm -r install
pnpm -r build
- name: Cargo cache
uses: actions/cache@v4
- uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo
./target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# The prefix cache key, this can be changed to start a new cache manually.
# default: "v0-rust"
prefix-key: ""
- name: Install rust target
run: |

View File

@@ -5,6 +5,7 @@ on:
branches: ["develop", "main", "releases/**"]
pull_request:
branches: ["develop", "main"]
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
@@ -15,6 +16,16 @@ defaults:
shell: bash
jobs:
cargo_fmt_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: fmt check
working-directory: ./easytier-contrib/easytier-ohrs
run: |
bash ../../.github/workflows/install_rust.sh
rustup component add rustfmt
cargo fmt --all -- --check
pre_job:
# continue-on-error: true # Uncomment once integration is finished
runs-on: ubuntu-latest
@@ -27,9 +38,9 @@ jobs:
uses: fkirc/skip-duplicate-actions@v5
with:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'same_content_newer'
skip_after_successful_duplicate: 'true'
cancel_others: 'true'
concurrent_skipping: "same_content_newer"
skip_after_successful_duplicate: "true"
cancel_others: "true"
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", "easytier-contrib/easytier-ohrs/**", ".github/workflows/ohos.yml", ".github/workflows/install_rust.sh"]'
build-ohos:
runs-on: ubuntu-latest
@@ -104,11 +115,13 @@ jobs:
cargo update easytier
ohrs doctor
ohrs build --release --arch aarch
ohrs artifact
mv package.har easytier-ohrs.har
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: easytier-ohos
path: ./easytier-contrib/easytier-ohrs/dist/arm64-v8a/libeasytier_ohrs.so
path: ./easytier-contrib/easytier-ohrs/easytier-ohrs.har
retention-days: 5
if-no-files-found: error

View File

@@ -21,7 +21,7 @@ on:
version:
description: 'Version for this release'
type: string
default: 'v2.4.1'
default: 'v2.4.5'
required: true
make_latest:
description: 'Mark this release as latest'

View File

@@ -28,7 +28,7 @@ jobs:
# All of these options are optional, so you can remove them if you are happy with the defaults
concurrent_skipping: 'never'
skip_after_successful_duplicate: 'true'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/test.yml"]'
paths: '["Cargo.toml", "Cargo.lock", "easytier/**", ".github/workflows/test.yml", ".github/workflows/install_gui_dep.sh", ".github/workflows/install_rust.sh"]'
test:
runs-on: ubuntu-22.04
needs: pre_job
@@ -89,6 +89,24 @@ jobs:
./target
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- name: Install GUI dependencies (Used by clippy)
run: |
bash ./.github/workflows/install_gui_dep.sh
bash ./.github/workflows/install_rust.sh
rustup component add rustfmt
rustup component add clippy
- name: Check formatting
if: ${{ !cancelled() }}
run: cargo fmt --all -- --check
- name: Check Clippy
if: ${{ !cancelled() }}
# NOTE: tauri need `dist` dir in build.rs
run: |
mkdir -p easytier-gui/dist
cargo clippy --all-targets --all-features --all -- -D warnings
- name: Run tests
run: |
sudo prlimit --pid $$ --nofile=1048576:1048576

1
.gitignore vendored
View File

@@ -41,3 +41,4 @@ easytier-gui/src-tauri/*.dll
/easytier-contrib/easytier-ohrs/dist/
.direnv
.flake-profile

View File

@@ -26,7 +26,7 @@ Thank you for your interest in contributing to EasyTier! This document provides
#### Required Tools
- Node.js v21 or higher
- pnpm v9 or higher
- Rust toolchain (version 1.87)
- Rust toolchain (version 1.89)
- LLVM and Clang
- Protoc (Protocol Buffers compiler)
@@ -79,8 +79,8 @@ sudo apt install -y bridge-utils
2. Install dependencies:
```bash
# Install Rust toolchain
rustup install 1.87
rustup default 1.87
rustup install 1.89
rustup default 1.89
# Install project dependencies
pnpm -r install

View File

@@ -34,7 +34,7 @@
#### 必需工具
- Node.js v21 或更高版本
- pnpm v9 或更高版本
- Rust 工具链(版本 1.87
- Rust 工具链(版本 1.89
- LLVM 和 Clang
- ProtocProtocol Buffers 编译器)
@@ -87,8 +87,8 @@ sudo apt install -y bridge-utils
2. 安装依赖:
```bash
# 安装 Rust 工具链
rustup install 1.87
rustup default 1.87
rustup install 1.89
rustup default 1.89
# 安装项目依赖
pnpm -r install

534
Cargo.lock generated
View File

@@ -129,6 +129,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_log-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
[[package]]
name = "android_logger"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
dependencies = [
"android_log-sys",
"env_logger",
"log",
"once_cell",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -527,8 +545,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"axum-core 0.4.5",
"axum-macros 0.4.2",
"bytes",
"futures-util",
"http",
@@ -537,7 +555,42 @@ dependencies = [
"hyper",
"hyper-util",
"itoa",
"matchit",
"matchit 0.7.3",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower 0.5.2",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [
"axum-core 0.5.2",
"axum-macros 0.5.0",
"bytes",
"form_urlencoded",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"itoa",
"matchit 0.8.4",
"memchr",
"mime",
"percent-encoding",
@@ -576,13 +629,33 @@ dependencies = [
"tracing",
]
[[package]]
name = "axum-core"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "axum-embed"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077959a7f8cf438676af90b483304528eb7e16eadadb7f44e9ada4f9dceb9e62"
dependencies = [
"axum-core",
"axum-core 0.4.5",
"chrono",
"http",
"mime_guess",
@@ -597,7 +670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5260ed0ecc8ace8e7e61a7406672faba598c8a86b8f4742fcdde0ddc979a318f"
dependencies = [
"async-trait",
"axum",
"axum 0.7.7",
"form_urlencoded",
"serde",
"subtle",
@@ -621,6 +694,17 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "axum-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "axum-messages"
version = "0.7.0"
@@ -628,7 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e85c86a8bd84f54833bca296a0204bd865958ade62bacadeae92dda34cfb8a"
dependencies = [
"async-trait",
"axum-core",
"axum-core 0.4.5",
"http",
"parking_lot",
"serde",
@@ -1304,6 +1388,45 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "console-api"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8030735ecb0d128428b64cd379809817e620a40e5001c54465b99ec5feec2857"
dependencies = [
"futures-core",
"prost",
"prost-types",
"tonic",
"tracing-core",
]
[[package]]
name = "console-subscriber"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6539aa9c6a4cd31f4b1c040f860a1eac9aa80e7df6b05d506a6e7179936d6a01"
dependencies = [
"console-api",
"crossbeam-channel",
"crossbeam-utils",
"futures-task",
"hdrhistogram",
"humantime",
"hyper-util",
"prost",
"prost-types",
"serde",
"serde_json",
"thread_local",
"tokio",
"tokio-stream",
"tonic",
"tracing",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "const-oid"
version = "0.9.6"
@@ -1935,6 +2058,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "downcast"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
[[package]]
name = "downcast-rs"
version = "1.2.1"
@@ -1979,7 +2108,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "easytier"
version = "2.4.0"
version = "2.4.5"
dependencies = [
"aes-gcm",
"anyhow",
@@ -2000,6 +2129,7 @@ dependencies = [
"cidr",
"clap",
"clap_complete",
"console-subscriber",
"crossbeam",
"dashmap",
"dbus",
@@ -2017,6 +2147,7 @@ dependencies = [
"hickory-proto",
"hickory-resolver",
"hickory-server",
"hmac",
"http",
"http_req",
"humansize",
@@ -2033,6 +2164,7 @@ dependencies = [
"network-interface",
"nix 0.29.0",
"once_cell",
"openssl",
"parking_lot",
"percent-encoding",
"petgraph 0.8.1",
@@ -2058,12 +2190,14 @@ dependencies = [
"serde_json",
"serial_test",
"service-manager",
"sha2",
"smoltcp",
"socket2",
"stun_codec",
"sys-locale",
"tabled",
"tachyonix",
"tempfile",
"thiserror 1.0.63",
"thunk-rs",
"tikv-jemalloc-ctl",
@@ -2080,7 +2214,6 @@ dependencies = [
"toml 0.8.19",
"tonic-build",
"tracing",
"tracing-appender",
"tracing-subscriber",
"tun-easytier",
"url",
@@ -2098,6 +2231,19 @@ dependencies = [
"zstd",
]
[[package]]
name = "easytier-android-jni"
version = "0.1.0"
dependencies = [
"android_logger",
"easytier",
"jni",
"log",
"once_cell",
"serde",
"serde_json",
]
[[package]]
name = "easytier-ffi"
version = "0.1.0"
@@ -2112,7 +2258,7 @@ dependencies = [
[[package]]
name = "easytier-gui"
version = "2.4.0"
version = "2.4.5"
dependencies = [
"anyhow",
"chrono",
@@ -2161,12 +2307,49 @@ dependencies = [
]
[[package]]
name = "easytier-web"
version = "2.4.0"
name = "easytier-uptime"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"axum",
"axum 0.8.4",
"chrono",
"clap",
"dashmap",
"easytier",
"futures",
"jsonwebtoken",
"mockall",
"once_cell",
"parking_lot",
"reqwest",
"sea-orm",
"sea-orm-migration",
"serde",
"serde_json",
"serde_yaml",
"sqlx",
"tempfile",
"thiserror 1.0.63",
"tokio",
"tokio-test",
"tokio-util",
"toml 0.8.19",
"tower 0.5.2",
"tower-http",
"tracing",
"tracing-subscriber",
"uuid",
"validator",
]
[[package]]
name = "easytier-web"
version = "2.4.5"
dependencies = [
"anyhow",
"async-trait",
"axum 0.7.7",
"axum-embed",
"axum-login",
"axum-messages",
@@ -2365,6 +2548,16 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
"log",
"regex",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -2443,9 +2636,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.1.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
@@ -2569,6 +2762,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fragile"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619"
[[package]]
name = "funty"
version = "2.0.0"
@@ -3192,10 +3391,23 @@ dependencies = [
]
[[package]]
name = "heapless"
version = "0.8.0"
name = "hdrhistogram"
version = "7.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
dependencies = [
"base64 0.21.7",
"byteorder",
"flate2",
"nom",
"num-traits",
]
[[package]]
name = "heapless"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1edcd5a338e64688fbdcb7531a846cfd3476a54784dcb918a0844682bc7ada5"
dependencies = [
"hash32",
"stable_deref_trait",
@@ -3263,7 +3475,7 @@ dependencies = [
"futures-channel",
"futures-io",
"futures-util",
"idna",
"idna 1.0.3",
"ipnet",
"once_cell",
"rand 0.9.1",
@@ -3486,6 +3698,19 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
@@ -3678,6 +3903,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "1.0.3"
@@ -4012,6 +4247,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "jsonwebtoken"
version = "9.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
dependencies = [
"base64 0.22.1",
"js-sys",
"pem",
"ring",
"serde",
"serde_json",
"simple_asn1",
]
[[package]]
name = "kcp-sys"
version = "0.1.0"
@@ -4339,6 +4589,12 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]]
name = "matchit"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "matrixmultiply"
version = "0.3.9"
@@ -4454,6 +4710,33 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mockall"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48"
dependencies = [
"cfg-if",
"downcast",
"fragile",
"lazy_static",
"mockall_derive",
"predicates",
"predicates-tree",
]
[[package]]
name = "mockall_derive"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "moka"
version = "0.12.10"
@@ -5234,6 +5517,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.5.2+3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.103"
@@ -5242,6 +5534,7 @@ checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]
@@ -5623,6 +5916,26 @@ dependencies = [
"siphasher 0.3.11",
]
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
@@ -5857,6 +6170,32 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "predicates"
version = "3.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573"
dependencies = [
"anstyle",
"predicates-core",
]
[[package]]
name = "predicates-core"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa"
[[package]]
name = "predicates-tree"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c"
dependencies = [
"predicates-core",
"termtree",
]
[[package]]
name = "prefix-trie"
version = "0.7.0"
@@ -7321,6 +7660,19 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.7.1",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "serde_yml"
version = "0.0.11"
@@ -7500,6 +7852,18 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "simple_asn1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
dependencies = [
"num-bigint",
"num-traits",
"thiserror 2.0.11",
"time",
]
[[package]]
name = "siphasher"
version = "0.3.11"
@@ -7533,8 +7897,7 @@ dependencies = [
[[package]]
name = "smoltcp"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb"
source = "git+https://github.com/smoltcp-rs/smoltcp.git?rev=0a926767a68bc88d5512afefa7529c5ecdade4ea#0a926767a68bc88d5512afefa7529c5ecdade4ea"
dependencies = [
"bitflags 1.3.2",
"byteorder",
@@ -8480,15 +8843,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.12.0"
version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53"
dependencies = [
"cfg-if",
"fastrand",
"getrandom 0.3.2",
"once_cell",
"rustix 0.38.34",
"windows-sys 0.59.0",
"rustix 1.0.7",
"windows-sys 0.60.2",
]
[[package]]
@@ -8512,6 +8875,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "termtree"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683"
[[package]]
name = "thiserror"
version = "1.0.63"
@@ -8688,6 +9057,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.52.0",
]
@@ -8737,15 +9107,28 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.15"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
@@ -8754,8 +9137,12 @@ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"futures-util",
"hashbrown 0.14.5",
"pin-project-lite",
"slab",
"tokio",
]
@@ -8889,6 +9276,36 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum 0.7.7",
"base64 0.22.1",
"bytes",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"socket2",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tonic-build"
version = "0.12.1"
@@ -8908,6 +9325,15 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
@@ -8936,7 +9362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fd0118512cf0b3768f7fcccf0bef1ae41d68f2b45edc1e77432b36c97c56c6d"
dependencies = [
"async-trait",
"axum-core",
"axum-core 0.4.5",
"cookie",
"futures-util",
"http",
@@ -9002,7 +9428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb6abbfcaf6436ec5a772cd9f965401da12db793e404ae6134eac066fa5a04f3"
dependencies = [
"async-trait",
"axum-core",
"axum-core 0.4.5",
"base64 0.22.1",
"futures",
"http",
@@ -9054,18 +9480,6 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror 1.0.63",
"time",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
@@ -9355,6 +9769,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -9368,7 +9788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"idna 1.0.3",
"percent-encoding",
"serde",
]
@@ -9440,6 +9860,36 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "validator"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e"
dependencies = [
"idna 0.5.0",
"once_cell",
"regex",
"serde",
"serde_derive",
"serde_json",
"url",
"validator_derive",
]
[[package]]
name = "validator_derive"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10"
dependencies = [
"darling",
"once_cell",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "valuable"
version = "0.1.0"

View File

@@ -6,6 +6,8 @@ members = [
"easytier-rpc-build",
"easytier-web",
"easytier-contrib/easytier-ffi",
"easytier-contrib/easytier-uptime",
"easytier-contrib/easytier-android-jni",
]
default-members = ["easytier", "easytier-web"]
exclude = [
@@ -14,6 +16,7 @@ exclude = [
[profile.dev]
panic = "unwind"
debug = 2
[profile.release]
panic = "abort"

View File

@@ -27,6 +27,10 @@
"name": "openharmony",
"path": "easytier-contrib/easytier-ohrs"
},
{
"name": "uptime",
"path": "easytier-contrib/easytier-uptime"
},
{
"name": "vpnservice",
"path": "tauri-plugin-vpnservice"
@@ -44,5 +48,10 @@
"prettier.enable": false,
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications",
"editor.formatOnPaste": false,
"editor.formatOnType": true,
"[nix]": {
"editor.formatOnSave": false,
},
}
}

View File

@@ -59,7 +59,7 @@ cargo install --git https://github.com/EasyTier/EasyTier.git easytier
# See https://easytier.cn/en/guide/installation.html#installation-methods
# 4. Linux Quick Install
wget -O- https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh | sudo bash
wget -O- https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh | sudo bash -s install
# 5. MacOS via Homebrew
brew tap brewforge/chinese
@@ -105,9 +105,9 @@ After successful execution, you can check the network status using `easytier-cli
```text
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.1-70e69a38~ |
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.1-70e69a38~ |
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.1-70e69a38~ |
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.5-70e69a38~ |
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.5-70e69a38~ |
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.5-70e69a38~ |
```
You can test connectivity between nodes:
@@ -286,7 +286,10 @@ sudo easytier-core --network-name mysharednode --network-secret mysharednode
### Contact Us
- 💬 **[Telegram Group](https://t.me/easytier)**
- 👥 **[QQ Group: 949700262](https://qm.qq.com/cgi-bin/qm/qr?k=kC8YJ6Jb8vWJIDbZrZJB8pB5YZgPJA5-)**
- 👥 **[QQ Group]**
- No.1 [949700262](https://qm.qq.com/q/wFoTUChqZW)
- No.2 [837676408](https://qm.qq.com/q/4V33DrfgHe)
- No.3 [957189589](https://qm.qq.com/q/YNyTQjwlai)
## License

View File

@@ -59,7 +59,7 @@ cargo install --git https://github.com/EasyTier/EasyTier.git easytier
# 参见 https://easytier.cn/guide/installation.html#%E5%AE%89%E8%A3%85%E6%96%B9%E5%BC%8F
# 4. Linux 快速安装
wget -O- https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh | sudo bash
wget -O- https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh | sudo bash -s install
# 5. MacOS 通过 Homebrew 安装
brew tap brewforge/chinese
@@ -106,9 +106,9 @@ sudo easytier-core -d --network-name abc --network-secret abc -p tcp://public.ea
```text
| ipv4 | hostname | cost | lat_ms | loss_rate | rx_bytes | tx_bytes | tunnel_proto | nat_type | id | version |
| ------------ | -------------- | ----- | ------ | --------- | -------- | -------- | ------------ | -------- | ---------- | --------------- |
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.1-70e69a38~ |
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.1-70e69a38~ |
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.1-70e69a38~ |
| 10.126.126.1 | abc-1 | Local | * | * | * | * | udp | FullCone | 439804259 | 2.4.5-70e69a38~ |
| 10.126.126.2 | abc-2 | p2p | 3.452 | 0 | 17.33 kB | 20.42 kB | udp | FullCone | 390879727 | 2.4.5-70e69a38~ |
| | PublicServer_a | p2p | 27.796 | 0.000 | 50.01 kB | 67.46 kB | tcp | Unknown | 3771642457 | 2.4.5-70e69a38~ |
```
您可以测试节点之间的连通性:
@@ -287,7 +287,10 @@ sudo easytier-core --network-name mysharednode --network-secret mysharednode
### 联系我们
- 💬 **[Telegram 群组](https://t.me/easytier)**
- 👥 **[QQ 群949700262](https://qm.qq.com/cgi-bin/qm/qr?k=kC8YJ6Jb8vWJIDbZrZJB8pB5YZgPJA5-)**
- 👥 **QQ 群**
- 一群 [949700262](https://qm.qq.com/q/wFoTUChqZW)
- 二群 [837676408](https://qm.qq.com/q/4V33DrfgHe)
- 三群 [957189589](https://qm.qq.com/q/YNyTQjwlai)
## 许可证

116
android.nix Normal file
View File

@@ -0,0 +1,116 @@
# Android build environment
{
pkgs,
nixpkgs,
system,
}:
let
androidEnv = pkgs.callPackage "${nixpkgs}/pkgs/development/mobile/androidenv" {
inherit pkgs;
licenseAccepted = true;
};
includeAuto = pkgs.stdenv.hostPlatform.isx86_64 || pkgs.stdenv.hostPlatform.isDarwin;
ndkVersion = "26.1.10909125";
ndkVersions = [ ndkVersion ];
sdkArgs = {
includeNDK = true;
includeSources = true;
includeSystemImages = false;
includeEmulator = false;
inherit ndkVersions;
useGoogleAPIs = true;
useGoogleTVAddOns = true;
buildToolsVersions = [ "34.0.0" ];
numLatestPlatformVersions = 10;
includeExtras = [
"extras;google;gcm"
]
++ pkgs.lib.optionals includeAuto [
"extras;google;auto"
];
extraLicenses = [
"android-sdk-preview-license"
"android-googletv-license"
"android-sdk-arm-dbt-license"
"google-gdk-license"
"intel-android-extra-license"
"intel-android-sysimage-license"
"mips-android-sysimage-license"
];
};
androidComposition = androidEnv.composeAndroidPackages sdkArgs;
androidSdk = androidComposition.androidsdk;
platformTools = androidComposition.platform-tools;
cmake = androidComposition.cmake;
ndkHostTag =
if pkgs.stdenv.isLinux then
"linux-x86_64"
else if pkgs.stdenv.isDarwin then
"darwin-x86_64"
else
"";
ndkToolchain = "${androidSdk}/libexec/android-sdk/ndk/${ndkVersion}/toolchains/llvm/prebuilt/${ndkHostTag}";
in
{
inherit
androidSdk
platformTools
cmake
ndkToolchain
ndkVersion
;
# List of packages required for Android development
packages = [
pkgs.jdk # openjdk 21
androidSdk
platformTools
cmake
pkgs.glibc_multi.dev
];
# Provide Rust extensions/targets for use by the upper-level flake
rust = {
extensions = [ "rust-std" ];
targets = [
"aarch64-linux-android"
"armv7-linux-androideabi"
"i686-linux-android"
"x86_64-linux-android"
];
};
# Android environment variables and shellHook
envVars = {
LANG = "C.UTF-8";
LC_ALL = "C.UTF-8";
JAVA_HOME = "${pkgs.jdk}/lib/openjdk";
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "\${ANDROID_SDK_ROOT}/ndk-bundle";
NDK_HOME = "${androidSdk}/libexec/android-sdk/ndk/${ndkVersion}";
LIBCLANG_PATH = "${ndkToolchain}/lib";
KCP_SYS_EXTRA_HEADER_PATH = "${ndkToolchain}/lib/clang/19/include:${pkgs.glibc_multi.dev}/include";
ZSTD_SYS_STATIC = "1";
BINDGEN_EXTRA_CLANG_ARGS = "--sysroot=${ndkToolchain}/sysroot -I${ndkToolchain}/lib/clang/17/include ";
shellHook = ''
echo "Android environment activated"
export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=$(echo "$ANDROID_SDK_ROOT/build-tools/"*"/aapt2")"
cmake_root="$(echo "$ANDROID_SDK_ROOT/cmake/"*/)"
export PATH="$cmake_root/bin:$PATH"
unset NIX_CFLAGS_COMPILE
unset NIX_CFLAGS_COMPILE_FOR_BUILD
cat <<EOF > easytier-gui/local.properties
sdk.dir=$ANDROID_SDK_ROOT
ndk.dir=$ANDROID_NDK_ROOT
cmake.dir=$cmake_root
EOF
'';
};
}

View File

@@ -0,0 +1,16 @@
[package]
name = "easytier-android-jni"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
jni = "0.21"
once_cell = "1.18.0"
log = "0.4"
android_logger = "0.13"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
easytier = { path = "../../easytier" }

View File

@@ -0,0 +1,267 @@
# EasyTier Android JNI
这是 EasyTier 的 Android JNI 绑定库,允许 Android 应用程序调用 EasyTier 的网络功能。
## 功能特性
- 🚀 完整的 EasyTier FFI 接口封装
- 📱 原生 Android JNI 支持
- 🔧 支持多种 Android 架构 (arm64-v8a, armeabi-v7a, x86, x86_64)
- 🛡️ 类型安全的 Java 接口
- 📝 详细的错误处理和日志记录
## 支持的架构
- `arm64-v8a` (aarch64-linux-android)
- `armeabi-v7a` (armv7-linux-androideabi)
- `x86` (i686-linux-android)
- `x86_64` (x86_64-linux-android)
## 构建要求
### 系统要求
- Rust 1.70+
- Android NDK r21+
- Linux/macOS 开发环境
### 环境设置
1. **安装 Rust**
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
```
2. **安装 Android NDK**
- 下载 Android NDK: https://developer.android.com/ndk/downloads
- 解压到合适的目录
- 设置环境变量:
```bash
export ANDROID_NDK_ROOT=/path/to/android-ndk
```
3. **添加 Android 目标**
```bash
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
```
## 构建步骤
1. **克隆项目并进入目录**
```bash
cd /path/to/EasyTier/easytier-contrib/easytier-android-jni
```
2. **运行构建脚本**
```bash
./build.sh
```
3. **构建完成后,库文件将生成在 `target/android/` 目录下**
```
target/android/
├── arm64-v8a/
│ └── libeasytier_android_jni.so
├── armeabi-v7a/
│ └── libeasytier_android_jni.so
├── x86/
│ └── libeasytier_android_jni.so
└── x86_64/
└── libeasytier_android_jni.so
```
## Android 项目集成
### 1. 复制库文件
将生成的 `.so` 文件复制到您的 Android 项目中:
```
your-android-project/
└── src/main/
├── jniLibs/
│ ├── arm64-v8a/
│ │ └── libeasytier_android_jni.so
│ ├── armeabi-v7a/
│ │ └── libeasytier_android_jni.so
│ ├── x86/
│ │ └── libeasytier_android_jni.so
│ └── x86_64/
│ └── libeasytier_android_jni.so
└── java/
└── com/easytier/jni/
└── EasyTierJNI.java
```
### 2. 复制 Java 接口
将 `java/com/easytier/jni/EasyTierJNI.java` 复制到您的 Android 项目的相应包路径下。
### 3. 添加权限
在 `AndroidManifest.xml` 中添加必要的权限:
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
```
## 使用示例
### 基本使用
```java
import com.easytier.jni.EasyTierJNI;
import java.util.Map;
public class EasyTierManager {
// 初始化网络实例
public void startNetwork() {
String config = """
inst_name = "my_instance"
network = "my_network"
""";
try {
// 解析配置
int result = EasyTierJNI.parseConfig(config);
if (result != 0) {
String error = EasyTierJNI.getLastError();
throw new RuntimeException("配置解析失败: " + error);
}
// 启动网络实例
result = EasyTierJNI.runNetworkInstance(config);
if (result != 0) {
String error = EasyTierJNI.getLastError();
throw new RuntimeException("网络实例启动失败: " + error);
}
System.out.println("EasyTier 网络实例启动成功");
} catch (RuntimeException e) {
System.err.println("启动失败: " + e.getMessage());
}
}
// 获取网络信息
public void getNetworkInfo() {
try {
Map<String, String> infos = EasyTierJNI.collectNetworkInfosAsMap(10);
for (Map.Entry<String, String> entry : infos.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
} catch (RuntimeException e) {
System.err.println("获取网络信息失败: " + e.getMessage());
}
}
// 停止所有实例
public void stopNetwork() {
try {
int result = EasyTierJNI.stopAllInstances();
if (result == 0) {
System.out.println("所有网络实例已停止");
}
} catch (RuntimeException e) {
System.err.println("停止网络失败: " + e.getMessage());
}
}
}
```
### VPN 服务集成
如果您要在 Android VPN 服务中使用:
```java
public class EasyTierVpnService extends VpnService {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 建立 VPN 连接
ParcelFileDescriptor vpnInterface = establishVpnInterface();
if (vpnInterface != null) {
int fd = vpnInterface.getFd();
// 设置 TUN 文件描述符
try {
EasyTierJNI.setTunFd("my_instance", fd);
} catch (RuntimeException e) {
Log.e("EasyTier", "设置 TUN FD 失败", e);
}
}
return START_STICKY;
}
private ParcelFileDescriptor establishVpnInterface() {
Builder builder = new Builder();
builder.setMtu(1500);
builder.addAddress("10.0.0.2", 24);
builder.addRoute("0.0.0.0", 0);
builder.setSession("EasyTier VPN");
return builder.establish();
}
}
```
## API 参考
### EasyTierJNI 类方法
| 方法 | 描述 | 参数 | 返回值 |
|------|------|------|--------|
| `parseConfig(String config)` | 解析 TOML 配置 | config: 配置字符串 | 0=成功, -1=失败 |
| `runNetworkInstance(String config)` | 启动网络实例 | config: 配置字符串 | 0=成功, -1=失败 |
| `setTunFd(String instanceName, int fd)` | 设置 TUN 文件描述符 | instanceName: 实例名, fd: 文件描述符 | 0=成功, -1=失败 |
| `retainNetworkInstance(String[] names)` | 保留指定实例 | names: 实例名数组 | 0=成功, -1=失败 |
| `collectNetworkInfos(int maxLength)` | 收集网络信息 | maxLength: 最大条目数 | 信息字符串数组 |
| `collectNetworkInfosAsMap(int maxLength)` | 收集网络信息为 Map | maxLength: 最大条目数 | Map<String, String> |
| `getLastError()` | 获取最后错误 | 无 | 错误消息字符串 |
| `stopAllInstances()` | 停止所有实例 | 无 | 0=成功, -1=失败 |
| `retainSingleInstance(String name)` | 保留单个实例 | name: 实例名 | 0=成功, -1=失败 |
## 故障排除
### 常见问题
1. **构建失败: "Android NDK not found"**
- 确保设置了 `ANDROID_NDK_ROOT` 环境变量
- 检查 NDK 路径是否正确
2. **运行时错误: "java.lang.UnsatisfiedLinkError"**
- 确保 `.so` 文件放在正确的 `jniLibs` 目录下
- 检查目标架构是否匹配
3. **配置解析失败**
- 检查 TOML 配置格式是否正确
- 使用 `getLastError()` 获取详细错误信息
### 调试技巧
- 启用 Android 日志查看 JNI 层的日志输出
- 使用 `adb logcat -s EasyTier-JNI` 查看相关日志
- 检查 `getLastError()` 返回的错误信息
## 许可证
本项目遵循与 EasyTier 主项目相同的许可证。
## 贡献
欢迎提交 Issue 和 Pull Request 来改进这个项目。
## 相关链接
- [EasyTier 主项目](https://github.com/EasyTier/EasyTier)
- [Android NDK 文档](https://developer.android.com/ndk)
- [Rust JNI 文档](https://docs.rs/jni/)

View File

@@ -0,0 +1,128 @@
#!/bin/bash
# EasyTier Android JNI 构建脚本
# 用于编译适用于 Android 平台的 JNI 库
# 使用 cargo-ndk 工具简化 Android 编译过程
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
REPO_ROOT=$(git rev-parse --show-toplevel)
echo -e "${GREEN}EasyTier Android JNI 构建脚本 (使用 cargo-ndk)${NC}"
echo "=============================================="
# 检查 Rust 是否安装
if ! command -v rustc &> /dev/null; then
echo -e "${RED}错误: 未找到 Rust 编译器,请先安装 Rust${NC}"
exit 1
fi
# 检查 cargo 是否安装
if ! command -v cargo &> /dev/null; then
echo -e "${RED}错误: 未找到 Cargo请先安装 Rust 工具链${NC}"
exit 1
fi
# 检查 cargo-ndk 是否安装
if ! cargo ndk --version &> /dev/null; then
echo -e "${YELLOW}cargo-ndk 未安装,正在安装...${NC}"
cargo install cargo-ndk
if ! cargo ndk --version &> /dev/null; then
echo -e "${RED}错误: cargo-ndk 安装失败${NC}"
exit 1
fi
fi
echo -e "${GREEN}cargo-ndk 版本: $(cargo ndk --version)${NC}"
# Android 目标架构映射 (cargo-ndk 使用的架构名称)
# ANDROID_TARGETS=("arm64-v8a" "armeabi-v7a" "x86" "x86_64")
ANDROID_TARGETS=("arm64-v8a")
# Android 架构到 Rust target 的映射
declare -A TARGET_MAP
TARGET_MAP["arm64-v8a"]="aarch64-linux-android"
TARGET_MAP["armeabi-v7a"]="armv7-linux-androideabi"
TARGET_MAP["x86"]="i686-linux-android"
TARGET_MAP["x86_64"]="x86_64-linux-android"
# 检查并安装所需的 Rust target
echo -e "${YELLOW}检查并安装 Android 目标架构...${NC}"
for android_target in "${ANDROID_TARGETS[@]}"; do
rust_target="${TARGET_MAP[$android_target]}"
if ! rustup target list --installed | grep -q "$rust_target"; then
echo -e "${YELLOW}安装目标架构: $rust_target (for $android_target)${NC}"
rustup target add "$rust_target"
else
echo -e "${GREEN}目标架构已安装: $rust_target (for $android_target)${NC}"
fi
done
# 创建输出目录
OUTPUT_DIR="./target/android"
mkdir -p "$OUTPUT_DIR"
# 构建函数
build_for_target() {
local android_target=$1
echo -e "${YELLOW}构建目标: $android_target${NC}"
# 首先构建 easytier-ffi
echo -e "${YELLOW}构建 easytier-ffi for $android_target${NC}"
(cd $REPO_ROOT/easytier-contrib/easytier-ffi && cargo ndk -t $android_target build --release)
# 构建 JNI 库
cargo ndk -t $android_target build --release
# 复制库文件到输出目录
# cargo-ndk 使用 Rust target 名称作为目录名,而不是 Android 架构名称
rust_target="${TARGET_MAP[$android_target]}"
mkdir -p "$OUTPUT_DIR/$android_target"
cp "$REPO_ROOT/target/$rust_target/release/libeasytier_android_jni.so" "$OUTPUT_DIR/$android_target/"
echo -e "${GREEN}库文件已复制到: $OUTPUT_DIR/$android_target/${NC}"
}
# 检查 Android NDK (cargo-ndk 会自动处理 NDK 路径)
if [ -z "$ANDROID_NDK_ROOT" ] && [ -z "$ANDROID_NDK_HOME" ] && [ -z "$NDK_HOME" ]; then
echo -e "${YELLOW}警告: 未设置 Android NDK 环境变量${NC}"
echo "cargo-ndk 将尝试自动检测 NDK 路径"
echo "如果构建失败,请设置以下环境变量之一:"
echo " - ANDROID_NDK_ROOT"
echo " - ANDROID_NDK_HOME"
echo " - NDK_HOME"
else
if [ -n "$ANDROID_NDK_ROOT" ]; then
echo -e "${GREEN}使用 Android NDK: $ANDROID_NDK_ROOT${NC}"
elif [ -n "$ANDROID_NDK_HOME" ]; then
echo -e "${GREEN}使用 Android NDK: $ANDROID_NDK_HOME${NC}"
elif [ -n "$NDK_HOME" ]; then
echo -e "${GREEN}使用 Android NDK: $NDK_HOME${NC}"
fi
fi
# 构建所有目标
echo -e "${YELLOW}开始构建所有目标架构...${NC}"
for target in "${ANDROID_TARGETS[@]}"; do
build_for_target "$target"
done
echo -e "${GREEN}构建完成!${NC}"
echo -e "${GREEN}所有库文件已生成到: $OUTPUT_DIR${NC}"
echo ""
echo "目录结构:"
ls -la "$OUTPUT_DIR"/*/
echo ""
echo -e "${YELLOW}使用说明:${NC}"
echo "1. 将生成的 .so 文件复制到您的 Android 项目的 src/main/jniLibs/ 目录下"
echo "2. 将 java/com/easytier/jni/EasyTierJNI.java 复制到您的 Android 项目中"
echo "3. 在您的 Android 代码中调用 EasyTierJNI 类的方法"
echo ""
echo -e "${GREEN}注意: 此脚本使用 cargo-ndk 工具,无需手动设置复杂的环境变量${NC}"
echo -e "${GREEN}cargo-ndk 会自动处理交叉编译所需的工具链配置${NC}"

View File

@@ -0,0 +1,56 @@
# EasyTier Android JNI 示例配置文件
# 这是一个基本的配置示例,展示如何配置 EasyTier 网络实例
# 实例名称 (必需)
inst_name = "android_instance"
# 网络名称 (必需)
network = "my_easytier_network"
# 网络密钥 (可选,用于网络加密)
# network_secret = "your_secret_key_here"
# 监听地址 (可选)
# listeners = ["tcp://0.0.0.0:11010", "udp://0.0.0.0:11010"]
# 对等节点地址 (可选)
# peers = ["tcp://peer1.example.com:11010", "udp://peer2.example.com:11010"]
# 虚拟 IP 地址 (可选)
# ipv4 = "10.144.144.1"
# 主机名 (可选)
# hostname = "android-device"
# 启用 IPv6 (可选)
# ipv6 = "fd00::1"
# 代理网络 (可选)
# proxy_networks = ["192.168.1.0/24"]
# 退出节点 (可选)
# exit_nodes = ["peer1"]
# 启用加密 (可选)
# enable_encryption = true
# 启用 IPv4 转发 (可选)
# enable_ipv4 = true
# 启用 IPv6 转发 (可选)
# enable_ipv6 = false
# MTU 设置 (可选)
# mtu = 1420
# 日志级别 (可选: error, warn, info, debug, trace)
# log_level = "info"
# 禁用 P2P (可选)
# disable_p2p = false
# 使用多路径 (可选)
# use_multi_path = true
# 延迟优先 (可选)
# latency_first = false

View File

@@ -0,0 +1,78 @@
package com.easytier.jni
/** EasyTier JNI 接口类 提供 Android 应用调用 EasyTier 网络功能的接口 */
object EasyTierJNI {
init {
// 加载本地库
System.loadLibrary("easytier_android_jni")
}
/**
* 设置 TUN 文件描述符
* @param instanceName 实例名称
* @param fd TUN 文件描述符
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic external fun setTunFd(instanceName: String, fd: Int): Int
/**
* 解析配置字符串
* @param config TOML 格式的配置字符串
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当配置解析失败时抛出异常
*/
@JvmStatic external fun parseConfig(config: String): Int
/**
* 运行网络实例
* @param config TOML 格式的配置字符串
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当实例启动失败时抛出异常
*/
@JvmStatic external fun runNetworkInstance(config: String): Int
/**
* 保留指定的网络实例,停止其他实例
* @param instanceNames 要保留的实例名称数组,传入 null 或空数组将停止所有实例
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic external fun retainNetworkInstance(instanceNames: Array<String>?): Int
/**
* 收集网络信息
* @param maxLength 最大返回条目数
* @return 包含网络信息的字符串数组,每个元素格式为 "key=value"
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic external fun collectNetworkInfos(maxLength: Int): String?
/**
* 获取最后的错误消息
* @return 错误消息字符串,如果没有错误则返回 null
*/
@JvmStatic external fun getLastError(): String?
/**
* 便利方法:停止所有网络实例
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic
fun stopAllInstances(): Int {
return retainNetworkInstance(null)
}
/**
* 便利方法:停止指定实例外的所有实例
* @param instanceName 要保留的实例名称
* @return 0 表示成功,-1 表示失败
* @throws RuntimeException 当操作失败时抛出异常
*/
@JvmStatic
fun retainSingleInstance(instanceName: String): Int {
return retainNetworkInstance(arrayOf(instanceName))
}
}

View File

@@ -0,0 +1,252 @@
package com.easytier.jni
import android.app.Activity
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.squareup.moshi.Moshi
import com.squareup.wire.WireJsonAdapterFactory
import common.Ipv4Inet
import web.NetworkInstanceRunningInfoMap
fun parseIpv4InetToString(inet: Ipv4Inet?): String? {
val addr = inet?.address?.addr ?: return null
val networkLength = inet.network_length
// 将 int32 转换为 IPv4 字符串
val ip =
String.format(
"%d.%d.%d.%d",
(addr shr 24) and 0xFF,
(addr shr 16) and 0xFF,
(addr shr 8) and 0xFF,
addr and 0xFF
)
return "$ip/$networkLength"
}
/** EasyTier 管理类 负责管理 EasyTier 实例的生命周期、监控网络状态变化、控制 VpnService */
class EasyTierManager(
private val activity: Activity,
private val instanceName: String,
private val networkConfig: String
) {
companion object {
private const val TAG = "EasyTierManager"
private const val MONITOR_INTERVAL = 3000L // 3秒监控间隔
}
private val handler = Handler(Looper.getMainLooper())
private var isRunning = false
private var currentIpv4: String? = null
private var currentProxyCidrs: List<String> = emptyList()
private var vpnServiceIntent: Intent? = null
// JSON 解析器
private val moshi = Moshi.Builder().add(WireJsonAdapterFactory()).build()
private val adapter = moshi.adapter(NetworkInstanceRunningInfoMap::class.java)
// 监控任务
private val monitorRunnable =
object : Runnable {
override fun run() {
if (isRunning) {
monitorNetworkStatus()
handler.postDelayed(this, MONITOR_INTERVAL)
}
}
}
/** 启动 EasyTier 实例和监控 */
fun start() {
if (isRunning) {
Log.w(TAG, "EasyTier 实例已经在运行中")
return
}
try {
// 启动 EasyTier 实例
val result = EasyTierJNI.runNetworkInstance(networkConfig)
if (result == 0) {
isRunning = true
Log.i(TAG, "EasyTier 实例启动成功: $instanceName")
// 开始监控网络状态
handler.post(monitorRunnable)
} else {
Log.e(TAG, "EasyTier 实例启动失败: $result")
val error = EasyTierJNI.getLastError()
Log.e(TAG, "错误信息: $error")
}
} catch (e: Exception) {
Log.e(TAG, "启动 EasyTier 实例时发生异常", e)
}
}
/** 停止 EasyTier 实例和监控 */
fun stop() {
if (!isRunning) {
Log.w(TAG, "EasyTier 实例未在运行")
return
}
isRunning = false
// 停止监控任务
handler.removeCallbacks(monitorRunnable)
try {
// 停止 VpnService
stopVpnService()
// 停止 EasyTier 实例
EasyTierJNI.stopAllInstances()
Log.i(TAG, "EasyTier 实例已停止: $instanceName")
// 重置状态
currentIpv4 = null
currentProxyCidrs = emptyList()
} catch (e: Exception) {
Log.e(TAG, "停止 EasyTier 实例时发生异常", e)
}
}
/** 监控网络状态 */
private fun monitorNetworkStatus() {
try {
val infosJson = EasyTierJNI.collectNetworkInfos(10)
if (infosJson.isNullOrEmpty()) {
Log.d(TAG, "未获取到网络信息")
return
}
val networkInfoMap = parseNetworkInfo(infosJson)
val networkInfo = networkInfoMap?.map?.get(instanceName)
if (networkInfo == null) {
Log.d(TAG, "未找到实例 $instanceName 的网络信息")
return
}
Log.d(TAG, "网络信息: $networkInfo")
// 检查实例是否正在运行
if (!networkInfo.running) {
Log.w(TAG, "EasyTier 实例未运行: ${networkInfo.error_msg}")
return
}
val newIpv4Inet = networkInfo.my_node_info?.virtual_ipv4
if (newIpv4Inet == null) {
Log.w(TAG, "EasyTier No Ipv4: $networkInfo")
return
}
// 获取当前节点的 IPv4 地址
val newIpv4 = parseIpv4InetToString(newIpv4Inet)
// 获取所有节点的 proxy_cidrs
val newProxyCidrs = mutableListOf<String>()
networkInfo.routes?.forEach { route ->
route.proxy_cidrs?.let { cidrs -> newProxyCidrs.addAll(cidrs) }
}
// 检查是否有变化
val ipv4Changed = newIpv4 != currentIpv4
val proxyCidrsChanged = newProxyCidrs != currentProxyCidrs
if (ipv4Changed || proxyCidrsChanged) {
Log.i(TAG, "网络状态发生变化:")
Log.i(TAG, " IPv4: $currentIpv4 -> $newIpv4")
Log.i(TAG, " Proxy CIDRs: $currentProxyCidrs -> $newProxyCidrs")
// 更新状态
currentIpv4 = newIpv4
currentProxyCidrs = newProxyCidrs.toList()
// 重启 VpnService
if (newIpv4 != null) {
restartVpnService(newIpv4, newProxyCidrs)
}
} else {
Log.d(TAG, "网络状态无变化 - IPv4: $currentIpv4, Proxy CIDRs: ${currentProxyCidrs.size}")
}
} catch (e: Exception) {
Log.e(TAG, "监控网络状态时发生异常", e)
}
}
/** 解析网络信息 JSON */
private fun parseNetworkInfo(jsonString: String): NetworkInstanceRunningInfoMap? {
return try {
adapter.fromJson(jsonString)
} catch (e: Exception) {
Log.e(TAG, "解析网络信息失败", e)
null
}
}
/** 重启 VpnService */
private fun restartVpnService(ipv4: String, proxyCidrs: List<String>) {
try {
// 先停止现有的 VpnService
stopVpnService()
// 启动新的 VpnService
startVpnService(ipv4, proxyCidrs)
} catch (e: Exception) {
Log.e(TAG, "重启 VpnService 时发生异常", e)
}
}
/** 启动 VpnService */
private fun startVpnService(ipv4: String, proxyCidrs: List<String>) {
try {
val intent = Intent(activity, EasyTierVpnService::class.java)
intent.putExtra("ipv4_address", ipv4)
intent.putStringArrayListExtra("proxy_cidrs", ArrayList(proxyCidrs))
intent.putExtra("instance_name", instanceName)
activity.startService(intent)
vpnServiceIntent = intent
Log.i(TAG, "VpnService 已启动 - IPv4: $ipv4, Proxy CIDRs: $proxyCidrs")
} catch (e: Exception) {
Log.e(TAG, "启动 VpnService 时发生异常", e)
}
}
/** 停止 VpnService */
private fun stopVpnService() {
try {
vpnServiceIntent?.let { intent ->
activity.stopService(intent)
Log.i(TAG, "VpnService 已停止")
}
vpnServiceIntent = null
} catch (e: Exception) {
Log.e(TAG, "停止 VpnService 时发生异常", e)
}
}
/** 获取当前状态信息 */
fun getStatus(): EasyTierStatus {
return EasyTierStatus(
isRunning = isRunning,
instanceName = instanceName,
currentIpv4 = currentIpv4,
currentProxyCidrs = currentProxyCidrs.toList()
)
}
/** 状态数据类 */
data class EasyTierStatus(
val isRunning: Boolean,
val instanceName: String,
val currentIpv4: String?,
val currentProxyCidrs: List<String>
)
}

View File

@@ -0,0 +1,143 @@
package com.easytier.jni
import android.content.Intent
import android.net.VpnService
import android.os.ParcelFileDescriptor
import android.util.Log
import kotlin.concurrent.thread
class EasyTierVpnService : VpnService() {
private var vpnInterface: ParcelFileDescriptor? = null
private var isRunning = false
private var instanceName: String? = null
companion object {
private const val TAG = "EasyTierVpnService"
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "VPN Service created")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// 获取传入的参数
val ipv4Address = intent?.getStringExtra("ipv4_address")
val proxyCidrs = intent?.getStringArrayListExtra("proxy_cidrs") ?: arrayListOf()
instanceName = intent?.getStringExtra("instance_name")
if (ipv4Address == null || instanceName == null) {
Log.e(TAG, "缺少必要参数: ipv4Address=$ipv4Address, instanceName=$instanceName")
stopSelf()
return START_NOT_STICKY
}
Log.i(
TAG,
"启动 VPN Service - IPv4: $ipv4Address, Proxy CIDRs: $proxyCidrs, Instance: $instanceName"
)
thread {
try {
setupVpnInterface(ipv4Address, proxyCidrs)
} catch (t: Throwable) {
Log.e(TAG, "VPN 设置失败", t)
stopSelf()
}
}
return START_STICKY
}
private fun setupVpnInterface(ipv4Address: String, proxyCidrs: List<String>) {
try {
// 解析 IPv4 地址和网络长度
val (ip, networkLength) = parseIpv4Address(ipv4Address)
// 1. 准备 VpnService.Builder
val builder = Builder()
builder.setSession("EasyTier VPN")
.addAddress(ip, networkLength)
.addDnsServer("223.5.5.5")
.addDnsServer("114.114.114.114")
.addDisallowedApplication("com.easytier.easytiervpn")
// 2. 添加路由表 - 为每个 proxy CIDR 添加路由
proxyCidrs.forEach { cidr ->
try {
val (routeIp, routeLength) = parseCidr(cidr)
builder.addRoute(routeIp, routeLength)
Log.d(TAG, "添加路由: $routeIp/$routeLength")
} catch (e: Exception) {
Log.w(TAG, "解析 CIDR 失败: $cidr", e)
}
}
// 3. 构建虚拟网络接口
vpnInterface = builder.establish()
if (vpnInterface == null) {
Log.e(TAG, "创建 VPN 接口失败")
return
}
Log.i(TAG, "VPN 接口创建成功")
// 4. 将 TUN 文件描述符传递给 EasyTier
instanceName?.let { name ->
val fd = vpnInterface!!.fd
val result = EasyTierJNI.setTunFd(name, fd)
if (result == 0) {
Log.i(TAG, "TUN 文件描述符设置成功: $fd")
} else {
Log.e(TAG, "TUN 文件描述符设置失败: $result")
}
}
isRunning = true
// 5. 保持服务运行
while (isRunning && vpnInterface != null) {
Thread.sleep(1000)
}
} catch (t: Throwable) {
Log.e(TAG, "VPN 接口设置过程中发生错误", t)
} finally {
cleanup()
}
}
/** 解析 IPv4 地址,返回 IP 和网络长度 */
private fun parseIpv4Address(ipv4Address: String): Pair<String, Int> {
return if (ipv4Address.contains("/")) {
val parts = ipv4Address.split("/")
Pair(parts[0], parts[1].toInt())
} else {
// 默认使用 /24 网络
Pair(ipv4Address, 24)
}
}
/** 解析 CIDR返回 IP 和网络长度 */
private fun parseCidr(cidr: String): Pair<String, Int> {
val parts = cidr.split("/")
if (parts.size != 2) {
throw IllegalArgumentException("无效的 CIDR 格式: $cidr")
}
return Pair(parts[0], parts[1].toInt())
}
private fun cleanup() {
isRunning = false
vpnInterface?.close()
vpnInterface = null
Log.i(TAG, "VPN 接口已清理")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "VPN Service destroyed")
cleanup()
}
}

View File

@@ -0,0 +1,41 @@
# 使用说明
1. 需要将 proto 文件放入 app/src/main/proto
2. android/gradle/libs.versions.toml 中加入依赖
```
# Wire 核心运行时
android-wire-runtime = { group = "com.squareup.wire", name = "wire-runtime", version = "5.3.11" }
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
android-wire-moshi-adapter = { group = "com.squareup.wire", name = "wire-moshi-adapter", version = "5.3.11" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version = "1.9.0" }
```
3. build.gradle.kts 中加入
```
plugins {
...
alias(libs.plugins.wire)
}
dependencies {
...
implementation(libs.android.wire.runtime)
implementation(libs.android.wire.moshi.adapter)
implementation(libs.moshi)
}
...
wire {
kotlin {
rpcRole = "none"
}
}
```
4. 调用 easytier-contrib/easytier-android-jni/build.sh 生成 jni 和 ffi 的 so 文件。
并将生成的 so 文件放到 android/app/src/main/jniLibs/arm64-v8a 目录下。
5. 使用 EasyTierManager 可以拉起 EasyTier 实例并启动 Android VpnService 组件。

View File

@@ -0,0 +1,319 @@
use easytier::proto::api::manage::{NetworkInstanceRunningInfo, NetworkInstanceRunningInfoMap};
use jni::objects::{JClass, JObjectArray, JString};
use jni::sys::{jint, jstring};
use jni::JNIEnv;
use once_cell::sync::Lazy;
use std::ffi::{CStr, CString};
use std::ptr;
// 定义 KeyValuePair 结构体
#[repr(C)]
#[derive(Clone, Copy)]
pub struct KeyValuePair {
pub key: *const std::ffi::c_char,
pub value: *const std::ffi::c_char,
}
// 声明外部 C 函数
extern "C" {
fn set_tun_fd(inst_name: *const std::ffi::c_char, fd: std::ffi::c_int) -> std::ffi::c_int;
fn get_error_msg(out: *mut *const std::ffi::c_char);
fn free_string(s: *const std::ffi::c_char);
fn parse_config(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int;
fn run_network_instance(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int;
fn retain_network_instance(
inst_names: *const *const std::ffi::c_char,
length: usize,
) -> std::ffi::c_int;
fn collect_network_infos(infos: *mut KeyValuePair, max_length: usize) -> std::ffi::c_int;
}
// 初始化 Android 日志
static LOGGER_INIT: Lazy<()> = Lazy::new(|| {
android_logger::init_once(
android_logger::Config::default()
.with_max_level(log::LevelFilter::Debug)
.with_tag("EasyTier-JNI"),
);
});
// 辅助函数:从 Java String 转换为 CString
fn jstring_to_cstring(env: &mut JNIEnv, jstr: &JString) -> Result<CString, String> {
let java_str = env
.get_string(jstr)
.map_err(|e| format!("Failed to get string: {:?}", e))?;
let rust_str = java_str.to_str().map_err(|_| "Invalid UTF-8".to_string())?;
CString::new(rust_str).map_err(|_| "String contains null byte".to_string())
}
// 辅助函数:获取错误消息
fn get_last_error() -> Option<String> {
unsafe {
let mut error_ptr: *const std::ffi::c_char = ptr::null();
get_error_msg(&mut error_ptr);
if error_ptr.is_null() {
None
} else {
let error_cstr = CStr::from_ptr(error_ptr);
let error_str = error_cstr.to_string_lossy().into_owned();
free_string(error_ptr);
Some(error_str)
}
}
}
// 辅助函数:抛出 Java 异常
fn throw_exception(env: &mut JNIEnv, message: &str) {
let _ = env.throw_new("java/lang/RuntimeException", message);
}
/// 设置 TUN 文件描述符
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_setTunFd(
mut env: JNIEnv,
_class: JClass,
inst_name: JString,
fd: jint,
) -> jint {
Lazy::force(&LOGGER_INIT);
let inst_name_cstr = match jstring_to_cstring(&mut env, &inst_name) {
Ok(cstr) => cstr,
Err(e) => {
throw_exception(&mut env, &format!("Invalid instance name: {}", e));
return -1;
}
};
unsafe {
let result = set_tun_fd(inst_name_cstr.as_ptr(), fd);
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
result
}
}
/// 解析配置
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_parseConfig(
mut env: JNIEnv,
_class: JClass,
config: JString,
) -> jint {
Lazy::force(&LOGGER_INIT);
let config_cstr = match jstring_to_cstring(&mut env, &config) {
Ok(cstr) => cstr,
Err(e) => {
throw_exception(&mut env, &format!("Invalid config string: {}", e));
return -1;
}
};
unsafe {
let result = parse_config(config_cstr.as_ptr());
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
result
}
}
/// 运行网络实例
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_runNetworkInstance(
mut env: JNIEnv,
_class: JClass,
config: JString,
) -> jint {
Lazy::force(&LOGGER_INIT);
let config_cstr = match jstring_to_cstring(&mut env, &config) {
Ok(cstr) => cstr,
Err(e) => {
throw_exception(&mut env, &format!("Invalid config string: {}", e));
return -1;
}
};
unsafe {
let result = run_network_instance(config_cstr.as_ptr());
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
result
}
}
/// 保持网络实例
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_retainNetworkInstance(
mut env: JNIEnv,
_class: JClass,
instance_names: JObjectArray,
) -> jint {
Lazy::force(&LOGGER_INIT);
// 处理 null 数组的情况
if instance_names.is_null() {
unsafe {
let result = retain_network_instance(ptr::null(), 0);
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
return result;
}
}
// 获取数组长度
let array_length = match env.get_array_length(&instance_names) {
Ok(len) => len as usize,
Err(e) => {
throw_exception(&mut env, &format!("Failed to get array length: {:?}", e));
return -1;
}
};
// 如果数组为空,停止所有实例
if array_length == 0 {
unsafe {
let result = retain_network_instance(ptr::null(), 0);
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
return result;
}
}
// 转换 Java 字符串数组为 C 字符串数组
let mut c_strings = Vec::with_capacity(array_length);
let mut c_string_ptrs = Vec::with_capacity(array_length);
for i in 0..array_length {
let java_string = match env.get_object_array_element(&instance_names, i as i32) {
Ok(obj) => obj,
Err(e) => {
throw_exception(
&mut env,
&format!("Failed to get array element {}: {:?}", i, e),
);
return -1;
}
};
if java_string.is_null() {
continue; // 跳过 null 元素
}
let jstring = JString::from(java_string);
let c_string = match jstring_to_cstring(&mut env, &jstring) {
Ok(cstr) => cstr,
Err(e) => {
throw_exception(
&mut env,
&format!("Invalid instance name at index {}: {}", i, e),
);
return -1;
}
};
c_string_ptrs.push(c_string.as_ptr());
c_strings.push(c_string); // 保持 CString 的所有权
}
unsafe {
let result = retain_network_instance(c_string_ptrs.as_ptr(), c_string_ptrs.len());
if result != 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
}
result
}
}
/// 收集网络信息
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_collectNetworkInfos(
mut env: JNIEnv,
_class: JClass,
) -> jstring {
Lazy::force(&LOGGER_INIT);
const MAX_INFOS: usize = 100;
let mut infos = vec![
KeyValuePair {
key: ptr::null(),
value: ptr::null(),
};
MAX_INFOS
];
unsafe {
let count = collect_network_infos(infos.as_mut_ptr(), MAX_INFOS);
if count < 0 {
if let Some(error) = get_last_error() {
throw_exception(&mut env, &error);
}
return ptr::null_mut();
}
let mut ret = NetworkInstanceRunningInfoMap::default();
// 使用 serde_json 构建 JSON
for info in infos.iter().take(count as usize) {
let key_ptr = info.key;
let val_ptr = info.value;
if key_ptr.is_null() || val_ptr.is_null() {
break;
}
let key = CStr::from_ptr(key_ptr).to_string_lossy();
let val = CStr::from_ptr(val_ptr).to_string_lossy();
let value = match serde_json::from_str::<NetworkInstanceRunningInfo>(val.as_ref()) {
Ok(v) => v,
Err(_) => {
throw_exception(&mut env, "Failed to parse JSON");
continue;
}
};
ret.map.insert(key.to_string(), value);
}
let json_str = serde_json::to_string(&ret).unwrap_or_else(|_| "{}".to_string());
match env.new_string(&json_str) {
Ok(jstr) => jstr.into_raw(),
Err(_) => {
throw_exception(&mut env, "Failed to create JSON string");
ptr::null_mut()
}
}
}
}
/// 获取最后的错误信息
#[no_mangle]
pub extern "system" fn Java_com_easytier_jni_EasyTierJNI_getLastError(
env: JNIEnv,
_class: JClass,
) -> jstring {
match get_last_error() {
Some(error) => match env.new_string(&error) {
Ok(jstr) => jstr.into_raw(),
Err(_) => ptr::null_mut(),
},
None => ptr::null_mut(),
}
}

View File

@@ -29,8 +29,10 @@ fn set_error_msg(msg: &str) {
msg_buf[..len].copy_from_slice(bytes);
}
/// # Safety
/// Set the tun fd
#[no_mangle]
pub extern "C" fn set_tun_fd(
pub unsafe extern "C" fn set_tun_fd(
inst_name: *const std::ffi::c_char,
fd: std::ffi::c_int,
) -> std::ffi::c_int {
@@ -43,18 +45,23 @@ pub extern "C" fn set_tun_fd(
if !INSTANCE_NAME_ID_MAP.contains_key(&inst_name) {
return -1;
}
match INSTANCE_MANAGER.set_tun_fd(&INSTANCE_NAME_ID_MAP.get(&inst_name).unwrap().value(), fd) {
Ok(_) => {
0
}
Err(_) => {
-1
}
let inst_id = *INSTANCE_NAME_ID_MAP
.get(&inst_name)
.as_ref()
.unwrap()
.value();
match INSTANCE_MANAGER.set_tun_fd(&inst_id, fd) {
Ok(_) => 0,
Err(_) => -1,
}
}
/// # Safety
/// Get the last error message
#[no_mangle]
pub extern "C" fn get_error_msg(out: *mut *const std::ffi::c_char) {
pub unsafe extern "C" fn get_error_msg(out: *mut *const std::ffi::c_char) {
let msg_buf = ERROR_MSG.lock().unwrap();
if msg_buf.is_empty() {
unsafe {
@@ -78,8 +85,10 @@ pub extern "C" fn free_string(s: *const std::ffi::c_char) {
}
}
/// # Safety
/// Parse the config
#[no_mangle]
pub extern "C" fn parse_config(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
pub unsafe extern "C" fn parse_config(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
let cfg_str = unsafe {
assert!(!cfg_str.is_null());
std::ffi::CStr::from_ptr(cfg_str)
@@ -95,8 +104,10 @@ pub extern "C" fn parse_config(cfg_str: *const std::ffi::c_char) -> std::ffi::c_
0
}
/// # Safety
/// Run the network instance
#[no_mangle]
pub extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
pub unsafe extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char) -> std::ffi::c_int {
let cfg_str = unsafe {
assert!(!cfg_str.is_null());
std::ffi::CStr::from_ptr(cfg_str)
@@ -131,8 +142,10 @@ pub extern "C" fn run_network_instance(cfg_str: *const std::ffi::c_char) -> std:
0
}
/// # Safety
/// Retain the network instance
#[no_mangle]
pub extern "C" fn retain_network_instance(
pub unsafe extern "C" fn retain_network_instance(
inst_names: *const *const std::ffi::c_char,
length: usize,
) -> std::ffi::c_int {
@@ -168,13 +181,15 @@ pub extern "C" fn retain_network_instance(
return -1;
}
let _ = INSTANCE_NAME_ID_MAP.retain(|k, _| inst_names.contains(k));
INSTANCE_NAME_ID_MAP.retain(|k, _| inst_names.contains(k));
0
}
/// # Safety
/// Collect the network infos
#[no_mangle]
pub extern "C" fn collect_network_infos(
pub unsafe extern "C" fn collect_network_infos(
infos: *mut KeyValuePair,
max_length: usize,
) -> std::ffi::c_int {
@@ -233,8 +248,10 @@ mod tests {
network = "test_network"
"#;
let cstr = std::ffi::CString::new(cfg_str).unwrap();
unsafe {
assert_eq!(parse_config(cstr.as_ptr()), 0);
}
}
#[test]
fn test_run_network_instance() {
@@ -243,6 +260,8 @@ mod tests {
network = "test_network"
"#;
let cstr = std::ffi::CString::new(cfg_str).unwrap();
unsafe {
assert_eq!(run_network_instance(cstr.as_ptr()), 0);
}
}
}

View File

@@ -1,14 +1,43 @@
#!/data/adb/magisk/busybox sh
MODDIR=${0%/*}
MODULE_PROP="${MODDIR}/module.prop"
# 查找 easytier-core 进程的 PID
PID=$(pgrep easytier-core)
ET_STATUS=""
REDIR_STATUS=""
# 更新module.prop文件中的description
update_module_description() {
local status_message=$1
sed -i "/^description=/c\description=[状态]${status_message}" ${MODULE_PROP}
}
# 检查是否找到了进程
if [ -z "$PID" ]; then
echo "easytier-core 进程未找到"
if [ -f "${MODDIR}/disable" ]; then
ET_STATUS="已关闭"
elif pgrep -f 'easytier-core' >/dev/null; then
if [ -f "${MODDIR}/config/command_args"]; then
ET_STATUS="主程序已开启(启动参数模式)"
else
# 结束进程
kill $PID
echo "已结束 easytier-core 进程 (PID: $PID)"
ET_STATUS="主程序已开启(配置文件模式)"
fi
fi
#ET_STATUS不存在说明开启模块未正常运行不修改状态
if [ -n "$ET_STATUS" ]; then
if [ -f "${MODDIR}/enable_IP_rule" ]; then
rm -f "${MODDIR}/enable_IP_rule"
${MODDIR}/hotspot_iprule.sh del
REDIR_STATUS="转发已禁用"
echo "热点子网转发已禁用"
echo "[ET-NAT] IP rule disabled." >> "${MODDIR}/log.log"
else
touch "${MODDIR}/enable_IP_rule"
${MODDIR}/hotspot_iprule.sh del
${MODDIR}/hotspot_iprule.sh add_once
REDIR_STATUS="转发已激活"
echo "热点子网转发已激活,热点开启后将自动将热点加入转发网络要求已配置本地网络cidr=参数)。转发规则将随着热点开关而自动开关。该状态将保持到转发被禁用为止。"
echo "[ET-NAT] IP rule enabled." >> "${MODDIR}/log.log"
fi
update_module_description "${ET_STATUS} | ${REDIR_STATUS}"
else
echo "主程序未正常启动,请先检查配置文件"
fi

View File

@@ -0,0 +1 @@
--config-server udp://127.0.0.1:22020/admin --machine-id easytier-magisk

View File

@@ -3,5 +3,7 @@ ui_print '当前架构为' + $ARCH
ui_print '当前系统版本为' + $API
ui_print '安装目录为: /data/adb/modules/easytier_magisk'
ui_print '配置文件位置: /data/adb/modules/easytier_magisk/config/config.toml'
ui_print '修改后配置文件后在magisk app点击操作按钮即可生效'
ui_print '如果需要自定义启动参数,可将 /data/adb/modules/easytier_magisk/config/command_args_sample 重命名为 command_args并修改其中内容使用自定义启动参数时会忽略配置文件'
ui_print '修改配置文件后在magisk app禁用应用再启动即可生效'
ui_print '点击操作按钮可启动/关闭热点子网转发配合easytier的子网代理功能实现手机热点访问easytier网络'
ui_print '记得重启'

View File

@@ -5,6 +5,7 @@ CONFIG_FILE="${MODDIR}/config/config.toml"
LOG_FILE="${MODDIR}/log.log"
MODULE_PROP="${MODDIR}/module.prop"
EASYTIER="${MODDIR}/easytier-core"
REDIR_STATUS=""
# 更新module.prop文件中的description
update_module_description() {
@@ -12,6 +13,12 @@ update_module_description() {
sed -i "/^description=/c\description=[状态]${status_message}" ${MODULE_PROP}
}
if [ -f "${MODDIR}/enable_IP_rule" ]; then
REDIR_STATUS="转发已激活"
else
REDIR_STATUS="转发已禁用"
fi
if [ ! -e /dev/net/tun ]; then
if [ ! -d /dev/net ]; then
mkdir -p /dev/net
@@ -22,7 +29,7 @@ fi
while true; do
if ls $MODDIR | grep -q "disable"; then
update_module_description "关闭中"
update_module_description "关闭中 | ${REDIR_STATUS}"
if pgrep -f 'easytier-core' >/dev/null; then
echo "开关控制$(date "+%Y-%m-%d %H:%M:%S") 进程已存在,正在关闭 ..."
pkill easytier-core # 关闭进程
@@ -35,10 +42,20 @@ while true; do
continue
fi
TZ=Asia/Shanghai ${EASYTIER} -c ${CONFIG_FILE} > ${LOG_FILE} &
# 如果 config 目录下存在 command_args 文件,则读取其中的内容作为启动参数
if [ -f "${MODDIR}/config/command_args" ]; then
TZ=Asia/Shanghai ${EASYTIER} $(cat ${MODDIR}/config/command_args) --hostname "$(getprop ro.product.brand)-$(getprop ro.product.model)" > ${LOG_FILE} &
sleep 5s # 等待easytier-core启动完成
update_module_description "已开启(不一定运行成功)"
update_module_description "主程序已开启(启动参数模式) | ${REDIR_STATUS}"
else
TZ=Asia/Shanghai ${EASYTIER} -c ${CONFIG_FILE} --hostname "$(getprop ro.product.brand)-$(getprop ro.product.model)" > ${LOG_FILE} &
sleep 5s # 等待easytier-core启动完成
update_module_description "主程序已开启(配置文件模式) | ${REDIR_STATUS}"
fi
ip rule add from all lookup main
if ! pgrep -f 'easytier-core' >/dev/null; then
update_module_descriptio "主程序启动失败,请检查配置文件"
fi
else
echo "开关控制$(date "+%Y-%m-%d %H:%M:%S") 进程已存在"
fi

View File

@@ -0,0 +1,104 @@
#!/system/bin/sh
MODDIR=${0%/*}
CONFIG_FILE="${MODDIR}/config/config.toml"
LOG_FILE="${MODDIR}/log.log"
ACTION="$1" # 参数add add_once del
# 获取接口/IP
get_et_iface() {
awk '
BEGIN { IGNORECASE = 1 }
/^[[:space:]]*dev_name[[:space:]]*=/ {
val = $0
sub(/^[^=]*=[[:space:]]*/, "", val)
gsub(/[" \t]/, "", val)
print val
exit
}
' "$CONFIG_FILE"
}
get_tun_iface() {
ip link | awk -F': ' '/ tun[[:alnum:]]+/ {print $2; exit}'
}
get_hot_iface() {
ip link | awk -F': ' '/(^| )(swlan[[:alnum:]_]*|softap[[:alnum:]_]*|p2p-wlan[[:alnum:]_]*|ap[[:alnum:]_]*)\:/ {print $2; exit}' | cut -d'@' -f1 | head -n1
}
get_usb_iface() {
ip link | awk -F': ' '/(^| )(usb[[:alnum:]_]*|rndis[[:alnum:]_]*|eth[[:alnum:]_]*)\:/ {print $2; exit}' | cut -d'@' -f1 | head -n1
}
get_hot_cidr() {
ip -4 addr show dev "$1" | awk '/inet /{print $2; exit}'
}
set_nat_rules() {
ET_IFACE=$(get_et_iface)
[ -z "$ET_IFACE" ] && ET_IFACE="$(get_tun_iface)"
HOT_IFACE=$(get_hot_iface)
USB_IFACE=$(get_usb_iface)
HOT_CIDR=$(get_hot_cidr "$HOT_IFACE")
USB_CIDR=$(get_hot_cidr "$USB_IFACE")
# 如果热点关闭就删除自定义链
[ -n "$ET_IFACE" ] && { [ -n "$HOT_CIDR" ] || [ -n "$USB_CIDR" ]; } || return 1
# 创建自定义链(如不存在)
iptables -t nat -N ET_NAT 2>/dev/null
iptables -N ET_FWD 2>/dev/null
# 确保主链首条跳转到自定义链
iptables -t nat -C POSTROUTING -j ET_NAT 2>/dev/null || \
iptables -t nat -I POSTROUTING 1 -j ET_NAT
iptables -C FORWARD -j ET_FWD 2>/dev/null || \
iptables -I FORWARD 1 -j ET_FWD
# 添加规则
if [ -n "$HOT_CIDR" ]; then
iptables -t nat -A ET_NAT -s "$HOT_CIDR" -o "$ET_IFACE" -j MASQUERADE
iptables -A ET_FWD -i "$HOT_IFACE" -o "$ET_IFACE" \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A ET_FWD -i "$ET_IFACE" -o "$HOT_IFACE" \
-m state --state ESTABLISHED,RELATED -j ACCEPT
echo "[ET-NAT] Rules applied: $HOT_IFACE $HOT_CIDR$ET_IFACE" >> "$LOG_FILE"
fi
if [ -n "$USB_CIDR" ]; then
iptables -t nat -A ET_NAT -s "$USB_CIDR" -o "$ET_IFACE" -j MASQUERADE
iptables -A ET_FWD -i "$USB_IFACE" -o "$ET_IFACE" \
-m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A ET_FWD -i "$ET_IFACE" -o "$USB_IFACE" \
-m state --state ESTABLISHED,RELATED -j ACCEPT
echo "[ET-NAT] Rules applied: $USB_IFACE $USB_CIDR$ET_IFACE" >> "$LOG_FILE"
fi
}
flush_rules() {
iptables -t nat -F ET_NAT 2>/dev/null
iptables -F ET_FWD 2>/dev/null
echo "[ET-NAT] Custom chains flushed." >> "$LOG_FILE"
}
case "$ACTION" in
add)
set_nat_rules
echo "[ET-NAT] Guard started." >> "$LOG_FILE"
ip monitor link addr | while read -r _; do
if [ -f "${MODDIR}/enable_IP_rule" ]; then
flush_rules
set_nat_rules
fi
done
;;
add_once)
flush_rules
set_nat_rules
echo "[ET-NAT] One-time rules applied." >> "$LOG_FILE"
;;
del)
flush_rules
;;
*)
echo "Usage: $0 [add|del]"
exit 1
;;
esac

View File

@@ -1,6 +1,6 @@
id=easytier_magisk
name=EasyTier_Magisk
version=v2.4.1
version=v2.4.5
versionCode=1
author=EasyTier
description=easytier magisk module @EasyTier(https://github.com/EasyTier/EasyTier)

View File

@@ -18,10 +18,7 @@ sed -i 's/$(description=)$[^"]*/\1[状态]关闭中/' "$MODDIR/module.prop"
sleep 3s
"${MODDIR}/easytier_core.sh" &
"${MODDIR}/hotspot_iprule.sh" add &
# 检查是否启用模块
while [ ! -f ${MODDIR}/disable ]; do
sleep 2
done
pkill easytier-core
# easytier_core.sh 和 hotspot_iprule.sh 都有内部循环做守护,
# 所以这里不需要再做守护了

View File

@@ -0,0 +1,9 @@
dist/
target/
.DS_Store
.idea/
package/libs
*.har
Cargo.lock

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,8 @@ crate-type=["cdylib"]
[dependencies]
ohos-hilog-binding = {version = "*", features = ["redirect"]}
easytier = { git = "https://github.com/EasyTier/EasyTier.git" }
napi-derive-ohos = "1.0.4"
napi-ohos = { version = "1.0.4", default-features = false, features = [
napi-derive-ohos = "1.1"
napi-ohos = { version = "1.1", default-features = false, features = [
"serde-json",
"latin1",
"chrono_date",
@@ -33,7 +33,7 @@ tracing = "0.1.41"
uuid = { version = "1.17.0", features = ["v4"] }
[build-dependencies]
napi-build-ohos = "1.0.4"
napi-build-ohos = "1.1"
[profile.dev]
panic = "unwind"
debug = true

View File

@@ -0,0 +1,2 @@
# 0.0.1
- init package

View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -0,0 +1,21 @@
# `easytier-ohrs`
## Install
use `ohpm` to install package.
```shell
ohpm install easytier-ohrs
```
## API
```ts
// todo
```
## Usage
```ts
// todo
```

View File

@@ -0,0 +1,4 @@
import * as api from "libeasytier_ohrs.so";
export * from 'libeasytier_ohrs.so';
export default api;

View File

@@ -0,0 +1,10 @@
{
"license": "LGPL-3.0",
"author": "easytier",
"name": "easytier-ohrs",
"description": "",
"main": "index.ets",
"version": "0.0.1",
"types": "libs/index.d.ts",
"dependencies": {}
}

View File

@@ -0,0 +1,7 @@
{
"module": {
"name": "easytier-ohrs",
"type": "har",
"deviceTypes": ["default", "tablet", "2in1"]
},
}

View File

@@ -18,13 +18,9 @@ pub struct KeyValuePair {
}
#[napi]
pub fn set_tun_fd(
inst_id: String,
fd: i32,
) -> bool {
pub fn set_tun_fd(inst_id: String, fd: i32) -> bool {
match Uuid::try_parse(&inst_id) {
Ok(uuid) => {
match INSTANCE_MANAGER.set_tun_fd(&uuid, fd) {
Ok(uuid) => match INSTANCE_MANAGER.set_tun_fd(&uuid, fd) {
Ok(_) => {
hilog_debug!("[Rust] set tun fd {} to {}.", fd, inst_id);
true
@@ -33,8 +29,7 @@ pub fn set_tun_fd(
hilog_error!("[Rust] cant set tun fd {} to {}. {}", fd, inst_id, e);
false
}
}
}
},
Err(e) => {
hilog_error!("[Rust] cant covert {} to uuid. {}", inst_id, e);
false
@@ -45,9 +40,7 @@ pub fn set_tun_fd(
#[napi]
pub fn parse_config(cfg_str: String) -> bool {
match TomlConfigLoader::new_from_str(&cfg_str) {
Ok(_) => {
true
}
Ok(_) => true,
Err(e) => {
hilog_error!("[Rust] parse config failed {}", e);
false
@@ -134,15 +127,10 @@ pub fn collect_running_network() -> Vec<String> {
#[napi]
pub fn is_running_network(inst_id: String) -> bool {
match Uuid::try_parse(&inst_id) {
Ok(uuid) => {
INSTANCE_MANAGER
.list_network_instance_ids()
.contains(&uuid)
}
Ok(uuid) => INSTANCE_MANAGER.list_network_instance_ids().contains(&uuid),
Err(e) => {
hilog_error!("[Rust] cant covert {} to uuid. {}", inst_id, e);
false
}
}
}

View File

@@ -1,7 +1,9 @@
use napi_derive_ohos::napi;
use ohos_hilog_binding::{
LogOptions, hilog_debug, hilog_error, hilog_info, hilog_warn, set_global_options,
};
use std::collections::HashMap;
use std::panic;
use napi_derive_ohos::napi;
use ohos_hilog_binding::{hilog_debug, hilog_error, hilog_info, hilog_warn, set_global_options, LogOptions};
use tracing::{Event, Subscriber};
use tracing_core::Level;
use tracing_subscriber::layer::{Context, Layer};
@@ -20,10 +22,7 @@ pub fn init_panic_hook() {
}
#[napi]
pub fn hilog_global_options(
domain: u32,
tag: String,
) {
pub fn hilog_global_options(domain: u32, tag: String) {
ohos_hilog_binding::forward_stdio_to_hilog();
set_global_options(LogOptions {
domain,
@@ -34,11 +33,9 @@ pub fn hilog_global_options(
#[napi]
pub fn init_tracing_subscriber() {
tracing_subscriber::registry()
.with(
CallbackLayer {
.with(CallbackLayer {
callback: Box::new(tracing_callback),
}
)
})
.init();
}
@@ -93,6 +90,7 @@ impl<'a> tracing::field::Visit for FieldCollector<'a> {
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.0.insert(field.name().to_string(), format!("{:?}", value));
self.0
.insert(field.name().to_string(), format!("{:?}", value));
}
}

View File

@@ -0,0 +1,17 @@
# Development Environment Configuration
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
DATABASE_PATH=uptime.db
DATABASE_MAX_CONNECTIONS=5
HEALTH_CHECK_INTERVAL=60
HEALTH_CHECK_TIMEOUT=15
HEALTH_CHECK_RETRIES=2
RUST_LOG=debug
LOG_LEVEL=debug
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=content-type,authorization
NODE_ENV=development
API_BASE_URL=/api
ENABLE_COMPRESSION=true
ENABLE_CORS=true

View File

@@ -0,0 +1,29 @@
# Server Configuration
SERVER_HOST=127.0.0.1
SERVER_PORT=8080
# Database Configuration
DATABASE_PATH=uptime.db
DATABASE_MAX_CONNECTIONS=10
# Health Check Configuration
HEALTH_CHECK_INTERVAL=30
HEALTH_CHECK_TIMEOUT=10
HEALTH_CHECK_RETRIES=3
# Logging Configuration
RUST_LOG=info
LOG_LEVEL=info
# CORS Configuration
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=content-type,authorization
# Production Configuration
NODE_ENV=development
API_BASE_URL=/api
# Security Configuration
ENABLE_COMPRESSION=true
ENABLE_CORS=true

View File

@@ -0,0 +1,21 @@
# Production Environment Configuration
SERVER_HOST=0.0.0.0
SERVER_PORT=8080
DATABASE_PATH=/var/lib/easytier-uptime/uptime.db
DATABASE_MAX_CONNECTIONS=20
HEALTH_CHECK_INTERVAL=30
HEALTH_CHECK_TIMEOUT=10
HEALTH_CHECK_RETRIES=3
RUST_LOG=info
LOG_LEVEL=info
CORS_ALLOWED_ORIGINS=https://yourdomain.com
CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=content-type,authorization
NODE_ENV=production
API_BASE_URL=/api
ENABLE_COMPRESSION=true
ENABLE_CORS=true
# Security
SECRET_KEY=your-secret-key-here
JWT_SECRET=your-jwt-secret-here

View File

@@ -0,0 +1,3 @@
*.db
*.db-shm
*.db-wal

View File

@@ -0,0 +1,63 @@
[package]
name = "easytier-uptime"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1.0", features = ["v4", "serde"] }
# Axum web framework
axum = { version = "0.8.4", features = ["macros"] }
tower-http = { version = "0.6", features = ["cors", "compression-full"] }
tower = "0.5"
# SeaORM dependencies
sea-orm = { version = "1.1", features = [
"sqlx-sqlite",
"runtime-tokio-rustls",
"macros",
"with-chrono",
"with-uuid",
"with-json"
] }
sea-orm-migration = { version = "1.1" }
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio-rustls", "chrono", "uuid"] }
# Validation
validator = { version = "0.18", features = ["derive"] }
thiserror = "1.0"
jsonwebtoken = "9.0"
# Configuration and serialization
serde_yaml = "0.9"
toml = "0.8"
# Network and async
async-trait = "0.1"
futures = "0.3"
tokio-util = { version = "0.7", features = ["full"] }
# Filesystem operations
tempfile = "3.8"
# Additional utilities
dashmap = "6.1.0"
clap = { version = "4.0", features = ["derive"] }
parking_lot = "0.12"
once_cell = "1.19"
# EasyTier core
easytier = { path = "../../easytier" }
# Testing
[dev-dependencies]
mockall = "0.12"
tokio-test = "0.4"
reqwest = "0.12"

View File

@@ -0,0 +1,272 @@
# EasyTier Uptime Monitor
一个用于监控 EasyTier 实例健康状态和运行时间的系统。
## 功能特性
- 🏥 **健康监控**: 实时监控 EasyTier 节点的健康状态
- 📊 **数据统计**: 提供详细的运行时间和响应时间统计
- 🔧 **实例管理**: 管理多个 EasyTier 实例
- 🌐 **Web界面**: 直观的 Web 管理界面
- 🚨 **告警系统**: 支持健康状态异常告警
- 📈 **图表展示**: 可视化展示监控数据
## 系统架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Database │
│ (Vue.js) │◄──►│ (Rust/Axum) │◄──►│ (SQLite) │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ Dashboard │ │ │ │ API Routes │ │ │ │ Nodes │ │
│ │ Health View │ │ │ │ Health │ │ │ │ Health │ │
│ │ Node Mgmt │ │ │ │ Instances │ │ │ │ Instances │ │
│ │ Charts │ │ │ │ Scheduler │ │ │ │ Stats │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## 快速开始
### 环境要求
- **Rust**: 1.70+
- **Node.js**: 16+
- **npm**: 8+
### 开发环境
1. **克隆项目**
```bash
git clone <repository-url>
cd easytier-uptime
```
2. **启动开发环境**
```bash
./start-dev.sh
```
3. **访问应用**
- 前端界面: http://localhost:3000
- 后端API: http://localhost:8080
- 健康检查: http://localhost:8080/health
### 生产环境
1. **启动生产环境**
```bash
./start-prod.sh
```
2. **停止生产环境**
```bash
./stop-prod.sh
```
## 配置说明
### 环境变量
#### 后端配置 (.env)
| 变量名 | 默认值 | 说明 |
|--------|--------|------|
| `SERVER_HOST` | `127.0.0.1` | 服务器监听地址 |
| `SERVER_PORT` | `8080` | 服务器端口 |
| `DATABASE_PATH` | `uptime.db` | 数据库文件路径 |
| `DATABASE_MAX_CONNECTIONS` | `10` | 数据库最大连接数 |
| `HEALTH_CHECK_INTERVAL` | `30` | 健康检查间隔(秒) |
| `HEALTH_CHECK_TIMEOUT` | `10` | 健康检查超时(秒) |
| `HEALTH_CHECK_RETRIES` | `3` | 健康检查重试次数 |
| `RUST_LOG` | `info` | 日志级别 |
| `CORS_ALLOWED_ORIGINS` | `http://localhost:3000` | 允许的跨域来源 |
| `ENABLE_CORS` | `true` | 是否启用CORS |
| `ENABLE_COMPRESSION` | `true` | 是否启用压缩 |
#### 前端配置 (frontend/.env)
| 变量名 | 默认值 | 说明 |
|--------|--------|------|
| `VITE_APP_TITLE` | `EasyTier Uptime Monitor` | 应用标题 |
| `VITE_API_BASE_URL` | `/api` | API基础URL |
| `VITE_APP_ENV` | `development` | 应用环境 |
| `VITE_ENABLE_DEV_TOOLS` | `true` | 是否启用开发工具 |
| `VITE_API_TIMEOUT` | `10000` | API超时时间(毫秒) |
## API 文档
### 健康检查
```http
GET /health
```
### 节点管理
```http
# 获取节点列表
GET /api/nodes
# 创建节点
POST /api/nodes
# 获取节点详情
GET /api/nodes/{id}
# 更新节点
PUT /api/nodes/{id}
# 删除节点
DELETE /api/nodes/{id}
```
### 健康记录
```http
# 获取节点健康历史
GET /api/nodes/{id}/health
# 获取节点健康统计
GET /api/nodes/{id}/health/stats
```
### 实例管理
```http
# 获取实例列表
GET /api/instances
# 创建实例
POST /api/instances
# 停止实例
DELETE /api/instances/{id}
```
## 测试
### 运行集成测试
```bash
./test-integration.sh
```
### 运行单元测试
```bash
cargo test
```
### 测试覆盖率
```bash
cargo tarpaulin
```
## 部署
### Docker 部署
```bash
# 构建镜像
docker build -t easytier-uptime .
# 运行容器
docker run -d -p 8080:8080 easytier-uptime
```
### 手动部署
1. **构建后端**
```bash
cargo build --release
```
2. **构建前端**
```bash
cd frontend
npm install
npm run build
cd ..
```
3. **配置环境**
```bash
cp .env.production .env
# 编辑 .env 文件
```
4. **启动服务**
```bash
./start-prod.sh
```
## 监控和日志
### 日志文件
- **后端日志**: `logs/backend.log`
- **前端日志**: `logs/frontend.log`
- **测试日志**: `test-results/`
### 健康检查
系统提供以下健康检查端点:
- `/health` - 基本健康检查
- `/api/health/stats` - 健康统计信息
- `/api/health/scheduler/status` - 调度器状态
## 故障排除
### 常见问题
1. **后端启动失败**
- 检查端口是否被占用
- 确认数据库文件权限
- 查看日志文件 `logs/backend.log`
2. **前端连接失败**
- 检查后端服务是否运行
- 确认API地址配置
- 检查CORS配置
3. **健康检查失败**
- 确认目标节点可访问
- 检查防火墙设置
- 验证健康检查配置
### 性能优化
1. **数据库优化**
- 定期清理过期数据
- 配置适当的连接池大小
- 使用索引优化查询
2. **前端优化**
- 启用代码分割
- 配置缓存策略
- 优化图片和资源
3. **网络优化**
- 启用压缩
- 配置CDN
- 优化API响应时间
## 贡献指南
1. Fork 项目
2. 创建特性分支
3. 提交更改
4. 推送到分支
5. 创建 Pull Request
## 许可证
MIT License
## 支持
如有问题或建议,请提交 Issue 或联系开发团队。

View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
{
"name": "easytier-uptime-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"easytier-uptime-frontend": "link:",
"element-plus": "^2.8.8",
"vue": "^3.5.18",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"unplugin-auto-import": "^0.18.6",
"unplugin-vue-components": "^0.27.4",
"vite": "^7.1.2"
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,326 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { healthApi } from './api'
import {
Monitor,
Plus,
CircleCheck,
CircleClose,
Loading,
Link
} from '@element-plus/icons-vue'
const router = useRouter()
const route = useRoute()
const healthStatus = ref(null)
const loading = ref(false)
// 安全地打开外部链接
const openExternalLink = (url) => {
try {
if (typeof window !== 'undefined' && window.open) {
window.open(url, '_blank')
} else {
// 备用方案:创建一个临时链接元素
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.rel = 'noopener noreferrer'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
} catch (error) {
console.error('Failed to open external link:', error)
// 最后的备用方案:直接跳转
if (typeof window !== 'undefined') {
window.location.href = url
}
}
}
// 检查后端健康状态
const checkHealth = async () => {
try {
loading.value = true
const response = await healthApi.check()
healthStatus.value = response.success
} catch (error) {
healthStatus.value = false
console.error('Health check failed:', error)
} finally {
loading.value = false
}
}
// 导航菜单项
const menuItems = [
{
path: '/',
name: 'dashboard',
title: '节点监控',
icon: 'Monitor'
},
{
path: '/submit',
name: 'submit',
title: '提交节点',
icon: 'Plus'
}
]
onMounted(() => {
checkHealth()
// 定期检查健康状态
setInterval(checkHealth, 60000) // 每分钟检查一次
})
</script>
<template>
<div id="app">
<!-- 顶部导航栏 -->
<el-header class="app-header">
<div class="header-content">
<div class="logo-section">
<el-icon size="32" color="#409EFF">
<Monitor />
</el-icon>
<h1 class="app-title">EasyTier Uptime</h1>
</div>
<el-menu :default-active="route.name" mode="horizontal" class="nav-menu"
@select="(key) => router.push(menuItems.find(item => item.name === key)?.path || '/')">
<el-menu-item v-for="item in menuItems" :key="item.name" :index="item.name">
<el-icon>
<component :is="item.icon" />
</el-icon>
<span>{{ item.title }}</span>
</el-menu-item>
</el-menu>
<div class="header-actions">
<!-- 健康状态指示器 -->
<el-tooltip :content="healthStatus === null ? '检查中...' : healthStatus ? '服务正常' : '服务异常'" placement="bottom">
<div class="health-indicator">
<el-icon :color="healthStatus === null ? '#909399' : healthStatus ? '#67C23A' : '#F56C6C'"
:class="{ 'loading': loading }">
<CircleCheck v-if="healthStatus === true" />
<CircleClose v-else-if="healthStatus === false" />
<Loading v-else />
</el-icon>
</div>
</el-tooltip>
<!-- 管理员入口 -->
<el-button type="warning" link @click="() => router.push('/admin/login')">
管理员
</el-button>
<!-- GitHub链接 -->
<el-button type="primary" link @click="() => openExternalLink('https://github.com/EasyTier/EasyTier')">
<el-icon>
<Link />
</el-icon>
GitHub
</el-button>
</div>
</div>
</el-header>
<!-- 主要内容区域 -->
<el-main class="app-main">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main>
<!-- 底部信息 -->
<el-footer class="app-footer">
<div class="footer-content">
<p>
© 2024 EasyTier Community |
<el-button type="primary" link size="small"
@click="() => openExternalLink('https://github.com/EasyTier/EasyTier')">
开源项目
</el-button>
|
<el-button type="primary" link size="small"
@click="() => openExternalLink('https://github.com/EasyTier/EasyTier/blob/main/README.md')">
使用文档
</el-button>
</p>
</div>
</el-footer>
</div>
</template>
<style>
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
background-color: #f5f7fa;
}
#app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* 顶部导航栏 */
.app-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
padding: 0;
height: 60px;
line-height: 60px;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.logo-section {
display: flex;
align-items: center;
gap: 12px;
}
.app-title {
color: white;
font-size: 20px;
font-weight: 600;
margin: 0;
}
.nav-menu {
background: transparent;
border: none;
flex: 1;
justify-content: center;
}
.nav-menu .el-menu-item {
color: rgba(255, 255, 255, 0.8);
border-bottom: 2px solid transparent;
transition: all 0.3s;
}
.nav-menu .el-menu-item:hover,
.nav-menu .el-menu-item.is-active {
color: white;
background: rgba(255, 255, 255, 0.1);
border-bottom-color: white;
}
.header-actions {
display: flex;
align-items: center;
gap: 15px;
}
.health-indicator {
display: flex;
align-items: center;
cursor: pointer;
}
.health-indicator .loading {
animation: spin 1s linear infinite;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
/* 主要内容区域 */
.app-main {
flex: 1;
padding: 0;
background-color: #f5f7fa;
}
/* 页面切换动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 底部信息 */
.app-footer {
background: white;
border-top: 1px solid #e4e7ed;
text-align: center;
height: 50px;
line-height: 50px;
}
.footer-content p {
color: #909399;
font-size: 14px;
margin: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header-content {
padding: 0 10px;
}
.app-title {
font-size: 16px;
}
.nav-menu {
display: none;
}
.header-actions {
gap: 10px;
}
}
/* Element Plus 组件样式覆盖 */
.el-card {
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.el-button {
border-radius: 6px;
}
.el-input {
border-radius: 6px;
}
.el-select {
border-radius: 6px;
}
</style>

View File

@@ -0,0 +1,155 @@
import axios from 'axios'
// 创建axios实例
const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 只在管理员相关的API请求中添加token
if (config.url && config.url.includes('/api/admin/')) {
const token = localStorage.getItem('admin_token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
// 直接返回完整的response对象让各个API方法自己处理数据格式
return response
},
error => {
console.error('API Error Details:', {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data,
config: {
url: error.config?.url,
method: error.config?.method,
headers: error.config?.headers
}
})
return Promise.reject(error)
}
)
// 节点相关API
export const nodeApi = {
// 获取节点列表
async getNodes(params = {}) {
const response = await api.get('/api/nodes', { params })
return response.data
},
// 创建节点
async createNode(data) {
const response = await api.post('/api/nodes', data)
return response.data
},
// 获取单个节点
async getNode(id) {
const response = await api.get(`/api/nodes/${id}`)
return response.data
},
// 更新节点
async updateNode(id, data) {
const response = await api.put(`/api/nodes/${id}`, data)
return response.data
},
// 删除节点
async deleteNode(id) {
const response = await api.delete(`/api/nodes/${id}`)
return response.data
},
// 获取节点健康记录
async getNodeHealth(id, params = {}) {
const response = await api.get(`/api/nodes/${id}/health`, { params })
return response.data
},
// 获取节点健康统计
async getNodeHealthStats(id, params = {}) {
const response = await api.get(`/api/nodes/${id}/health/stats`, { params })
return response.data
},
// 测试节点连接
async testConnection(data) {
const response = await api.post('/api/test_connection', data)
return response.data
}
}
// 健康检查API
export const healthApi = {
async check() {
const response = await api.get('/health')
return response.data
}
}
// 管理员API
export const adminApi = {
// 管理员登录
async login(password) {
const response = await api.post('/api/admin/login', { password })
return response.data
},
// 验证token有效性
async verifyToken() {
const response = await api.get('/api/admin/verify')
return response.data
},
// 获取所有节点(包括未审批的)
async getNodes(params = {}) {
const response = await api.get('/api/admin/nodes', { params })
return response.data
},
// 审批节点
async approveNode(id) {
const response = await api.put(`/api/admin/nodes/${id}/approve`)
return response.data
},
// 撤销审批节点
async revokeApproval(id) {
const response = await api.put(`/api/admin/nodes/${id}/revoke`)
return response.data
},
// 删除节点
async deleteNode(id) {
const response = await api.delete(`/api/admin/nodes/${id}`)
return response.data
},
// 更新节点
async updateNode(id, data) {
const response = await api.put(`/api/admin/nodes/${id}`, data)
return response.data
}
}
export default api

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,405 @@
<template>
<div class="health-timeline" :class="{ 'compact': compact }">
<div class="timeline-header">
<span class="timeline-title">最近24小时健康状态</span>
<div class="timeline-legend">
<span class="legend-item">
<span class="legend-dot perfect"></span>
<span class="legend-text">100%</span>
</span>
<span class="legend-item">
<span class="legend-dot excellent"></span>
<span class="legend-text">90-99%</span>
</span>
<span class="legend-item">
<span class="legend-dot good"></span>
<span class="legend-text">80-89%</span>
</span>
<span class="legend-item">
<span class="legend-dot fair"></span>
<span class="legend-text">60-79%</span>
</span>
<span class="legend-item">
<span class="legend-dot poor"></span>
<span class="legend-text">1-59%</span>
</span>
<span class="legend-item">
<span class="legend-dot unknown"></span>
<span class="legend-text">未知</span>
</span>
</div>
</div>
<div class="timeline-container" v-loading="loading">
<div class="timeline-grid">
<!-- 时间刻度 -->
<div class="time-labels">
<span v-for="(hour, idx) in timeLabels" :key="idx" class="time-label">
{{ hour }}
</span>
</div>
<!-- 健康状态条 -->
<div class="health-bars">
<div v-for="(segment, index) in healthSegments" :key="index" class="health-segment" :class="segment.status"
:style="{ width: segment.width + '%', backgroundColor: segment.color }" :title="getSegmentTooltip(segment)">
</div>
</div>
</div>
<!-- 统计信息 -->
<div class="health-summary">
<div class="summary-item">
<span class="summary-value">{{ uptimePercentage }}%</span>
<span class="summary-label">在线率</span>
</div>
<div class="summary-item">
<span class="summary-value">{{ avgResponseTime }}ms</span>
<span class="summary-label">平均响应</span>
</div>
<div class="summary-item">
<span class="summary-value">{{ totalChecks }}</span>
<span class="summary-label">检查次数</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { nodeApi } from '../api'
import dayjs from 'dayjs'
const props = defineProps({
nodeInfo: {
type: Object,
required: true
},
compact: {
type: Boolean,
default: true
}
})
const loading = ref(false)
const avg_response_time = ref(0)
// 时间标签24小时每4小时一个标签
const timeLabels = computed(() => {
const nodeInfo = props.nodeInfo
const granularity = nodeInfo.ring_granularity
const total_ring = nodeInfo.health_record_total_counter_ring
const totalDuration = granularity * total_ring.length
const now = dayjs(nodeInfo.last_check_time)
const startTime = now.subtract(totalDuration, 'second')
const labelCount = 6
const labelIntervalDuration = totalDuration / (labelCount - 1)
let labels = []
for (let i = 0; i < labelCount; i++) {
const time = startTime.add(i * labelIntervalDuration, 'second')
labels.push(time.format('HH:mm'))
}
return labels
})
const total_checks = computed(() => {
let total = 0
for (let i = 0; i < props.nodeInfo.health_record_total_counter_ring.length; i++) {
total += props.nodeInfo.health_record_total_counter_ring[i]
}
return total
})
const healthy_checks = computed(() => {
let total = 0
for (let i = 0; i < props.nodeInfo.health_record_healthy_counter_ring.length; i++) {
total += props.nodeInfo.health_record_healthy_counter_ring[i]
}
return total
})
const uptime_percentage = computed(() => {
return (healthy_checks.value / total_checks.value) * 100
})
// 根据成功率获取颜色
const getColorBySuccessRate = (rate) => {
if (rate === 1) {
return '#67c23a' // 100% 绿色
} else if (rate >= 0.9) {
return '#85ce61' // 90-99% 浅绿色
} else if (rate >= 0.8) {
return '#e6a23c' // 80-89% 橙色
} else if (rate >= 0.6) {
return '#f78989' // 60-79% 浅红色
} else if (rate > 0) {
return '#f56c6c' // 1-59% 红色
} else {
return '#c0c4cc' // 0% 或未知 灰色
}
}
// 健康状态分段
const healthSegments = computed(() => {
const nodeInfo = props.nodeInfo
const total_ring = nodeInfo.health_record_total_counter_ring
const healthy_ring = nodeInfo.health_record_healthy_counter_ring
const granularity = nodeInfo.ring_granularity
const totalDuration = granularity * total_ring.length
const segments = []
const now = dayjs(nodeInfo.last_check_time)
const startTime = now.subtract(totalDuration, 'second')
for (let i = total_ring.length - 1; i >= 0; i--) {
const total_counter = total_ring[i]
const healthy_counter = healthy_ring[i]
const currentTime = startTime.subtract((i + 1) * granularity, 'second')
const currentEndTime = currentTime.add(granularity, 'second')
let successRate = 0
let currentStatus = 'unknown'
if (total_counter !== 0) {
successRate = healthy_counter / total_counter
if (successRate === 1) {
currentStatus = 'perfect'
} else if (successRate >= 0.9) {
currentStatus = 'excellent'
} else if (successRate >= 0.8) {
currentStatus = 'good'
} else if (successRate >= 0.6) {
currentStatus = 'fair'
} else if (successRate > 0) {
currentStatus = 'poor'
} else {
currentStatus = 'failed'
}
}
segments.push({
status: currentStatus,
successRate: successRate,
color: getColorBySuccessRate(successRate),
width: (granularity / totalDuration) * 100,
duration: granularity / 60.0,
startTime: currentTime.format('HH:mm'),
endTime: currentEndTime.format('HH:mm'),
})
}
return segments
})
// 统计数据
const uptimePercentage = computed(() => {
return uptime_percentage.value.toFixed(1) || '0.0'
})
const avgResponseTime = computed(() => {
return (props.nodeInfo.last_response_time / 1000).toFixed(1) || '0.0'
})
const totalChecks = computed(() => {
return total_checks.value || 0
})
// 获取分段提示信息
const getSegmentTooltip = (segment) => {
const statusText = {
perfect: '完美',
excellent: '优秀',
good: '良好',
fair: '一般',
poor: '较差',
failed: '失败',
unknown: '未知'
}[segment.status] || '未知'
const successRateText = segment.successRate > 0 ? `${(segment.successRate * 100).toFixed(1)}%` : '0%'
return `${segment.startTime} - ${segment.endTime}: ${statusText} (${successRateText}) - ${Math.round(segment.duration)}分钟`
}
</script>
<style scoped>
.health-timeline {
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
margin-top: 8px;
border: 1px solid #e4e7ed;
}
.timeline-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.timeline-title {
font-size: 13px;
font-weight: 500;
color: #606266;
}
.timeline-legend {
display: flex;
gap: 12px;
}
.legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.legend-dot.perfect {
background-color: #67c23a;
}
.legend-dot.excellent {
background-color: #85ce61;
}
.legend-dot.good {
background-color: #e6a23c;
}
.legend-dot.fair {
background-color: #f78989;
}
.legend-dot.poor {
background-color: #f56c6c;
}
.legend-dot.unknown {
background-color: #c0c4cc;
}
.legend-text {
font-size: 11px;
color: #909399;
}
.timeline-container {
position: relative;
min-height: 60px;
}
.timeline-grid {
position: relative;
}
.time-labels {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.time-label {
font-size: 10px;
color: #c0c4cc;
font-family: monospace;
}
.health-bars {
display: flex;
height: 12px;
border-radius: 6px;
overflow: hidden;
background-color: #f0f0f0;
margin-bottom: 8px;
}
.health-segment {
height: 100%;
transition: all 0.3s ease;
cursor: pointer;
}
/* 颜色现在通过动态样式设置不再需要这些CSS类 */
.health-segment:hover {
opacity: 0.8;
transform: scaleY(1.2);
}
.response-time-chart {
height: 30px;
margin-bottom: 8px;
}
.response-chart {
width: 100%;
height: 100%;
}
.health-summary {
display: flex;
justify-content: space-around;
padding-top: 8px;
border-top: 1px solid #e4e7ed;
}
.summary-item {
text-align: center;
}
.summary-value {
display: block;
font-size: 14px;
font-weight: 600;
color: #409eff;
line-height: 1;
}
.summary-label {
font-size: 10px;
color: #909399;
margin-top: 2px;
}
/* 紧凑模式 */
.health-timeline.compact {
padding: 8px;
}
.health-timeline.compact .timeline-header {
margin-bottom: 8px;
}
.health-timeline.compact .timeline-title {
font-size: 12px;
}
.health-timeline.compact .health-bars {
height: 8px;
margin-bottom: 6px;
}
.health-timeline.compact .health-summary {
padding-top: 6px;
}
.health-timeline.compact .summary-value {
font-size: 12px;
}
.health-timeline.compact .summary-label {
font-size: 9px;
}
</style>

View File

@@ -0,0 +1,507 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" label-position="left"
@submit.prevent="handleSubmit">
<el-form-item label="节点名称" prop="name" required>
<el-input v-model="form.name" placeholder="请输入节点名称,如:北京-联通-01" maxlength="100" show-word-limit clearable>
<template #prefix>
<el-icon>
<Monitor />
</el-icon>
</template>
</el-input>
<div class="form-tip">建议使用地区-运营商-编号的格式命名</div>
</el-form-item>
<el-row :gutter="20">
<el-col :span="16">
<el-form-item label="主机地址" prop="host" required>
<el-input v-model="form.host" placeholder="请输入IP地址或域名" clearable>
<template #prefix>
<el-icon>
<Location />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="端口" prop="port" required>
<el-input-number v-model="form.port" :min="1" :max="65535" placeholder="端口号" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="协议类型" prop="protocol" required>
<el-radio-group v-model="form.protocol">
<el-radio value="tcp">TCP</el-radio>
<el-radio value="udp">UDP</el-radio>
<el-radio value="ws">WebSocket</el-radio>
<el-radio value="wss">WebSocket Secure</el-radio>
</el-radio-group>
<div class="form-tip">选择节点支持的连接协议</div>
</el-form-item>
<el-form-item label="允许中转" prop="allow_relay" required>
<el-radio-group v-model="form.allow_relay">
<el-radio :value="true">允许中转数据</el-radio>
<el-radio :value="false">仅用于打洞</el-radio>
</el-radio-group>
<div class="form-tip">选择节点是否允许中转其他用户的数据流量</div>
</el-form-item>
<el-form-item label="网络名称" prop="network_name" required>
<el-input v-model="form.network_name" placeholder="请输入EasyTier网络名称" maxlength="100" clearable>
<template #prefix>
<el-icon>
<Connection />
</el-icon>
</template>
</el-input>
<div class="form-tip"> EasyTier network name 一致用于后端探活</div>
</el-form-item>
<el-form-item label="网络密码" prop="network_secret" required>
<el-input v-model="form.network_secret" type="password" placeholder="请输入网络密码" maxlength="100" clearable
show-password>
<template #prefix>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
<div class="form-tip"> EasyTier network secret 一致</div>
</el-form-item>
<el-form-item label="最大网络数" prop="max_connections" required>
<el-input-number v-model="form.max_connections" :min="1" :max="10000" placeholder="最大网络数量"
style="width: 200px" />
<div class="form-tip">节点能够承载的最大网络数量</div>
</el-form-item>
<el-form-item label="节点描述" prop="description">
<el-input v-model="form.description" type="textarea" :rows="4" placeholder="请描述您的节点特点,如:地理位置、网络质量、使用限制等"
maxlength="500" show-word-limit />
<div class="form-tip">详细描述有助于用户选择合适的节点</div>
</el-form-item>
<!-- 联系方式 -->
<el-form-item label="联系方式" prop="contact_info">
<div class="contact-section">
<el-form-item label="微信" prop="wechat">
<el-input v-model="form.wechat" placeholder="请输入微信号" maxlength="50" clearable>
<template #prefix>
<el-icon>
<ChatDotRound />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="QQ" prop="qq_number">
<el-input v-model="form.qq_number" placeholder="请输入QQ号" maxlength="20" clearable>
<template #prefix>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="邮箱" prop="mail">
<el-input v-model="form.mail" placeholder="请输入邮箱地址" maxlength="100" clearable>
<template #prefix>
<el-icon>
<Message />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="form-tip">请至少填写一种联系方式便于节点问题时联系您仅管理员可见</div>
</div>
</el-form-item>
<!-- 连接测试 -->
<el-form-item label="连接测试">
<div class="test-section">
<el-button type="warning" @click="testConnection" :loading="testing" :disabled="!canTest">
<el-icon>
<Connection />
</el-icon>
测试连接
</el-button>
<div v-if="testResult" class="test-result">
<el-tag :type="testResult.success ? 'success' : 'danger'" size="large">
{{ testResult.success ? '连接成功' : '连接失败' }}
</el-tag>
<span v-if="testResult.message" class="test-message">
{{ testResult.message }}
</span>
</div>
</div>
<div class="form-tip">建议在提交前测试连接以确保节点可用</div>
</el-form-item>
<!-- 使用条款 -->
<el-form-item prop="agreed" v-if="props.showAgreement">
<el-checkbox v-model="form.agreed">
我已阅读并同意
<el-button type="primary" link @click="showTerms = true">
节点共享协议
</el-button>
</el-checkbox>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item>
<div class="submit-section">
<el-button type="primary" size="large" @click="handleSubmit" :loading="submitting"
:disabled="!form.agreed && props.showAgreement">
<el-icon>
<Upload />
</el-icon>
提交节点
</el-button>
<el-button size="large" @click="resetFields">
<el-icon>
<RefreshLeft />
</el-icon>
重置表单
</el-button>
</div>
</el-form-item>
</el-form> <!-- 使用条款对话框 -->
<el-dialog v-model="showTerms" title="节点共享协议" width="600px">
<div class="terms-content">
<h3>1. 节点共享原则</h3>
<p> 节点提供者应确保节点的稳定性和可用性</p>
<p> 不得利用共享节点进行违法违规活动</p>
<p> 尊重其他用户的使用权益</p>
<h3>2. 服务质量要求</h3>
<p> 节点应保持7x24小时稳定运行</p>
<p> 网络延迟应控制在合理范围内</p>
<p> 及时处理连接问题和故障</p>
<h3>3. 数据安全</h3>
<p> 不得记录或泄露用户传输数据</p>
<p> 保护用户隐私和数据安全</p>
<p> 遵守相关法律法规</p>
<h3>4. 免责声明</h3>
<p> 平台不对节点服务质量承担责任</p>
<p> 用户使用节点服务的风险自担</p>
<p> 平台有权移除不符合要求的节点</p>
</div>
<template #footer>
<el-button @click="showTerms = false">关闭</el-button>
<el-button type="primary" @click="acceptTerms">同意并关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
import {
Monitor,
Location,
PriceTag,
Connection,
Upload,
Edit,
RefreshLeft,
ChatDotRound,
User,
Message
} from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { nodeApi } from '../api'
const props = defineProps({
modelValue: {
type: Object,
default: () => ({
name: '',
host: '',
port: 11010,
protocol: 'tcp',
allow_relay: true,
network_name: '',
network_secret: '',
max_connections: 100,
description: '',
wechat: '',
qq_number: '',
mail: '',
agreed: false
})
},
submitting: {
type: Boolean,
default: false
},
submitText: {
type: String,
default: '提交节点'
},
submitIcon: {
type: String,
default: 'Upload'
},
showConnectionTest: {
type: Boolean,
default: true
},
showAgreement: {
type: Boolean,
default: true
},
showCancel: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'submit', 'reset', 'cancel', 'show-terms'])
const formRef = ref()
const testing = ref(false)
const testResult = ref(null)
const showTerms = ref(false)
// 表单数据
const form = reactive({ ...props.modelValue })
// 监听props变化更新表单数据
watch(() => props.modelValue, (newValue) => {
Object.assign(form, newValue)
}, { deep: true })
// 监听表单变化,向上传递
watch(form, (newValue) => {
emit('update:modelValue', { ...newValue })
}, { deep: true })
// 表单验证规则
const rules = {
name: [
{ required: true, message: '请输入节点名称', trigger: 'blur' },
{ min: 1, max: 100, message: '节点名称长度应在1-100个字符之间', trigger: 'blur' }
],
host: [
{ required: true, message: '请输入主机地址', trigger: 'blur' },
{ min: 1, max: 255, message: '主机地址长度应在1-255个字符之间', trigger: 'blur' },
{
pattern: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/,
message: '请输入有效的IP地址或域名',
trigger: 'blur'
}
],
port: [
{ required: true, message: '请输入端口号', trigger: 'blur' },
{ type: 'number', min: 1, max: 65535, message: '端口号应在1-65535之间', trigger: 'blur' }
],
protocol: [
{ required: true, message: '请选择协议类型', trigger: 'change' }
],
max_connections: [
{ required: true, message: '请输入最大连接数', trigger: 'blur' },
{ type: 'number', min: 1, max: 10000, message: '最大连接数应在1-10000之间', trigger: 'blur' }
],
version: [
{ max: 50, message: '版本信息长度不能超过50个字符', trigger: 'blur' }
],
description: [
{ max: 500, message: '描述长度不能超过500个字符', trigger: 'blur' }
],
wechat: [
{ max: 50, message: '微信号长度不能超过50个字符', trigger: 'blur' }
],
qq_number: [
{ max: 20, message: 'QQ号长度不能超过20个字符', trigger: 'blur' },
{ pattern: /^[1-9][0-9]{4,19}$/, message: '请输入有效的QQ号', trigger: 'blur' }
],
mail: [
{ max: 100, message: '邮箱地址长度不能超过100个字符', trigger: 'blur' },
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
],
contact_info: [
{
validator: (rule, value, callback) => {
if (!form.wechat && !form.qq_number && !form.mail) {
callback(new Error('请至少填写一种联系方式'))
} else {
callback()
}
},
trigger: 'blur'
}
],
agreed: [
{
validator: (rule, value, callback) => {
if (!value) {
callback(new Error('请阅读并同意节点共享协议'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
// 是否可以测试连接
const canTest = computed(() => {
return form.host && form.port && form.protocol && form.network_name && form.network_secret
})
const buildDataFromForm = () => {
return {
name: form.name || 'Test Node',
host: form.host,
port: form.port,
protocol: form.protocol,
description: form.description || null,
max_connections: form.max_connections || 100,
allow_relay: form.allow_relay,
network_name: form.network_name || null,
network_secret: form.network_secret || null,
wechat: form.wechat || null,
qq_number: form.qq_number || null,
mail: form.mail || null
}
}
// 测试连接
const testConnection = async () => {
if (!canTest.value) {
ElMessage.warning('请先填写主机地址、端口、协议、网络名称和网络密码')
return
}
testing.value = true
testResult.value = null
try {
// 构建测试数据
const testData = buildDataFromForm()
// 调用实际的连接测试API
const response = await nodeApi.testConnection(testData)
if (response.success) {
testResult.value = {
success: true,
message: '连接测试成功,节点可正常访问'
}
ElMessage.success('连接测试成功')
} else {
testResult.value = {
success: false,
message: response.error || '连接测试失败'
}
ElMessage.error('连接测试失败')
}
} catch (error) {
console.error('连接测试失败:', error)
testResult.value = {
success: false,
message: error.response?.data?.error || '测试过程中发生错误,请检查网络连接'
}
ElMessage.error('连接测试失败')
} finally {
testing.value = false
}
}
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
try {
const valid = await formRef.value.validate()
if (!valid) return
const submitData = buildDataFromForm()
emit('submit', submitData)
} catch (error) {
console.error('表单验证失败:', error)
}
}
// 重置表单
const resetFields = () => {
if (formRef.value) {
formRef.value.resetFields()
}
testResult.value = null
emit('reset')
}
const acceptTerms = () => {
form.agreed = true
showTerms.value = false
ElMessage.success('已同意节点共享协议')
}
// 暴露方法给父组件
defineExpose({
validate: () => formRef.value?.validate(),
resetFields: () => formRef.value?.resetFields()
})
</script>
<style scoped>
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
}
.test-section {
display: flex;
align-items: center;
gap: 12px;
}
.test-result {
display: flex;
align-items: center;
gap: 8px;
}
.test-message {
font-size: 12px;
color: #606266;
}
.submit-section {
display: flex;
gap: 12px;
}
.contact-section {
width: 100%;
}
.contact-section .el-form-item {
margin-bottom: 16px;
}
.contact-section .el-form-item:last-of-type {
margin-bottom: 8px;
}
.contact-section .el-form-item__label {
font-size: 14px;
color: #606266;
font-weight: 500;
}
</style>

View File

@@ -0,0 +1,22 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import router from './router'
import App from './App.vue'
import './style.css'
const app = createApp(App)
// 注册Element Plus
app.use(ElementPlus)
// 注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 注册路由
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,78 @@
import { createRouter, createWebHistory } from 'vue-router'
import NodeDashboard from '../views/NodeDashboard.vue'
import SubmitNode from '../views/SubmitNode.vue'
import AdminLogin from '../views/AdminLogin.vue'
import AdminDashboard from '../views/AdminDashboard.vue'
const routes = [
{
path: '/',
name: 'Dashboard',
component: NodeDashboard,
meta: {
title: '节点状态监控'
}
},
{
path: '/submit',
name: 'Submit',
component: SubmitNode,
meta: {
title: '提交共享节点'
}
},
{
path: '/admin/login',
name: 'AdminLogin',
component: AdminLogin,
meta: {
title: '管理员登录'
}
},
{
path: '/admin',
name: 'AdminDashboard',
component: AdminDashboard,
meta: {
title: '管理员面板',
requiresAuth: true
}
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由守卫
router.beforeEach(async (to, from, next) => {
// 设置页面标题
if (to.meta.title) {
document.title = `${to.meta.title} - EasyTier Uptime`
}
// 检查管理员权限
if (to.meta.requiresAuth) {
const token = localStorage.getItem('admin_token')
if (!token) {
next('/admin/login')
return
}
// 验证token有效性
try {
const { adminApi } = await import('../api')
await adminApi.verifyToken()
} catch (error) {
console.error('Token verification failed:', error)
localStorage.removeItem('admin_token')
next('/admin/login')
return
}
}
next()
})
export default router

View File

@@ -0,0 +1,243 @@
/* 自定义样式 */
:root {
--primary-color: #409EFF;
--success-color: #67C23A;
--warning-color: #E6A23C;
--danger-color: #F56C6C;
--info-color: #909399;
--text-primary: #303133;
--text-regular: #606266;
--text-secondary: #909399;
--text-placeholder: #C0C4CC;
--border-base: #DCDFE6;
--border-light: #E4E7ED;
--border-lighter: #EBEEF5;
--border-extra-light: #F2F6FC;
--background-base: #F5F7FA;
--background-light: #FAFAFA;
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 工具类 */
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.flex {
display: flex;
}
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-column {
flex-direction: column;
}
.flex-1 {
flex: 1;
}
.mb-10 {
margin-bottom: 10px;
}
.mb-20 {
margin-bottom: 20px;
}
.mt-10 {
margin-top: 10px;
}
.mt-20 {
margin-top: 20px;
}
.p-10 {
padding: 10px;
}
.p-20 {
padding: 20px;
}
/* 动画效果 */
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式断点 */
@media (max-width: 768px) {
.mobile-hidden {
display: none !important;
}
}
@media (min-width: 769px) {
.desktop-hidden {
display: none !important;
}
}
/* 状态指示器 */
.status-online {
color: var(--success-color);
}
.status-offline {
color: var(--danger-color);
}
.status-warning {
color: var(--warning-color);
}
/* 卡片阴影效果 */
.card-shadow {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s;
}
.card-shadow:hover {
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.15);
}
/* 加载状态 */
.loading-overlay {
position: relative;
}
.loading-overlay::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
/* 表格样式增强 */
.el-table .el-table__row:hover {
cursor: pointer;
}
/* 按钮组样式 */
.button-group {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.button-group .el-button {
margin: 0;
}
/* 统计卡片样式 */
.stat-card {
text-align: center;
padding: 10px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 8px;
transition: transform 0.3s;
}
.stat-card:hover {
transform: translateY(-2px);
}
/* 标签样式 */
.tag-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* 描述列表样式 */
.description-list {
display: grid;
grid-template-columns: auto 1fr;
gap: 10px 20px;
align-items: center;
}
.description-list .label {
font-weight: 600;
color: var(--text-regular);
}
.description-list .value {
color: var(--text-primary);
}

View File

@@ -0,0 +1,579 @@
<template>
<div>
<el-container class="admin-dashboard">
<!-- 头部导航 -->
<el-header class="admin-header">
<div class="header-content">
<div class="flex">
<h1 class="header-title">管理员面板</h1>
</div>
<div class="header-actions">
<router-link to="/" class="nav-link">
返回首页
</router-link>
<el-button type="danger" @click="logout">
退出登录
</el-button>
</div>
</div>
</el-header>
<!-- 主要内容 -->
<el-main class="main-content">
<!-- 统计卡片 -->
<el-row :gutter="20" class="mb-20">
<el-col :xs="24" :sm="12" :md="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon success">
<el-icon>
<Check />
</el-icon>
</div>
<div class="stat-info">
<div class="stat-label">已审批节点</div>
<div class="stat-value">{{ stats.approved }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon warning">
<el-icon>
<Clock />
</el-icon>
</div>
<div class="stat-info">
<div class="stat-label">待审批节点</div>
<div class="stat-value">{{ stats.pending }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon info">
<el-icon>
<DataAnalysis />
</el-icon>
</div>
<div class="stat-info">
<div class="stat-label">总节点数</div>
<div class="stat-value">{{ stats.total }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-icon success">
<el-icon>
<CircleCheck />
</el-icon>
</div>
<div class="stat-info">
<div class="stat-label">在线节点</div>
<div class="stat-value">{{ stats.active }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 筛选器 -->
<el-card class="mb-20">
<template #header>
<span>筛选条件</span>
</template>
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="审批状态">
<el-select v-model="filters.approved" @change="loadNodes" placeholder="全部" clearable>
<el-option label="全部" value="" />
<el-option label="已审批" value="true" />
<el-option label="待审批" value="false" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="在线状态">
<el-select v-model="filters.active" @change="loadNodes" placeholder="全部" clearable>
<el-option label="全部" value="" />
<el-option label="在线" value="true" />
<el-option label="离线" value="false" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="协议">
<el-select v-model="filters.protocol" @change="loadNodes" placeholder="全部" clearable>
<el-option label="全部" value="" />
<el-option label="TCP" value="tcp" />
<el-option label="UDP" value="udp" />
<el-option label="WireGuard" value="wg" />
<el-option label="WebSocket" value="ws" />
<el-option label="WebSocket Secure" value="wss" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<el-form-item label="搜索">
<el-input v-model="filters.search" @input="debounceSearch" placeholder="搜索节点名称或主机" clearable />
</el-form-item>
</el-col>
</el-row>
</el-card>
<!-- 节点列表 -->
<el-card>
<template #header>
<div class="flex-between">
<div>
<h3>节点列表</h3>
<p class="text-secondary">管理所有共享节点</p>
</div>
</div>
</template>
<div v-if="loading" class="text-center p-20">
<el-icon class="is-loading" size="32">
<Loading />
</el-icon>
<p class="mt-10">加载中...</p>
</div>
<el-table v-else-if="nodes.length > 0" :data="nodes" stripe>
<el-table-column prop="name" label="节点名称" min-width="120">
<template #default="{ row }">
<div class="flex items-center">
<el-icon class="mr-2"
:color="row.is_active && row.is_approved ? '#67C23A' : !row.is_approved ? '#E6A23C' : '#F56C6C'">
<CircleCheck v-if="row.is_active && row.is_approved" />
<Clock v-else-if="!row.is_approved" />
<el-icon v-else></el-icon>
</el-icon>
<span>{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="host" label="主机地址" min-width="150">
<template #default="{ row }">
{{ row.host }}:{{ row.port }}
</template>
</el-table-column>
<el-table-column prop="protocol" label="协议" width="80">
<template #default="{ row }">
<el-tag :type="getProtocolType(row.protocol)" size="small">
{{ row.protocol.toUpperCase() }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_approved" label="审批状态" width="100">
<template #default="{ row }">
<el-tag :type="row.is_approved ? 'success' : 'warning'" size="small">
{{ row.is_approved ? '已审批' : '待审批' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_active" label="在线状态" width="100">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'danger'" size="small">
{{ row.is_active ? '在线' : '离线' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
<el-table-column prop="created_at" label="创建时间" width="160">
<template #default="{ row }">
{{ formatDate(row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="editNode(row)">
编辑
</el-button>
<el-button v-if="!row.is_approved" type="success" size="small" @click="approveNode(row.id)">
审批
</el-button>
<el-button v-if="row.is_approved" type="warning" size="small" @click="revokeApproval(row.id)">
撤销
</el-button>
<el-button type="danger" size="small" @click="deleteNode(row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-else description="暂无节点数据" />
</el-card>
</el-main>
</el-container>
<!-- 编辑节点对话框 -->
<el-dialog v-model="editDialogVisible" title="编辑节点" width="800px" destroy-on-close>
<NodeForm v-if="editDialogVisible" v-model="editForm" :submitting="updating" submit-text="更新节点" submit-icon="Edit"
:show-connection-test="false" :show-agreement="false" :show-cancel="true" @submit="handleUpdateNode"
@cancel="editDialogVisible = false" @reset="resetEditForm" />
</el-dialog>
</div>
</template>
<script>
import { adminApi } from '../api'
import dayjs from 'dayjs'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Check, Clock, DataAnalysis, CircleCheck, Loading } from '@element-plus/icons-vue'
import NodeForm from '../components/NodeForm.vue'
export default {
name: 'AdminDashboard',
components: {
Check,
Clock,
DataAnalysis,
CircleCheck,
Loading,
NodeForm
},
data() {
return {
loading: false,
nodes: [],
filters: {
approved: '',
active: '',
protocol: '',
search: ''
},
searchTimeout: null,
editDialogVisible: false,
editForm: {
name: '',
host: '',
port: 11010,
protocol: 'tcp',
version: '',
max_connections: 100,
description: ''
},
editingNodeId: null,
updating: false
}
},
computed: {
stats() {
const total = this.nodes.length
const approved = this.nodes.filter(node => node.is_approved).length
const pending = this.nodes.filter(node => !node.is_approved).length
const active = this.nodes.filter(node => node.is_active).length
return {
total,
approved,
pending,
active
}
}
},
async mounted() {
// 先验证token有效性
try {
await adminApi.verifyToken()
await this.loadNodes()
} catch (error) {
console.error('Token verification failed in mounted:', error)
this.logout()
}
},
methods: {
async loadNodes() {
try {
this.loading = true
const params = {}
if (this.filters.approved !== '') {
params.approved = this.filters.approved
}
if (this.filters.active !== '') {
params.active = this.filters.active
}
if (this.filters.protocol) {
params.protocol = this.filters.protocol
}
if (this.filters.search) {
params.search = this.filters.search
}
const response = await adminApi.getNodes(params)
this.nodes = response.data?.items || []
} catch (error) {
console.error('加载节点失败:', error)
if (error.response?.status === 401) {
this.logout()
} else {
ElMessage.error('加载节点失败')
}
} finally {
this.loading = false
}
},
async approveNode(nodeId) {
try {
await ElMessageBox.confirm('确定要审批通过这个节点吗?', '确认审批', {
type: 'warning'
})
await adminApi.approveNode(nodeId)
ElMessage.success('审批成功')
await this.loadNodes()
} catch (error) {
if (error !== 'cancel') {
console.error('审批失败:', error)
ElMessage.error('审批失败')
}
}
},
async revokeApproval(nodeId) {
try {
await ElMessageBox.confirm('确定要撤销这个节点的审批吗?撤销后节点将变为待审批状态。', '确认撤销审批', {
type: 'warning'
})
await adminApi.revokeApproval(nodeId)
ElMessage.success('撤销审批成功')
await this.loadNodes()
} catch (error) {
if (error !== 'cancel') {
console.error('撤销审批失败:', error)
ElMessage.error('撤销审批失败')
}
}
},
async deleteNode(nodeId) {
try {
await ElMessageBox.confirm('确定要删除这个节点吗?此操作不可恢复!', '确认删除', {
type: 'warning'
})
await adminApi.deleteNode(nodeId)
ElMessage.success('删除成功')
await this.loadNodes()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}
},
editNode(node) {
this.editingNodeId = node.id
this.editForm = node
this.editDialogVisible = true
},
async handleUpdateNode(formData) {
try {
this.updating = true
await adminApi.updateNode(this.editingNodeId, formData)
ElMessage.success('节点更新成功')
this.editDialogVisible = false
await this.loadNodes()
} catch (error) {
console.error('更新节点失败:', error)
ElMessage.error('更新节点失败')
} finally {
this.updating = false
}
},
resetEditForm() {
this.editForm = {
name: '',
host: '',
port: 11010,
protocol: 'tcp',
version: '',
max_connections: 100,
description: ''
}
},
debounceSearch() {
if (this.searchTimeout) {
clearTimeout(this.searchTimeout)
}
this.searchTimeout = setTimeout(() => {
this.loadNodes()
}, 500)
},
formatDate(dateString) {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss')
},
getProtocolType(protocol) {
const typeMap = {
tcp: 'primary',
udp: 'success',
wg: 'warning',
ws: 'info',
wss: 'danger'
}
return typeMap[protocol] || 'info'
},
async logout() {
try {
await ElMessageBox.confirm('确定要退出登录吗?', '确认退出', {
type: 'warning'
})
localStorage.removeItem('admin_token')
this.$router.push('/admin/login')
} catch (error) {
// 用户取消
}
}
}
}
</script>
<style scoped>
.admin-dashboard {
min-height: 100vh;
}
.admin-header {
background: white;
border-bottom: 1px solid #e4e7ed;
padding: 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
height: 100%;
}
.header-title {
margin: 0;
color: #303133;
}
.header-actions {
display: flex;
align-items: center;
gap: 16px;
}
.nav-link {
color: #409eff;
text-decoration: none;
}
.nav-link:hover {
color: #66b1ff;
}
.main-content {
background: #f5f7fa;
padding: 20px;
}
.mb-20 {
margin-bottom: 20px;
}
.stat-card {
position: relative;
overflow: hidden;
height: 100px;
}
.stat-content {
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
}
.stat-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.stat-label {
font-size: 12px;
color: #909399;
margin: 0 0 4px 0;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #303133;
line-height: 1;
margin: 0;
}
.stat-icon {
font-size: 28px;
opacity: 0.3;
margin-left: 16px;
}
.stat-icon.success {
color: #67c23a;
}
.stat-icon.warning {
color: #e6a23c;
}
.stat-icon.info {
color: #409eff;
}
.flex {
display: flex;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.items-center {
align-items: center;
}
.mr-2 {
margin-right: 8px;
}
.mt-10 {
margin-top: 10px;
}
.p-20 {
padding: 20px;
}
.text-center {
text-align: center;
}
.text-secondary {
color: #909399;
}
</style>

View File

@@ -0,0 +1,251 @@
<template>
<div class="login-container">
<div class="login-card">
<div class="login-header">
<div class="login-icon">
<el-icon :size="48" color="#409EFF">
<Lock />
</el-icon>
</div>
<h2 class="login-title">管理员登录</h2>
<p class="login-subtitle">请输入管理员密码以访问管理面板</p>
</div>
<div class="login-form">
<el-form @submit.prevent="handleLogin" :model="form" :rules="rules" ref="loginForm">
<el-form-item prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入管理员密码" size="large" show-password
:prefix-icon="Lock" @keyup.enter="handleLogin" />
</el-form-item>
<el-form-item v-if="error">
<el-alert :title="error" type="error" :closable="false" show-icon />
</el-form-item>
<el-form-item>
<el-button type="primary" size="large" :loading="loading" @click="handleLogin" class="login-button">
{{ loading ? '登录中...' : '登录' }}
</el-button>
</el-form-item>
</el-form>
<div class="login-divider">
<el-divider></el-divider>
</div>
<div class="login-actions">
<el-button size="large" @click="$router.push('/')" class="back-button">
<el-icon class="mr-2">
<ArrowLeft />
</el-icon>
返回首页
</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { adminApi } from '../api'
import { Lock, ArrowLeft } from '@element-plus/icons-vue'
export default {
name: 'AdminLogin',
components: {
Lock,
ArrowLeft
},
data() {
return {
loading: false,
error: '',
form: {
password: ''
},
rules: {
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 1, message: '密码不能为空', trigger: 'blur' }
]
}
}
},
methods: {
async handleLogin() {
if (!this.form.password) {
this.error = '请输入密码'
return
}
this.loading = true
this.error = ''
try {
const response = await adminApi.login(this.form.password)
// 保存token
const token = response.data?.token || response.token
if (token) {
localStorage.setItem('admin_token', token)
// 跳转到管理面板
this.$router.push('/admin')
} else {
throw new Error('No token received from server')
}
} catch (error) {
console.error('Login error:', error)
console.error('Error details:', {
message: error.message,
status: error.response?.status,
statusText: error.response?.statusText,
data: error.response?.data
})
if (error.response?.status === 401) {
this.error = '密码错误,请重新输入'
} else if (error.response?.data?.message) {
this.error = error.response.data.message
} else if (error.message) {
this.error = error.message
} else {
this.error = '登录失败,请检查网络连接'
}
} finally {
this.loading = false
}
}
},
mounted() {
// 如果已经登录,直接跳转到管理面板
const token = localStorage.getItem('admin_token')
if (token) {
this.$router.push('/admin')
}
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-card {
background: white;
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 40px;
width: 100%;
max-width: 400px;
backdrop-filter: blur(10px);
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-icon {
margin-bottom: 16px;
}
.login-title {
font-size: 28px;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 8px 0;
}
.login-subtitle {
font-size: 14px;
color: var(--text-secondary);
margin: 0;
}
.login-form {
width: 100%;
}
.login-button {
width: 100%;
height: 48px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
}
.login-divider {
margin: 24px 0;
}
.login-actions {
width: 100%;
}
.back-button {
width: 100%;
height: 48px;
font-size: 16px;
border-radius: 8px;
}
.mr-2 {
margin-right: 8px;
}
/* 响应式设计 */
@media (max-width: 480px) {
.login-card {
padding: 24px;
margin: 16px;
}
.login-title {
font-size: 24px;
}
}
/* 动画效果 */
.login-card {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Element Plus 组件样式覆盖 */
:deep(.el-input__wrapper) {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
:deep(.el-button) {
transition: all 0.3s ease;
}
:deep(.el-button:hover) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(64, 158, 255, 0.3);
}
</style>

View File

@@ -0,0 +1,573 @@
<template>
<div class="node-dashboard">
<!-- 页面头部 -->
<div class="dashboard-header">
<h1>EasyTier 节点状态监控</h1>
<p class="subtitle">实时监控所有共享节点的健康状态和连接信息</p>
</div>
<!-- 统计卡片 -->
<el-row :gutter="20" class="stats-row">
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-number">{{ totalNodes }}</div>
<div class="stat-label">总节点数</div>
</div>
<el-icon class="stat-icon" color="#409EFF">
<Monitor />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-number">{{ activeNodes }}</div>
<div class="stat-label">在线节点</div>
</div>
<el-icon class="stat-icon" color="#67C23A">
<CircleCheck />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-number">{{ averageLoad }} %</div>
<div class="stat-label">平均负载</div>
</div>
<el-icon class="stat-icon" color="#E6A23C">
<Link />
</el-icon>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stat-card">
<div class="stat-content">
<div class="stat-number">{{ averageUptime }}%</div>
<div class="stat-label">平均在线率</div>
</div>
<el-icon class="stat-icon" color="#F56C6C">
<TrendCharts />
</el-icon>
</el-card>
</el-col>
</el-row>
<!-- 搜索和筛选 -->
<el-card class="filter-card">
<el-row :gutter="20">
<el-col :span="8">
<el-input v-model="searchText" placeholder="搜索节点名称、主机地址或描述" prefix-icon="Search" clearable
@input="handleSearch" />
</el-col>
<el-col :span="4">
<el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="handleFilter">
<el-option label="全部" value="" />
<el-option label="在线" value="true" />
<el-option label="离线" value="false" />
</el-select>
</el-col>
<el-col :span="4">
<el-select v-model="protocolFilter" placeholder="协议筛选" clearable @change="handleFilter">
<el-option label="全部" value="" />
<el-option label="TCP" value="tcp" />
<el-option label="UDP" value="udp" />
<el-option label="WS" value="ws" />
<el-option label="WSS" value="wss" />
</el-select>
</el-col>
<el-col :span="4">
<el-button type="primary" @click="refreshData" :loading="loading">
<el-icon>
<Refresh />
</el-icon>
刷新
</el-button>
</el-col>
<el-col :span="4">
<el-button type="success" @click="$router.push('/submit')">
<el-icon>
<Plus />
</el-icon>
提交节点
</el-button>
</el-col>
</el-row>
</el-card>
<!-- 节点列表 -->
<el-card class="nodes-card">
<template #header>
<div class="card-header">
<span>节点列表</span>
<el-tag :type="loading ? 'info' : 'success'">
{{ loading ? '加载中...' : `${pagination.total} 个节点` }}
</el-tag>
</div>
</template>
<el-table :data="nodes" v-loading="loading" stripe style="width: 100%" row-key="id">
<!-- 展开列 -->
<el-table-column type="expand" width="50">
<template #default="{ row }">
<div class="expanded-content">
<HealthTimeline :node-info="row" :compact="true" />
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="节点名称" width="150">
<template #default="{ row }">
<div class="node-name">
<el-icon :color="row.is_active ? '#67C23A' : '#F56C6C'">
<CircleCheck v-if="row.is_active" />
<CircleClose v-else />
</el-icon>
<span>{{ row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="address" label="节点连接地址" width="250">
<template #header>
<span>节点连接地址</span>
<el-tooltip content="可以将节点链接填入命令行的 -p 参数,或者图形界面的节点地址字段(公共服务器或手动皆可)" placement="top" effect="light">
<el-icon class="help-icon">
<QuestionFilled />
</el-icon>
</el-tooltip>
</template>
<template #default="{ row }">
<el-tag type="primary" size="" style="margin-bottom: 0.2rem;"
@click="copyAddress(apiUrl + 'node/' + row.id)"> {{
apiUrl
}}node/{{ row.id }}</el-tag>
<el-tag type="info" size="" @click="copyAddress(row.address)">{{ row.address }}</el-tag>
</template>
</el-table-column>
<el-table-column label="版本" width="90">
<template #default="{ row }">
<div style="display: flex; flex-direction: column; gap: 1px; align-items: flex-start;">
<el-tag v-if="row.version" size="small" style="font-size: 11px; padding: 1px 4px;">{{ row.version
}}</el-tag>
<span v-else class="text-muted" style="font-size: 11px;">未知</span>
<el-tag :type="row.allow_relay ? 'success' : 'info'" size="small"
style="font-size: 9px; padding: 1px 3px;">
{{ row.allow_relay ? '可中转' : '禁中转' }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column label="连接状态" width="150">
<template #default="{ row }">
<div class="connection-info">
<span>{{ row.current_connections }}/{{ row.max_connections }}</span>
<el-progress :percentage="row.usage_percentage" :color="getProgressColor(row.usage_percentage)"
:stroke-width="6" :show-text="false" />
</div>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200">
<template #default="{ row }">
<span class="description">{{ row.description || '暂无描述' }}</span>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.created_at) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click.stop="viewNodeDetails(row)">
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination v-model:current-page="pagination.page" v-model:page-size="pagination.per_page"
:page-sizes="[10, 20, 50, 100]" :total="pagination.total" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</el-card>
<!-- 节点详情对话框 -->
<el-dialog v-model="detailDialogVisible" :title="selectedNode?.name + ' - 详细信息'" width="800px" destroy-on-close>
<div v-if="selectedNode" class="node-details">
<el-descriptions :column="2" border>
<el-descriptions-item label="节点名称">{{ selectedNode.name }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="selectedNode.is_active ? 'success' : 'danger'">
{{ selectedNode.is_active ? '在线' : '离线' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="主机地址">{{ selectedNode.host }}</el-descriptions-item>
<el-descriptions-item label="端口">{{ selectedNode.port }}</el-descriptions-item>
<el-descriptions-item label="协议">{{ selectedNode.protocol.toUpperCase() }}</el-descriptions-item>
<el-descriptions-item label="版本">{{ selectedNode.version || '未知' }}</el-descriptions-item>
<el-descriptions-item label="允许中转">
<el-tag :type="selectedNode.allow_relay ? 'success' : 'info'" size="small">
{{ selectedNode.allow_relay ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="使用率">{{ selectedNode.usage_percentage.toFixed(1) }}%</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ formatDate(selectedNode.created_at) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ formatDate(selectedNode.updated_at) }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ selectedNode.description || '暂无描述' }}</el-descriptions-item>
</el-descriptions>
<!-- 健康状态统计 -->
<div class="health-stats" v-if="healthStats">
<h3>健康状态统计 (最近24小时)</h3>
<el-row :gutter="20">
<el-col :span="6">
<div class="health-stat-item">
<div class="stat-value">{{ healthStats.uptime_percentage?.toFixed(1) || 0 }}%</div>
<div class="stat-label">在线率</div>
</div>
</el-col>
<el-col :span="6">
<div class="health-stat-item">
<div class="stat-value">{{ (selectedNode.last_response_time / 1000) || 0 }}ms</div>
<div class="stat-label">平均响应时间</div>
</div>
</el-col>
<el-col :span="6">
<div class="health-stat-item">
<div class="stat-value">{{ healthStats.total_checks || 0 }}</div>
<div class="stat-label">检查次数</div>
</div>
</el-col>
<el-col :span="6">
<div class="health-stat-item">
<div class="stat-value">{{ healthStats.failed_checks || 0 }}</div>
<div class="stat-label">失败次数</div>
</div>
</el-col>
</el-row>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { nodeApi } from '../api'
import dayjs from 'dayjs'
import HealthTimeline from '../components/HealthTimeline.vue'
import {
Monitor,
CircleCheck,
CircleClose,
Link,
TrendCharts,
Search,
Refresh,
Plus
} from '@element-plus/icons-vue'
// 响应式数据
const loading = ref(false)
const nodes = ref([])
const searchText = ref('')
const statusFilter = ref('')
const protocolFilter = ref('')
const detailDialogVisible = ref(false)
const selectedNode = ref(null)
const healthStats = ref(null)
const expandedRows = ref([])
const apiUrl = ref(window.location.href)
// 分页数据
const pagination = reactive({
page: 1,
per_page: 50,
total: 0
})
// 计算属性
const totalNodes = computed(() => nodes.value.length)
const activeNodes = computed(() => nodes.value.filter(node => node.is_active).length)
const averageLoad = computed(() =>
(nodes.value.reduce((sum, node) => sum + node.current_connections, 0) / (nodes.value.length)).toFixed(2)
)
const averageUptime = computed(() => {
if (nodes.value.length === 0) return 0
const activeCount = nodes.value.filter(node => node.is_active).length
return ((activeCount / nodes.value.length) * 100).toFixed(1)
})
// 方法
const fetchNodes = async (with_loading = true) => {
try {
if (with_loading) {
loading.value = true
}
const params = {
page: pagination.page,
per_page: pagination.per_page
}
if (searchText.value) {
params.search = searchText.value
}
if (statusFilter.value !== '') {
params.is_active = statusFilter.value === 'true'
}
if (protocolFilter.value) {
params.protocol = protocolFilter.value
}
const response = await nodeApi.getNodes(params)
if (response.success && response.data) {
nodes.value = response.data.items
pagination.total = response.data.total
}
} catch (error) {
console.error('获取节点列表失败:', error)
ElMessage.error('获取节点列表失败')
} finally {
if (with_loading) {
loading.value = false
}
}
}
const refreshData = () => {
fetchNodes()
}
const handleSearch = () => {
pagination.page = 1
fetchNodes()
}
const handleFilter = () => {
pagination.page = 1
fetchNodes()
}
const handleSizeChange = (size) => {
pagination.per_page = size
pagination.page = 1
fetchNodes()
}
const handleCurrentChange = (page) => {
pagination.page = page
fetchNodes()
}
const viewNodeDetails = async (node) => {
selectedNode.value = node
detailDialogVisible.value = true
// 获取健康状态统计
try {
const response = await nodeApi.getNodeHealthStats(node.id, { hours: 24 })
if (response.success && response.data) {
healthStats.value = response.data
}
} catch (error) {
console.error('获取健康状态统计失败:', error)
}
}
const formatDate = (dateString) => {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss')
}
const getProgressColor = (percentage) => {
if (percentage < 50) return '#67C23A'
if (percentage < 80) return '#E6A23C'
return '#F56C6C'
}
const copyAddress = (address) => {
try {
navigator.clipboard.writeText(address).then(() => {
ElMessage.success(`地址已复制, ${address}`)
}).catch(() => {
ElMessage.error(`复制失败, ${address}`)
})
} catch (error) {
ElMessage.error(`复制失败, ${address}`)
}
}
// 生命周期
onMounted(() => {
fetchNodes()
// 设置定时刷新
setInterval(() => {
fetchNodes(false)
}, 3000) // 每30秒刷新一次
})
</script>
<style scoped>
.node-dashboard {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.dashboard-header {
text-align: center;
margin-bottom: 30px;
}
.dashboard-header h1 {
color: #303133;
margin-bottom: 10px;
font-size: 32px;
font-weight: 600;
}
.subtitle {
color: #606266;
font-size: 16px;
margin: 0;
}
.stats-row {
margin-bottom: 16px;
}
.stat-card {
position: relative;
overflow: hidden;
height: 100px;
}
.stat-content {
padding: 0 16px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.stat-number {
font-size: 24px;
font-weight: bold;
color: #303133;
line-height: 1;
margin-bottom: 4px;
}
.stat-label {
font-size: 12px;
color: #909399;
margin: 0;
}
.stat-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: 28px;
opacity: 0.3;
}
.filter-card {
margin-bottom: 20px;
}
.nodes-card {
background: white;
border-radius: 8px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.node-name {
display: flex;
align-items: center;
gap: 8px;
}
.address {
margin-left: 8px;
font-family: monospace;
}
.connection-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.description {
color: #606266;
font-size: 13px;
}
.text-muted {
color: #C0C4CC;
}
.pagination-wrapper {
display: flex;
justify-content: center;
margin-top: 20px;
}
.node-details {
padding: 10px 0;
}
.health-stats {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
.health-stats h3 {
margin-bottom: 20px;
color: #303133;
}
.health-stat-item {
text-align: center;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
}
.health-stat-item .stat-value {
font-size: 24px;
font-weight: bold;
color: #409EFF;
margin-bottom: 5px;
}
.health-stat-item .stat-label {
font-size: 12px;
color: #909399;
}
.expanded-content {
padding: 16px 24px;
background-color: #fafafa;
border-top: 1px solid #ebeef5;
}
</style>

View File

@@ -0,0 +1,351 @@
<template>
<div class="submit-node">
<!-- 页面头部 -->
<div class="page-header">
<el-button type="primary" @click="$router.back()" class="back-btn">
<el-icon>
<ArrowLeft />
</el-icon>
返回
</el-button>
<h1>提交共享节点</h1>
<p class="subtitle">分享您的EasyTier节点为社区贡献力量</p>
</div>
<el-row :gutter="20" justify="center">
<el-col :span="16">
<!-- 提交表单 -->
<el-card class="form-card">
<template #header>
<div class="card-header">
<el-icon>
<Plus />
</el-icon>
<span>节点信息</span>
</div>
</template>
<NodeForm ref="formRef" @submit="handleSubmit" :submitting="submitting" />
</el-card>
</el-col>
<!-- 侧边栏信息 -->
<el-col :span="8">
<el-card class="info-card">
<template #header>
<div class="card-header">
<el-icon>
<InfoFilled />
</el-icon>
<span>提交须知</span>
</div>
</template>
<div class="info-content">
<div class="info-item">
<el-icon color="#409EFF">
<CircleCheck />
</el-icon>
<div>
<h4>节点要求</h4>
<p>确保您的节点稳定运行具有良好的网络连接</p>
</div>
</div>
<div class="info-item">
<el-icon color="#67C23A">
<Lock />
</el-icon>
<div>
<h4>隐私保护</h4>
<p>关键信息仅社区管理员可见</p>
</div>
</div>
<div class="info-item">
<el-icon color="#E6A23C">
<Warning />
</el-icon>
<div>
<h4>注意事项</h4>
<p>请确保节点信息准确避免提交虚假信息</p>
</div>
</div>
<div class="info-item">
<el-icon color="#F56C6C">
<Delete />
</el-icon>
<div>
<h4>移除条件</h4>
<p>长期离线或不稳定的节点将被自动移除</p>
</div>
</div>
<div class="info-item">
<el-icon color="#F56C6C">
<DocumentChecked />
</el-icon>
<div>
<h4>审核机制</h4>
<p>所有节点提交均需要审核审核通过后才会展示在节点列表中</p>
</div>
</div>
</div>
</el-card>
<!-- 统计信息 -->
<el-card class="stats-card">
<template #header>
<div class="card-header">
<el-icon>
<DataAnalysis />
</el-icon>
<span>社区统计</span>
</div>
</template>
<div class="stats-content">
<div class="stat-item">
<div class="stat-number">{{ communityStats.totalNodes }}</div>
<div class="stat-label">总节点数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ communityStats.activeNodes }}</div>
<div class="stat-label">在线节点</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { nodeApi } from '../api'
import {
ArrowLeft,
Plus,
InfoFilled,
CircleCheck,
Lock,
Warning,
DocumentChecked,
Delete,
DataAnalysis
} from '@element-plus/icons-vue'
import NodeForm from '../components/NodeForm.vue'
const formRef = ref()
const router = useRouter()
const submitting = ref(false)
// 社区统计数据
const communityStats = reactive({
totalNodes: 0,
activeNodes: 0,
})
const handleSubmit = async (submitData) => {
try {
const response = await nodeApi.createNode(submitData)
if (response.success) {
ElMessage.success('节点提交成功!')
ElMessageBox.confirm(
'节点已成功提交,等待管理员审核后将会展示在节点列表中。如果信息填写错误请重新提交或者联系管理员更改。',
'提交成功',
{
confirmButtonText: '查看列表',
cancelButtonText: '继续提交',
type: 'success'
}
).then(() => {
router.push('/')
}).catch(() => {
})
} else {
ElMessage.error(response.error || '提交失败,请重试')
}
} catch (error) {
console.error('提交节点失败:', error)
ElMessage.error('提交失败,请检查网络连接')
} finally {
submitting.value = false
}
}
const fetchCommunityStats = async () => {
try {
const response = await nodeApi.getNodes({ page: 1, per_page: 1 })
if (response.success && response.data) {
communityStats.totalNodes = response.data.total
// 获取活跃节点数
const activeResponse = await nodeApi.getNodes({ page: 1, per_page: 1, is_active: true })
if (activeResponse.success && activeResponse.data) {
communityStats.activeNodes = activeResponse.data.total
}
}
} catch (error) {
console.error('获取社区统计失败:', error)
}
}
// 生命周期
onMounted(() => {
fetchCommunityStats()
})
</script>
<style scoped>
.submit-node {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
text-align: center;
margin-bottom: 30px;
position: relative;
}
.back-btn {
position: absolute;
left: 0;
top: 0;
}
.page-header h1 {
color: #303133;
margin-bottom: 10px;
font-size: 28px;
font-weight: 600;
}
.subtitle {
color: #606266;
font-size: 16px;
margin: 0;
}
.info-card {
margin-bottom: 20px;
}
.info-content {
padding: 10px 0;
}
.info-item {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 20px;
}
.info-item:last-child {
margin-bottom: 0;
}
.info-item h4 {
margin: 0 0 5px 0;
font-size: 14px;
color: #303133;
}
.info-item p {
margin: 0;
font-size: 13px;
color: #606266;
line-height: 1.4;
}
.stats-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.stats-card :deep(.el-card__header) {
border-bottom-color: rgba(255, 255, 255, 0.2);
}
.stats-card :deep(.card-header) {
color: white;
}
.stats-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.stat-item {
text-align: center;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
backdrop-filter: blur(10px);
}
.stat-number {
font-size: 24px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 12px;
opacity: 0.8;
}
.terms-content {
max-height: 400px;
overflow-y: auto;
padding: 10px;
}
.terms-content h3 {
color: #303133;
margin: 20px 0 10px 0;
font-size: 16px;
}
.terms-content h3:first-child {
margin-top: 0;
}
.terms-content p {
margin: 5px 0;
color: #606266;
line-height: 1.6;
}
/* 响应式设计 */
@media (max-width: 768px) {
.submit-node {
padding: 10px;
}
.page-header {
margin-bottom: 20px;
}
.back-btn {
position: static;
margin-bottom: 10px;
}
.submit-section {
flex-direction: column;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,30 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
'/health': {
target: 'http://localhost:8080',
changeOrigin: true,
}
}
}
})

View File

@@ -0,0 +1,80 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::json;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ApiError {
#[error("Database error: {0}")]
Database(#[from] sea_orm::DbErr),
#[error("Validation error: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Bad request: {0}")]
BadRequest(String),
#[error("Internal server error: {0}")]
Internal(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Forbidden: {0}")]
Forbidden(String),
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
ApiError::Database(err) => (
StatusCode::INTERNAL_SERVER_ERROR,
format!("Database error: {}", err),
),
ApiError::Validation(msg) => (StatusCode::BAD_REQUEST, msg),
ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
ApiError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
ApiError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
ApiError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg),
};
let body = json!({
"error": {
"code": status.as_u16(),
"message": error_message
}
});
(status, axum::Json(body)).into_response()
}
}
pub type ApiResult<T> = Result<T, ApiError>;
impl From<validator::ValidationErrors> for ApiError {
fn from(err: validator::ValidationErrors) -> Self {
let errors: Vec<String> = err
.field_errors()
.iter()
.map(|(field, errors)| {
let error_msgs: Vec<String> = errors
.iter()
.map(|error| {
if let Some(msg) = &error.message {
msg.to_string()
} else {
format!("Validation failed for field: {}", field)
}
})
.collect();
error_msgs.join(", ")
})
.collect();
ApiError::Validation(errors.join("; "))
}
}

View File

@@ -0,0 +1,507 @@
use std::ops::{Div, Mul};
use axum::extract::{Path, Query, State};
use axum::Json;
use sea_orm::{
ColumnTrait, Condition, EntityTrait, IntoActiveModel, ModelTrait, Order, PaginatorTrait,
QueryFilter, QueryOrder, QuerySelect, Set, TryIntoModel,
};
use serde::Deserialize;
use validator::Validate;
use crate::api::{
error::{ApiError, ApiResult},
models::*,
};
use crate::db::entity::{self, health_records, shared_nodes};
use crate::db::{operations::*, Db};
use crate::health_checker_manager::HealthCheckerManager;
use std::sync::Arc;
#[derive(Clone)]
pub struct AppState {
pub db: Db,
pub health_checker_manager: Arc<HealthCheckerManager>,
}
pub async fn health_check() -> Json<ApiResponse<String>> {
Json(ApiResponse::message("Service is healthy".to_string()))
}
pub async fn get_nodes(
State(app_state): State<AppState>,
Query(pagination): Query<PaginationParams>,
Query(filters): Query<NodeFilterParams>,
) -> ApiResult<Json<ApiResponse<PaginatedResponse<NodeResponse>>>> {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
let offset = (page - 1) * per_page;
let mut query = entity::shared_nodes::Entity::find();
// 普通用户只能看到已审核的节点
query = query.filter(entity::shared_nodes::Column::IsApproved.eq(true));
if let Some(is_active) = filters.is_active {
query = query.filter(entity::shared_nodes::Column::IsActive.eq(is_active));
}
if let Some(protocol) = filters.protocol {
query = query.filter(entity::shared_nodes::Column::Protocol.eq(protocol));
}
if let Some(search) = filters.search {
query = query.filter(
sea_orm::Condition::any()
.add(entity::shared_nodes::Column::Name.contains(&search))
.add(entity::shared_nodes::Column::Host.contains(&search))
.add(entity::shared_nodes::Column::Description.contains(&search)),
);
}
let total = query.clone().count(app_state.db.orm_db()).await?;
let nodes = query
.order_by_asc(entity::shared_nodes::Column::Id)
.limit(Some(per_page as u64))
.offset(Some(offset as u64))
.all(app_state.db.orm_db())
.await?;
let mut node_responses: Vec<NodeResponse> = nodes.into_iter().map(NodeResponse::from).collect();
let total_pages = total.div_ceil(per_page as u64);
// 为每个节点添加健康状态信息
for node_response in &mut node_responses {
if let Some(mut health_record) = app_state
.health_checker_manager
.get_node_memory_record(node_response.id)
{
node_response.current_health_status =
Some(health_record.get_current_health_status().to_string());
node_response.last_check_time = Some(health_record.get_last_check_time());
node_response.last_response_time = health_record.get_last_response_time();
// 获取24小时健康统计
if let Some(stats) = app_state
.health_checker_manager
.get_node_health_stats(node_response.id, 24)
{
node_response.health_percentage_24h = Some(stats.health_percentage);
}
let (total_ring, healthy_ring) = health_record.get_counter_ring();
node_response.health_record_total_counter_ring = total_ring;
node_response.health_record_healthy_counter_ring = healthy_ring;
node_response.ring_granularity = health_record.get_ring_granularity();
}
}
// remove sensitive information
node_responses.iter_mut().for_each(|node| {
tracing::info!("node: {:?}", node);
node.network_name = None;
node.network_secret = None;
// make cur connection and max conn round to percentage
if node.max_connections != 0 {
node.current_connections = node.current_connections.mul(100).div(node.max_connections);
node.max_connections = 100;
} else {
node.current_connections = 0;
node.max_connections = 0;
}
node.wechat = None;
node.qq_number = None;
node.mail = None;
});
Ok(Json(ApiResponse::success(PaginatedResponse {
items: node_responses,
total,
page,
per_page,
total_pages: total_pages as u32,
})))
}
pub async fn create_node(
State(app_state): State<AppState>,
Json(request): Json<CreateNodeRequest>,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
request.validate()?;
let node = NodeOperations::create_node(&app_state.db, request).await?;
Ok(Json(ApiResponse::success(NodeResponse::from(node))))
}
pub async fn test_connection(
State(app_state): State<AppState>,
Json(request): Json<CreateNodeRequest>,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
let mut node = NodeOperations::create_node_model(request);
node.id = Set(0);
let node = node.try_into_model()?;
app_state
.health_checker_manager
.test_connection(&node, std::time::Duration::from_secs(5))
.await
.map_err(|e| ApiError::Internal(e.to_string()))?;
Ok(Json(ApiResponse::success(NodeResponse::from(node))))
}
pub async fn get_node(
State(app_state): State<AppState>,
Path(id): Path<i32>,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
let node = NodeOperations::get_node_by_id(&app_state.db, id)
.await?
.ok_or_else(|| ApiError::NotFound(format!("Node with id {} not found", id)))?;
Ok(Json(ApiResponse::success(NodeResponse::from(node))))
}
pub async fn get_node_health(
State(app_state): State<AppState>,
Path(node_id): Path<i32>,
Query(pagination): Query<PaginationParams>,
Query(filters): Query<HealthFilterParams>,
) -> ApiResult<Json<ApiResponse<PaginatedResponse<HealthRecordResponse>>>> {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
let offset = (page - 1) * per_page;
let mut query = entity::health_records::Entity::find()
.filter(entity::health_records::Column::NodeId.eq(node_id));
if let Some(status) = filters.status {
query = query.filter(entity::health_records::Column::Status.eq(status));
}
if let Some(since) = filters.since {
query = query.filter(entity::health_records::Column::CheckedAt.gte(since.naive_utc()));
}
let total = query.clone().count(app_state.db.orm_db()).await?;
let records = query
.order_by_desc(entity::health_records::Column::CheckedAt)
.limit(Some(per_page as u64))
.offset(Some(offset as u64))
.all(app_state.db.orm_db())
.await?;
let record_responses: Vec<HealthRecordResponse> = records
.into_iter()
.map(HealthRecordResponse::from)
.collect();
let total_pages = total.div_ceil(per_page as u64);
Ok(Json(ApiResponse::success(PaginatedResponse {
items: record_responses,
total,
page,
per_page,
total_pages: total_pages as u32,
})))
}
pub async fn get_node_health_stats(
State(app_state): State<AppState>,
Path(node_id): Path<i32>,
Query(params): Query<HealthStatsParams>,
) -> ApiResult<Json<ApiResponse<HealthStatsResponse>>> {
let hours = params.hours.unwrap_or(24);
let stats = HealthOperations::get_health_stats(&app_state.db, node_id, hours).await?;
Ok(Json(ApiResponse::success(HealthStatsResponse::from(stats))))
}
#[derive(Debug, Deserialize)]
pub struct HealthStatsParams {
pub hours: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct InstanceFilterParams {
pub node_id: Option<i32>,
pub status: Option<String>,
}
// 管理员相关处理器
use crate::config::AppConfig;
use axum::http::{HeaderMap, StatusCode};
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::Serialize;
#[derive(Debug, Serialize, Deserialize)]
struct AdminClaims {
sub: String,
exp: usize,
iat: usize,
}
pub async fn get_node_connect_url(
State(app_state): State<AppState>,
Path(id): Path<i32>,
) -> ApiResult<String> {
let node = NodeOperations::get_node_by_id(&app_state.db, id)
.await?
.ok_or_else(|| ApiError::NotFound(format!("Node with id {} not found", id)))?;
let connect_url = format!("{}://{}:{}", node.protocol, node.host, node.port);
Ok(connect_url)
}
pub async fn admin_login(
Json(request): Json<AdminLoginRequest>,
) -> ApiResult<Json<ApiResponse<AdminLoginResponse>>> {
request
.validate()
.map_err(|e| ApiError::Validation(e.to_string()))?;
let config = AppConfig::default();
if request.password != config.security.admin_password {
return Err(ApiError::Unauthorized("Invalid password".to_string()));
}
let now = Utc::now();
let expires_at = now + Duration::hours(24);
let claims = AdminClaims {
sub: "admin".to_string(),
exp: expires_at.timestamp() as usize,
iat: now.timestamp() as usize,
};
let token = encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(config.security.jwt_secret.as_ref()),
)
.map_err(|e| ApiError::Internal(format!("Token generation failed: {}", e)))?;
Ok(Json(ApiResponse::success(AdminLoginResponse {
token,
expires_at,
})))
}
pub async fn admin_get_nodes(
State(app_state): State<AppState>,
Query(pagination): Query<PaginationParams>,
Query(filters): Query<AdminNodeFilterParams>,
headers: HeaderMap,
) -> ApiResult<Json<ApiResponse<PaginatedResponse<NodeResponse>>>> {
verify_admin_token(&headers)?;
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(200);
let offset = (page - 1) * per_page;
let mut query = entity::shared_nodes::Entity::find();
if let Some(is_active) = filters.is_active {
query = query.filter(entity::shared_nodes::Column::IsActive.eq(is_active));
}
if let Some(is_approved) = filters.is_approved {
query = query.filter(entity::shared_nodes::Column::IsApproved.eq(is_approved));
}
if let Some(protocol) = filters.protocol {
query = query.filter(entity::shared_nodes::Column::Protocol.eq(protocol));
}
if let Some(search) = filters.search {
query = query.filter(
sea_orm::Condition::any()
.add(entity::shared_nodes::Column::Name.contains(&search))
.add(entity::shared_nodes::Column::Host.contains(&search))
.add(entity::shared_nodes::Column::Description.contains(&search)),
);
}
let total = query.clone().count(app_state.db.orm_db()).await?;
let nodes = query
.order_by(entity::shared_nodes::Column::CreatedAt, Order::Desc)
.offset(offset as u64)
.limit(per_page as u64)
.all(app_state.db.orm_db())
.await?;
let node_responses: Vec<NodeResponse> = nodes.into_iter().map(NodeResponse::from).collect();
let total_pages = (total as f64 / per_page as f64).ceil() as u32;
Ok(Json(ApiResponse::success(PaginatedResponse {
items: node_responses,
total,
page,
per_page,
total_pages,
})))
}
pub async fn admin_approve_node(
State(app_state): State<AppState>,
Path(id): Path<i32>,
headers: HeaderMap,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
verify_admin_token(&headers)?;
let node = entity::shared_nodes::Entity::find_by_id(id)
.one(app_state.db.orm_db())
.await?
.ok_or_else(|| ApiError::NotFound("Node not found".to_string()))?;
let mut active_model = node.into_active_model();
active_model.is_approved = sea_orm::Set(true);
let updated_node = entity::shared_nodes::Entity::update(active_model)
.exec(app_state.db.orm_db())
.await?;
Ok(Json(ApiResponse::success(NodeResponse::from(updated_node))))
}
pub async fn admin_update_node(
State(app_state): State<AppState>,
Path(id): Path<i32>,
headers: HeaderMap,
Json(request): Json<UpdateNodeRequest>,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
verify_admin_token(&headers)?;
request.validate()?;
let mut node = NodeOperations::get_node_by_id(&app_state.db, id)
.await?
.ok_or_else(|| ApiError::NotFound(format!("Node with id {} not found", id)))?;
let mut node = node.into_active_model();
if let Some(name) = request.name {
node.name = Set(name);
}
if let Some(host) = request.host {
node.host = Set(host);
}
if let Some(port) = request.port {
node.port = Set(port);
}
if let Some(protocol) = request.protocol {
node.protocol = Set(protocol);
}
if let Some(description) = request.description {
node.description = Set(description);
}
if let Some(max_connections) = request.max_connections {
node.max_connections = Set(max_connections);
}
if let Some(is_active) = request.is_active {
node.is_active = Set(is_active);
}
if let Some(allow_relay) = request.allow_relay {
node.allow_relay = Set(allow_relay);
}
if let Some(network_name) = request.network_name {
node.network_name = Set(network_name);
}
if let Some(network_secret) = request.network_secret {
node.network_secret = Set(network_secret);
}
if let Some(wechat) = request.wechat {
node.wechat = Set(wechat);
}
if let Some(mail) = request.mail {
node.mail = Set(mail);
}
if let Some(qq_number) = request.qq_number {
node.qq_number = Set(qq_number);
}
node.updated_at = Set(chrono::Utc::now().fixed_offset());
tracing::info!("updated node: {:?}", node);
let updated_node = entity::shared_nodes::Entity::update(node)
.exec(app_state.db.orm_db())
.await?;
Ok(Json(ApiResponse::success(NodeResponse::from(updated_node))))
}
pub async fn admin_revoke_approval(
State(app_state): State<AppState>,
Path(id): Path<i32>,
headers: HeaderMap,
) -> ApiResult<Json<ApiResponse<NodeResponse>>> {
verify_admin_token(&headers)?;
let node = entity::shared_nodes::Entity::find_by_id(id)
.one(app_state.db.orm_db())
.await?
.ok_or_else(|| ApiError::NotFound("Node not found".to_string()))?;
let mut active_model = node.into_active_model();
active_model.is_approved = sea_orm::Set(false);
let updated_node = entity::shared_nodes::Entity::update(active_model)
.exec(app_state.db.orm_db())
.await?;
Ok(Json(ApiResponse::success(NodeResponse::from(updated_node))))
}
pub async fn admin_delete_node(
State(app_state): State<AppState>,
Path(id): Path<i32>,
headers: HeaderMap,
) -> ApiResult<Json<ApiResponse<String>>> {
verify_admin_token(&headers)?;
let node = entity::shared_nodes::Entity::find_by_id(id)
.one(app_state.db.orm_db())
.await?
.ok_or_else(|| ApiError::NotFound("Node not found".to_string()))?;
node.delete(app_state.db.orm_db()).await?;
Ok(Json(ApiResponse::message(
"Node deleted successfully".to_string(),
)))
}
pub async fn admin_verify_token(headers: HeaderMap) -> ApiResult<Json<ApiResponse<String>>> {
verify_admin_token(&headers)?;
Ok(Json(ApiResponse::message("Token is valid".to_string())))
}
fn verify_admin_token(headers: &HeaderMap) -> ApiResult<()> {
let config = AppConfig::default();
let auth_header = headers
.get("authorization")
.ok_or_else(|| ApiError::Unauthorized("Missing authorization header".to_string()))?;
let auth_str = auth_header
.to_str()
.map_err(|_| ApiError::Unauthorized("Invalid authorization header".to_string()))?;
let token = auth_str
.strip_prefix("Bearer ")
.ok_or_else(|| ApiError::Unauthorized("Invalid authorization format".to_string()))?;
let _claims = decode::<AdminClaims>(
token,
&DecodingKey::from_secret(config.security.jwt_secret.as_ref()),
&Validation::default(),
)
.map_err(|_| ApiError::Unauthorized("Invalid token".to_string()))?;
Ok(())
}

View File

@@ -0,0 +1,8 @@
pub mod error;
pub mod handlers;
pub mod models;
pub mod routes;
pub use error::{ApiError, ApiResult};
pub use handlers::*;
pub use models::*;

View File

@@ -0,0 +1,316 @@
use crate::db::entity;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Debug, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub success: bool,
pub data: Option<T>,
pub error: Option<String>,
pub message: Option<String>,
}
impl<T> ApiResponse<T> {
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
message: None,
}
}
pub fn error(error: String) -> Self {
Self {
success: false,
data: None,
error: Some(error),
message: None,
}
}
pub fn message(message: String) -> Self {
Self {
success: true,
data: None,
error: None,
message: Some(message),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaginatedResponse<T> {
pub items: Vec<T>,
pub total: u64,
pub page: u32,
pub per_page: u32,
pub total_pages: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PaginationParams {
pub page: Option<u32>,
pub per_page: Option<u32>,
}
impl Default for PaginationParams {
fn default() -> Self {
Self {
page: Some(1),
per_page: Some(20),
}
}
}
#[derive(Debug, Serialize, Deserialize, Validate)]
#[validate(schema(function = "validate_contact_info", skip_on_field_errors = false))]
pub struct CreateNodeRequest {
#[validate(length(min = 1, max = 100))]
pub name: String,
#[validate(length(min = 1, max = 255))]
pub host: String,
#[validate(range(min = 1, max = 65535))]
pub port: i32,
#[validate(length(min = 1, max = 20))]
pub protocol: String,
#[validate(length(max = 500))]
pub description: Option<String>,
#[validate(range(min = 1, max = 10000))]
pub max_connections: i32,
pub allow_relay: bool,
#[validate(length(min = 1, max = 100))]
pub network_name: String,
#[validate(length(max = 100))]
pub network_secret: Option<String>,
// 联系方式字段
#[validate(length(max = 20))]
pub qq_number: Option<String>,
#[validate(length(max = 50))]
pub wechat: Option<String>,
#[validate(email)]
pub mail: Option<String>,
}
// 自定义验证函数:确保至少填写一种联系方式
fn validate_contact_info(request: &CreateNodeRequest) -> Result<(), validator::ValidationError> {
let has_qq = request
.qq_number
.as_ref()
.is_some_and(|s| !s.trim().is_empty());
let has_wechat = request
.wechat
.as_ref()
.is_some_and(|s| !s.trim().is_empty());
let has_mail = request.mail.as_ref().is_some_and(|s| !s.trim().is_empty());
if !has_qq && !has_wechat && !has_mail {
return Err(validator::ValidationError::new("contact_required"));
}
Ok(())
}
#[derive(Debug, Serialize, Deserialize, Validate)]
pub struct UpdateNodeRequest {
#[validate(length(min = 1, max = 100))]
pub name: Option<String>,
#[validate(length(min = 1, max = 255))]
pub host: Option<String>,
#[validate(range(min = 1, max = 65535))]
pub port: Option<i32>,
#[validate(length(min = 1, max = 20))]
pub protocol: Option<String>,
#[validate(length(max = 500))]
pub description: Option<String>,
#[validate(range(min = 1, max = 10000))]
pub max_connections: Option<i32>,
pub is_active: Option<bool>,
pub allow_relay: Option<bool>,
#[validate(length(min = 1, max = 100))]
pub network_name: Option<String>,
#[validate(length(max = 100))]
pub network_secret: Option<String>,
// 联系方式字段
#[validate(length(max = 20))]
pub qq_number: Option<String>,
#[validate(length(max = 50))]
pub wechat: Option<String>,
#[validate(email)]
pub mail: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeResponse {
pub id: i32,
pub name: String,
pub host: String,
pub port: i32,
pub protocol: String,
pub version: Option<String>,
pub description: Option<String>,
pub max_connections: i32,
pub current_connections: i32,
pub is_active: bool,
pub is_approved: bool,
pub allow_relay: bool,
pub network_name: Option<String>,
pub network_secret: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub address: String,
pub usage_percentage: f64,
// 健康状态相关字段
pub current_health_status: Option<String>,
pub last_check_time: Option<chrono::DateTime<chrono::Utc>>,
pub last_response_time: Option<i32>,
pub health_percentage_24h: Option<f64>,
pub health_record_total_counter_ring: Vec<u64>,
pub health_record_healthy_counter_ring: Vec<u64>,
pub ring_granularity: u32,
// 联系方式字段
pub qq_number: Option<String>,
pub wechat: Option<String>,
pub mail: Option<String>,
}
impl From<entity::shared_nodes::Model> for NodeResponse {
fn from(node: entity::shared_nodes::Model) -> Self {
Self {
id: node.id,
name: node.name.clone(),
host: node.host.clone(),
port: node.port,
protocol: node.protocol.clone(),
version: Some(node.version.clone()),
description: Some(node.description.clone()),
max_connections: node.max_connections,
current_connections: node.current_connections,
is_active: node.is_active,
is_approved: node.is_approved,
allow_relay: node.allow_relay,
network_name: Some(node.network_name.clone()),
network_secret: Some(node.network_secret.clone()),
created_at: node.created_at.into(),
updated_at: node.updated_at.into(),
address: format!("{}://{}:{}", node.protocol, node.host, node.port),
usage_percentage: node.current_connections as f64 / node.max_connections as f64 * 100.0,
// 健康状态字段初始化为 None将在 handlers 中填充
current_health_status: None,
last_check_time: None,
last_response_time: None,
health_percentage_24h: None,
health_record_healthy_counter_ring: Vec::new(),
health_record_total_counter_ring: Vec::new(),
ring_granularity: 0,
// 联系方式字段
qq_number: if node.qq_number.is_empty() {
None
} else {
Some(node.qq_number)
},
wechat: if node.wechat.is_empty() {
None
} else {
Some(node.wechat)
},
mail: if node.mail.is_empty() {
None
} else {
Some(node.mail)
},
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct HealthRecordResponse {
pub id: i32,
pub node_id: i32,
pub status: String,
pub response_time: Option<i32>,
pub error_message: Option<String>,
pub checked_at: chrono::DateTime<chrono::Utc>,
}
impl From<entity::health_records::Model> for HealthRecordResponse {
fn from(record: entity::health_records::Model) -> Self {
Self {
id: record.id,
node_id: record.node_id,
status: record.status.to_string(),
response_time: Some(record.response_time),
error_message: Some(record.error_message),
checked_at: record.checked_at.into(),
}
}
}
pub type HealthStatsResponse = crate::db::HealthStats;
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeFilterParams {
pub is_active: Option<bool>,
pub protocol: Option<String>,
pub search: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct HealthFilterParams {
pub status: Option<String>,
pub since: Option<DateTime<Utc>>,
}
// 管理员相关模型
#[derive(Debug, Serialize, Deserialize, Validate)]
pub struct AdminLoginRequest {
#[validate(length(min = 1))]
pub password: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AdminLoginResponse {
pub token: String,
pub expires_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ApproveNodeRequest {
pub approved: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AdminNodeFilterParams {
pub is_active: Option<bool>,
pub is_approved: Option<bool>,
pub protocol: Option<String>,
pub search: Option<String>,
}

View File

@@ -0,0 +1,64 @@
use axum::routing::{delete, get, post, put};
use axum::Router;
use tower_http::compression::CompressionLayer;
use tower_http::cors::CorsLayer;
use super::handlers::AppState;
use super::handlers::{
admin_approve_node, admin_delete_node, admin_get_nodes, admin_login, admin_revoke_approval,
admin_update_node, admin_verify_token, create_node, get_node, get_node_health,
get_node_health_stats, get_nodes, health_check,
};
use crate::api::{get_node_connect_url, test_connection};
use crate::config::AppConfig;
use crate::db::Db;
pub fn create_routes() -> Router<AppState> {
let config = AppConfig::default();
let compression_layer = if config.security.enable_compression {
Some(
CompressionLayer::new()
.br(true)
.deflate(true)
.gzip(true)
.zstd(true),
)
} else {
None
};
let cors_layer = if config.cors.enabled {
Some(CorsLayer::very_permissive())
} else {
None
};
let mut router = Router::new()
.route("/node/{id}", get(get_node_connect_url))
.route("/health", get(health_check))
.route("/api/nodes", get(get_nodes).post(create_node))
.route("/api/test_connection", post(test_connection))
.route("/api/nodes/{id}/health", get(get_node_health))
.route("/api/nodes/{id}/health/stats", get(get_node_health_stats))
// 管理员路由
.route("/api/admin/login", post(admin_login))
.route("/api/admin/verify", get(admin_verify_token))
.route("/api/admin/nodes", get(admin_get_nodes))
.route("/api/admin/nodes/{id}/approve", put(admin_approve_node))
.route("/api/admin/nodes/{id}/revoke", put(admin_revoke_approval))
.route(
"/api/admin/nodes/{id}",
put(admin_update_node).delete(admin_delete_node),
);
if let Some(layer) = compression_layer {
router = router.layer(layer);
}
if let Some(layer) = cors_layer {
router = router.layer(layer);
}
router
}

View File

@@ -0,0 +1,198 @@
use std::env;
use std::net::{IpAddr, SocketAddr};
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub database: DatabaseConfig,
pub health_check: HealthCheckConfig,
pub logging: LoggingConfig,
pub cors: CorsConfig,
pub security: SecurityConfig,
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub host: String,
pub port: u16,
pub addr: SocketAddr,
}
#[derive(Debug, Clone)]
pub struct DatabaseConfig {
pub path: PathBuf,
pub max_connections: u32,
}
#[derive(Debug, Clone)]
pub struct HealthCheckConfig {
pub interval_seconds: u64,
pub timeout_seconds: u64,
pub max_retries: u32,
}
#[derive(Debug, Clone)]
pub struct LoggingConfig {
pub level: String,
pub rust_log: String,
}
#[derive(Debug, Clone)]
pub struct CorsConfig {
pub allowed_origins: Vec<String>,
pub allowed_methods: Vec<String>,
pub allowed_headers: Vec<String>,
pub enabled: bool,
}
#[derive(Debug, Clone)]
pub struct SecurityConfig {
pub enable_compression: bool,
pub secret_key: String,
pub jwt_secret: String,
pub admin_password: String,
}
impl Default for AppConfig {
fn default() -> Self {
Self::from_env().unwrap_or_else(|_| Self::default_config())
}
}
impl AppConfig {
pub fn from_env() -> Result<Self, env::VarError> {
let server_config = ServerConfig {
host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()),
port: env::var("SERVER_PORT")
.map(|s| s.parse().unwrap_or(8080))
.unwrap_or(8080),
addr: SocketAddr::from((
env::var("SERVER_HOST")
.unwrap_or_else(|_| "127.0.0.1".to_string())
.parse::<IpAddr>()
.unwrap(),
env::var("SERVER_PORT")
.map(|s| s.parse().unwrap_or(8080))
.unwrap_or(8080),
)),
};
let database_config = DatabaseConfig {
path: PathBuf::from(
env::var("DATABASE_PATH").unwrap_or_else(|_| "uptime.db".to_string()),
),
max_connections: env::var("DATABASE_MAX_CONNECTIONS")
.map(|s| s.parse().unwrap_or(10))
.unwrap_or(10),
};
let health_check_config = HealthCheckConfig {
interval_seconds: env::var("HEALTH_CHECK_INTERVAL")
.map(|s| s.parse().unwrap_or(30))
.unwrap_or(30),
timeout_seconds: env::var("HEALTH_CHECK_TIMEOUT")
.map(|s| s.parse().unwrap_or(10))
.unwrap_or(10),
max_retries: env::var("HEALTH_CHECK_RETRIES")
.map(|s| s.parse().unwrap_or(3))
.unwrap_or(3),
};
let logging_config = LoggingConfig {
level: env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()),
rust_log: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
};
let cors_config = CorsConfig {
allowed_origins: env::var("CORS_ALLOWED_ORIGINS")
.unwrap_or_else(|_| "http://localhost:3000,http://localhost:8080".to_string())
.split(',')
.map(|s| s.trim().to_string())
.collect(),
allowed_methods: env::var("CORS_ALLOWED_METHODS")
.unwrap_or_else(|_| "GET,POST,PUT,DELETE,OPTIONS".to_string())
.split(',')
.map(|s| s.trim().to_string())
.collect(),
allowed_headers: env::var("CORS_ALLOWED_HEADERS")
.unwrap_or_else(|_| "content-type,authorization".to_string())
.split(',')
.map(|s| s.trim().to_string())
.collect(),
enabled: env::var("ENABLE_CORS")
.map(|s| s.parse().unwrap_or(true))
.unwrap_or(true),
};
let security_config = SecurityConfig {
enable_compression: env::var("ENABLE_COMPRESSION")
.map(|s| s.parse().unwrap_or(true))
.unwrap_or(true),
secret_key: env::var("SECRET_KEY").unwrap_or_else(|_| "default-secret-key".to_string()),
jwt_secret: env::var("JWT_SECRET").unwrap_or_else(|_| "default-jwt-secret".to_string()),
admin_password: env::var("ADMIN_PASSWORD").unwrap_or_else(|_| "admin123".to_string()),
};
Ok(AppConfig {
server: server_config,
database: database_config,
health_check: health_check_config,
logging: logging_config,
cors: cors_config,
security: security_config,
})
}
pub fn default_config() -> Self {
Self {
server: ServerConfig {
host: "127.0.0.1".to_string(),
port: 8080,
addr: SocketAddr::from(([127, 0, 0, 1], 8080)),
},
database: DatabaseConfig {
path: PathBuf::from("uptime.db"),
max_connections: 10,
},
health_check: HealthCheckConfig {
interval_seconds: 30,
timeout_seconds: 10,
max_retries: 3,
},
logging: LoggingConfig {
level: "info".to_string(),
rust_log: "info".to_string(),
},
cors: CorsConfig {
allowed_origins: vec![
"http://localhost:3000".to_string(),
"http://localhost:8080".to_string(),
],
allowed_methods: vec![
"GET".to_string(),
"POST".to_string(),
"PUT".to_string(),
"DELETE".to_string(),
"OPTIONS".to_string(),
],
allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
enabled: true,
},
security: SecurityConfig {
enable_compression: true,
secret_key: "default-secret-key".to_string(),
jwt_secret: "default-jwt-secret".to_string(),
admin_password: "admin123".to_string(),
},
}
}
pub fn is_development(&self) -> bool {
env::var("NODE_ENV").unwrap_or_else(|_| "development".to_string()) == "development"
}
pub fn is_production(&self) -> bool {
env::var("NODE_ENV").unwrap_or_else(|_| "development".to_string()) == "production"
}
}

View File

@@ -0,0 +1,360 @@
use crate::db::entity::*;
use crate::db::Db;
use sea_orm::*;
use tokio::time::{sleep, Duration};
use tracing::{error, info, warn};
/// 数据清理策略配置
#[derive(Debug, Clone)]
pub struct CleanupConfig {
/// 健康记录保留天数
pub health_record_retention_days: i64,
/// 每个节点保留的健康记录最大数量
pub max_health_records_per_node: u64,
/// 清理任务运行间隔(秒)
pub cleanup_interval_seconds: u64,
/// 是否启用自动清理
pub auto_cleanup_enabled: bool,
}
impl Default for CleanupConfig {
fn default() -> Self {
Self {
health_record_retention_days: 30,
max_health_records_per_node: 70000,
cleanup_interval_seconds: 1200, // 20分钟
auto_cleanup_enabled: true,
}
}
}
/// 数据清理管理器
pub struct CleanupManager {
db: Db,
config: CleanupConfig,
running: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
impl CleanupManager {
/// 创建新的清理管理器
pub fn new(db: Db, config: CleanupConfig) -> Self {
Self {
db,
config,
running: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
/// 使用默认配置创建清理管理器
pub fn with_default_config(db: Db) -> Self {
Self::new(db, CleanupConfig::default())
}
/// 启动自动清理任务
pub async fn start_auto_cleanup(&self) -> anyhow::Result<()> {
if self.config.auto_cleanup_enabled {
let running = self.running.clone();
let db = self.db.clone();
let config = self.config.clone();
running.store(true, std::sync::atomic::Ordering::SeqCst);
tokio::spawn(async move {
info!("Auto cleanup task started");
while running.load(std::sync::atomic::Ordering::SeqCst) {
if let Err(e) = Self::perform_cleanup(&db, &config).await {
error!("Auto cleanup failed: {}", e);
}
sleep(Duration::from_secs(config.cleanup_interval_seconds)).await;
}
info!("Auto cleanup task stopped");
});
}
Ok(())
}
/// 停止自动清理任务
pub fn stop_auto_cleanup(&self) {
self.running
.store(false, std::sync::atomic::Ordering::SeqCst);
}
/// 执行一次完整的清理操作
pub async fn perform_cleanup(db: &Db, config: &CleanupConfig) -> anyhow::Result<CleanupResult> {
let mut result = CleanupResult::default();
// 清理旧的健康记录
let health_cleanup_result =
Self::cleanup_old_health_records(db, config.health_record_retention_days).await?;
result.old_health_records_cleaned = health_cleanup_result.records_removed;
// 清理过量的健康记录
let excess_cleanup_result =
Self::cleanup_excess_health_records(db, config.max_health_records_per_node).await?;
result.excess_health_records_cleaned = excess_cleanup_result.records_removed;
// 数据库维护
let maintenance_result = Self::perform_database_maintenance(db).await?;
result.vacuum_performed = maintenance_result.vacuum_performed;
result.analyze_performed = maintenance_result.analyze_performed;
info!("Cleanup completed: {:?}", result);
Ok(result)
}
/// 清理旧的健康记录
async fn cleanup_old_health_records(
db: &Db,
days: i64,
) -> anyhow::Result<CleanupHealthRecordsResult> {
let cutoff = chrono::Local::now().fixed_offset() - chrono::Duration::days(days);
let result = health_records::Entity::delete_many()
.filter(health_records::Column::CheckedAt.lt(cutoff))
.exec(db.orm_db())
.await?;
let records_removed = result.rows_affected;
if records_removed > 0 {
info!(
"Cleaned {} old health records (older than {} days)",
records_removed, days
);
}
Ok(CleanupHealthRecordsResult { records_removed })
}
/// 清理过量的健康记录
async fn cleanup_excess_health_records(
db: &Db,
max_records: u64,
) -> anyhow::Result<CleanupExcessRecordsResult> {
// 获取所有节点
let nodes = shared_nodes::Entity::find().all(db.orm_db()).await?;
let mut total_removed = 0;
for node in nodes {
// 计算需要删除的记录数量
let total_count = health_records::Entity::find()
.filter(health_records::Column::NodeId.eq(node.id))
.count(db.orm_db())
.await?;
if total_count > max_records {
let to_remove = total_count - max_records;
// 获取需要保留的最小ID
let keep_id = health_records::Entity::find()
.filter(health_records::Column::NodeId.eq(node.id))
.order_by_desc(health_records::Column::CheckedAt)
.offset(max_records)
.limit(1)
.into_model::<health_records::Model>()
.one(db.orm_db())
.await?;
info!(
"Node {}: total count: {}, to remove: {}, last keep record: {:?}",
node.id, total_count, to_remove, keep_id
);
if let Some(keep_record) = keep_id {
// 删除比保留记录更早的记录
let result = health_records::Entity::delete_many()
.filter(health_records::Column::NodeId.eq(node.id))
.filter(health_records::Column::Id.lt(keep_record.id))
.exec(db.orm_db())
.await?;
total_removed += result.rows_affected;
}
}
}
if total_removed > 0 {
info!(
"Cleaned {} excess health records (max {} per node)",
total_removed, max_records
);
}
Ok(CleanupExcessRecordsResult {
records_removed: total_removed,
})
}
/// 执行数据库维护操作
async fn perform_database_maintenance(db: &Db) -> anyhow::Result<DatabaseMaintenanceResult> {
let mut vacuum_performed = false;
let mut analyze_performed = false;
// 执行 ANALYZE
match db
.orm_db()
.execute(Statement::from_string(
DatabaseBackend::Sqlite,
"ANALYZE".to_string(),
))
.await
{
Ok(_) => {
analyze_performed = true;
info!("Database ANALYZE completed");
}
Err(e) => {
warn!("Database ANALYZE failed: {}", e);
}
}
// 执行 VACUUM仅在需要时
if vacuum_performed || analyze_performed {
match db
.orm_db()
.execute(Statement::from_string(
DatabaseBackend::Sqlite,
"VACUUM".to_string(),
))
.await
{
Ok(_) => {
vacuum_performed = true;
info!("Database VACUUM completed");
}
Err(e) => {
warn!("Database VACUUM failed: {}", e);
}
}
}
Ok(DatabaseMaintenanceResult {
vacuum_performed,
analyze_performed,
})
}
/// 获取数据库统计信息
pub async fn get_database_stats(db: &Db) -> anyhow::Result<DatabaseStats> {
let total_nodes = shared_nodes::Entity::find().count(db.orm_db()).await?;
let total_health_records = health_records::Entity::find().count(db.orm_db()).await?;
let active_nodes = shared_nodes::Entity::find()
.filter(shared_nodes::Column::IsActive.eq(true))
.count(db.orm_db())
.await?;
Ok(DatabaseStats {
total_nodes,
active_nodes,
total_health_records,
})
}
/// 获取清理配置
pub fn get_config(&self) -> &CleanupConfig {
&self.config
}
/// 更新清理配置
pub fn update_config(&mut self, config: CleanupConfig) {
self.config = config;
}
}
/// 清理结果
#[derive(Default, Debug, Clone, serde::Serialize)]
pub struct CleanupResult {
pub old_health_records_cleaned: u64,
pub old_instances_cleaned: u64,
pub excess_health_records_cleaned: u64,
pub vacuum_performed: bool,
pub analyze_performed: bool,
}
/// 健康记录清理结果
#[derive(Debug, Clone, serde::Serialize)]
pub struct CleanupHealthRecordsResult {
pub records_removed: u64,
}
/// 停止实例清理结果
#[derive(Debug, Clone, serde::Serialize)]
pub struct CleanupStoppedInstancesResult {
pub instances_removed: u64,
}
/// 过量记录清理结果
#[derive(Debug, Clone, serde::Serialize)]
pub struct CleanupExcessRecordsResult {
pub records_removed: u64,
}
/// 数据库维护结果
#[derive(Debug, Clone, serde::Serialize)]
pub struct DatabaseMaintenanceResult {
pub vacuum_performed: bool,
pub analyze_performed: bool,
}
/// 数据库统计信息
#[derive(Debug, Clone, serde::Serialize)]
pub struct DatabaseStats {
pub total_nodes: u64,
pub active_nodes: u64,
pub total_health_records: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Db;
#[tokio::test]
async fn test_cleanup_manager() {
let db = Db::memory_db().await;
let cleanup_manager = CleanupManager::with_default_config(db.clone());
// 测试获取配置
let config = cleanup_manager.get_config();
assert_eq!(config.health_record_retention_days, 30);
// 测试清理操作
let result = CleanupManager::perform_cleanup(&db, config).await.unwrap();
println!("Cleanup result: {:?}", result);
// 测试获取统计信息
let stats = CleanupManager::get_database_stats(&db).await.unwrap();
println!("Database stats: {:?}", stats);
}
#[tokio::test]
async fn test_cleanup_config() {
let config = CleanupConfig {
health_record_retention_days: 7,
max_health_records_per_node: 500,
cleanup_interval_seconds: 1800,
auto_cleanup_enabled: false,
};
let db = Db::memory_db().await;
let mut cleanup_manager = CleanupManager::new(db, config.clone());
assert_eq!(cleanup_manager.get_config().health_record_retention_days, 7);
// 测试更新配置
let new_config = CleanupConfig::default();
cleanup_manager.update_config(new_config);
assert_eq!(
cleanup_manager.get_config().health_record_retention_days,
30
);
}
}

View File

@@ -0,0 +1,39 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "connection_instances")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub node_id: i32,
#[sea_orm(unique)]
pub instance_id: String,
pub status: String,
#[sea_orm(column_type = "Text")]
pub config: String,
pub started_at: DateTimeWithTimeZone,
pub stopped_at: DateTimeWithTimeZone,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::shared_nodes::Entity",
from = "Column::NodeId",
to = "super::shared_nodes::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
SharedNodes,
}
impl Related<super::shared_nodes::Entity> for Entity {
fn to() -> RelationDef {
Relation::SharedNodes.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,37 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "health_records")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub node_id: i32,
pub status: String,
pub response_time: i32,
#[sea_orm(column_type = "Text")]
pub error_message: String,
pub checked_at: DateTimeWithTimeZone,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::shared_nodes::Entity",
from = "Column::NodeId",
to = "super::shared_nodes::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
SharedNodes,
}
impl Related<super::shared_nodes::Entity> for Entity {
fn to() -> RelationDef {
Relation::SharedNodes.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
pub mod prelude;
pub mod health_records;
pub mod shared_nodes;

View File

@@ -0,0 +1,4 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
pub use super::health_records::Entity as HealthRecords;
pub use super::shared_nodes::Entity as SharedNodes;

View File

@@ -0,0 +1,44 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "shared_nodes")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub host: String,
pub port: i32,
pub protocol: String,
pub version: String,
pub allow_relay: bool,
pub network_name: String,
pub network_secret: String,
#[sea_orm(column_type = "Text")]
pub description: String,
pub max_connections: i32,
pub current_connections: i32,
pub is_active: bool,
pub is_approved: bool,
pub qq_number: String,
pub wechat: String,
pub mail: String,
pub created_at: DateTimeWithTimeZone,
pub updated_at: DateTimeWithTimeZone,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::health_records::Entity")]
HealthRecords,
}
impl Related<super::health_records::Entity> for Entity {
fn to() -> RelationDef {
Relation::HealthRecords.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,351 @@
pub mod cleanup;
pub mod entity;
pub mod operations;
use std::fmt;
use sea_orm::{
prelude::*, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
QueryFilter as _, Set, SqlxSqliteConnector, Statement, TransactionTrait as _,
};
use sea_orm_migration::MigratorTrait as _;
use serde::{Deserialize, Serialize};
use sqlx::{migrate::MigrateDatabase as _, Sqlite, SqlitePool};
use crate::migrator;
#[derive(Debug, Clone)]
pub struct Db {
db_path: String,
db: SqlitePool,
orm_db: DatabaseConnection,
}
impl Db {
pub async fn new<T: ToString>(db_path: T) -> anyhow::Result<Self> {
let db = Self::prepare_db(db_path.to_string().as_str()).await?;
let orm_db = SqlxSqliteConnector::from_sqlx_sqlite_pool(db.clone());
// 运行数据库迁移
migrator::Migrator::up(&orm_db, None).await?;
// 优化 SQLite 性能
Self::optimize_sqlite(&orm_db).await?;
Ok(Self {
db_path: db_path.to_string(),
db,
orm_db,
})
}
pub async fn memory_db() -> Self {
Self::new(":memory:").await.unwrap()
}
#[tracing::instrument(ret)]
async fn prepare_db(db_path: &str) -> anyhow::Result<SqlitePool> {
if !Sqlite::database_exists(db_path).await.unwrap_or(false) {
tracing::info!("Database not found, creating a new one");
Sqlite::create_database(db_path).await?;
}
let db = sqlx::pool::PoolOptions::new()
.max_lifetime(None)
.idle_timeout(None)
.connect(db_path)
.await?;
Ok(db)
}
async fn optimize_sqlite(db: &DatabaseConnection) -> Result<(), DbErr> {
// 优化 SQLite 性能
let pragmas = vec![
"PRAGMA journal_mode = WAL", // 使用 WAL 模式提高并发性能
"PRAGMA synchronous = NORMAL", // 平衡性能和数据安全
"PRAGMA cache_size = 10000", // 增加缓存大小
"PRAGMA temp_store = memory", // 临时存储使用内存
"PRAGMA mmap_size = 268435456", // 内存映射大小 256MB
"PRAGMA foreign_keys = ON", // 启用外键约束
];
for pragma in pragmas {
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::Sqlite,
pragma.to_string(),
))
.await?;
}
Ok(())
}
pub fn inner(&self) -> SqlitePool {
self.db.clone()
}
pub fn orm_db(&self) -> &DatabaseConnection {
&self.orm_db
}
/// 清理旧的健康度记录删除30天前的记录
pub async fn cleanup_old_health_records(&self) -> Result<u64, DbErr> {
use chrono::Duration;
use entity::health_records;
let cutoff_date = chrono::Utc::now().naive_utc() - Duration::days(30);
let result = health_records::Entity::delete_many()
.filter(health_records::Column::CheckedAt.lt(cutoff_date))
.exec(self.orm_db())
.await?;
Ok(result.rows_affected)
}
/// 获取数据库统计信息
pub async fn get_database_stats(&self) -> anyhow::Result<DatabaseStats> {
use entity::{health_records, shared_nodes};
let node_count = shared_nodes::Entity::find().count(self.orm_db()).await?;
let health_record_count = health_records::Entity::find().count(self.orm_db()).await?;
let active_nodes_count = shared_nodes::Entity::find()
.filter(shared_nodes::Column::IsActive.eq(true))
.count(self.orm_db())
.await?;
Ok(DatabaseStats {
total_nodes: node_count,
active_nodes: active_nodes_count,
total_health_records: health_record_count,
})
}
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct DatabaseStats {
pub total_nodes: u64,
pub active_nodes: u64,
pub total_health_records: u64,
}
/// 健康状态枚举
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum HealthStatus {
/// 健康状态
Healthy,
/// 不健康状态
Unhealthy,
/// 超时状态
Timeout,
/// 连接错误
ConnectionError,
/// 未知错误
Unknown,
}
impl fmt::Display for HealthStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HealthStatus::Healthy => write!(f, "healthy"),
HealthStatus::Unhealthy => write!(f, "unhealthy"),
HealthStatus::Timeout => write!(f, "timeout"),
HealthStatus::ConnectionError => write!(f, "connection_error"),
HealthStatus::Unknown => write!(f, "unknown"),
}
}
}
impl From<String> for HealthStatus {
fn from(s: String) -> Self {
match s.to_lowercase().as_str() {
"healthy" => HealthStatus::Healthy,
"unhealthy" => HealthStatus::Unhealthy,
"timeout" => HealthStatus::Timeout,
"connection_error" => HealthStatus::ConnectionError,
_ => HealthStatus::Unknown,
}
}
}
impl From<&str> for HealthStatus {
fn from(s: &str) -> Self {
HealthStatus::from(s.to_string())
}
}
/// 健康统计信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStats {
/// 总检查次数
pub total_checks: u64,
/// 健康检查次数
pub healthy_count: u64,
/// 不健康检查次数
pub unhealthy_count: u64,
/// 健康百分比
pub health_percentage: f64,
/// 平均响应时间(毫秒)
pub average_response_time: Option<f64>,
/// 正常运行时间百分比
pub uptime_percentage: f64,
/// 最后检查时间
pub last_check_time: Option<chrono::DateTime<chrono::Utc>>,
/// 最后健康状态
pub last_status: Option<HealthStatus>,
}
impl Default for HealthStats {
fn default() -> Self {
Self {
total_checks: 0,
healthy_count: 0,
unhealthy_count: 0,
health_percentage: 0.0,
average_response_time: None,
uptime_percentage: 0.0,
last_check_time: None,
last_status: None,
}
}
}
impl HealthStats {
/// 从健康记录列表创建统计信息
pub fn from_records(records: &[self::entity::health_records::Model]) -> Self {
if records.is_empty() {
return Self::default();
}
let total_checks = records.len() as u64;
let healthy_count = records.iter().filter(|r| r.is_healthy()).count() as u64;
let unhealthy_count = total_checks - healthy_count;
let health_percentage = if total_checks > 0 {
(healthy_count as f64 / total_checks as f64) * 100.0
} else {
0.0
};
// 计算平均响应时间(只计算健康状态的记录)
let healthy_records: Vec<_> = records
.iter()
.filter(|r| r.is_healthy() && r.response_time > 0)
.collect();
let average_response_time = if !healthy_records.is_empty() {
let total_time: i32 = healthy_records.iter().map(|r| r.response_time).sum();
Some(total_time as f64 / healthy_records.len() as f64)
} else {
None
};
// 正常运行时间百分比(基于健康状态)
let uptime_percentage = health_percentage;
// 获取最后的检查信息
let last_record = records.first(); // records 应该按时间倒序排列
let last_check_time = last_record.map(|r| r.checked_at.into());
let last_status = last_record.map(|r| HealthStatus::from(r.status.clone()));
Self {
total_checks,
healthy_count,
unhealthy_count,
health_percentage,
average_response_time,
uptime_percentage,
last_check_time,
last_status,
}
}
}
/// Model 的扩展方法
impl entity::health_records::Model {
/// 检查记录是否为健康状态
pub fn is_healthy(&self) -> bool {
let status = HealthStatus::from(self.status.clone());
matches!(status, HealthStatus::Healthy)
}
/// 创建新的活动模型
pub fn new_active_model(
node_id: i32,
status: HealthStatus,
response_time: Option<i32>,
error_message: Option<String>,
) -> entity::health_records::ActiveModel {
entity::health_records::ActiveModel {
node_id: Set(node_id),
status: Set(status.to_string()),
response_time: Set(response_time.unwrap_or(0)),
error_message: Set(error_message.unwrap_or_default()),
checked_at: Set(chrono::Utc::now().fixed_offset()),
..Default::default()
}
}
/// 获取健康状态
pub fn get_status(&self) -> HealthStatus {
HealthStatus::from(self.status.clone())
}
}
/// Model 的扩展方法
impl entity::shared_nodes::Model {
/// 创建新的活动模型
#[allow(clippy::too_many_arguments)]
pub fn new_active_model(
name: String,
host: String,
port: i32,
protocol: String,
version: Option<String>,
description: Option<String>,
max_connections: i32,
allow_relay: bool,
network_name: String,
network_secret: Option<String>,
) -> entity::shared_nodes::ActiveModel {
let now = chrono::Utc::now().fixed_offset();
entity::shared_nodes::ActiveModel {
name: Set(name),
host: Set(host),
port: Set(port),
protocol: Set(protocol),
version: Set(version.unwrap_or_default()),
description: Set(description.unwrap_or_default()),
max_connections: Set(max_connections),
current_connections: Set(0),
is_active: Set(true),
is_approved: Set(false),
allow_relay: Set(allow_relay),
network_name: Set(network_name),
network_secret: Set(network_secret.unwrap_or_default()),
created_at: Set(now),
updated_at: Set(now),
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
#[tokio::test]
async fn test_database_creation() {
let db = Db::memory_db().await;
let stats = db.get_database_stats().await.unwrap();
// 初始状态下应该没有记录
assert_eq!(stats.total_nodes, 0);
assert_eq!(stats.active_nodes, 0);
assert_eq!(stats.total_health_records, 0);
}
}

View File

@@ -0,0 +1,343 @@
use crate::api::CreateNodeRequest;
use crate::db::entity::*;
use crate::db::Db;
use crate::db::HealthStats;
use crate::db::HealthStatus;
use sea_orm::*;
/// 节点管理操作
pub struct NodeOperations;
impl NodeOperations {
pub fn create_node_model(req: CreateNodeRequest) -> shared_nodes::ActiveModel {
shared_nodes::ActiveModel {
id: NotSet,
name: Set(req.name),
host: Set(req.host),
port: Set(req.port),
protocol: Set(req.protocol),
version: Set("".to_string()),
description: Set(req.description.unwrap_or_default()),
max_connections: Set(req.max_connections),
current_connections: Set(0),
is_active: Set(false),
is_approved: Set(false),
allow_relay: Set(req.allow_relay),
network_name: Set(req.network_name),
network_secret: Set(req.network_secret.unwrap_or_default()),
qq_number: Set(req.qq_number.unwrap_or_default()),
wechat: Set(req.wechat.unwrap_or_default()),
mail: Set(req.mail.unwrap_or_default()),
created_at: Set(chrono::Utc::now().fixed_offset()),
updated_at: Set(chrono::Utc::now().fixed_offset()),
}
}
/// 创建新节点
pub async fn create_node(
db: &Db,
req: CreateNodeRequest,
) -> Result<shared_nodes::Model, DbErr> {
let node = Self::create_node_model(req);
let insert_result = shared_nodes::Entity::insert(node).exec(db.orm_db()).await?;
shared_nodes::Entity::find_by_id(insert_result.last_insert_id)
.one(db.orm_db())
.await?
.ok_or(DbErr::RecordNotFound(
"Failed to retrieve created node".to_string(),
))
}
/// 获取所有节点
pub async fn get_all_nodes(db: &Db) -> Result<Vec<shared_nodes::Model>, DbErr> {
shared_nodes::Entity::find()
.order_by_asc(shared_nodes::Column::Id)
.all(db.orm_db())
.await
}
/// 根据ID获取节点
pub async fn get_node_by_id(db: &Db, id: i32) -> Result<Option<shared_nodes::Model>, DbErr> {
shared_nodes::Entity::find_by_id(id).one(db.orm_db()).await
}
/// 更新节点状态
pub async fn update_node_status(
db: &Db,
id: i32,
is_active: bool,
current_connections: Option<i32>,
) -> Result<shared_nodes::Model, DbErr> {
let mut node = shared_nodes::Entity::find_by_id(id)
.one(db.orm_db())
.await?
.ok_or(DbErr::RecordNotFound("Node not found".to_string()))?;
let mut node = node.into_active_model();
node.is_active = Set(is_active);
if let Some(connections) = current_connections {
node.current_connections = Set(connections);
}
node.updated_at = Set(chrono::Utc::now().fixed_offset());
let updated_node = shared_nodes::Entity::update(node).exec(db.orm_db()).await?;
Ok(updated_node)
}
/// 删除节点
pub async fn delete_node(db: &Db, id: i32) -> Result<u64, DbErr> {
let result = shared_nodes::Entity::delete_by_id(id)
.exec(db.orm_db())
.await?;
Ok(result.rows_affected)
}
/// 获取活跃节点
pub async fn get_active_nodes(db: &Db) -> Result<Vec<shared_nodes::Model>, DbErr> {
shared_nodes::Entity::find()
.filter(shared_nodes::Column::IsActive.eq(true))
.order_by_asc(shared_nodes::Column::Id)
.all(db.orm_db())
.await
}
/// 检查节点是否存在根据host、port、protocol
pub async fn node_exists(
db: &Db,
host: &str,
port: i32,
protocol: &str,
) -> Result<bool, DbErr> {
let count = shared_nodes::Entity::find()
.filter(shared_nodes::Column::Host.eq(host))
.filter(shared_nodes::Column::Port.eq(port))
.filter(shared_nodes::Column::Protocol.eq(protocol))
.count(db.orm_db())
.await?;
Ok(count > 0)
}
pub async fn update_node_version(
db: &Db,
node_id: i32,
version: String,
) -> Result<shared_nodes::Model, DbErr> {
let mut node = shared_nodes::Entity::find_by_id(node_id)
.one(db.orm_db())
.await?
.ok_or(DbErr::RecordNotFound("Node not found".to_string()))?;
let mut node = node.into_active_model();
node.version = Set(version);
node.updated_at = Set(chrono::Utc::now().fixed_offset());
let updated_node = shared_nodes::Entity::update(node).exec(db.orm_db()).await?;
Ok(updated_node)
}
}
/// 健康记录操作
pub struct HealthOperations;
impl HealthOperations {
/// 创建健康记录
pub async fn create_health_record(
db: &Db,
node_id: i32,
status: HealthStatus,
response_time: Option<i32>,
error_message: Option<String>,
) -> Result<health_records::Model, DbErr> {
let record =
health_records::Model::new_active_model(node_id, status, response_time, error_message);
let insert_result = health_records::Entity::insert(record)
.exec(db.orm_db())
.await?;
health_records::Entity::find_by_id(insert_result.last_insert_id)
.one(db.orm_db())
.await?
.ok_or(DbErr::RecordNotFound(
"Failed to retrieve created health record".to_string(),
))
}
/// 获取节点的健康记录
pub async fn get_node_health_records(
db: &Db,
node_id: i32,
from_date: Option<chrono::NaiveDateTime>,
limit: Option<u64>,
) -> Result<Vec<health_records::Model>, DbErr> {
let mut query = health_records::Entity::find()
.filter(health_records::Column::NodeId.eq(node_id))
.order_by_desc(health_records::Column::CheckedAt);
if let Some(from_date) = from_date {
query = query.filter(health_records::Column::CheckedAt.gte(from_date));
}
if let Some(limit) = limit {
query = query.limit(Some(limit));
}
query.all(db.orm_db()).await
}
/// 获取节点最近的健康状态
pub async fn get_latest_health_status(
db: &Db,
node_id: i32,
) -> Result<Option<health_records::Model>, DbErr> {
health_records::Entity::find()
.filter(health_records::Column::NodeId.eq(node_id))
.order_by_desc(health_records::Column::CheckedAt)
.one(db.orm_db())
.await
}
/// 获取健康统计信息
pub async fn get_health_stats(db: &Db, node_id: i32, hours: i64) -> Result<HealthStats, DbErr> {
let since = chrono::Utc::now().naive_utc() - chrono::Duration::hours(hours);
let records = health_records::Entity::find()
.filter(health_records::Column::NodeId.eq(node_id))
.filter(health_records::Column::CheckedAt.gte(since))
.order_by_desc(health_records::Column::CheckedAt)
.all(db.orm_db())
.await?;
Ok(HealthStats::from_records(&records))
}
/// 清理旧的健康记录
pub async fn cleanup_old_records(db: &Db, days: i64) -> Result<u64, DbErr> {
let cutoff = chrono::Utc::now().naive_utc() - chrono::Duration::days(days);
let result = health_records::Entity::delete_many()
.filter(health_records::Column::CheckedAt.lt(cutoff))
.exec(db.orm_db())
.await?;
Ok(result.rows_affected)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Db;
#[tokio::test]
async fn test_node_operations() {
let db = Db::memory_db().await;
let req = CreateNodeRequest {
name: "Test Node".to_string(),
host: "test.example.com".to_string(),
port: 11010,
protocol: "tcp".to_string(),
description: Some("Test node".to_string()),
max_connections: 100,
allow_relay: false,
network_name: "test-network".to_string(),
network_secret: Some("test-secret".to_string()),
qq_number: Some("123456789".to_string()),
wechat: Some("test_wechat".to_string()),
mail: Some("test@example.com".to_string()),
};
// 测试创建节点
let node = NodeOperations::create_node(&db, req).await.unwrap();
assert_eq!(node.name, "Test Node");
assert_eq!(node.host, "test.example.com");
assert_eq!(node.port, 11010);
assert!(node.is_active);
// 测试获取节点
let found_node = NodeOperations::get_node_by_id(&db, node.id).await.unwrap();
assert!(found_node.is_some());
assert_eq!(found_node.unwrap().id, node.id);
// 测试获取所有节点
let all_nodes = NodeOperations::get_all_nodes(&db).await.unwrap();
assert_eq!(all_nodes.len(), 1);
// 测试节点存在性检查
let exists = NodeOperations::node_exists(&db, "test.example.com", 11010, "tcp")
.await
.unwrap();
assert!(exists);
let not_exists = NodeOperations::node_exists(&db, "nonexistent.com", 8080, "tcp")
.await
.unwrap();
assert!(!not_exists);
}
#[tokio::test]
async fn test_health_operations() {
let db = Db::memory_db().await;
let req = CreateNodeRequest {
name: "Test Node".to_string(),
host: "test.example.com".to_string(),
port: 11010,
protocol: "tcp".to_string(),
description: Some("Test node".to_string()),
max_connections: 100,
allow_relay: false,
network_name: "test-network".to_string(),
network_secret: Some("test-secret".to_string()),
qq_number: Some("123456789".to_string()),
wechat: Some("test_wechat".to_string()),
mail: Some("test@example.com".to_string()),
};
// 创建测试节点
let node = NodeOperations::create_node(&db, req).await.unwrap();
// 测试创建健康记录
let record = HealthOperations::create_health_record(
&db,
node.id,
HealthStatus::Healthy,
Some(100),
None,
)
.await
.unwrap();
assert_eq!(record.node_id, node.id);
assert!(record.is_healthy());
assert_eq!(record.response_time, 100);
// 测试获取健康记录
let records = HealthOperations::get_node_health_records(&db, node.id, None, None)
.await
.unwrap();
assert_eq!(records.len(), 1);
// 测试获取最新状态
let latest = HealthOperations::get_latest_health_status(&db, node.id)
.await
.unwrap();
assert!(latest.is_some());
assert_eq!(latest.unwrap().id, record.id);
// 测试健康统计
let stats = HealthOperations::get_health_stats(&db, node.id, 24)
.await
.unwrap();
assert_eq!(stats.total_checks, 1);
assert_eq!(stats.healthy_count, 1);
assert_eq!(stats.health_percentage, 100.0);
}
}

View File

@@ -0,0 +1,660 @@
use std::{
ops::{DerefMut, Div},
sync::Arc,
time::{Duration, Instant},
};
use anyhow::Context as _;
use dashmap::DashMap;
use easytier::{
common::{
config::{ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader},
scoped_task::ScopedTask,
},
defer,
instance_manager::NetworkInstanceManager,
launcher::ConfigSource,
};
use serde::{Deserialize, Serialize};
use sqlx::any;
use tracing::{debug, error, info, instrument, warn};
use crate::db::{
entity::shared_nodes,
operations::{HealthOperations, NodeOperations},
Db, HealthStatus,
};
pub struct HealthCheckOneNode {
node_id: String,
}
const HEALTH_CHECK_RING_GRANULARITY_SEC: usize = 60 * 15; // 15分钟
const HEALTH_CHECK_RING_MAX_DURATION_SEC: usize = 60 * 60 * 24; // 最多一天
// const HEALTH_CHECK_RING_GRANULARITY_SEC: usize = 10;
// const HEALTH_CHECK_RING_MAX_DURATION_SEC: usize = 60;
const HEALTH_CHECK_RING_SIZE: usize =
HEALTH_CHECK_RING_MAX_DURATION_SEC / HEALTH_CHECK_RING_GRANULARITY_SEC;
#[derive(Debug, Default, Clone)]
struct RingItem {
counter: u64,
round: u64,
}
impl RingItem {
fn try_update_round(&mut self, timestamp: u64) {
let cur_round =
timestamp.div((HEALTH_CHECK_RING_GRANULARITY_SEC * HEALTH_CHECK_RING_SIZE) as u64);
if self.round != cur_round {
self.round = cur_round;
self.counter = 0;
}
}
fn inc(&mut self, timestamp: u64) {
self.try_update_round(timestamp);
self.counter += 1;
}
fn get(&mut self, timestamp: u64) -> u64 {
self.try_update_round(timestamp);
self.counter
}
}
#[derive(Debug, Clone)]
pub struct HealthyMemRecord {
node_id: i32,
current_health_status: HealthStatus,
last_error_info: Option<String>,
last_check_time: chrono::DateTime<chrono::Utc>,
last_response_time: Option<i32>,
// the current time is corresponding to the index by modulo with UNIX-timestamp.
total_check_counter_ring: Vec<RingItem>,
healthy_counter_ring: Vec<RingItem>,
}
impl HealthyMemRecord {
pub fn new(node_id: i32) -> Self {
Self {
node_id,
current_health_status: HealthStatus::Unknown,
last_error_info: None,
last_check_time: chrono::Utc::now(),
last_response_time: None,
total_check_counter_ring: vec![Default::default(); HEALTH_CHECK_RING_SIZE],
healthy_counter_ring: vec![Default::default(); HEALTH_CHECK_RING_SIZE],
}
}
/// 从数据库记录初始化内存记录
pub fn from_db_records(
node_id: i32,
records: &[crate::db::entity::health_records::Model],
) -> Self {
let mut mem_record = Self::new(node_id);
if let Some(latest) = records.first() {
mem_record.current_health_status = latest.get_status();
mem_record.last_check_time = latest.checked_at.to_utc();
mem_record.last_response_time = if latest.response_time == 0 {
None
} else {
Some(latest.response_time)
};
mem_record.last_error_info = if latest.error_message.is_empty() {
None
} else {
Some(latest.error_message.clone())
};
}
// 填充环形缓冲区
mem_record.populate_ring_from_records(records);
mem_record
}
/// 从历史记录填充环形缓冲区
fn populate_ring_from_records(&mut self, records: &[crate::db::entity::health_records::Model]) {
let now = chrono::Utc::now().timestamp() as usize;
for record in records {
let record_time = record.checked_at.to_utc().timestamp() as usize;
let time_diff = now.saturating_sub(record_time);
// 只处理在环形缓冲区时间范围内的记录
if time_diff < HEALTH_CHECK_RING_MAX_DURATION_SEC {
let ring_index =
(record_time / HEALTH_CHECK_RING_GRANULARITY_SEC) % HEALTH_CHECK_RING_SIZE;
self.total_check_counter_ring[ring_index].inc(record_time as u64);
if record.get_status() == HealthStatus::Healthy {
self.healthy_counter_ring[ring_index].inc(record_time as u64);
}
}
}
}
/// 更新健康状态并记录到环形缓冲区
pub fn update_health_status(
&mut self,
status: HealthStatus,
response_time: Option<i32>,
error_message: Option<String>,
) {
self.current_health_status = status.clone();
self.last_check_time = chrono::Utc::now();
self.last_response_time = response_time;
self.last_error_info = error_message;
// 更新环形缓冲区
let now = chrono::Utc::now().timestamp() as usize;
let ring_index = (now / HEALTH_CHECK_RING_GRANULARITY_SEC) % HEALTH_CHECK_RING_SIZE;
self.total_check_counter_ring[ring_index].inc(now as u64);
self.healthy_counter_ring[ring_index].try_update_round(now as u64);
if status == HealthStatus::Healthy {
self.healthy_counter_ring[ring_index].inc(now as u64);
}
}
/// 获取健康统计信息
pub fn get_health_stats(&self, hours: u64) -> crate::db::HealthStats {
let now = chrono::Utc::now().timestamp() as usize;
let mut total_checks = 0;
let mut healthy_count = 0;
for ring_index in 0..HEALTH_CHECK_RING_SIZE {
total_checks += self.total_check_counter_ring[ring_index].counter;
healthy_count += self.healthy_counter_ring[ring_index].counter;
}
let health_percentage = if total_checks > 0 {
(healthy_count as f64 / total_checks as f64) * 100.0
} else {
0.0
};
crate::db::HealthStats {
total_checks,
healthy_count,
unhealthy_count: total_checks - healthy_count,
health_percentage,
average_response_time: self.last_response_time.map(|rt| rt as f64),
uptime_percentage: health_percentage,
last_check_time: Some(self.last_check_time),
last_status: Some(self.current_health_status.clone()),
}
}
/// 获取当前健康状态
pub fn get_current_health_status(&self) -> &HealthStatus {
&self.current_health_status
}
/// 获取最后检查时间
pub fn get_last_check_time(&self) -> chrono::DateTime<chrono::Utc> {
self.last_check_time
}
/// 获取最后响应时间
pub fn get_last_response_time(&self) -> Option<i32> {
self.last_response_time
}
/// 获取最后错误信息
pub fn get_last_error_info(&self) -> &Option<String> {
&self.last_error_info
}
pub fn get_counter_ring(&mut self) -> (Vec<u64>, Vec<u64>) {
let now = self.last_check_time.timestamp() as usize;
let mut total_ring = vec![0; HEALTH_CHECK_RING_SIZE];
let mut healthy_ring = vec![0; HEALTH_CHECK_RING_SIZE];
let mut total_checks = 0;
let mut healthy_count = 0;
for i in 0..HEALTH_CHECK_RING_SIZE {
let ring_time = now - (i * HEALTH_CHECK_RING_GRANULARITY_SEC);
let ring_index =
ring_time.div_euclid(HEALTH_CHECK_RING_GRANULARITY_SEC) % HEALTH_CHECK_RING_SIZE;
total_ring[i] = self.total_check_counter_ring[ring_index].get(ring_time as u64);
healthy_ring[i] = self.healthy_counter_ring[ring_index].counter;
}
(total_ring, healthy_ring)
}
pub fn get_ring_granularity(&self) -> u32 {
HEALTH_CHECK_RING_GRANULARITY_SEC as u32
}
}
pub struct HealthChecker {
db: Db,
instance_mgr: Arc<NetworkInstanceManager>,
inst_id_map: DashMap<i32, uuid::Uuid>,
node_tasks: DashMap<i32, ScopedTask<()>>,
node_records: Arc<DashMap<i32, HealthyMemRecord>>,
node_cfg: Arc<DashMap<i32, TomlConfigLoader>>,
}
impl HealthChecker {
pub fn new(db: Db) -> Self {
let instance_mgr = Arc::new(NetworkInstanceManager::new());
Self {
db,
instance_mgr,
inst_id_map: DashMap::new(),
node_tasks: DashMap::new(),
node_records: Arc::new(DashMap::new()),
node_cfg: Arc::new(DashMap::new()),
}
}
/// 启动时从数据库加载所有节点的健康记录到内存
pub async fn load_health_records_from_db(&self) -> anyhow::Result<()> {
info!("Loading health records from database...");
// 获取所有活跃节点
let nodes = NodeOperations::get_all_nodes(&self.db)
.await
.with_context(|| "Failed to get all nodes from database")?;
let from_date = chrono::Utc::now().naive_utc()
- chrono::Duration::seconds(HEALTH_CHECK_RING_MAX_DURATION_SEC as i64);
for node in nodes {
// 获取每个节点最近的健康记录(用于初始化环形缓冲区)
let records =
HealthOperations::get_node_health_records(&self.db, node.id, Some(from_date), None)
.await
.with_context(|| {
format!("Failed to get health records for node {}", node.id)
})?;
// 创建内存记录
let mem_record = HealthyMemRecord::from_db_records(node.id, &records);
self.node_records.insert(node.id, mem_record);
debug!(
"Loaded {} health records for node {} ({})",
records.len(),
node.id,
node.name
);
}
info!(
"Loaded health records for {} nodes",
self.node_records.len()
);
Ok(())
}
/// 获取节点的内存健康记录
pub fn get_node_memory_record(&self, node_id: i32) -> Option<HealthyMemRecord> {
self.node_records.get(&node_id).map(|entry| entry.clone())
}
/// 获取节点的健康统计信息(从内存)
pub fn get_node_health_stats(
&self,
node_id: i32,
hours: u64,
) -> Option<crate::db::HealthStats> {
self.node_records
.get(&node_id)
.map(|record| record.get_health_stats(hours))
}
/// 获取所有节点的当前健康状态(从内存)
pub fn get_all_nodes_health_status(&self) -> Vec<(i32, HealthStatus, Option<String>)> {
self.node_records
.iter()
.map(|entry| {
let record = entry.value();
(
record.node_id,
record.current_health_status.clone(),
record.last_error_info.clone(),
)
})
.collect()
}
pub async fn try_update_node(&self, node_id: i32) -> anyhow::Result<()> {
let old_cfg = self
.node_cfg
.get(&node_id)
.ok_or_else(|| anyhow::anyhow!("old node cfg not found, node_id: {}", node_id))?
.clone();
let new_cfg = self.get_node_cfg(node_id, Some(old_cfg.get_id())).await?;
if new_cfg.dump() != old_cfg.dump() {
self.remove_node(node_id).await?;
self.add_node(node_id).await?;
info!("node {} cfg updated", node_id);
}
Ok(())
}
async fn get_node_cfg_with_model(
&self,
node_info: &shared_nodes::Model,
inst_id: Option<uuid::Uuid>,
) -> anyhow::Result<TomlConfigLoader> {
let cfg = TomlConfigLoader::default();
cfg.set_peers(vec![PeerConfig {
uri: format!(
"{}://{}:{}",
node_info.protocol, node_info.host, node_info.port
)
.parse()
.with_context(|| "failed to parse peer uri")?,
}]);
let inst_id = inst_id.unwrap_or(uuid::Uuid::new_v4());
cfg.set_id(inst_id);
cfg.set_network_identity(NetworkIdentity::new(
node_info.network_name.clone(),
node_info.network_secret.clone(),
));
cfg.set_hostname(Some("HealthCheckNode".to_string()));
let mut flags = cfg.get_flags();
flags.no_tun = true;
flags.disable_p2p = true;
flags.disable_udp_hole_punching = true;
cfg.set_flags(flags);
Ok(cfg)
}
pub async fn test_connection(
&self,
node_info: &shared_nodes::Model,
max_time: Duration,
) -> anyhow::Result<()> {
let cfg = self.get_node_cfg_with_model(node_info, None).await?;
defer!({
let _ = self
.instance_mgr
.delete_network_instance(vec![cfg.get_id()]);
});
self.instance_mgr
.run_network_instance(cfg.clone(), ConfigSource::FFI)
.with_context(|| "failed to run network instance")?;
let now = Instant::now();
let mut err = None;
while now.elapsed() < max_time {
match Self::test_node_healthy(cfg.get_id(), self.instance_mgr.clone()).await {
Ok(_) => {
return Ok(());
}
Err(e) => {
warn!(
"test node healthy failed, node_info: {:?}, err: {}",
node_info, e
);
err = Some(e);
}
}
tokio::time::sleep(Duration::from_millis(100)).await;
}
Err(anyhow::anyhow!("test node healthy failed, err: {:?}", err))
}
async fn get_node_cfg(
&self,
node_id: i32,
inst_id: Option<uuid::Uuid>,
) -> anyhow::Result<TomlConfigLoader> {
let node_info = NodeOperations::get_node_by_id(&self.db, node_id)
.await
.with_context(|| format!("failed to get node by id: {}", node_id))?
.ok_or_else(|| anyhow::anyhow!("node not found"))?;
self.get_node_cfg_with_model(&node_info, inst_id).await
}
pub async fn add_node(&self, node_id: i32) -> anyhow::Result<()> {
let cfg = self.get_node_cfg(node_id, None).await?;
info!(
"Add node {} to health checker, cfg: {}",
node_id,
cfg.dump()
);
self.instance_mgr
.run_network_instance(cfg.clone(), ConfigSource::Web)
.with_context(|| "failed to run network instance")?;
self.inst_id_map.insert(node_id, cfg.get_id());
// 初始化内存记录(如果不存在)
if !self.node_records.contains_key(&node_id) {
// 从数据库加载历史记录
let from_date = chrono::Utc::now().naive_utc()
- chrono::Duration::seconds(HEALTH_CHECK_RING_MAX_DURATION_SEC as i64);
if let Ok(records) =
HealthOperations::get_node_health_records(&self.db, node_id, Some(from_date), None)
.await
{
let mem_record = HealthyMemRecord::from_db_records(node_id, &records);
self.node_records.insert(node_id, mem_record);
info!(
"Initialized memory record for node {} with {} historical records",
node_id,
records.len()
);
} else {
self.node_records
.insert(node_id, HealthyMemRecord::new(node_id));
info!("Initialized new memory record for node {}", node_id);
}
}
// 启动健康检查任务
let task = ScopedTask::from(tokio::spawn(Self::node_health_check_task(
node_id,
cfg.get_id(),
Arc::clone(&self.instance_mgr),
self.db.clone(),
Arc::clone(&self.node_records),
)));
self.node_tasks.insert(node_id, task);
self.node_cfg.insert(node_id, cfg.clone());
Ok(())
}
pub async fn remove_node(&self, node_id: i32) -> anyhow::Result<()> {
self.node_tasks.remove(&node_id);
if let Some(inst_id) = self.inst_id_map.remove(&node_id) {
let _ = self.instance_mgr.delete_network_instance(vec![inst_id.1]);
}
self.node_cfg.remove(&node_id);
// 保留内存记录,不删除,以便后续查询历史数据
info!(
"Removed health check task for node {}, memory record retained",
node_id
);
Ok(())
}
#[instrument(err, ret, skip(instance_mgr))]
async fn test_node_healthy(
inst_id: uuid::Uuid,
instance_mgr: Arc<NetworkInstanceManager>,
// return version, response time on healthy, conn_count
) -> anyhow::Result<(String, u64, u32)> {
let Some(instance) = instance_mgr.get_network_info(&inst_id) else {
anyhow::bail!("healthy check node is not started");
};
let running = instance.running;
// health check node is not running, update db
if !running {
anyhow::bail!("healthy check node is not running");
}
if let Some(err) = instance.error_msg {
anyhow::bail!("healthy check node has error: {}", err);
}
let p = instance.peer_route_pairs;
// dst node is not online
let Some(dst_node) = p.iter().find(|x| {
// we disable p2p, so we only check direct connected peer
x.route.as_ref().is_some_and(|route| {
!route.feature_flag.unwrap().is_public_server && route.hostname != "HealthCheckNode"
}) && x.peer.as_ref().is_some_and(|p| !p.conns.is_empty())
}) else {
anyhow::bail!("dst node is not online");
};
let Some(route_info) = &dst_node.route else {
anyhow::bail!("dst node route is not found");
};
let Some(peer_info) = &dst_node.peer else {
anyhow::bail!("dst node peer is not found");
};
let version = route_info
.version
.clone()
.split("-")
.next()
.unwrap_or("")
.to_string();
// 计算响应时间(这里可以根据实际需要实现)
let response_time = peer_info
.conns
.iter()
.filter_map(|x| x.stats)
.map(|x| x.latency_us)
.min()
.unwrap_or(0);
let peer_id = peer_info.peer_id;
let conn_count = if let Some(summary) = instance.foreign_network_summary {
summary
.info_map
.get(&peer_id)
.map(|x| x.network_count)
.unwrap_or(0)
} else {
0
};
Ok((version, response_time, conn_count))
}
async fn node_health_check_task(
node_id: i32,
inst_id: uuid::Uuid,
instance_mgr: Arc<NetworkInstanceManager>,
db: Db,
node_records: Arc<DashMap<i32, HealthyMemRecord>>,
) {
/// 记录健康状态到数据库和内存
async fn record_health_status(
db: &Db,
node_records: &Arc<DashMap<i32, HealthyMemRecord>>,
node_id: i32,
status: HealthStatus,
response_time: Option<i32>,
error_message: Option<String>,
) {
// 写入数据库
if let Err(e) = HealthOperations::create_health_record(
db,
node_id,
status.clone(),
response_time,
error_message.clone(),
)
.await
{
error!("Failed to create health record for node {}: {}", node_id, e);
}
// 更新内存记录
if let Some(mut record) = node_records.get_mut(&node_id) {
record.update_health_status(status, response_time, error_message);
} else {
let mut new_record = HealthyMemRecord::new(node_id);
new_record.update_health_status(status, response_time, error_message);
node_records.insert(node_id, new_record);
}
}
let mut tick = tokio::time::interval(Duration::from_secs(5));
let mut counter: u64 = 0;
loop {
if counter != 0 {
tick.tick().await;
}
counter += 1;
match Self::test_node_healthy(inst_id, instance_mgr.clone()).await {
Ok((version, response_time, conn_count)) => {
if let Err(e) = NodeOperations::update_node_status(
&db,
node_id,
true,
Some(conn_count as i32),
)
.await
{
error!("Failed to update node status for node {}: {}", node_id, e);
}
record_health_status(
&db,
&node_records,
node_id,
HealthStatus::Healthy,
Some(response_time as i32),
None,
)
.await;
// update node version
if let Err(e) = NodeOperations::update_node_version(&db, node_id, version).await
{
error!("Failed to update node version for node {}: {}", node_id, e);
}
}
Err(e) => {
if let Err(e) =
NodeOperations::update_node_status(&db, node_id, false, None).await
{
error!("Failed to update node status for node {}: {}", node_id, e);
}
record_health_status(
&db,
&node_records,
node_id,
HealthStatus::Unhealthy,
None,
Some(format!("inst id: {}, err: {}", inst_id, e)),
)
.await;
}
}
}
}
}

View File

@@ -0,0 +1,160 @@
use std::{collections::HashSet, sync::Arc, time::Duration};
use anyhow::Context as _;
use tokio::time::{interval, Interval};
use tracing::{error, info};
use crate::{
db::{entity::shared_nodes, operations::NodeOperations, Db},
health_checker::HealthChecker,
};
/// HealthChecker的封装器用于监控数据库中节点的添加和删除
pub struct HealthCheckerManager {
health_checker: Arc<HealthChecker>,
db: Db,
current_nodes: Arc<tokio::sync::RwLock<HashSet<i32>>>,
monitor_interval: Duration,
}
impl HealthCheckerManager {
/// 创建新的HealthCheckerManager实例
pub fn new(health_checker: Arc<HealthChecker>, db: Db) -> Self {
Self {
health_checker,
db,
current_nodes: Arc::new(tokio::sync::RwLock::new(HashSet::new())),
monitor_interval: Duration::from_secs(1), // 默认每1秒检查一次
}
}
/// 设置监控间隔
pub fn with_monitor_interval(mut self, interval: Duration) -> Self {
self.monitor_interval = interval;
self
}
/// 启动监控任务
pub async fn start_monitoring(&self) -> anyhow::Result<()> {
// 启动定期检查任务
let health_checker = Arc::clone(&self.health_checker);
let db = self.db.clone();
let current_nodes = Arc::clone(&self.current_nodes);
let monitor_interval = self.monitor_interval;
tokio::spawn(async move {
let mut ticker = interval(monitor_interval);
loop {
if let Err(e) = Self::check_node_changes(&health_checker, &db, &current_nodes).await
{
tracing::error!("Error checking node changes: {}", e);
}
ticker.tick().await;
}
});
Ok(())
}
/// 检查节点变化并更新监控
async fn check_node_changes(
health_checker: &Arc<HealthChecker>,
db: &Db,
current_nodes: &Arc<tokio::sync::RwLock<HashSet<i32>>>,
) -> anyhow::Result<()> {
// 获取数据库中当前的所有节点
let db_nodes = NodeOperations::get_all_nodes(db)
.await
.with_context(|| "Failed to get all nodes from database")?;
let db_node_ids: HashSet<i32> = db_nodes.iter().map(|node| node.id).collect();
let mut current_nodes_guard = current_nodes.write().await;
// 检查新增的节点
for &node_id in &db_node_ids {
if !current_nodes_guard.contains(&node_id) {
// 新节点,添加到监控
if let Err(e) = health_checker.add_node(node_id).await {
error!("Failed to add node {} to health checker: {}", node_id, e);
continue;
}
current_nodes_guard.insert(node_id);
info!("Added new node {} to health monitoring", node_id);
} else if let Err(e) = health_checker.try_update_node(node_id).await {
error!("Failed to add node {} to health checker: {}", node_id, e);
}
}
// 检查删除的节点
let nodes_to_remove: Vec<i32> = current_nodes_guard
.iter()
.filter(|&&node_id| !db_node_ids.contains(&node_id))
.copied()
.collect();
for node_id in nodes_to_remove {
// 节点已删除,从监控中移除
if let Err(e) = health_checker.remove_node(node_id).await {
error!(
"Failed to remove node {} from health checker: {}",
node_id, e
);
continue;
}
current_nodes_guard.remove(&node_id);
info!("Removed node {} from health monitoring", node_id);
}
Ok(())
}
/// 手动触发节点变化检查
pub async fn refresh_nodes(&self) -> anyhow::Result<()> {
Self::check_node_changes(&self.health_checker, &self.db, &self.current_nodes).await
}
/// 获取当前监控的节点数量
pub async fn get_monitored_node_count(&self) -> usize {
self.current_nodes.read().await.len()
}
/// 获取当前监控的节点ID列表
pub async fn get_monitored_nodes(&self) -> Vec<i32> {
self.current_nodes.read().await.iter().copied().collect()
}
/// 获取节点的内存健康记录
pub fn get_node_memory_record(
&self,
node_id: i32,
) -> Option<crate::health_checker::HealthyMemRecord> {
self.health_checker.get_node_memory_record(node_id)
}
/// 获取节点的健康统计信息
pub fn get_node_health_stats(
&self,
node_id: i32,
hours: u64,
) -> Option<crate::db::HealthStats> {
self.health_checker.get_node_health_stats(node_id, hours)
}
/// 获取所有节点的当前健康状态
pub fn get_all_nodes_health_status(
&self,
) -> Vec<(i32, crate::db::HealthStatus, Option<String>)> {
self.health_checker.get_all_nodes_health_status()
}
pub async fn test_connection(
&self,
node_info: &shared_nodes::Model,
max_time: Duration,
) -> anyhow::Result<()> {
self.health_checker
.test_connection(node_info, max_time)
.await
}
}

View File

@@ -0,0 +1,153 @@
#![allow(unused)]
mod api;
mod config;
mod db;
mod health_checker;
mod health_checker_manager;
mod migrator;
use api::routes::create_routes;
use clap::Parser;
use config::AppConfig;
use db::{operations::NodeOperations, Db};
use health_checker::HealthChecker;
use health_checker_manager::HealthCheckerManager;
use std::env;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tracing_subscriber::EnvFilter;
use crate::db::cleanup::{CleanupConfig, CleanupManager};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Admin password for management access
#[arg(long, env = "ADMIN_PASSWORD")]
admin_password: Option<String>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// 加载配置
let config = AppConfig::default();
// 初始化日志
tracing_subscriber::fmt()
.with_max_level(match config.logging.level.as_str() {
"debug" => tracing::Level::DEBUG,
"info" => tracing::Level::INFO,
"warn" => tracing::Level::WARN,
"error" => tracing::Level::ERROR,
_ => tracing::Level::INFO,
})
.with_target(false)
.with_thread_ids(true)
.with_env_filter(EnvFilter::new("easytier_uptime"))
.init();
// 解析命令行参数
let args = Args::parse();
// 如果提供了管理员密码,设置环境变量
if let Some(password) = args.admin_password {
env::set_var("ADMIN_PASSWORD", password);
}
tracing::info!(
"Admin password configured: {}",
!config.security.admin_password.is_empty()
);
// 创建数据库连接
let db = Db::new(&config.database.path.to_string_lossy()).await?;
// 获取数据库统计信息
let stats = db.get_database_stats().await?;
tracing::info!("Database initialized successfully!");
tracing::info!("Database stats: {:?}", stats);
// 创建配置目录
let config_dir = PathBuf::from("./configs");
tokio::fs::create_dir_all(&config_dir).await?;
// 创建健康检查器和管理器
let health_checker = Arc::new(HealthChecker::new(db.clone()));
let health_checker_manager = HealthCheckerManager::new(health_checker, db.clone())
.with_monitor_interval(Duration::from_secs(1)); // 每30秒检查一次节点变化
let cleanup_manager = CleanupManager::new(db.clone(), CleanupConfig::default());
cleanup_manager.start_auto_cleanup().await?;
// 启动节点监控
health_checker_manager.start_monitoring().await?;
tracing::info!("Health checker manager started successfully!");
let monitored_count = health_checker_manager.get_monitored_node_count().await;
tracing::info!("Currently monitoring {} nodes", monitored_count);
// 创建应用状态
let app_state = crate::api::handlers::AppState {
db: db.clone(),
health_checker_manager: Arc::new(health_checker_manager),
};
// 创建 API 路由
let app = create_routes().with_state(app_state);
// 配置服务器地址
let addr = config.server.addr;
tracing::info!("Starting server on http://{}", addr);
tracing::info!("Available endpoints:");
tracing::info!(" GET /health - Health check");
tracing::info!(" GET /api/nodes - Get nodes (paginated, approved only)");
tracing::info!(" POST /api/nodes - Create node (pending approval)");
tracing::info!(" GET /api/nodes/:id - Get node by ID");
tracing::info!(" PUT /api/nodes/:id - Update node");
tracing::info!(" DELETE /api/nodes/:id - Delete node");
tracing::info!(" GET /api/nodes/:id/health - Get node health history");
tracing::info!(" GET /api/nodes/:id/health/stats - Get node health stats");
tracing::info!("Admin endpoints:");
tracing::info!(" POST /api/admin/login - Admin login");
tracing::info!(" GET /api/admin/nodes - Get all nodes (including pending)");
tracing::info!(" PUT /api/admin/nodes/:id/approve - Approve/reject node");
tracing::info!(" DELETE /api/admin/nodes/:id - Delete node (admin only)");
// 启动服务器
let listener = tokio::net::TcpListener::bind(addr).await?;
// 设置优雅关闭
let shutdown_signal = Arc::new(tokio::sync::Notify::new());
let server_shutdown_signal = shutdown_signal.clone();
// 启动服务器任务
let server_handle = tokio::spawn(async move {
axum::serve(listener, app)
.with_graceful_shutdown(async move {
server_shutdown_signal.notified().await;
})
.await
.unwrap();
});
// 等待 Ctrl+C 信号
tokio::select! {
_ = tokio::signal::ctrl_c() => {
tracing::info!("Received shutdown signal");
}
_ = server_handle => {
tracing::info!("Server task completed");
}
}
// 优雅关闭
tracing::info!("Shutting down gracefully...");
shutdown_signal.notify_waiters();
tracing::info!("Shutdown complete");
Ok(())
}

View File

@@ -0,0 +1,181 @@
use sea_orm_migration::{prelude::*, schema::*};
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20250101_000001_create_tables"
}
}
#[derive(DeriveIden)]
pub enum SharedNodes {
Table,
Id,
Name,
Host,
Port,
Protocol,
Version,
AllowRelay,
NetworkName,
NetworkSecret,
Description,
MaxConnections,
CurrentConnections,
IsActive,
IsApproved,
QQNumber,
Wechat,
Mail,
CreatedAt,
UpdatedAt,
}
#[derive(DeriveIden)]
pub enum HealthRecords {
Table,
Id,
NodeId,
Status,
ResponseTime,
ErrorMessage,
CheckedAt,
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 创建共享节点表
manager
.create_table(
Table::create()
.if_not_exists()
.table(SharedNodes::Table)
.col(pk_auto(SharedNodes::Id).not_null())
.col(string(SharedNodes::Name).not_null())
.col(string(SharedNodes::Host).not_null())
.col(integer(SharedNodes::Port).not_null())
.col(string(SharedNodes::Protocol).not_null().default("tcp"))
.col(string(SharedNodes::Version))
.col(boolean(SharedNodes::AllowRelay).default(false))
.col(string(SharedNodes::NetworkName))
.col(string(SharedNodes::NetworkSecret))
.col(text(SharedNodes::Description))
.col(integer(SharedNodes::MaxConnections).default(100))
.col(integer(SharedNodes::CurrentConnections).default(0))
.col(boolean(SharedNodes::IsActive).default(true))
.col(boolean(SharedNodes::IsApproved).default(false))
.col(string(SharedNodes::QQNumber))
.col(string(SharedNodes::Wechat))
.col(string(SharedNodes::Mail))
.col(
timestamp_with_time_zone(SharedNodes::CreatedAt)
.default(Expr::current_timestamp()),
)
.col(
timestamp_with_time_zone(SharedNodes::UpdatedAt)
.default(Expr::current_timestamp()),
)
.to_owned(),
)
.await?;
// 创建唯一约束
manager
.create_index(
Index::create()
.name("idx_shared_nodes_host_port_protocol")
.table(SharedNodes::Table)
.col(SharedNodes::Host)
.col(SharedNodes::Port)
.col(SharedNodes::Protocol)
.unique()
.to_owned(),
)
.await?;
// 创建健康度记录表
manager
.create_table(
Table::create()
.if_not_exists()
.table(HealthRecords::Table)
.col(pk_auto(HealthRecords::Id).not_null())
.col(integer(HealthRecords::NodeId).not_null())
.col(string(HealthRecords::Status).not_null())
.col(integer(HealthRecords::ResponseTime))
.col(text(HealthRecords::ErrorMessage).null())
.col(
timestamp_with_time_zone(HealthRecords::CheckedAt)
.default(Expr::current_timestamp()),
)
.foreign_key(
ForeignKey::create()
.name("fk_health_records_node_id_to_shared_nodes_id")
.from(HealthRecords::Table, HealthRecords::NodeId)
.to(SharedNodes::Table, SharedNodes::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade),
)
.to_owned(),
)
.await?;
// 创建健康度记录索引
manager
.create_index(
Index::create()
.name("idx_health_records_node_id")
.table(HealthRecords::Table)
.col(HealthRecords::NodeId)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_health_records_checked_at")
.table(HealthRecords::Table)
.col(HealthRecords::CheckedAt)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_health_records_node_time")
.table(HealthRecords::Table)
.col(HealthRecords::NodeId)
.col(HealthRecords::CheckedAt)
.to_owned(),
)
.await?;
manager
.create_index(
Index::create()
.name("idx_health_records_status")
.table(HealthRecords::Table)
.col(HealthRecords::Status)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(HealthRecords::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(SharedNodes::Table).to_owned())
.await?;
Ok(())
}
}

View File

@@ -0,0 +1,12 @@
use sea_orm_migration::prelude::*;
mod m20250101_000001_create_tables;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20250101_000001_create_tables::Migration)]
}
}

View File

@@ -0,0 +1,95 @@
#!/bin/bash
# EasyTier Uptime Monitor 开发环境启动脚本
set -e
echo "🚀 Starting EasyTier Uptime Monitor Development Environment..."
# 检查依赖
echo "📦 Checking dependencies..."
# 检查 Rust
if ! command -v cargo &> /dev/null; then
echo "❌ Rust is not installed. Please install Rust first."
exit 1
fi
# 检查 Node.js
if ! command -v node &> /dev/null; then
echo "❌ Node.js is not installed. Please install Node.js first."
exit 1
fi
# 检查 npm
if ! command -v npm &> /dev/null; then
echo "❌ npm is not installed. Please install npm first."
exit 1
fi
# 设置环境变量
export RUST_LOG=debug
export NODE_ENV=development
# 创建必要的目录
echo "📁 Creating directories..."
mkdir -p logs
mkdir -p configs
mkdir -p frontend/dist
# 复制环境配置文件
if [ ! -f .env ]; then
echo "📝 Creating environment configuration..."
cp .env.development .env
fi
# 安装前端依赖
echo "📦 Installing frontend dependencies..."
cd frontend
if [ ! -d "node_modules" ]; then
npm install
fi
cd ..
# 启动后端服务
echo "🔧 Starting backend server..."
cargo run &
BACKEND_PID=$!
# 等待后端服务启动
echo "⏳ Waiting for backend server to start..."
sleep 5
# 启动前端开发服务器
echo "🎨 Starting frontend development server..."
cd frontend
npm run dev &
FRONTEND_PID=$!
cd ..
# 等待前端服务启动
echo "⏳ Waiting for frontend server to start..."
sleep 3
echo "✅ Development environment started successfully!"
echo "🌐 Frontend: http://localhost:3000"
echo "🔧 Backend API: http://localhost:8080"
echo "📊 API Health Check: http://localhost:8080/health"
echo ""
echo "Press Ctrl+C to stop all services"
# 清理函数
cleanup() {
echo ""
echo "🛑 Stopping services..."
kill $BACKEND_PID 2>/dev/null || true
kill $FRONTEND_PID 2>/dev/null || true
echo "✅ All services stopped"
exit 0
}
# 设置信号处理
trap cleanup SIGINT SIGTERM
# 等待用户中断
wait

View File

@@ -0,0 +1,98 @@
#!/bin/bash
# EasyTier Uptime Monitor 生产环境启动脚本
set -e
echo "🚀 Starting EasyTier Uptime Monitor Production Environment..."
# 检查依赖
echo "📦 Checking dependencies..."
# 检查 Rust
if ! command -v cargo &> /dev/null; then
echo "❌ Rust is not installed. Please install Rust first."
exit 1
fi
# 检查 Node.js
if ! command -v node &> /dev/null; then
echo "❌ Node.js is not installed. Please install Node.js first."
exit 1
fi
# 检查 npm
if ! command -v npm &> /dev/null; then
echo "❌ npm is not installed. Please install npm first."
exit 1
fi
# 设置环境变量
export RUST_LOG=info
export NODE_ENV=production
# 创建必要的目录
echo "📁 Creating directories..."
mkdir -p logs
mkdir -p configs
mkdir -p /var/lib/easytier-uptime
mkdir -p frontend/dist
# 复制环境配置文件
if [ ! -f .env ]; then
echo "📝 Creating environment configuration..."
cp .env.production .env
fi
# 构建后端
echo "🔧 Building backend..."
cargo build --release
# 构建前端
echo "🎨 Building frontend..."
cd frontend
if [ ! -d "node_modules" ]; then
npm install
fi
npm run build
cd ..
# 启动后端服务
echo "🔧 Starting backend server..."
nohup ./target/release/easytier-uptime > logs/backend.log 2>&1 &
BACKEND_PID=$!
# 等待后端服务启动
echo "⏳ Waiting for backend server to start..."
sleep 5
# 设置静态文件服务
echo "🌐 Setting up static file server..."
cd frontend/dist
python3 -m http.server 8081 > ../../logs/frontend.log 2>&1 &
FRONTEND_PID=$!
cd ../..
# 等待前端服务启动
echo "⏳ Waiting for frontend server to start..."
sleep 3
echo "✅ Production environment started successfully!"
echo "🌐 Frontend: http://localhost:8081"
echo "🔧 Backend API: http://localhost:8080"
echo "📊 API Health Check: http://localhost:8080/health"
echo ""
echo "Backend PID: $BACKEND_PID"
echo "Frontend PID: $FRONTEND_PID"
echo ""
echo "To stop services:"
echo " kill $BACKEND_PID"
echo " kill $FRONTEND_PID"
echo ""
echo "Or use the stop script: ./stop-prod.sh"
# 保存PID到文件
echo $BACKEND_PID > logs/backend.pid
echo $FRONTEND_PID > logs/frontend.pid
echo "✅ PIDs saved to logs/"

View File

@@ -0,0 +1,46 @@
#!/bin/bash
# EasyTier Uptime Monitor 停止服务脚本
set -e
echo "🛑 Stopping EasyTier Uptime Monitor services..."
# 检查PID文件
if [ -f "logs/backend.pid" ]; then
BACKEND_PID=$(cat logs/backend.pid)
echo "🔧 Stopping backend server (PID: $BACKEND_PID)..."
kill $BACKEND_PID 2>/dev/null || true
rm logs/backend.pid
echo "✅ Backend server stopped"
else
echo "⚠️ Backend PID file not found"
fi
if [ -f "logs/frontend.pid" ]; then
FRONTEND_PID=$(cat logs/frontend.pid)
echo "🌐 Stopping frontend server (PID: $FRONTEND_PID)..."
kill $FRONTEND_PID 2>/dev/null || true
rm logs/frontend.pid
echo "✅ Frontend server stopped"
else
echo "⚠️ Frontend PID file not found"
fi
# 强制杀死可能残留的进程
echo "🔍 Checking for remaining processes..."
REMAINING_BACKEND=$(ps aux | grep 'easytier-uptime' | grep -v grep | awk '{print $2}' || true)
if [ ! -z "$REMAINING_BACKEND" ]; then
echo "🔧 Killing remaining backend processes..."
echo $REMAINING_BACKEND | xargs kill -9 2>/dev/null || true
echo "✅ Remaining backend processes killed"
fi
REMAINING_FRONTEND=$(ps aux | grep 'python3 -m http.server' | grep -v grep | awk '{print $2}' || true)
if [ ! -z "$REMAINING_FRONTEND" ]; then
echo "🌐 Killing remaining frontend processes..."
echo $REMAINING_FRONTEND | xargs kill -9 2>/dev/null || true
echo "✅ Remaining frontend processes killed"
fi
echo "✅ All services stopped successfully!"

View File

@@ -0,0 +1,144 @@
#!/bin/bash
# EasyTier Uptime Monitor 集成测试脚本
set -e
echo "🧪 Running EasyTier Uptime Monitor Integration Tests..."
# 检查依赖
echo "📦 Checking dependencies..."
# 检查 Rust
if ! command -v cargo &> /dev/null; then
echo "❌ Rust is not installed. Please install Rust first."
exit 1
fi
# 检查 curl
if ! command -v curl &> /dev/null; then
echo "❌ curl is not installed. Please install curl first."
exit 1
fi
# 设置环境变量
export RUST_LOG=info
export NODE_ENV=test
# 创建测试目录
echo "📁 Creating test directories..."
mkdir -p test-results
mkdir -p test-logs
# 复制测试环境配置
if [ ! -f .env ]; then
echo "📝 Creating test environment configuration..."
cp .env.development .env
fi
# 构建项目
echo "🔧 Building project..."
cargo build
# 启动后端服务进行测试
echo "🚀 Starting backend server for testing..."
cargo run &
BACKEND_PID=$!
# 等待后端服务启动
echo "⏳ Waiting for backend server to start..."
sleep 5
# 检查服务是否运行
echo "🔍 Checking if server is running..."
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
echo "✅ Backend server is running"
else
echo "❌ Backend server failed to start"
kill $BACKEND_PID 2>/dev/null || true
exit 1
fi
# 运行API测试
echo "🧪 Running API tests..."
if cargo test api_test --lib -- --nocapture > test-results/api-test.log 2>&1; then
echo "✅ API tests passed"
else
echo "❌ API tests failed"
echo "Check test-results/api-test.log for details"
fi
# 运行健康检查测试
echo "🏥 Running health check tests..."
curl -s http://localhost:8080/health | jq . > test-results/health-check.json
if [ $? -eq 0 ]; then
echo "✅ Health check test passed"
else
echo "❌ Health check test failed"
fi
# 运行节点管理测试
echo "🔧 Running node management tests..."
# 创建测试节点
curl -s -X POST http://localhost:8080/api/nodes \
-H "Content-Type: application/json" \
-d '{
"name": "Test Node",
"host": "127.0.0.1",
"port": 11010,
"protocol": "tcp",
"version": "1.0.0",
"description": "Test node for integration testing",
"max_connections": 100
}' > test-results/create-node.json
# 获取节点列表
curl -s http://localhost:8080/api/nodes > test-results/get-nodes.json
echo "✅ Node management tests completed"
# 停止后端服务
echo "🛑 Stopping backend server..."
kill $BACKEND_PID 2>/dev/null || true
sleep 2
# 强制杀死可能残留的进程
pkill -f easytier-uptime 2>/dev/null || true
echo "✅ Integration tests completed!"
echo "📊 Test results saved to test-results/"
echo "📋 Test logs saved to test-logs/"
# 生成测试报告
echo "📝 Generating test report..."
cat > test-results/test-report.md << EOF
# EasyTier Uptime Monitor Integration Test Report
## Test Summary
- **Test Date**: $(date)
- **Test Environment**: Integration
- **Backend PID**: $BACKEND_PID
## Test Results
### API Tests
- Status: $(grep -q "test result: ok" test-results/api-test.log && echo "PASSED" || echo "FAILED")
- Log: [api-test.log](api-test.log)
### Health Check
- Status: $(jq -r '.success' test-results/health-check.json 2>/dev/null || echo "FAILED")
- Response: $(cat test-results/health-check.json 2>/dev/null || echo "No response")
### Node Management
- Status: COMPLETED
- Create Node: [create-node.json](create-node.json)
- Get Nodes: [get-nodes.json](get-nodes.json)
## System Information
- **Rust Version**: $(rustc --version)
- **Cargo Version**: $(cargo --version)
- **System**: $(uname -a)
EOF
echo "✅ Test report generated: test-results/test-report.md"

View File

@@ -0,0 +1,66 @@
//! 测试 HealthyStats 功能的示例代码
use easytier_uptime::db::entity::health_records::{HealthStatus, HealthStats, Model};
use sea_orm::prelude::*;
fn main() {
// 创建一些模拟的健康记录
let records = vec![
Model {
id: 1,
node_id: 1,
status: HealthStatus::Healthy.to_string(),
response_time: 100,
error_message: String::new(),
checked_at: chrono::Utc::now().fixed_offset(),
},
Model {
id: 2,
node_id: 1,
status: HealthStatus::Healthy.to_string(),
response_time: 150,
error_message: String::new(),
checked_at: chrono::Utc::now().fixed_offset(),
},
Model {
id: 3,
node_id: 1,
status: HealthStatus::Unhealthy.to_string(),
response_time: 0,
error_message: "Connection failed".to_string(),
checked_at: chrono::Utc::now().fixed_offset(),
},
];
// 从记录创建统计信息
let stats = HealthStats::from_records(&records);
println!("健康统计信息:");
println!("总检查次数: {}", stats.total_checks);
println!("健康检查次数: {}", stats.healthy_count);
println!("不健康检查次数: {}", stats.unhealthy_count);
println!("健康百分比: {:.2}%", stats.health_percentage);
println!("平均响应时间: {:?} ms", stats.average_response_time);
println!("正常运行时间百分比: {:.2}%", stats.uptime_percentage);
println!("最后检查时间: {:?}", stats.last_check_time);
println!("最后状态: {:?}", stats.last_status);
// 测试健康状态转换
println!("\n健康状态测试:");
let status_healthy = HealthStatus::from("healthy");
let status_unhealthy = HealthStatus::from("unhealthy");
let status_timeout = HealthStatus::from("timeout");
let status_unknown = HealthStatus::from("invalid_status");
println!("healthy -> {:?}", status_healthy);
println!("unhealthy -> {:?}", status_unhealthy);
println!("timeout -> {:?}", status_timeout);
println!("invalid_status -> {:?}", status_unknown);
// 测试记录的健康状态检查
println!("\n记录健康状态检查:");
for record in &records {
println!("记录 {} 是否健康: {}", record.id, record.is_healthy());
println!("记录 {} 状态: {:?}", record.id, record.get_status());
}
}

View File

@@ -25,3 +25,5 @@ dist-ssr
*.sw?
vite.config.ts.timestamp*
local.properties

View File

@@ -1,7 +1,7 @@
{
"name": "easytier-gui",
"type": "module",
"version": "2.4.1",
"version": "2.4.5",
"private": true,
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
"scripts": {
@@ -13,18 +13,17 @@
"lint:fix": "eslint . --ignore-pattern src-tauri --fix"
},
"dependencies": {
"@primevue/themes": "4.3.3",
"@primeuix/themes": "^1.2.3",
"@tauri-apps/plugin-autostart": "2.0.0",
"@tauri-apps/plugin-clipboard-manager": "2.3.0",
"@tauri-apps/plugin-os": "2.3.0",
"@tauri-apps/plugin-process": "2.3.0",
"@tauri-apps/plugin-shell": "2.3.0",
"@vueuse/core": "^11.2.0",
"aura": "link:@primevue\\themes\\aura",
"easytier-frontend-lib": "workspace:*",
"ip-num": "1.5.1",
"pinia": "^2.2.4",
"primevue": "4.3.3",
"primevue": "^4.3.9",
"tauri-plugin-vpnservice-api": "workspace:*",
"vue": "^3.5.12",
"vue-router": "^4.4.5"
@@ -32,7 +31,7 @@
"devDependencies": {
"@antfu/eslint-config": "^3.7.3",
"@intlify/unplugin-vue-i18n": "^5.2.0",
"@primevue/auto-import-resolver": "4.3.3",
"@primevue/auto-import-resolver": "4.3.9",
"@tauri-apps/api": "2.7.0",
"@tauri-apps/cli": "2.7.1",
"@types/default-gateway": "^7.2.2",

File diff suppressed because it is too large Load Diff

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