From 0498b55d39edbdc697f18f29454f2650b050c619 Mon Sep 17 00:00:00 2001 From: m1m1sha <18262227804@163.com> Date: Wed, 8 May 2024 14:47:22 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=E2=9C=A8=20feat:=20custom=20hostname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 3 ++- easytier-gui/locales/cn.yml | 1 + easytier-gui/locales/en.yml | 1 + easytier-gui/src-tauri/src/main.rs | 2 ++ easytier-gui/src/components/Config.vue | 20 ++++++++++++++++++ easytier-gui/src/types/network.ts | 1 + easytier/Cargo.toml | 2 ++ easytier/src/common/config.rs | 17 +++++++++++++++ easytier/src/common/global_ctx.rs | 29 +++++++++++++++++++------- easytier/src/easytier-core.rs | 23 ++++++++++++++++++++ 10 files changed, 91 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3dc6e30..5bb95a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,7 +387,7 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" -source = "git+https://github.com/KKRainbow/boringtun.git#449204c3eca736dc23b075d81426527a357e2f2a" +source = "git+https://github.com/EasyTier/boringtun.git#449204c3eca736dc23b075d81426527a357e2f2a" dependencies = [ "aead", "atomic-shim", @@ -1309,6 +1309,7 @@ dependencies = [ "quinn", "rand 0.8.5", "rcgen", + "regex", "reqwest", "ring 0.17.8", "rstest", diff --git a/easytier-gui/locales/cn.yml b/easytier-gui/locales/cn.yml index 4e6be22..aa2ce02 100644 --- a/easytier-gui/locales/cn.yml +++ b/easytier-gui/locales/cn.yml @@ -32,6 +32,7 @@ settings: 设置 exchange_language: Switch to English exit: 退出 chips_placeholder: 例如: {0}, 按回车添加 +hostname_placeholder: 留空默认为设备名称 off_text: 点击关闭 on_text: 点击开启 show_config: 显示配置 diff --git a/easytier-gui/locales/en.yml b/easytier-gui/locales/en.yml index 6c80c27..7221c0e 100644 --- a/easytier-gui/locales/en.yml +++ b/easytier-gui/locales/en.yml @@ -33,6 +33,7 @@ exchange_language: 切换中文 exit: Exit chips_placeholder: 'e.g: {0}, press Enter to add' +hostname_placeholder: Leave blank and default to device name off_text: Press to disable on_text: Press to enable show_config: Show Config diff --git a/easytier-gui/src-tauri/src/main.rs b/easytier-gui/src-tauri/src/main.rs index ad4a472..6124ee5 100644 --- a/easytier-gui/src-tauri/src/main.rs +++ b/easytier-gui/src-tauri/src/main.rs @@ -42,6 +42,7 @@ struct NetworkConfig { instance_id: String, virtual_ipv4: String, + hostname: Option, network_name: String, network_secret: String, networking_method: NetworkingMethod, @@ -70,6 +71,7 @@ impl NetworkConfig { .parse() .with_context(|| format!("failed to parse instance id: {}", self.instance_id))?, ); + cfg.set_hostname(self.hostname.clone()); cfg.set_inst_name(self.network_name.clone()); cfg.set_network_identity(NetworkIdentity::new( self.network_name.clone(), diff --git a/easytier-gui/src/components/Config.vue b/easytier-gui/src/components/Config.vue index 947a1a8..0a65ca3 100644 --- a/easytier-gui/src/components/Config.vue +++ b/easytier-gui/src/components/Config.vue @@ -32,6 +32,18 @@ const curNetwork = computed(() => { const presetPublicServers = [ 'tcp://easytier.public.kkrainbow.top:11010', ] + +function validateHostname() { + if (curNetwork.value.hostname) { + // eslint no-useless-escape + let name = curNetwork.value.hostname!.replaceAll(/[^\u4E00-\u9FA5a-zA-Z0-9\-]*/g, '') + if (name.length > 32) + name = name.substring(0, 32) + + if (curNetwork.value.hostname !== name) + curNetwork.value.hostname = name + } +} From bde5b7f6ea132192dd87588228f82d799f10ef99 Mon Sep 17 00:00:00 2001 From: m1m1sha <18262227804@163.com> Date: Wed, 8 May 2024 16:06:11 +0800 Subject: [PATCH 03/18] =?UTF-8?q?=F0=9F=8E=88=20perf:=20get=20hostname?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- easytier/src/common/config.rs | 25 +++++++++++++++++------ easytier/src/common/global_ctx.rs | 29 ++++----------------------- easytier/src/easytier-core.rs | 19 +----------------- easytier/src/peers/peer_ospf_route.rs | 10 +++------ easytier/src/peers/peer_rip_route.rs | 8 ++------ 5 files changed, 29 insertions(+), 62 deletions(-) diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 92ea475..7201172 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -194,6 +194,7 @@ impl TomlConfigLoader { config_str, config_str ) })?; + Ok(TomlConfigLoader { config: Arc::new(Mutex::new(config)), }) @@ -221,12 +222,24 @@ impl ConfigLoader for TomlConfigLoader { } fn get_hostname(&self) -> String { - self.config - .lock() - .unwrap() - .hostname - .clone() - .unwrap_or(gethostname::gethostname().to_string_lossy().to_string()) + let hostname = self.config.lock().unwrap().hostname.clone(); + + match hostname { + Some(hostname) => { + let re = regex::Regex::new(r"[^\u4E00-\u9FA5a-zA-Z0-9\-]*").unwrap(); + let mut name = re.replace_all(&hostname, "").to_string(); + + if name.len() > 32 { + name = name.chars().take(32).collect::(); + } + + if hostname != name { + self.set_hostname(Some(name.clone())); + } + name + } + None => gethostname::gethostname().to_string_lossy().to_string(), + } } fn set_hostname(&self, name: Option) { diff --git a/easytier/src/common/global_ctx.rs b/easytier/src/common/global_ctx.rs index 969988c..d574311 100644 --- a/easytier/src/common/global_ctx.rs +++ b/easytier/src/common/global_ctx.rs @@ -54,7 +54,7 @@ pub struct GlobalCtx { ip_collector: Arc, - hostname: AtomicCell>, + hostname: String, stun_info_collection: Box, @@ -97,7 +97,7 @@ impl GlobalCtx { ip_collector: Arc::new(IPCollector::new(net_ns)), - hostname: AtomicCell::new(Some(hostname)), + hostname, stun_info_collection: Box::new(StunInfoCollector::new_with_default_servers()), @@ -166,29 +166,8 @@ impl GlobalCtx { self.ip_collector.clone() } - pub fn get_hostname(&self) -> Option { - let hostname = gethostname::gethostname().to_string_lossy().to_string(); - - let hostname = match self.hostname.take() { - Some(name) => { - // when allowing custom hostname, there may be empty - if name.is_empty() { - hostname - } else { - name - } - }, - None => hostname, - }; - - let re = regex::Regex::new(r"[^\u4E00-\u9FA5a-zA-Z0-9\-]*").unwrap(); - let mut hostname = re.replace_all(&hostname, "").to_string(); - if hostname.len() > 32 { - hostname = hostname.chars().take(32).collect::(); - } - - self.hostname.store(Some(hostname.clone())); - return Some(hostname); + pub fn get_hostname(&self) -> String { + return self.hostname.clone(); } pub fn get_stun_info_collector(&self) -> impl StunInfoCollectorTrait + '_ { diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index ec67338..3860664 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -181,24 +181,7 @@ impl From for TomlConfigLoader { cfg.set_inst_name(cli.instance_name.clone()); - let hostname = gethostname::gethostname().to_string_lossy().to_string(); - let hostname = match cli.hostname { - Some(name) => { - // when allowing custom hostname, there may be empty - if name.is_empty() { - hostname - } else { - name - } - } - None => hostname, - }; - let re = regex::Regex::new(r"[^\u4E00-\u9FA5a-zA-Z0-9\-]*").unwrap(); - let mut hostname = re.replace_all(&hostname, "").to_string(); - if hostname.len() > 32 { - hostname = hostname.chars().take(32).collect::(); - } - cfg.set_hostname(Some(hostname)); + cfg.set_hostname(cli.hostname.clone()); cfg.set_network_identity(NetworkIdentity::new( cli.network_name.clone(), diff --git a/easytier/src/peers/peer_ospf_route.rs b/easytier/src/peers/peer_ospf_route.rs index b7b9624..1ea5152 100644 --- a/easytier/src/peers/peer_ospf_route.rs +++ b/easytier/src/peers/peer_ospf_route.rs @@ -68,7 +68,7 @@ struct RoutePeerInfo { cost: u8, ipv4_addr: Option, proxy_cidrs: Vec, - hostname: Option, + hostname: String, udp_stun_info: i8, last_update: SystemTime, version: Version, @@ -82,7 +82,7 @@ impl RoutePeerInfo { cost: 0, ipv4_addr: None, proxy_cidrs: Vec::new(), - hostname: None, + hostname: String::new(), udp_stun_info: 0, last_update: SystemTime::now(), version: 0, @@ -138,11 +138,7 @@ impl Into for RoutePeerInfo { next_hop_peer_id: 0, cost: self.cost as i32, proxy_cidrs: self.proxy_cidrs.clone(), - hostname: if let Some(hostname) = &self.hostname { - hostname.clone() - } else { - "".to_string() - }, + hostname: self.hostname, stun_info: { let mut stun_info = StunInfo::default(); if let Ok(udp_nat_type) = NatType::try_from(self.udp_stun_info as i32) { diff --git a/easytier/src/peers/peer_rip_route.rs b/easytier/src/peers/peer_rip_route.rs index fda9e5a..3137e01 100644 --- a/easytier/src/peers/peer_rip_route.rs +++ b/easytier/src/peers/peer_rip_route.rs @@ -36,7 +36,7 @@ pub struct SyncPeerInfo { pub cost: u32, pub ipv4_addr: Option, pub proxy_cidrs: Vec, - pub hostname: Option, + pub hostname: String, pub udp_stun_info: i8, } @@ -585,11 +585,7 @@ impl Route for BasicRoute { route.next_hop_peer_id = route_info.peer_id; route.cost = route_info.cost as i32; route.proxy_cidrs = route_info.proxy_cidrs.clone(); - route.hostname = if let Some(hostname) = &route_info.hostname { - hostname.clone() - } else { - "".to_string() - }; + route.hostname = route_info.hostname.clone(); let mut stun_info = StunInfo::default(); if let Ok(udp_nat_type) = NatType::try_from(route_info.udp_stun_info as i32) { From 30ccfab28890a734ce3557761445f4f7877cd001 Mon Sep 17 00:00:00 2001 From: m1m1sha <18262227804@163.com> Date: Wed, 8 May 2024 16:18:09 +0800 Subject: [PATCH 04/18] =?UTF-8?q?=F0=9F=90=9E=20fix:=20hostname=20empty?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- easytier/src/common/config.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 7201172..9604e37 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -226,17 +226,22 @@ impl ConfigLoader for TomlConfigLoader { match hostname { Some(hostname) => { - let re = regex::Regex::new(r"[^\u4E00-\u9FA5a-zA-Z0-9\-]*").unwrap(); - let mut name = re.replace_all(&hostname, "").to_string(); + if !hostname.is_empty() { + let re = regex::Regex::new(r"[^\u4E00-\u9FA5a-zA-Z0-9\-]*").unwrap(); + let mut name = re.replace_all(&hostname, "").to_string(); - if name.len() > 32 { - name = name.chars().take(32).collect::(); - } + if name.len() > 32 { + name = name.chars().take(32).collect::(); + } - if hostname != name { - self.set_hostname(Some(name.clone())); + if hostname != name { + self.set_hostname(Some(name.clone())); + } + name + } else { + self.set_hostname(None); + gethostname::gethostname().to_string_lossy().to_string() } - name } None => gethostname::gethostname().to_string_lossy().to_string(), } From 4ca840239ab9fcf2db98989770f12deaa9cdf7cf Mon Sep 17 00:00:00 2001 From: Yumin Wu Date: Wed, 8 May 2024 17:40:43 +0800 Subject: [PATCH 05/18] wireguard client keepalive --- README.md | 1 + README_CN.md | 1 + easytier/src/vpn_portal/wireguard.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/README.md b/README.md index dfedce5..ba86c35 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,7 @@ Address = 10.14.14.0/24 # should assign an ip from this cidr manually PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 Endpoint = 0.0.0.0:11013 # should be the public ip of the vpn server +PersistentKeepalive = 25 connected_clients: [] diff --git a/README_CN.md b/README_CN.md index e36c184..99007ee 100644 --- a/README_CN.md +++ b/README_CN.md @@ -228,6 +228,7 @@ Address = 10.14.14.0/24 # should assign an ip from this cidr manually PublicKey = zhrZQg4QdPZs8CajT3r4fmzcNsWpBL9ImQCUsnlXyGM= AllowedIPs = 192.168.80.0/20,10.147.223.0/24,10.144.144.0/24 Endpoint = 0.0.0.0:11013 # should be the public ip of the vpn server +PersistentKeepalive = 25 connected_clients: [] diff --git a/easytier/src/vpn_portal/wireguard.rs b/easytier/src/vpn_portal/wireguard.rs index 2423ccd..dccdeda 100644 --- a/easytier/src/vpn_portal/wireguard.rs +++ b/easytier/src/vpn_portal/wireguard.rs @@ -284,6 +284,7 @@ Address = {client_cidr} # should assign an ip from this cidr manually PublicKey = {my_public_key} AllowedIPs = {allow_ips} Endpoint = {listenr_addr} # should be the public ip of the vpn server +PersistentKeepalive = 25 "#, peer_secret_key = BASE64_STANDARD.encode(cfg.peer_secret_key()), my_public_key = BASE64_STANDARD.encode(cfg.my_public_key()), From e6ad308cd5d236e32c4ed2262ca51c3da5f809da Mon Sep 17 00:00:00 2001 From: m1m1sha <18262227804@163.com> Date: Wed, 8 May 2024 20:49:33 +0800 Subject: [PATCH 06/18] =?UTF-8?q?=E2=86=A9=20revert:=20=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- easytier/src/peers/peer_ospf_route.rs | 8 ++++---- easytier/src/peers/peer_rip_route.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/easytier/src/peers/peer_ospf_route.rs b/easytier/src/peers/peer_ospf_route.rs index 1ea5152..59bdbf4 100644 --- a/easytier/src/peers/peer_ospf_route.rs +++ b/easytier/src/peers/peer_ospf_route.rs @@ -68,7 +68,7 @@ struct RoutePeerInfo { cost: u8, ipv4_addr: Option, proxy_cidrs: Vec, - hostname: String, + hostname: Option, udp_stun_info: i8, last_update: SystemTime, version: Version, @@ -82,7 +82,7 @@ impl RoutePeerInfo { cost: 0, ipv4_addr: None, proxy_cidrs: Vec::new(), - hostname: String::new(), + hostname: None, udp_stun_info: 0, last_update: SystemTime::now(), version: 0, @@ -101,7 +101,7 @@ impl RoutePeerInfo { .map(|x| x.to_string()) .chain(global_ctx.get_vpn_portal_cidr().map(|x| x.to_string())) .collect(), - hostname: global_ctx.get_hostname(), + hostname: Some(global_ctx.get_hostname()), udp_stun_info: global_ctx .get_stun_info_collector() .get_stun_info() @@ -138,7 +138,7 @@ impl Into for RoutePeerInfo { next_hop_peer_id: 0, cost: self.cost as i32, proxy_cidrs: self.proxy_cidrs.clone(), - hostname: self.hostname, + hostname: self.hostname.unwrap_or_default(), stun_info: { let mut stun_info = StunInfo::default(); if let Ok(udp_nat_type) = NatType::try_from(self.udp_stun_info as i32) { diff --git a/easytier/src/peers/peer_rip_route.rs b/easytier/src/peers/peer_rip_route.rs index 3137e01..2ce0a05 100644 --- a/easytier/src/peers/peer_rip_route.rs +++ b/easytier/src/peers/peer_rip_route.rs @@ -36,7 +36,7 @@ pub struct SyncPeerInfo { pub cost: u32, pub ipv4_addr: Option, pub proxy_cidrs: Vec, - pub hostname: String, + pub hostname: Option, pub udp_stun_info: i8, } @@ -52,7 +52,7 @@ impl SyncPeerInfo { .map(|x| x.to_string()) .chain(global_ctx.get_vpn_portal_cidr().map(|x| x.to_string())) .collect(), - hostname: global_ctx.get_hostname(), + hostname: Some(global_ctx.get_hostname()), udp_stun_info: global_ctx .get_stun_info_collector() .get_stun_info() @@ -585,7 +585,7 @@ impl Route for BasicRoute { route.next_hop_peer_id = route_info.peer_id; route.cost = route_info.cost as i32; route.proxy_cidrs = route_info.proxy_cidrs.clone(); - route.hostname = route_info.hostname.clone(); + route.hostname = route_info.hostname.clone().unwrap_or_default(); let mut stun_info = StunInfo::default(); if let Ok(udp_nat_type) = NatType::try_from(route_info.udp_stun_info as i32) { From 52fef9fd4f8cee31c2c0471dd5061a27a0d8be1a Mon Sep 17 00:00:00 2001 From: m1m1sha <18262227804@163.com> Date: Wed, 8 May 2024 21:02:14 +0800 Subject: [PATCH 07/18] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E5=90=8D=E6=8F=90=E7=A4=BA=E6=98=BE=E7=A4=BA=E6=9C=AC=E6=9C=BA?= =?UTF-8?q?=E4=B8=BB=E6=9C=BA=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + easytier-gui/locales/cn.yml | 2 +- easytier-gui/locales/en.yml | 2 +- easytier-gui/src-tauri/Cargo.toml | 8 ++++++-- easytier-gui/src-tauri/src/main.rs | 8 +++++++- easytier-gui/src/auto-imports.d.ts | 3 +++ easytier-gui/src/components/Config.vue | 10 +++++++++- easytier-gui/src/composables/network.ts | 4 ++++ 8 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bb95a4..91b2dfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1349,6 +1349,7 @@ dependencies = [ "chrono", "dashmap", "easytier", + "gethostname", "once_cell", "privilege", "serde", diff --git a/easytier-gui/locales/cn.yml b/easytier-gui/locales/cn.yml index aa2ce02..63cb2bb 100644 --- a/easytier-gui/locales/cn.yml +++ b/easytier-gui/locales/cn.yml @@ -32,7 +32,7 @@ settings: 设置 exchange_language: Switch to English exit: 退出 chips_placeholder: 例如: {0}, 按回车添加 -hostname_placeholder: 留空默认为设备名称 +hostname_placeholder: '留空默认为主机名: {0}' off_text: 点击关闭 on_text: 点击开启 show_config: 显示配置 diff --git a/easytier-gui/locales/en.yml b/easytier-gui/locales/en.yml index 7221c0e..b19c362 100644 --- a/easytier-gui/locales/en.yml +++ b/easytier-gui/locales/en.yml @@ -33,7 +33,7 @@ exchange_language: 切换中文 exit: Exit chips_placeholder: 'e.g: {0}, press Enter to add' -hostname_placeholder: Leave blank and default to device name +hostname_placeholder: 'Leave blank and default to host name: {0}' off_text: Press to disable on_text: Press to enable show_config: Show Config diff --git a/easytier-gui/src-tauri/Cargo.toml b/easytier-gui/src-tauri/Cargo.toml index c98b231..bf702f8 100644 --- a/easytier-gui/src-tauri/Cargo.toml +++ b/easytier-gui/src-tauri/Cargo.toml @@ -11,7 +11,11 @@ edition = "2021" tauri-build = { version = "1", features = [] } [dependencies] -tauri = { version = "1", features = [ "process-exit", "system-tray", "shell-open"] } +tauri = { version = "1", features = [ + "process-exit", + "system-tray", + "shell-open", +] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -24,7 +28,7 @@ once_cell = "1.18.0" dashmap = "5.5.3" privilege = "0.3" - +gethostname = "0.4.3" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! custom-protocol = ["tauri/custom-protocol"] diff --git a/easytier-gui/src-tauri/src/main.rs b/easytier-gui/src-tauri/src/main.rs index 6124ee5..6ce7ce3 100644 --- a/easytier-gui/src-tauri/src/main.rs +++ b/easytier-gui/src-tauri/src/main.rs @@ -283,6 +283,11 @@ fn collect_network_infos() -> Result { Ok(serde_json::to_string(&ret).map_err(|e| e.to_string())?) } +#[tauri::command] +fn get_os_hostname() -> Result { + Ok(gethostname::gethostname().to_string_lossy().to_string()) +} + fn toggle_window_visibility(window: &Window) { if window.is_visible().unwrap() { window.hide().unwrap(); @@ -320,7 +325,8 @@ fn main() { parse_network_config, run_network_instance, retain_network_instance, - collect_network_infos + collect_network_infos, + get_os_hostname ]) .system_tray(SystemTray::new().with_menu(tray_menu)) .on_system_tray_event(|app, event| match event { diff --git a/easytier-gui/src/auto-imports.d.ts b/easytier-gui/src/auto-imports.d.ts index 9fbc786..d337cfb 100644 --- a/easytier-gui/src/auto-imports.d.ts +++ b/easytier-gui/src/auto-imports.d.ts @@ -20,6 +20,7 @@ declare global { const getActivePinia: typeof import('pinia')['getActivePinia'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] + const getOsHostname: typeof import('./composables/network')['getOsHostname'] const h: typeof import('vue')['h'] const inject: typeof import('vue')['inject'] const isProxy: typeof import('vue')['isProxy'] @@ -108,6 +109,7 @@ declare module 'vue' { readonly getActivePinia: UnwrapRef readonly getCurrentInstance: UnwrapRef readonly getCurrentScope: UnwrapRef + readonly getOsHostname: UnwrapRef readonly h: UnwrapRef readonly inject: UnwrapRef readonly isProxy: UnwrapRef @@ -189,6 +191,7 @@ declare module '@vue/runtime-core' { readonly getActivePinia: UnwrapRef readonly getCurrentInstance: UnwrapRef readonly getCurrentScope: UnwrapRef + readonly getOsHostname: UnwrapRef readonly h: UnwrapRef readonly inject: UnwrapRef readonly isProxy: UnwrapRef diff --git a/easytier-gui/src/components/Config.vue b/easytier-gui/src/components/Config.vue index 0a65ca3..aac1b77 100644 --- a/easytier-gui/src/components/Config.vue +++ b/easytier-gui/src/components/Config.vue @@ -1,6 +1,7 @@