From eba9504fc2c37210e1c170d36232e0e973117c67 Mon Sep 17 00:00:00 2001 From: Mg Pig Date: Mon, 20 Oct 2025 22:07:01 +0800 Subject: [PATCH] refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component (#1489) * refactor(gui): refactor gui to use RemoteClient trait and RemoteManagement component * feat(gui): Add network config saving and refactor RemoteManagement --- Cargo.lock | 5 +- easytier-gui/src-tauri/Cargo.toml | 1 + easytier-gui/src-tauri/src/lib.rs | 514 ++++++++++++++++-- easytier-gui/src/auto-imports.d.ts | 54 +- easytier-gui/src/components/About.vue | 2 +- easytier-gui/src/composables/backend.ts | 65 +++ easytier-gui/src/composables/event.ts | 51 ++ easytier-gui/src/composables/mobile_vpn.ts | 54 +- easytier-gui/src/composables/network.ts | 45 -- easytier-gui/src/main.ts | 22 +- easytier-gui/src/modules/api.ts | 44 ++ easytier-gui/src/pages/index.vue | 364 +++---------- easytier-gui/src/stores/network.ts | 148 ----- .../src/components/RemoteManagement.vue | 161 +++--- easytier-web/frontend-lib/src/locales/cn.yaml | 4 +- easytier-web/frontend-lib/src/locales/en.yaml | 4 +- easytier-web/frontend-lib/src/modules/api.ts | 17 +- easytier-web/frontend/src/modules/api.ts | 24 +- .../db/entity/user_running_network_configs.rs | 8 +- easytier-web/src/db/mod.rs | 40 +- easytier-web/src/restful/network.rs | 33 +- easytier/src/common/ifcfg/netlink.rs | 18 +- easytier/src/instance_manager.rs | 25 + easytier/src/peers/peer_rpc_service.rs | 2 +- easytier/src/rpc_service/api.rs | 44 +- easytier/src/rpc_service/mod.rs | 2 +- easytier/src/rpc_service/remote_client.rs | 82 +-- 27 files changed, 1040 insertions(+), 793 deletions(-) create mode 100644 easytier-gui/src/composables/backend.ts create mode 100644 easytier-gui/src/composables/event.ts delete mode 100644 easytier-gui/src/composables/network.ts create mode 100644 easytier-gui/src/modules/api.ts delete mode 100644 easytier-gui/src/stores/network.ts diff --git a/Cargo.lock b/Cargo.lock index 06603d7..1174a14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -444,9 +444,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -2267,6 +2267,7 @@ name = "easytier-gui" version = "2.4.5" dependencies = [ "anyhow", + "async-trait", "chrono", "dashmap", "dunce", diff --git a/easytier-gui/src-tauri/Cargo.toml b/easytier-gui/src-tauri/Cargo.toml index f98a52c..65d458c 100644 --- a/easytier-gui/src-tauri/Cargo.toml +++ b/easytier-gui/src-tauri/Cargo.toml @@ -52,6 +52,7 @@ tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" } tauri-plugin-os = "2.3.0" tauri-plugin-autostart = "2.5.0" uuid = "1.17.0" +async-trait = "0.1.89" [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.52", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } diff --git a/easytier-gui/src-tauri/src/lib.rs b/easytier-gui/src-tauri/src/lib.rs index 1c7eb46..0fa1a01 100644 --- a/easytier-gui/src-tauri/src/lib.rs +++ b/easytier-gui/src-tauri/src/lib.rs @@ -3,28 +3,44 @@ mod elevate; -use std::collections::BTreeMap; - +use easytier::proto::api::manage::{ + CollectNetworkInfoResponse, ValidateConfigResponse, WebClientService, + WebClientServiceClientFactory, +}; +use easytier::rpc_service::remote_client::{ + ListNetworkInstanceIdsJsonResp, ListNetworkProps, RemoteClientManager, Storage, +}; use easytier::{ common::config::{ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, TomlConfigLoader}, instance_manager::NetworkInstanceManager, - launcher::{ConfigSource, NetworkConfig, NetworkInstanceRunningInfo}, + launcher::NetworkConfig, + rpc_service::ApiRpcServer, + tunnel::ring::RingTunnelListener, utils::{self, NewFilterSender}, }; +use std::ops::Deref; +use std::sync::Arc; +use uuid::Uuid; -use tauri::Manager as _; - -pub const AUTOSTART_ARG: &str = "--autostart"; +use tauri::{AppHandle, Emitter, Manager as _}; #[cfg(not(target_os = "android"))] use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; -static INSTANCE_MANAGER: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(NetworkInstanceManager::new); +pub const AUTOSTART_ARG: &str = "--autostart"; + +static INSTANCE_MANAGER: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| Arc::new(NetworkInstanceManager::new())); static mut LOGGER_LEVEL_SENDER: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(Default::default); +static RPC_RING_UUID: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(uuid::Uuid::new_v4); + +static CLIENT_MANAGER: once_cell::sync::OnceCell = + once_cell::sync::OnceCell::new(); + #[tauri::command] fn easytier_version() -> Result { Ok(easytier::VERSION.to_string()) @@ -47,14 +63,6 @@ fn set_dock_visibility(app: tauri::AppHandle, visible: bool) -> Result<(), Strin Ok(()) } -#[tauri::command] -fn is_autostart() -> Result { - let args: Vec = std::env::args().collect(); - println!("{:?}", args); - Ok(args.contains(&AUTOSTART_ARG.to_owned())) -} - -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] fn parse_network_config(cfg: NetworkConfig) -> Result { let toml = cfg.gen_config().map_err(|e| e.to_string())?; @@ -69,47 +77,48 @@ fn generate_network_config(toml_config: String) -> Result } #[tauri::command] -fn run_network_instance(cfg: NetworkConfig) -> Result<(), String> { +async fn run_network_instance(app: AppHandle, cfg: NetworkConfig) -> Result<(), String> { let instance_id = cfg.instance_id().to_string(); - let cfg = cfg.gen_config().map_err(|e| e.to_string())?; - INSTANCE_MANAGER - .run_network_instance(cfg, ConfigSource::GUI) - .map_err(|e| e.to_string())?; - println!("instance {} started", instance_id); - Ok(()) -} -#[tauri::command] -fn retain_network_instance(instance_ids: Vec) -> Result<(), String> { - let instance_ids = instance_ids - .into_iter() - .filter_map(|id| uuid::Uuid::parse_str(&id).ok()) - .collect(); - let retained = INSTANCE_MANAGER - .retain_network_instance(instance_ids) + app.emit("pre_run_network_instance", cfg.instance_id()) .map_err(|e| e.to_string())?; - println!("instance {:?} retained", retained); - Ok(()) -} -#[tauri::command] -async fn collect_network_infos() -> Result, String> { - let infos = INSTANCE_MANAGER - .collect_network_infos() + #[cfg(target_os = "android")] + if cfg.no_tun() == false { + CLIENT_MANAGER + .get() + .unwrap() + .disable_instances_with_tun(&app) + .await + .map_err(|e| e.to_string())?; + } + + CLIENT_MANAGER + .get() + .unwrap() + .handle_run_network_instance(app.clone(), cfg) .await .map_err(|e| e.to_string())?; - let mut ret = BTreeMap::new(); - for (uuid, info) in infos { - ret.insert(uuid.to_string(), info); - } - - Ok(ret) + app.emit("post_run_network_instance", instance_id) + .map_err(|e| e.to_string())?; + Ok(()) } #[tauri::command] -fn get_os_hostname() -> Result { - Ok(gethostname::gethostname().to_string_lossy().to_string()) +async fn collect_network_info( + app: AppHandle, + instance_id: String, +) -> Result { + let instance_id = instance_id + .parse() + .map_err(|e: uuid::Error| e.to_string())?; + CLIENT_MANAGER + .get() + .unwrap() + .handle_collect_network_info(app, Some(vec![instance_id])) + .await + .map_err(|e| e.to_string()) } #[tauri::command] @@ -121,10 +130,121 @@ fn set_logging_level(level: String) -> Result<(), String> { } #[tauri::command] -fn set_tun_fd(instance_id: String, fd: i32) -> Result<(), String> { - let uuid = uuid::Uuid::parse_str(&instance_id).map_err(|e| e.to_string())?; - INSTANCE_MANAGER - .set_tun_fd(&uuid, fd) +fn set_tun_fd(fd: i32) -> Result<(), String> { + if let Some(uuid) = CLIENT_MANAGER + .get() + .unwrap() + .get_enabled_instances_with_tun_ids() + .next() + { + INSTANCE_MANAGER + .set_tun_fd(&uuid, fd) + .map_err(|e| e.to_string())?; + } + Ok(()) +} + +#[tauri::command] +async fn list_network_instance_ids( + app: AppHandle, +) -> Result { + CLIENT_MANAGER + .get() + .unwrap() + .handle_list_network_instance_ids(app) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command] +async fn remove_network_instance(app: AppHandle, instance_id: String) -> Result<(), String> { + let instance_id = instance_id + .parse() + .map_err(|e: uuid::Error| e.to_string())?; + CLIENT_MANAGER + .get() + .unwrap() + .handle_remove_network_instances(app.clone(), vec![instance_id]) + .await + .map_err(|e| e.to_string())?; + CLIENT_MANAGER + .get() + .unwrap() + .notify_vpn_stop_if_no_tun(&app)?; + Ok(()) +} + +#[tauri::command] +async fn update_network_config_state( + app: AppHandle, + instance_id: String, + disabled: bool, +) -> Result<(), String> { + let instance_id = instance_id + .parse() + .map_err(|e: uuid::Error| e.to_string())?; + CLIENT_MANAGER + .get() + .unwrap() + .handle_update_network_state(app.clone(), instance_id, disabled) + .await + .map_err(|e| e.to_string())?; + if disabled { + CLIENT_MANAGER + .get() + .unwrap() + .notify_vpn_stop_if_no_tun(&app)?; + } + Ok(()) +} + +#[tauri::command] +async fn save_network_config(app: AppHandle, cfg: NetworkConfig) -> Result<(), String> { + let instance_id = cfg + .instance_id() + .parse() + .map_err(|e: uuid::Error| e.to_string())?; + CLIENT_MANAGER + .get() + .unwrap() + .handle_save_network_config(app, instance_id, cfg) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command] +async fn validate_config( + app: AppHandle, + config: NetworkConfig, +) -> Result { + CLIENT_MANAGER + .get() + .unwrap() + .handle_validate_config(app, config) + .await + .map_err(|e| e.to_string()) +} + +#[tauri::command] +async fn get_config(app: AppHandle, instance_id: String) -> Result { + let cfg = CLIENT_MANAGER + .get() + .unwrap() + .storage + .get_network_config(app, &instance_id) + .await + .map_err(|e| e.to_string())? + .ok_or_else(|| format!("Config not found for instance ID: {}", instance_id))?; + Ok(cfg.1) +} + +#[tauri::command] +fn load_configs(configs: Vec, enabled_networks: Vec) -> Result<(), String> { + CLIENT_MANAGER + .get() + .unwrap() + .storage + .load_configs(configs, enabled_networks) .map_err(|e| e.to_string())?; Ok(()) } @@ -166,6 +286,266 @@ fn check_sudo() -> bool { is_elevated } +mod manager { + use super::*; + use async_trait::async_trait; + use dashmap::{DashMap, DashSet}; + use easytier::launcher::{ConfigSource, NetworkConfig}; + use easytier::proto::rpc_impl::bidirect::BidirectRpcManager; + use easytier::proto::rpc_types::controller::BaseController; + use easytier::rpc_service::remote_client::PersistentConfig; + use easytier::tunnel::ring::RingTunnelConnector; + use easytier::tunnel::TunnelConnector; + + #[derive(Clone)] + pub(super) struct GUIConfig(String, pub(crate) NetworkConfig); + impl PersistentConfig for GUIConfig { + fn get_network_inst_id(&self) -> &str { + &self.0 + } + fn get_network_config(&self) -> Result { + Ok(self.1.clone()) + } + } + + pub(super) struct GUIStorage { + network_configs: DashMap, + enabled_networks: DashSet, + } + impl GUIStorage { + fn new() -> Self { + Self { + network_configs: DashMap::new(), + enabled_networks: DashSet::new(), + } + } + + pub(super) fn load_configs( + &self, + configs: Vec, + enabled_networks: Vec, + ) -> anyhow::Result<()> { + self.network_configs.clear(); + for cfg in configs { + let instance_id = cfg.instance_id(); + self.network_configs.insert( + instance_id.parse()?, + GUIConfig(instance_id.to_string(), cfg), + ); + } + + self.enabled_networks.clear(); + INSTANCE_MANAGER + .filter_network_instance(|_, _| true) + .into_iter() + .for_each(|id| { + self.enabled_networks.insert(id); + }); + for id in enabled_networks { + if let Ok(uuid) = id.parse() { + if !self.enabled_networks.contains(&uuid) { + let config = self + .network_configs + .get(&uuid) + .map(|i| i.value().1.gen_config()) + .ok_or_else(|| anyhow::anyhow!("Config not found"))??; + INSTANCE_MANAGER.run_network_instance(config, ConfigSource::GUI)?; + self.enabled_networks.insert(uuid); + } + } + } + Ok(()) + } + + fn save_configs(&self, app: &AppHandle) -> anyhow::Result<()> { + let configs: Result, _> = self + .network_configs + .iter() + .map(|entry| serde_json::to_string(&entry.value().1)) + .collect(); + let payload = format!("[{}]", configs?.join(",")); + app.emit_str("save_configs", payload)?; + Ok(()) + } + + fn save_enabled_networks(&self, app: &AppHandle) -> anyhow::Result<()> { + let payload: Vec = self + .enabled_networks + .iter() + .map(|entry| entry.key().to_string()) + .collect(); + app.emit("save_enabled_networks", payload)?; + Ok(()) + } + + fn save_config( + &self, + app: &AppHandle, + inst_id: Uuid, + cfg: NetworkConfig, + ) -> anyhow::Result<()> { + let config = GUIConfig(inst_id.to_string(), cfg); + self.network_configs.insert(inst_id, config); + self.save_configs(app) + } + } + #[async_trait] + impl Storage for GUIStorage { + async fn insert_or_update_user_network_config( + &self, + app: AppHandle, + network_inst_id: Uuid, + network_config: NetworkConfig, + ) -> Result<(), anyhow::Error> { + self.save_config(&app, network_inst_id, network_config)?; + self.enabled_networks.insert(network_inst_id); + self.save_enabled_networks(&app)?; + Ok(()) + } + + async fn delete_network_configs( + &self, + app: AppHandle, + network_inst_ids: &[Uuid], + ) -> Result<(), anyhow::Error> { + for network_inst_id in network_inst_ids { + self.network_configs.remove(network_inst_id); + self.enabled_networks.remove(network_inst_id); + } + self.save_configs(&app) + } + + async fn update_network_config_state( + &self, + app: AppHandle, + network_inst_id: Uuid, + disabled: bool, + ) -> Result { + if disabled { + self.enabled_networks.remove(&network_inst_id); + } else { + self.enabled_networks.insert(network_inst_id); + } + self.save_enabled_networks(&app)?; + let cfg = self + .network_configs + .get(&network_inst_id) + .ok_or_else(|| anyhow::anyhow!("Config not found"))?; + Ok(cfg.value().clone()) + } + + async fn list_network_configs( + &self, + _: AppHandle, + props: ListNetworkProps, + ) -> Result, anyhow::Error> { + let mut ret = Vec::new(); + for entry in self.network_configs.iter() { + let id: Uuid = entry.key().to_owned(); + match props { + ListNetworkProps::All => { + ret.push(entry.value().clone()); + } + ListNetworkProps::EnabledOnly => { + if self.enabled_networks.contains(&id) { + ret.push(entry.value().clone()); + } + } + ListNetworkProps::DisabledOnly => { + if !self.enabled_networks.contains(&id) { + ret.push(entry.value().clone()); + } + } + } + } + Ok(ret) + } + + async fn get_network_config( + &self, + _: AppHandle, + network_inst_id: &str, + ) -> Result, anyhow::Error> { + let uuid = Uuid::parse_str(network_inst_id)?; + Ok(self + .network_configs + .get(&uuid) + .map(|entry| entry.value().clone())) + } + } + + pub(super) struct GUIClientManager { + pub(super) storage: GUIStorage, + rpc_manager: BidirectRpcManager, + } + impl GUIClientManager { + pub async fn new() -> Result { + let mut connector = RingTunnelConnector::new( + format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap(), + ); + let tunnel = connector.connect().await?; + let rpc_manager = BidirectRpcManager::new(); + rpc_manager.run_with_tunnel(tunnel); + + Ok(Self { + storage: GUIStorage::new(), + rpc_manager, + }) + } + + pub fn get_enabled_instances_with_tun_ids(&self) -> impl Iterator + '_ { + self.storage + .network_configs + .iter() + .filter(|v| self.storage.enabled_networks.contains(v.key())) + .filter(|v| !v.1.no_tun()) + .filter_map(|c| c.1.instance_id().parse::().ok()) + } + + #[cfg(target_os = "android")] + pub(super) async fn disable_instances_with_tun( + &self, + app: &AppHandle, + ) -> Result<(), easytier::rpc_service::remote_client::RemoteClientError> + { + for inst_id in self.get_enabled_instances_with_tun_ids() { + self.handle_update_network_state(app.clone(), inst_id, true) + .await?; + } + Ok(()) + } + + pub(super) fn notify_vpn_stop_if_no_tun(&self, app: &AppHandle) -> Result<(), String> { + let has_tun = self.get_enabled_instances_with_tun_ids().any(|_| true); + if !has_tun { + app.emit("vpn_service_stop", "") + .map_err(|e| e.to_string())?; + } + Ok(()) + } + } + impl RemoteClientManager for GUIClientManager { + fn get_rpc_client( + &self, + _: AppHandle, + ) -> Option + Send>> { + Some( + self.rpc_manager + .rpc_client() + .scoped_client::>( + 1, + 1, + "".to_string(), + ), + ) + } + + fn get_storage(&self) -> &impl Storage { + &self.storage + } + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { #[cfg(not(target_os = "android"))] @@ -176,6 +556,24 @@ pub fn run() { utils::setup_panic_handler(); + let _rpc_server_handle = tauri::async_runtime::spawn(async move { + let rpc_server = ApiRpcServer::from_tunnel( + RingTunnelListener::new(format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap()), + INSTANCE_MANAGER.clone(), + ) + .serve() + .await + .expect("Failed to start RPC server"); + + let _ = CLIENT_MANAGER.set( + manager::GUIClientManager::new() + .await + .expect("Failed to create GUI client manager"), + ); + + rpc_server + }); + let mut builder = tauri::Builder::default(); #[cfg(not(target_os = "android"))] @@ -257,14 +655,18 @@ pub fn run() { parse_network_config, generate_network_config, run_network_instance, - retain_network_instance, - collect_network_infos, - get_os_hostname, + collect_network_info, set_logging_level, set_tun_fd, - is_autostart, easytier_version, - set_dock_visibility + set_dock_visibility, + list_network_instance_ids, + remove_network_instance, + update_network_config_state, + save_network_config, + validate_config, + get_config, + load_configs, ]) .on_window_event(|_win, event| match event { #[cfg(not(target_os = "android"))] diff --git a/easytier-gui/src/auto-imports.d.ts b/easytier-gui/src/auto-imports.d.ts index d96e2af..65e9849 100644 --- a/easytier-gui/src/auto-imports.d.ts +++ b/easytier-gui/src/auto-imports.d.ts @@ -10,7 +10,7 @@ declare global { const MenuItemExit: typeof import('./composables/tray')['MenuItemExit'] const MenuItemShow: typeof import('./composables/tray')['MenuItemShow'] const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] - const collectNetworkInfos: typeof import('./composables/network')['collectNetworkInfos'] + const collectNetworkInfo: typeof import('./composables/backend')['collectNetworkInfo'] const computed: typeof import('vue')['computed'] const createApp: typeof import('vue')['createApp'] const createPinia: typeof import('pinia')['createPinia'] @@ -18,22 +18,24 @@ declare global { const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] const defineStore: typeof import('pinia')['defineStore'] + const deleteNetworkInstance: typeof import('./composables/backend')['deleteNetworkInstance'] const effectScope: typeof import('vue')['effectScope'] const generateMenuItem: typeof import('./composables/tray')['generateMenuItem'] - const generateNetworkConfig: typeof import('./composables/network')['generateNetworkConfig'] + const generateNetworkConfig: typeof import('./composables/backend')['generateNetworkConfig'] const getActivePinia: typeof import('pinia')['getActivePinia'] + const getConfig: typeof import('./composables/backend')['getConfig'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] - const getEasytierVersion: typeof import('./composables/network')['getEasytierVersion'] - const getOsHostname: typeof import('./composables/network')['getOsHostname'] + const getEasytierVersion: typeof import('./composables/backend')['getEasytierVersion'] const h: typeof import('vue')['h'] const initMobileVpnService: typeof import('./composables/mobile_vpn')['initMobileVpnService'] const inject: typeof import('vue')['inject'] - const isAutostart: typeof import('./composables/network')['isAutostart'] const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import('vue')['isReactive'] const isReadonly: typeof import('vue')['isReadonly'] const isRef: typeof import('vue')['isRef'] + const listNetworkInstanceIds: typeof import('./composables/backend')['listNetworkInstanceIds'] + const listenGlobalEvents: typeof import('./composables/event')['listenGlobalEvents'] const mapActions: typeof import('pinia')['mapActions'] const mapGetters: typeof import('pinia')['mapGetters'] const mapState: typeof import('pinia')['mapState'] @@ -50,6 +52,7 @@ declare global { const onDeactivated: typeof import('vue')['onDeactivated'] const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onMounted: typeof import('vue')['onMounted'] + const onNetworkInstanceChange: typeof import('./composables/mobile_vpn')['onNetworkInstanceChange'] const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onScopeDispose: typeof import('vue')['onScopeDispose'] @@ -57,22 +60,23 @@ declare global { const onUnmounted: typeof import('vue')['onUnmounted'] const onUpdated: typeof import('vue')['onUpdated'] const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] - const parseNetworkConfig: typeof import('./composables/network')['parseNetworkConfig'] + const parseNetworkConfig: typeof import('./composables/backend')['parseNetworkConfig'] const prepareVpnService: typeof import('./composables/mobile_vpn')['prepareVpnService'] const provide: typeof import('vue')['provide'] const reactive: typeof import('vue')['reactive'] const readonly: typeof import('vue')['readonly'] const ref: typeof import('vue')['ref'] const resolveComponent: typeof import('vue')['resolveComponent'] - const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance'] - const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance'] + const runNetworkInstance: typeof import('./composables/backend')['runNetworkInstance'] + const saveNetworkConfig: typeof import('./composables/backend')['saveNetworkConfig'] + const sendConfigs: typeof import('./composables/backend')['sendConfigs'] const setActivePinia: typeof import('pinia')['setActivePinia'] - const setLoggingLevel: typeof import('./composables/network')['setLoggingLevel'] + const setLoggingLevel: typeof import('./composables/backend')['setLoggingLevel'] const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] const setTrayMenu: typeof import('./composables/tray')['setTrayMenu'] const setTrayRunState: typeof import('./composables/tray')['setTrayRunState'] const setTrayTooltip: typeof import('./composables/tray')['setTrayTooltip'] - const setTunFd: typeof import('./composables/network')['setTunFd'] + const setTunFd: typeof import('./composables/backend')['setTunFd'] const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import('vue')['shallowRef'] @@ -83,6 +87,7 @@ declare global { const toValue: typeof import('vue')['toValue'] const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import('vue')['unref'] + const updateNetworkConfigState: typeof import('./composables/backend')['updateNetworkConfigState'] const useAttrs: typeof import('vue')['useAttrs'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVars: typeof import('vue')['useCssVars'] @@ -90,12 +95,12 @@ declare global { const useId: typeof import('vue')['useId'] const useLink: typeof import('vue-router/auto')['useLink'] const useModel: typeof import('vue')['useModel'] - const useNetworkStore: typeof import('./stores/network')['useNetworkStore'] const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] const useSlots: typeof import('vue')['useSlots'] const useTemplateRef: typeof import('vue')['useTemplateRef'] const useTray: typeof import('./composables/tray')['useTray'] + const validateConfig: typeof import('./composables/backend')['validateConfig'] const watch: typeof import('vue')['watch'] const watchEffect: typeof import('vue')['watchEffect'] const watchPostEffect: typeof import('vue')['watchPostEffect'] @@ -117,7 +122,7 @@ declare module 'vue' { readonly MenuItemExit: UnwrapRef readonly MenuItemShow: UnwrapRef readonly acceptHMRUpdate: UnwrapRef - readonly collectNetworkInfos: UnwrapRef + readonly collectNetworkInfo: UnwrapRef readonly computed: UnwrapRef readonly createApp: UnwrapRef readonly createPinia: UnwrapRef @@ -125,22 +130,24 @@ declare module 'vue' { readonly defineAsyncComponent: UnwrapRef readonly defineComponent: UnwrapRef readonly defineStore: UnwrapRef + readonly deleteNetworkInstance: UnwrapRef readonly effectScope: UnwrapRef readonly generateMenuItem: UnwrapRef - readonly generateNetworkConfig: UnwrapRef + readonly generateNetworkConfig: UnwrapRef readonly getActivePinia: UnwrapRef + readonly getConfig: UnwrapRef readonly getCurrentInstance: UnwrapRef readonly getCurrentScope: UnwrapRef - readonly getEasytierVersion: UnwrapRef - readonly getOsHostname: UnwrapRef + readonly getEasytierVersion: UnwrapRef readonly h: UnwrapRef readonly initMobileVpnService: UnwrapRef readonly inject: UnwrapRef - readonly isAutostart: UnwrapRef readonly isProxy: UnwrapRef readonly isReactive: UnwrapRef readonly isReadonly: UnwrapRef readonly isRef: UnwrapRef + readonly listNetworkInstanceIds: UnwrapRef + readonly listenGlobalEvents: UnwrapRef readonly mapActions: UnwrapRef readonly mapGetters: UnwrapRef readonly mapState: UnwrapRef @@ -157,6 +164,7 @@ declare module 'vue' { readonly onDeactivated: UnwrapRef readonly onErrorCaptured: UnwrapRef readonly onMounted: UnwrapRef + readonly onNetworkInstanceChange: UnwrapRef readonly onRenderTracked: UnwrapRef readonly onRenderTriggered: UnwrapRef readonly onScopeDispose: UnwrapRef @@ -164,22 +172,23 @@ declare module 'vue' { readonly onUnmounted: UnwrapRef readonly onUpdated: UnwrapRef readonly onWatcherCleanup: UnwrapRef - readonly parseNetworkConfig: UnwrapRef + readonly parseNetworkConfig: UnwrapRef readonly prepareVpnService: UnwrapRef readonly provide: UnwrapRef readonly reactive: UnwrapRef readonly readonly: UnwrapRef readonly ref: UnwrapRef readonly resolveComponent: UnwrapRef - readonly retainNetworkInstance: UnwrapRef - readonly runNetworkInstance: UnwrapRef + readonly runNetworkInstance: UnwrapRef + readonly saveNetworkConfig: UnwrapRef + readonly sendConfigs: UnwrapRef readonly setActivePinia: UnwrapRef - readonly setLoggingLevel: UnwrapRef + readonly setLoggingLevel: UnwrapRef readonly setMapStoreSuffix: UnwrapRef readonly setTrayMenu: UnwrapRef readonly setTrayRunState: UnwrapRef readonly setTrayTooltip: UnwrapRef - readonly setTunFd: UnwrapRef + readonly setTunFd: UnwrapRef readonly shallowReactive: UnwrapRef readonly shallowReadonly: UnwrapRef readonly shallowRef: UnwrapRef @@ -190,6 +199,7 @@ declare module 'vue' { readonly toValue: UnwrapRef readonly triggerRef: UnwrapRef readonly unref: UnwrapRef + readonly updateNetworkConfigState: UnwrapRef readonly useAttrs: UnwrapRef readonly useCssModule: UnwrapRef readonly useCssVars: UnwrapRef @@ -197,12 +207,12 @@ declare module 'vue' { readonly useId: UnwrapRef readonly useLink: UnwrapRef readonly useModel: UnwrapRef - readonly useNetworkStore: UnwrapRef readonly useRoute: UnwrapRef readonly useRouter: UnwrapRef readonly useSlots: UnwrapRef readonly useTemplateRef: UnwrapRef readonly useTray: UnwrapRef + readonly validateConfig: UnwrapRef readonly watch: UnwrapRef readonly watchEffect: UnwrapRef readonly watchPostEffect: UnwrapRef diff --git a/easytier-gui/src/components/About.vue b/easytier-gui/src/components/About.vue index 168fa3e..9cb3de2 100644 --- a/easytier-gui/src/components/About.vue +++ b/easytier-gui/src/components/About.vue @@ -1,5 +1,5 @@ - - diff --git a/easytier-gui/src/stores/network.ts b/easytier-gui/src/stores/network.ts deleted file mode 100644 index 6f1b7d6..0000000 --- a/easytier-gui/src/stores/network.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { NetworkTypes } from 'easytier-frontend-lib' - -export const useNetworkStore = defineStore('networkStore', { - state: () => { - const networkList = [NetworkTypes.DEFAULT_NETWORK_CONFIG()] - return { - // for initially empty lists - networkList: networkList as NetworkTypes.NetworkConfig[], - // for data that is not yet loaded - curNetwork: networkList[0], - - // uuid -> instance - instances: {} as Record, - - networkInfos: {} as Record, - - autoStartInstIds: [] as string[], - } - }, - - getters: { - lastNetwork(): NetworkTypes.NetworkConfig { - return this.networkList[this.networkList.length - 1] - }, - - curNetworkId(): string { - return this.curNetwork.instance_id - }, - - networkInstances(): Array { - return Object.values(this.instances) - }, - - networkInstanceIds(): Array { - return Object.keys(this.instances) - }, - }, - - actions: { - addNewNetwork() { - this.networkList.push(NetworkTypes.DEFAULT_NETWORK_CONFIG()) - }, - - delCurNetwork() { - const curNetworkIdx = this.networkList.indexOf(this.curNetwork) - this.networkList.splice(curNetworkIdx, 1) - const nextCurNetworkIdx = Math.min(curNetworkIdx, this.networkList.length - 1) - this.curNetwork = this.networkList[nextCurNetworkIdx] - }, - - replaceCurNetwork(cfg: NetworkTypes.NetworkConfig) { - const curNetworkIdx = this.networkList.indexOf(this.curNetwork) - this.networkList[curNetworkIdx] = cfg - this.curNetwork = cfg - }, - - removeNetworkInstance(instanceId: string) { - delete this.instances[instanceId] - }, - - addNetworkInstance(instanceId: string) { - this.instances[instanceId] = { - instance_id: instanceId, - running: false, - error_msg: '', - detail: undefined, - } - }, - - clearNetworkInstances() { - this.instances = {} - }, - - updateWithNetworkInfos(networkInfos: Record) { - this.networkInfos = networkInfos - for (const [instanceId, info] of Object.entries(networkInfos)) { - if (this.instances[instanceId] === undefined) - this.addNetworkInstance(instanceId) - - this.instances[instanceId].running = info.running - this.instances[instanceId].error_msg = info.error_msg || '' - this.instances[instanceId].detail = info - } - }, - - loadFromLocalStorage() { - let networkList: NetworkTypes.NetworkConfig[] - - // if localStorage default is [{}], instanceId will be undefined - networkList = JSON.parse(localStorage.getItem('networkList') || '[]') - networkList = networkList.map((cfg) => { - return { ...NetworkTypes.DEFAULT_NETWORK_CONFIG(), ...cfg } as NetworkTypes.NetworkConfig - }) - - // prevent a empty list from localStorage, should not happen - if (networkList.length === 0) - networkList = [NetworkTypes.DEFAULT_NETWORK_CONFIG()] - - this.networkList = networkList - this.curNetwork = this.networkList[0] - - this.loadAutoStartInstIdsFromLocalStorage() - }, - - saveToLocalStorage() { - localStorage.setItem('networkList', JSON.stringify(this.networkList)) - }, - - saveAutoStartInstIdsToLocalStorage() { - localStorage.setItem('autoStartInstIds', JSON.stringify(this.autoStartInstIds)) - }, - - loadAutoStartInstIdsFromLocalStorage() { - try { - this.autoStartInstIds = JSON.parse(localStorage.getItem('autoStartInstIds') || '[]') - } - catch (e) { - console.error(e) - this.autoStartInstIds = [] - } - }, - - addAutoStartInstId(instanceId: string) { - if (!this.autoStartInstIds.includes(instanceId)) { - this.autoStartInstIds.push(instanceId) - } - this.saveAutoStartInstIdsToLocalStorage() - }, - - removeAutoStartInstId(instanceId: string) { - const idx = this.autoStartInstIds.indexOf(instanceId) - if (idx !== -1) { - this.autoStartInstIds.splice(idx, 1) - } - this.saveAutoStartInstIdsToLocalStorage() - }, - - isNoTunEnabled(instanceId: string): boolean { - const cfg = this.networkList.find((cfg) => cfg.instance_id === instanceId) - if (!cfg) - return false - return cfg.no_tun ?? false - }, - }, -}) - -if (import.meta.hot) - import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot)) diff --git a/easytier-web/frontend-lib/src/components/RemoteManagement.vue b/easytier-web/frontend-lib/src/components/RemoteManagement.vue index 3a71d27..433ba40 100644 --- a/easytier-web/frontend-lib/src/components/RemoteManagement.vue +++ b/easytier-web/frontend-lib/src/components/RemoteManagement.vue @@ -1,16 +1,16 @@