mirror of
https://github.com/EasyTier/EasyTier.git
synced 2024-11-16 11:42:27 +08:00
support start on reboot (#132)
* move launcher to eastier lib * support auto start after reboot
This commit is contained in:
parent
f9e6264f31
commit
6e77e6b5e7
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -247,6 +247,17 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9a3820bc9e9aaf60c8389c2a4808548599f4ff254ce6bdb608ac3631d4ad76"
|
checksum = "8a9a3820bc9e9aaf60c8389c2a4808548599f4ff254ce6bdb608ac3631d4ad76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "auto-launch"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"thiserror",
|
||||||
|
"winreg 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "auto_impl"
|
name = "auto_impl"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -1128,6 +1139,15 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs"
|
||||||
|
version = "4.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-next"
|
name = "dirs-next"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
@ -1138,6 +1158,17 @@ dependencies = [
|
||||||
"dirs-sys-next",
|
"dirs-sys-next",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs-sys-next"
|
name = "dirs-sys-next"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -1260,8 +1291,10 @@ name = "easytier-gui"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"auto-launch",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"dunce",
|
||||||
"easytier",
|
"easytier",
|
||||||
"gethostname",
|
"gethostname",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -6260,6 +6293,15 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winreg"
|
name = "winreg"
|
||||||
version = "0.50.0"
|
version = "0.50.0"
|
||||||
|
|
|
@ -32,6 +32,8 @@ retain_network_instance: 保留网络实例
|
||||||
collect_network_infos: 收集网络信息
|
collect_network_infos: 收集网络信息
|
||||||
settings: 设置
|
settings: 设置
|
||||||
exchange_language: Switch to English
|
exchange_language: Switch to English
|
||||||
|
disable_auto_launch: 关闭开机自启
|
||||||
|
enable_auto_launch: 开启开机自启
|
||||||
exit: 退出
|
exit: 退出
|
||||||
chips_placeholder: 例如: {0}, 按回车添加
|
chips_placeholder: 例如: {0}, 按回车添加
|
||||||
hostname_placeholder: '留空默认为主机名: {0}'
|
hostname_placeholder: '留空默认为主机名: {0}'
|
||||||
|
|
|
@ -32,6 +32,8 @@ retain_network_instance: Retain Network Instance
|
||||||
collect_network_infos: Collect Network Infos
|
collect_network_infos: Collect Network Infos
|
||||||
settings: Settings
|
settings: Settings
|
||||||
exchange_language: 切换中文
|
exchange_language: 切换中文
|
||||||
|
disable_auto_launch: Disable Launch on Reboot
|
||||||
|
enable_auto_launch: Enable Launch on Reboot
|
||||||
exit: Exit
|
exit: Exit
|
||||||
|
|
||||||
chips_placeholder: 'e.g: {0}, press Enter to add'
|
chips_placeholder: 'e.g: {0}, press Enter to add'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "easytier-gui"
|
name = "easytier-gui"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "A Tauri App"
|
description = "EasyTier GUI"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
@ -29,6 +29,10 @@ dashmap = "5.5.3"
|
||||||
|
|
||||||
privilege = "0.3"
|
privilege = "0.3"
|
||||||
gethostname = "0.4.3"
|
gethostname = "0.4.3"
|
||||||
|
|
||||||
|
auto-launch = "0.5.0"
|
||||||
|
dunce = "1.0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
mod launcher;
|
|
||||||
|
|
||||||
use std::{collections::BTreeMap, env::current_exe, process};
|
use std::{collections::BTreeMap, env::current_exe, process};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::{DateTime, Local};
|
use auto_launch::AutoLaunchBuilder;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use easytier::{
|
use easytier::{
|
||||||
common::{
|
common::config::{
|
||||||
config::{ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader, VpnPortalConfig},
|
ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader, VpnPortalConfig,
|
||||||
global_ctx::GlobalCtxEvent,
|
|
||||||
},
|
},
|
||||||
rpc::{PeerInfo, Route},
|
launcher::{NetworkInstance, NetworkInstanceRunningInfo},
|
||||||
utils::{list_peer_route_pair, PeerRoutePair},
|
|
||||||
};
|
};
|
||||||
use launcher::{EasyTierLauncher, MyNodeInfo};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use tauri::{
|
use tauri::{
|
||||||
|
@ -167,71 +162,6 @@ impl NetworkConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct NetworkInstanceRunningInfo {
|
|
||||||
my_node_info: MyNodeInfo,
|
|
||||||
events: Vec<(DateTime<Local>, GlobalCtxEvent)>,
|
|
||||||
node_info: MyNodeInfo,
|
|
||||||
routes: Vec<Route>,
|
|
||||||
peers: Vec<PeerInfo>,
|
|
||||||
peer_route_pairs: Vec<PeerRoutePair>,
|
|
||||||
running: bool,
|
|
||||||
error_msg: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NetworkInstance {
|
|
||||||
config: TomlConfigLoader,
|
|
||||||
launcher: Option<EasyTierLauncher>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetworkInstance {
|
|
||||||
fn new(cfg: NetworkConfig) -> Result<Self, anyhow::Error> {
|
|
||||||
Ok(Self {
|
|
||||||
config: cfg.gen_config()?,
|
|
||||||
launcher: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_easytier_running(&self) -> bool {
|
|
||||||
self.launcher.is_some() && self.launcher.as_ref().unwrap().running()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_running_info(&self) -> Option<NetworkInstanceRunningInfo> {
|
|
||||||
if self.launcher.is_none() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let launcher = self.launcher.as_ref().unwrap();
|
|
||||||
|
|
||||||
let peers = launcher.get_peers();
|
|
||||||
let routes = launcher.get_routes();
|
|
||||||
let peer_route_pairs = list_peer_route_pair(peers.clone(), routes.clone());
|
|
||||||
|
|
||||||
Some(NetworkInstanceRunningInfo {
|
|
||||||
my_node_info: launcher.get_node_info(),
|
|
||||||
events: launcher.get_events(),
|
|
||||||
node_info: launcher.get_node_info(),
|
|
||||||
routes,
|
|
||||||
peers,
|
|
||||||
peer_route_pairs,
|
|
||||||
running: launcher.running(),
|
|
||||||
error_msg: launcher.error_msg(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start(&mut self) -> Result<(), anyhow::Error> {
|
|
||||||
if self.is_easytier_running() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut launcher = EasyTierLauncher::new();
|
|
||||||
launcher.start(|| Ok(self.config.clone()));
|
|
||||||
|
|
||||||
self.launcher = Some(launcher);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static INSTANCE_MAP: once_cell::sync::Lazy<DashMap<String, NetworkInstance>> =
|
static INSTANCE_MAP: once_cell::sync::Lazy<DashMap<String, NetworkInstance>> =
|
||||||
once_cell::sync::Lazy::new(DashMap::new);
|
once_cell::sync::Lazy::new(DashMap::new);
|
||||||
|
|
||||||
|
@ -249,7 +179,8 @@ fn run_network_instance(cfg: NetworkConfig) -> Result<(), String> {
|
||||||
}
|
}
|
||||||
let instance_id = cfg.instance_id.clone();
|
let instance_id = cfg.instance_id.clone();
|
||||||
|
|
||||||
let mut instance = NetworkInstance::new(cfg).map_err(|e| e.to_string())?;
|
let cfg = cfg.gen_config().map_err(|e| e.to_string())?;
|
||||||
|
let mut instance = NetworkInstance::new(cfg);
|
||||||
instance.start().map_err(|e| e.to_string())?;
|
instance.start().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
println!("instance {} started", instance_id);
|
println!("instance {} started", instance_id);
|
||||||
|
@ -286,6 +217,11 @@ fn get_os_hostname() -> Result<String, String> {
|
||||||
Ok(gethostname::gethostname().to_string_lossy().to_string())
|
Ok(gethostname::gethostname().to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn set_auto_launch_status(app_handle: tauri::AppHandle, enable: bool) -> Result<bool, String> {
|
||||||
|
Ok(init_launch(&app_handle, enable).map_err(|e| e.to_string())?)
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_window_visibility(window: &Window) {
|
fn toggle_window_visibility(window: &Window) {
|
||||||
if window.is_visible().unwrap() {
|
if window.is_visible().unwrap() {
|
||||||
window.hide().unwrap();
|
window.hide().unwrap();
|
||||||
|
@ -307,6 +243,65 @@ fn check_sudo() -> bool {
|
||||||
is_elevated
|
is_elevated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// init the auto launch
|
||||||
|
pub fn init_launch(_app_handle: &tauri::AppHandle, enable: bool) -> Result<bool, anyhow::Error> {
|
||||||
|
let app_exe = current_exe()?;
|
||||||
|
let app_exe = dunce::canonicalize(app_exe)?;
|
||||||
|
let app_name = app_exe
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.ok_or(anyhow::anyhow!("failed to get file stem"))?;
|
||||||
|
|
||||||
|
let app_path = app_exe
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.ok_or(anyhow::anyhow!("failed to get app_path"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let app_path = format!("\"{app_path}\"");
|
||||||
|
|
||||||
|
// use the /Applications/easytier-gui.app
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let app_path = (|| -> Option<String> {
|
||||||
|
let path = std::path::PathBuf::from(&app_path);
|
||||||
|
let path = path.parent()?.parent()?.parent()?;
|
||||||
|
let extension = path.extension()?.to_str()?;
|
||||||
|
match extension == "app" {
|
||||||
|
true => Some(path.as_os_str().to_str()?.to_string()),
|
||||||
|
false => None,
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.unwrap_or(app_path);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let app_path = {
|
||||||
|
let appimage = _app_handle.env().appimage;
|
||||||
|
appimage
|
||||||
|
.and_then(|p| p.to_str().map(|s| s.to_string()))
|
||||||
|
.unwrap_or(app_path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let auto = AutoLaunchBuilder::new()
|
||||||
|
.set_app_name(app_name)
|
||||||
|
.set_app_path(&app_path)
|
||||||
|
.build()
|
||||||
|
.with_context(|| "failed to build auto launch")?;
|
||||||
|
|
||||||
|
if enable && !auto.is_enabled().unwrap_or(false) {
|
||||||
|
// 避免重复设置登录项
|
||||||
|
let _ = auto.disable();
|
||||||
|
auto.enable()
|
||||||
|
.with_context(|| "failed to enable auto launch")?
|
||||||
|
} else if !enable {
|
||||||
|
let _ = auto.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
let enabled = auto.is_enabled()?;
|
||||||
|
|
||||||
|
Ok(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if !check_sudo() {
|
if !check_sudo() {
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
|
@ -324,7 +319,8 @@ fn main() {
|
||||||
run_network_instance,
|
run_network_instance,
|
||||||
retain_network_instance,
|
retain_network_instance,
|
||||||
collect_network_infos,
|
collect_network_infos,
|
||||||
get_os_hostname
|
get_os_hostname,
|
||||||
|
set_auto_launch_status
|
||||||
])
|
])
|
||||||
.system_tray(SystemTray::new().with_menu(tray_menu))
|
.system_tray(SystemTray::new().with_menu(tray_menu))
|
||||||
.on_system_tray_event(|app, event| match event {
|
.on_system_tray_event(|app, event| match event {
|
||||||
|
|
6
easytier-gui/src/auto-imports.d.ts
vendored
6
easytier-gui/src/auto-imports.d.ts
vendored
|
@ -27,6 +27,7 @@ declare global {
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
const isReadonly: typeof import('vue')['isReadonly']
|
const isReadonly: typeof import('vue')['isReadonly']
|
||||||
const isRef: typeof import('vue')['isRef']
|
const isRef: typeof import('vue')['isRef']
|
||||||
|
const loadRunningInstanceIdsFromLocalStorage: typeof import('./stores/network')['loadRunningInstanceIdsFromLocalStorage']
|
||||||
const mapActions: typeof import('pinia')['mapActions']
|
const mapActions: typeof import('pinia')['mapActions']
|
||||||
const mapGetters: typeof import('pinia')['mapGetters']
|
const mapGetters: typeof import('pinia')['mapGetters']
|
||||||
const mapState: typeof import('pinia')['mapState']
|
const mapState: typeof import('pinia')['mapState']
|
||||||
|
@ -58,6 +59,7 @@ declare global {
|
||||||
const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance']
|
const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance']
|
||||||
const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance']
|
const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance']
|
||||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||||
|
const setAutoLaunchStatus: typeof import('./composables/network')['setAutoLaunchStatus']
|
||||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
|
@ -116,6 +118,7 @@ declare module 'vue' {
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly loadRunningInstanceIdsFromLocalStorage: UnwrapRef<typeof import('./stores/network')['loadRunningInstanceIdsFromLocalStorage']>
|
||||||
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
@ -147,6 +150,7 @@ declare module 'vue' {
|
||||||
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
||||||
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
||||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setAutoLaunchStatus: UnwrapRef<typeof import('./composables/network')['setAutoLaunchStatus']>
|
||||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
@ -198,6 +202,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||||
|
readonly loadRunningInstanceIdsFromLocalStorage: UnwrapRef<typeof import('./stores/network')['loadRunningInstanceIdsFromLocalStorage']>
|
||||||
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||||
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||||
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||||
|
@ -229,6 +234,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
||||||
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
||||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
|
readonly setAutoLaunchStatus: UnwrapRef<typeof import('./composables/network')['setAutoLaunchStatus']>
|
||||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||||
|
|
|
@ -20,3 +20,7 @@ export async function collectNetworkInfos() {
|
||||||
export async function getOsHostname() {
|
export async function getOsHostname() {
|
||||||
return await invoke<string>('get_os_hostname')
|
return await invoke<string>('get_os_hostname')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setAutoLaunchStatus(enable: boolean) {
|
||||||
|
return await invoke<boolean>('set_auto_launch_status', { enable })
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import 'primevue/resources/themes/aura-light-green/theme.css'
|
||||||
import 'primeicons/primeicons.css'
|
import 'primeicons/primeicons.css'
|
||||||
import 'primeflex/primeflex.css'
|
import 'primeflex/primeflex.css'
|
||||||
import { i18n, loadLanguageAsync } from '~/modules/i18n'
|
import { i18n, loadLanguageAsync } from '~/modules/i18n'
|
||||||
|
import { loadAutoLaunchStatusAsync, getAutoLaunchStatusAsync } from './modules/auto_launch'
|
||||||
|
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
|
@ -28,6 +29,7 @@ if (import.meta.env.PROD) {
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await loadLanguageAsync(localStorage.getItem('lang') || 'en')
|
await loadLanguageAsync(localStorage.getItem('lang') || 'en')
|
||||||
|
await loadAutoLaunchStatusAsync(getAutoLaunchStatusAsync())
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
|
16
easytier-gui/src/modules/auto_launch.ts
Normal file
16
easytier-gui/src/modules/auto_launch.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { setAutoLaunchStatus } from "~/composables/network"
|
||||||
|
|
||||||
|
export async function loadAutoLaunchStatusAsync(enable: boolean): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const ret = await setAutoLaunchStatus(enable)
|
||||||
|
localStorage.setItem('auto_launch', JSON.stringify(ret))
|
||||||
|
return ret
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAutoLaunchStatusAsync(): boolean {
|
||||||
|
return localStorage.getItem('auto_launch') === 'true'
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import Status from '~/components/Status.vue'
|
||||||
|
|
||||||
import type { NetworkConfig } from '~/types/network'
|
import type { NetworkConfig } from '~/types/network'
|
||||||
import { loadLanguageAsync } from '~/modules/i18n'
|
import { loadLanguageAsync } from '~/modules/i18n'
|
||||||
|
import { getAutoLaunchStatusAsync as getAutoLaunchStatus, loadAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
||||||
|
import { loadRunningInstanceIdsFromLocalStorage } from '~/stores/network'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
@ -63,6 +65,7 @@ function addNewNetwork() {
|
||||||
|
|
||||||
networkStore.$subscribe(async () => {
|
networkStore.$subscribe(async () => {
|
||||||
networkStore.saveToLocalStorage()
|
networkStore.saveToLocalStorage()
|
||||||
|
networkStore.saveRunningInstanceIdsToLocalStorage()
|
||||||
try {
|
try {
|
||||||
await parseNetworkConfig(networkStore.curNetwork)
|
await parseNetworkConfig(networkStore.curNetwork)
|
||||||
messageBarSeverity.value = Severity.None
|
messageBarSeverity.value = Severity.None
|
||||||
|
@ -123,9 +126,16 @@ const setting_menu_items = ref([
|
||||||
await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
|
await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () => getAutoLaunchStatus() ? t('disable_auto_launch') : t('enable_auto_launch'),
|
||||||
|
icon: 'pi pi-desktop',
|
||||||
|
command: async () => {
|
||||||
|
await loadAutoLaunchStatusAsync(!getAutoLaunchStatus())
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: () => t('exit'),
|
label: () => t('exit'),
|
||||||
icon: 'pi pi-times',
|
icon: 'pi pi-power-off',
|
||||||
command: async () => {
|
command: async () => {
|
||||||
await exit(1)
|
await exit(1)
|
||||||
},
|
},
|
||||||
|
@ -140,6 +150,16 @@ function toggle_setting_menu(event: any) {
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
networkStore.loadFromLocalStorage()
|
networkStore.loadFromLocalStorage()
|
||||||
|
if (getAutoLaunchStatus()) {
|
||||||
|
let prev_running_ids = loadRunningInstanceIdsFromLocalStorage()
|
||||||
|
for (let id of prev_running_ids) {
|
||||||
|
let cfg = networkStore.networkList.find((item) => item.instance_id === id)
|
||||||
|
if (cfg) {
|
||||||
|
networkStore.addNetworkInstance(cfg.instance_id)
|
||||||
|
await runNetworkInstance(cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function isRunning(id: string) {
|
function isRunning(id: string) {
|
||||||
|
@ -168,35 +188,28 @@ function isRunning(id: string) {
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<template #start>
|
<template #start>
|
||||||
<div class="flex align-items-center gap-2">
|
<div class="flex align-items-center gap-2">
|
||||||
<Button
|
<Button icon="pi pi-plus" class="mr-2" severity="primary" :label="t('add_new_network')"
|
||||||
icon="pi pi-plus" class="mr-2" severity="primary" :label="t('add_new_network')"
|
@click="addNewNetwork" />
|
||||||
@click="addNewNetwork"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #center>
|
<template #center>
|
||||||
<div class="min-w-80 mr-20">
|
<div class="min-w-80 mr-20">
|
||||||
<Dropdown
|
<Dropdown v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
||||||
v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
:placeholder="t('select_network')" class="w-full">
|
||||||
:placeholder="t('select_network')" class="w-full"
|
|
||||||
>
|
|
||||||
<template #value="slotProps">
|
<template #value="slotProps">
|
||||||
<div class="flex items-start content-center">
|
<div class="flex items-start content-center">
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
<span>{{ slotProps.value.network_name }}</span>
|
<span>{{ slotProps.value.network_name }}</span>
|
||||||
<span
|
<span
|
||||||
v-if="isRunning(slotProps.value.instance_id) && networkStore.instances[slotProps.value.instance_id].detail && (networkStore.instances[slotProps.value.instance_id].detail?.my_node_info.virtual_ipv4 !== '')"
|
v-if="isRunning(slotProps.value.instance_id) && networkStore.instances[slotProps.value.instance_id].detail && (networkStore.instances[slotProps.value.instance_id].detail?.my_node_info.virtual_ipv4 !== '')"
|
||||||
class="ml-3"
|
class="ml-3">
|
||||||
>
|
|
||||||
{{ networkStore.instances[slotProps.value.instance_id].detail
|
{{ networkStore.instances[slotProps.value.instance_id].detail
|
||||||
? networkStore.instances[slotProps.value.instance_id].detail?.my_node_info.virtual_ipv4 : '' }}
|
? networkStore.instances[slotProps.value.instance_id].detail?.my_node_info.virtual_ipv4 : '' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Tag
|
<Tag class="my-auto" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
||||||
class="my-auto" :severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||||
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #option="slotProps">
|
<template #option="slotProps">
|
||||||
|
@ -205,10 +218,8 @@ function isRunning(id: string) {
|
||||||
<div class="mr-3">
|
<div class="mr-3">
|
||||||
{{ t('network_name') }}: {{ slotProps.option.network_name }}
|
{{ t('network_name') }}: {{ slotProps.option.network_name }}
|
||||||
</div>
|
</div>
|
||||||
<Tag
|
<Tag class="my-auto" :severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
||||||
class="my-auto" :severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" />
|
||||||
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>{{ slotProps.option.public_server_url }}</div>
|
<div>{{ slotProps.option.public_server_url }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -218,10 +229,8 @@ function isRunning(id: string) {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #end>
|
<template #end>
|
||||||
<Button
|
<Button icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
||||||
icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
||||||
aria-controls="overlay_setting_menu" @click="toggle_setting_menu"
|
|
||||||
/>
|
|
||||||
<Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
<Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
||||||
</template>
|
</template>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
@ -230,10 +239,8 @@ function isRunning(id: string) {
|
||||||
<Stepper class="h-full overflow-y-auto" :active-step="activeStep">
|
<Stepper class="h-full overflow-y-auto" :active-step="activeStep">
|
||||||
<StepperPanel :header="t('config_network')">
|
<StepperPanel :header="t('config_network')">
|
||||||
<template #content="{ nextCallback }">
|
<template #content="{ nextCallback }">
|
||||||
<Config
|
<Config :instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
||||||
:instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
@run-network="runNetworkCb($event, nextCallback)" />
|
||||||
@run-network="runNetworkCb($event, nextCallback)"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</StepperPanel>
|
</StepperPanel>
|
||||||
<StepperPanel :header="t('running')">
|
<StepperPanel :header="t('running')">
|
||||||
|
@ -242,10 +249,8 @@ function isRunning(id: string) {
|
||||||
<Status :instance-id="networkStore.curNetworkId" />
|
<Status :instance-id="networkStore.curNetworkId" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex pt-4 justify-content-center">
|
<div class="flex pt-4 justify-content-center">
|
||||||
<Button
|
<Button :label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
||||||
:label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)" />
|
||||||
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</StepperPanel>
|
</StepperPanel>
|
||||||
|
|
|
@ -70,6 +70,7 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
this.instances[instanceId].error_msg = info.error_msg || ''
|
this.instances[instanceId].error_msg = info.error_msg || ''
|
||||||
this.instances[instanceId].detail = info
|
this.instances[instanceId].detail = info
|
||||||
}
|
}
|
||||||
|
this.saveRunningInstanceIdsToLocalStorage()
|
||||||
},
|
},
|
||||||
|
|
||||||
loadFromLocalStorage() {
|
loadFromLocalStorage() {
|
||||||
|
@ -92,8 +93,22 @@ export const useNetworkStore = defineStore('networkStore', {
|
||||||
saveToLocalStorage() {
|
saveToLocalStorage() {
|
||||||
localStorage.setItem('networkList', JSON.stringify(this.networkList))
|
localStorage.setItem('networkList', JSON.stringify(this.networkList))
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveRunningInstanceIdsToLocalStorage() {
|
||||||
|
let instance_ids = Object.keys(this.instances).filter((instanceId) => this.instances[instanceId].running)
|
||||||
|
localStorage.setItem('runningInstanceIds', JSON.stringify(instance_ids))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (import.meta.hot)
|
if (import.meta.hot)
|
||||||
import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot))
|
import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot))
|
||||||
|
|
||||||
|
export function loadRunningInstanceIdsFromLocalStorage(): string[] {
|
||||||
|
try {
|
||||||
|
return JSON.parse(localStorage.getItem('runningInstanceIds') || '[]')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ auto_impl = "1.1.0"
|
||||||
crossbeam = "0.8.4"
|
crossbeam = "0.8.4"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
toml = "0.8.12"
|
toml = "0.8.12"
|
||||||
chrono = "0.4.35"
|
chrono = { version = "0.4.37", features = ["serde"] }
|
||||||
|
|
||||||
gethostname = "0.4.3"
|
gethostname = "0.4.3"
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ async fn run_shell_cmd(cmd: &str) -> Result<(), Error> {
|
||||||
{
|
{
|
||||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
cmd_out = Command::new("cmd")
|
cmd_out = Command::new("cmd")
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
.arg("/C")
|
.arg("/C")
|
||||||
.arg(cmd)
|
.arg(cmd)
|
||||||
.creation_flags(CREATE_NO_WINDOW)
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
|
|
|
@ -3,8 +3,7 @@ use std::{
|
||||||
sync::{atomic::AtomicBool, Arc, RwLock},
|
sync::{atomic::AtomicBool, Arc, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
use crate::{
|
||||||
use easytier::{
|
|
||||||
common::{
|
common::{
|
||||||
config::{ConfigLoader, TomlConfigLoader},
|
config::{ConfigLoader, TomlConfigLoader},
|
||||||
global_ctx::GlobalCtxEvent,
|
global_ctx::GlobalCtxEvent,
|
||||||
|
@ -16,7 +15,9 @@ use easytier::{
|
||||||
cli::{PeerInfo, Route, StunInfo},
|
cli::{PeerInfo, Route, StunInfo},
|
||||||
peer::GetIpListResponse,
|
peer::GetIpListResponse,
|
||||||
},
|
},
|
||||||
|
utils::{list_peer_route_pair, PeerRoutePair},
|
||||||
};
|
};
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
@ -212,3 +213,68 @@ impl Drop for EasyTierLauncher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct NetworkInstanceRunningInfo {
|
||||||
|
pub my_node_info: MyNodeInfo,
|
||||||
|
pub events: Vec<(DateTime<Local>, GlobalCtxEvent)>,
|
||||||
|
pub node_info: MyNodeInfo,
|
||||||
|
pub routes: Vec<Route>,
|
||||||
|
pub peers: Vec<PeerInfo>,
|
||||||
|
pub peer_route_pairs: Vec<PeerRoutePair>,
|
||||||
|
pub running: bool,
|
||||||
|
pub error_msg: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NetworkInstance {
|
||||||
|
config: TomlConfigLoader,
|
||||||
|
launcher: Option<EasyTierLauncher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkInstance {
|
||||||
|
pub fn new(config: TomlConfigLoader) -> Self {
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
launcher: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_easytier_running(&self) -> bool {
|
||||||
|
self.launcher.is_some() && self.launcher.as_ref().unwrap().running()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_running_info(&self) -> Option<NetworkInstanceRunningInfo> {
|
||||||
|
if self.launcher.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let launcher = self.launcher.as_ref().unwrap();
|
||||||
|
|
||||||
|
let peers = launcher.get_peers();
|
||||||
|
let routes = launcher.get_routes();
|
||||||
|
let peer_route_pairs = list_peer_route_pair(peers.clone(), routes.clone());
|
||||||
|
|
||||||
|
Some(NetworkInstanceRunningInfo {
|
||||||
|
my_node_info: launcher.get_node_info(),
|
||||||
|
events: launcher.get_events(),
|
||||||
|
node_info: launcher.get_node_info(),
|
||||||
|
routes,
|
||||||
|
peers,
|
||||||
|
peer_route_pairs,
|
||||||
|
running: launcher.running(),
|
||||||
|
error_msg: launcher.error_msg(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) -> Result<(), anyhow::Error> {
|
||||||
|
if self.is_easytier_running() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut launcher = EasyTierLauncher::new();
|
||||||
|
launcher.start(|| Ok(self.config.clone()));
|
||||||
|
|
||||||
|
self.launcher = Some(launcher);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod arch;
|
mod arch;
|
||||||
|
mod connector;
|
||||||
|
mod gateway;
|
||||||
|
mod instance;
|
||||||
|
mod peer_center;
|
||||||
|
mod peers;
|
||||||
|
mod vpn_portal;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod connector;
|
pub mod launcher;
|
||||||
pub mod gateway;
|
|
||||||
pub mod instance;
|
|
||||||
pub mod peer_center;
|
|
||||||
pub mod peers;
|
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod tunnel;
|
pub mod tunnel;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod vpn_portal;
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user