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
This commit is contained in:
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -444,9 +444,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.81"
|
version = "0.1.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2267,6 +2267,7 @@ name = "easytier-gui"
|
|||||||
version = "2.4.5"
|
version = "2.4.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dunce",
|
"dunce",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" }
|
|||||||
tauri-plugin-os = "2.3.0"
|
tauri-plugin-os = "2.3.0"
|
||||||
tauri-plugin-autostart = "2.5.0"
|
tauri-plugin-autostart = "2.5.0"
|
||||||
uuid = "1.17.0"
|
uuid = "1.17.0"
|
||||||
|
async-trait = "0.1.89"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows = { version = "0.52", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
windows = { version = "0.52", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||||
|
|||||||
@@ -3,28 +3,44 @@
|
|||||||
|
|
||||||
mod elevate;
|
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::{
|
use easytier::{
|
||||||
common::config::{ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, TomlConfigLoader},
|
common::config::{ConfigLoader, FileLoggerConfig, LoggingConfigBuilder, TomlConfigLoader},
|
||||||
instance_manager::NetworkInstanceManager,
|
instance_manager::NetworkInstanceManager,
|
||||||
launcher::{ConfigSource, NetworkConfig, NetworkInstanceRunningInfo},
|
launcher::NetworkConfig,
|
||||||
|
rpc_service::ApiRpcServer,
|
||||||
|
tunnel::ring::RingTunnelListener,
|
||||||
utils::{self, NewFilterSender},
|
utils::{self, NewFilterSender},
|
||||||
};
|
};
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use tauri::Manager as _;
|
use tauri::{AppHandle, Emitter, Manager as _};
|
||||||
|
|
||||||
pub const AUTOSTART_ARG: &str = "--autostart";
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||||
|
|
||||||
static INSTANCE_MANAGER: once_cell::sync::Lazy<NetworkInstanceManager> =
|
pub const AUTOSTART_ARG: &str = "--autostart";
|
||||||
once_cell::sync::Lazy::new(NetworkInstanceManager::new);
|
|
||||||
|
static INSTANCE_MANAGER: once_cell::sync::Lazy<Arc<NetworkInstanceManager>> =
|
||||||
|
once_cell::sync::Lazy::new(|| Arc::new(NetworkInstanceManager::new()));
|
||||||
|
|
||||||
static mut LOGGER_LEVEL_SENDER: once_cell::sync::Lazy<Option<NewFilterSender>> =
|
static mut LOGGER_LEVEL_SENDER: once_cell::sync::Lazy<Option<NewFilterSender>> =
|
||||||
once_cell::sync::Lazy::new(Default::default);
|
once_cell::sync::Lazy::new(Default::default);
|
||||||
|
|
||||||
|
static RPC_RING_UUID: once_cell::sync::Lazy<uuid::Uuid> =
|
||||||
|
once_cell::sync::Lazy::new(uuid::Uuid::new_v4);
|
||||||
|
|
||||||
|
static CLIENT_MANAGER: once_cell::sync::OnceCell<manager::GUIClientManager> =
|
||||||
|
once_cell::sync::OnceCell::new();
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn easytier_version() -> Result<String, String> {
|
fn easytier_version() -> Result<String, String> {
|
||||||
Ok(easytier::VERSION.to_string())
|
Ok(easytier::VERSION.to_string())
|
||||||
@@ -47,14 +63,6 @@ fn set_dock_visibility(app: tauri::AppHandle, visible: bool) -> Result<(), Strin
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn is_autostart() -> Result<bool, String> {
|
|
||||||
let args: Vec<String> = 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]
|
#[tauri::command]
|
||||||
fn parse_network_config(cfg: NetworkConfig) -> Result<String, String> {
|
fn parse_network_config(cfg: NetworkConfig) -> Result<String, String> {
|
||||||
let toml = cfg.gen_config().map_err(|e| e.to_string())?;
|
let toml = cfg.gen_config().map_err(|e| e.to_string())?;
|
||||||
@@ -69,47 +77,48 @@ fn generate_network_config(toml_config: String) -> Result<NetworkConfig, String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[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 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]
|
app.emit("pre_run_network_instance", cfg.instance_id())
|
||||||
fn retain_network_instance(instance_ids: Vec<String>) -> 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)
|
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
println!("instance {:?} retained", retained);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[cfg(target_os = "android")]
|
||||||
async fn collect_network_infos() -> Result<BTreeMap<String, NetworkInstanceRunningInfo>, String> {
|
if cfg.no_tun() == false {
|
||||||
let infos = INSTANCE_MANAGER
|
CLIENT_MANAGER
|
||||||
.collect_network_infos()
|
.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
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut ret = BTreeMap::new();
|
app.emit("post_run_network_instance", instance_id)
|
||||||
for (uuid, info) in infos {
|
.map_err(|e| e.to_string())?;
|
||||||
ret.insert(uuid.to_string(), info);
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_os_hostname() -> Result<String, String> {
|
async fn collect_network_info(
|
||||||
Ok(gethostname::gethostname().to_string_lossy().to_string())
|
app: AppHandle,
|
||||||
|
instance_id: String,
|
||||||
|
) -> Result<CollectNetworkInfoResponse, String> {
|
||||||
|
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]
|
#[tauri::command]
|
||||||
@@ -121,10 +130,121 @@ fn set_logging_level(level: String) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn set_tun_fd(instance_id: String, fd: i32) -> Result<(), String> {
|
fn set_tun_fd(fd: i32) -> Result<(), String> {
|
||||||
let uuid = uuid::Uuid::parse_str(&instance_id).map_err(|e| e.to_string())?;
|
if let Some(uuid) = CLIENT_MANAGER
|
||||||
INSTANCE_MANAGER
|
.get()
|
||||||
.set_tun_fd(&uuid, fd)
|
.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<ListNetworkInstanceIdsJsonResp, String> {
|
||||||
|
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<ValidateConfigResponse, String> {
|
||||||
|
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<NetworkConfig, String> {
|
||||||
|
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<NetworkConfig>, enabled_networks: Vec<String>) -> Result<(), String> {
|
||||||
|
CLIENT_MANAGER
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.storage
|
||||||
|
.load_configs(configs, enabled_networks)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -166,6 +286,266 @@ fn check_sudo() -> bool {
|
|||||||
is_elevated
|
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<anyhow::Error> for GUIConfig {
|
||||||
|
fn get_network_inst_id(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
fn get_network_config(&self) -> Result<NetworkConfig, anyhow::Error> {
|
||||||
|
Ok(self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct GUIStorage {
|
||||||
|
network_configs: DashMap<Uuid, GUIConfig>,
|
||||||
|
enabled_networks: DashSet<Uuid>,
|
||||||
|
}
|
||||||
|
impl GUIStorage {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
network_configs: DashMap::new(),
|
||||||
|
enabled_networks: DashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn load_configs(
|
||||||
|
&self,
|
||||||
|
configs: Vec<NetworkConfig>,
|
||||||
|
enabled_networks: Vec<String>,
|
||||||
|
) -> 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<Vec<String>, _> = 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<String> = 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<AppHandle, GUIConfig, anyhow::Error> 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<GUIConfig, anyhow::Error> {
|
||||||
|
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<Vec<GUIConfig>, 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<Option<GUIConfig>, 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<Self, anyhow::Error> {
|
||||||
|
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<Item = uuid::Uuid> + '_ {
|
||||||
|
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::<uuid::Uuid>().ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub(super) async fn disable_instances_with_tun(
|
||||||
|
&self,
|
||||||
|
app: &AppHandle,
|
||||||
|
) -> Result<(), easytier::rpc_service::remote_client::RemoteClientError<anyhow::Error>>
|
||||||
|
{
|
||||||
|
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<AppHandle, GUIConfig, anyhow::Error> for GUIClientManager {
|
||||||
|
fn get_rpc_client(
|
||||||
|
&self,
|
||||||
|
_: AppHandle,
|
||||||
|
) -> Option<Box<dyn WebClientService<Controller = BaseController> + Send>> {
|
||||||
|
Some(
|
||||||
|
self.rpc_manager
|
||||||
|
.rpc_client()
|
||||||
|
.scoped_client::<WebClientServiceClientFactory<BaseController>>(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
"".to_string(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage(&self) -> &impl Storage<AppHandle, GUIConfig, anyhow::Error> {
|
||||||
|
&self.storage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
@@ -176,6 +556,24 @@ pub fn run() {
|
|||||||
|
|
||||||
utils::setup_panic_handler();
|
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();
|
let mut builder = tauri::Builder::default();
|
||||||
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
@@ -257,14 +655,18 @@ pub fn run() {
|
|||||||
parse_network_config,
|
parse_network_config,
|
||||||
generate_network_config,
|
generate_network_config,
|
||||||
run_network_instance,
|
run_network_instance,
|
||||||
retain_network_instance,
|
collect_network_info,
|
||||||
collect_network_infos,
|
|
||||||
get_os_hostname,
|
|
||||||
set_logging_level,
|
set_logging_level,
|
||||||
set_tun_fd,
|
set_tun_fd,
|
||||||
is_autostart,
|
|
||||||
easytier_version,
|
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 {
|
.on_window_event(|_win, event| match event {
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
|
|||||||
54
easytier-gui/src/auto-imports.d.ts
vendored
54
easytier-gui/src/auto-imports.d.ts
vendored
@@ -10,7 +10,7 @@ declare global {
|
|||||||
const MenuItemExit: typeof import('./composables/tray')['MenuItemExit']
|
const MenuItemExit: typeof import('./composables/tray')['MenuItemExit']
|
||||||
const MenuItemShow: typeof import('./composables/tray')['MenuItemShow']
|
const MenuItemShow: typeof import('./composables/tray')['MenuItemShow']
|
||||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
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 computed: typeof import('vue')['computed']
|
||||||
const createApp: typeof import('vue')['createApp']
|
const createApp: typeof import('vue')['createApp']
|
||||||
const createPinia: typeof import('pinia')['createPinia']
|
const createPinia: typeof import('pinia')['createPinia']
|
||||||
@@ -18,22 +18,24 @@ declare global {
|
|||||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||||
const defineComponent: typeof import('vue')['defineComponent']
|
const defineComponent: typeof import('vue')['defineComponent']
|
||||||
const defineStore: typeof import('pinia')['defineStore']
|
const defineStore: typeof import('pinia')['defineStore']
|
||||||
|
const deleteNetworkInstance: typeof import('./composables/backend')['deleteNetworkInstance']
|
||||||
const effectScope: typeof import('vue')['effectScope']
|
const effectScope: typeof import('vue')['effectScope']
|
||||||
const generateMenuItem: typeof import('./composables/tray')['generateMenuItem']
|
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 getActivePinia: typeof import('pinia')['getActivePinia']
|
||||||
|
const getConfig: typeof import('./composables/backend')['getConfig']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
const getEasytierVersion: typeof import('./composables/network')['getEasytierVersion']
|
const getEasytierVersion: typeof import('./composables/backend')['getEasytierVersion']
|
||||||
const getOsHostname: typeof import('./composables/network')['getOsHostname']
|
|
||||||
const h: typeof import('vue')['h']
|
const h: typeof import('vue')['h']
|
||||||
const initMobileVpnService: typeof import('./composables/mobile_vpn')['initMobileVpnService']
|
const initMobileVpnService: typeof import('./composables/mobile_vpn')['initMobileVpnService']
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: typeof import('vue')['inject']
|
||||||
const isAutostart: typeof import('./composables/network')['isAutostart']
|
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
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 listNetworkInstanceIds: typeof import('./composables/backend')['listNetworkInstanceIds']
|
||||||
|
const listenGlobalEvents: typeof import('./composables/event')['listenGlobalEvents']
|
||||||
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']
|
||||||
@@ -50,6 +52,7 @@ declare global {
|
|||||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||||
const onMounted: typeof import('vue')['onMounted']
|
const onMounted: typeof import('vue')['onMounted']
|
||||||
|
const onNetworkInstanceChange: typeof import('./composables/mobile_vpn')['onNetworkInstanceChange']
|
||||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||||
@@ -57,22 +60,23 @@ declare global {
|
|||||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
|
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 prepareVpnService: typeof import('./composables/mobile_vpn')['prepareVpnService']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
const reactive: typeof import('vue')['reactive']
|
const reactive: typeof import('vue')['reactive']
|
||||||
const readonly: typeof import('vue')['readonly']
|
const readonly: typeof import('vue')['readonly']
|
||||||
const ref: typeof import('vue')['ref']
|
const ref: typeof import('vue')['ref']
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
const retainNetworkInstance: typeof import('./composables/network')['retainNetworkInstance']
|
const runNetworkInstance: typeof import('./composables/backend')['runNetworkInstance']
|
||||||
const runNetworkInstance: typeof import('./composables/network')['runNetworkInstance']
|
const saveNetworkConfig: typeof import('./composables/backend')['saveNetworkConfig']
|
||||||
|
const sendConfigs: typeof import('./composables/backend')['sendConfigs']
|
||||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
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 setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||||
const setTrayMenu: typeof import('./composables/tray')['setTrayMenu']
|
const setTrayMenu: typeof import('./composables/tray')['setTrayMenu']
|
||||||
const setTrayRunState: typeof import('./composables/tray')['setTrayRunState']
|
const setTrayRunState: typeof import('./composables/tray')['setTrayRunState']
|
||||||
const setTrayTooltip: typeof import('./composables/tray')['setTrayTooltip']
|
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 shallowReactive: typeof import('vue')['shallowReactive']
|
||||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||||
const shallowRef: typeof import('vue')['shallowRef']
|
const shallowRef: typeof import('vue')['shallowRef']
|
||||||
@@ -83,6 +87,7 @@ declare global {
|
|||||||
const toValue: typeof import('vue')['toValue']
|
const toValue: typeof import('vue')['toValue']
|
||||||
const triggerRef: typeof import('vue')['triggerRef']
|
const triggerRef: typeof import('vue')['triggerRef']
|
||||||
const unref: typeof import('vue')['unref']
|
const unref: typeof import('vue')['unref']
|
||||||
|
const updateNetworkConfigState: typeof import('./composables/backend')['updateNetworkConfigState']
|
||||||
const useAttrs: typeof import('vue')['useAttrs']
|
const useAttrs: typeof import('vue')['useAttrs']
|
||||||
const useCssModule: typeof import('vue')['useCssModule']
|
const useCssModule: typeof import('vue')['useCssModule']
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
@@ -90,12 +95,12 @@ declare global {
|
|||||||
const useId: typeof import('vue')['useId']
|
const useId: typeof import('vue')['useId']
|
||||||
const useLink: typeof import('vue-router/auto')['useLink']
|
const useLink: typeof import('vue-router/auto')['useLink']
|
||||||
const useModel: typeof import('vue')['useModel']
|
const useModel: typeof import('vue')['useModel']
|
||||||
const useNetworkStore: typeof import('./stores/network')['useNetworkStore']
|
|
||||||
const useRoute: typeof import('vue-router')['useRoute']
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
const useRouter: typeof import('vue-router')['useRouter']
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
const useTemplateRef: typeof import('vue')['useTemplateRef']
|
||||||
const useTray: typeof import('./composables/tray')['useTray']
|
const useTray: typeof import('./composables/tray')['useTray']
|
||||||
|
const validateConfig: typeof import('./composables/backend')['validateConfig']
|
||||||
const watch: typeof import('vue')['watch']
|
const watch: typeof import('vue')['watch']
|
||||||
const watchEffect: typeof import('vue')['watchEffect']
|
const watchEffect: typeof import('vue')['watchEffect']
|
||||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||||
@@ -117,7 +122,7 @@ declare module 'vue' {
|
|||||||
readonly MenuItemExit: UnwrapRef<typeof import('./composables/tray')['MenuItemExit']>
|
readonly MenuItemExit: UnwrapRef<typeof import('./composables/tray')['MenuItemExit']>
|
||||||
readonly MenuItemShow: UnwrapRef<typeof import('./composables/tray')['MenuItemShow']>
|
readonly MenuItemShow: UnwrapRef<typeof import('./composables/tray')['MenuItemShow']>
|
||||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||||
readonly collectNetworkInfos: UnwrapRef<typeof import('./composables/network')['collectNetworkInfos']>
|
readonly collectNetworkInfo: UnwrapRef<typeof import('./composables/backend')['collectNetworkInfo']>
|
||||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||||
@@ -125,22 +130,24 @@ declare module 'vue' {
|
|||||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||||
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||||
|
readonly deleteNetworkInstance: UnwrapRef<typeof import('./composables/backend')['deleteNetworkInstance']>
|
||||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||||
readonly generateMenuItem: UnwrapRef<typeof import('./composables/tray')['generateMenuItem']>
|
readonly generateMenuItem: UnwrapRef<typeof import('./composables/tray')['generateMenuItem']>
|
||||||
readonly generateNetworkConfig: UnwrapRef<typeof import('./composables/network')['generateNetworkConfig']>
|
readonly generateNetworkConfig: UnwrapRef<typeof import('./composables/backend')['generateNetworkConfig']>
|
||||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||||
|
readonly getConfig: UnwrapRef<typeof import('./composables/backend')['getConfig']>
|
||||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||||
readonly getEasytierVersion: UnwrapRef<typeof import('./composables/network')['getEasytierVersion']>
|
readonly getEasytierVersion: UnwrapRef<typeof import('./composables/backend')['getEasytierVersion']>
|
||||||
readonly getOsHostname: UnwrapRef<typeof import('./composables/network')['getOsHostname']>
|
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly initMobileVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['initMobileVpnService']>
|
readonly initMobileVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['initMobileVpnService']>
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
readonly isAutostart: UnwrapRef<typeof import('./composables/network')['isAutostart']>
|
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
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 listNetworkInstanceIds: UnwrapRef<typeof import('./composables/backend')['listNetworkInstanceIds']>
|
||||||
|
readonly listenGlobalEvents: UnwrapRef<typeof import('./composables/event')['listenGlobalEvents']>
|
||||||
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']>
|
||||||
@@ -157,6 +164,7 @@ declare module 'vue' {
|
|||||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||||
|
readonly onNetworkInstanceChange: UnwrapRef<typeof import('./composables/mobile_vpn')['onNetworkInstanceChange']>
|
||||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||||
@@ -164,22 +172,23 @@ declare module 'vue' {
|
|||||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
|
||||||
readonly parseNetworkConfig: UnwrapRef<typeof import('./composables/network')['parseNetworkConfig']>
|
readonly parseNetworkConfig: UnwrapRef<typeof import('./composables/backend')['parseNetworkConfig']>
|
||||||
readonly prepareVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['prepareVpnService']>
|
readonly prepareVpnService: UnwrapRef<typeof import('./composables/mobile_vpn')['prepareVpnService']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
readonly retainNetworkInstance: UnwrapRef<typeof import('./composables/network')['retainNetworkInstance']>
|
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/backend')['runNetworkInstance']>
|
||||||
readonly runNetworkInstance: UnwrapRef<typeof import('./composables/network')['runNetworkInstance']>
|
readonly saveNetworkConfig: UnwrapRef<typeof import('./composables/backend')['saveNetworkConfig']>
|
||||||
|
readonly sendConfigs: UnwrapRef<typeof import('./composables/backend')['sendConfigs']>
|
||||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||||
readonly setLoggingLevel: UnwrapRef<typeof import('./composables/network')['setLoggingLevel']>
|
readonly setLoggingLevel: UnwrapRef<typeof import('./composables/backend')['setLoggingLevel']>
|
||||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||||
readonly setTrayMenu: UnwrapRef<typeof import('./composables/tray')['setTrayMenu']>
|
readonly setTrayMenu: UnwrapRef<typeof import('./composables/tray')['setTrayMenu']>
|
||||||
readonly setTrayRunState: UnwrapRef<typeof import('./composables/tray')['setTrayRunState']>
|
readonly setTrayRunState: UnwrapRef<typeof import('./composables/tray')['setTrayRunState']>
|
||||||
readonly setTrayTooltip: UnwrapRef<typeof import('./composables/tray')['setTrayTooltip']>
|
readonly setTrayTooltip: UnwrapRef<typeof import('./composables/tray')['setTrayTooltip']>
|
||||||
readonly setTunFd: UnwrapRef<typeof import('./composables/network')['setTunFd']>
|
readonly setTunFd: UnwrapRef<typeof import('./composables/backend')['setTunFd']>
|
||||||
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']>
|
||||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||||
@@ -190,6 +199,7 @@ declare module 'vue' {
|
|||||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||||
|
readonly updateNetworkConfigState: UnwrapRef<typeof import('./composables/backend')['updateNetworkConfigState']>
|
||||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||||
@@ -197,12 +207,12 @@ declare module 'vue' {
|
|||||||
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
readonly useId: UnwrapRef<typeof import('vue')['useId']>
|
||||||
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
readonly useLink: UnwrapRef<typeof import('vue-router/auto')['useLink']>
|
||||||
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
|
||||||
readonly useNetworkStore: UnwrapRef<typeof import('./stores/network')['useNetworkStore']>
|
|
||||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
|
||||||
readonly useTray: UnwrapRef<typeof import('./composables/tray')['useTray']>
|
readonly useTray: UnwrapRef<typeof import('./composables/tray')['useTray']>
|
||||||
|
readonly validateConfig: UnwrapRef<typeof import('./composables/backend')['validateConfig']>
|
||||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getEasytierVersion } from '~/composables/network'
|
import { getEasytierVersion } from '~/composables/backend'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
|||||||
65
easytier-gui/src/composables/backend.ts
Normal file
65
easytier-gui/src/composables/backend.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
import { Api, type NetworkTypes } from 'easytier-frontend-lib'
|
||||||
|
import { getAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
||||||
|
|
||||||
|
type NetworkConfig = NetworkTypes.NetworkConfig
|
||||||
|
type ValidateConfigResponse = Api.ValidateConfigResponse
|
||||||
|
type ListNetworkInstanceIdResponse = Api.ListNetworkInstanceIdResponse
|
||||||
|
|
||||||
|
export async function parseNetworkConfig(cfg: NetworkConfig) {
|
||||||
|
return invoke<string>('parse_network_config', { cfg })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateNetworkConfig(tomlConfig: string) {
|
||||||
|
return invoke<NetworkConfig>('generate_network_config', { tomlConfig })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runNetworkInstance(cfg: NetworkConfig) {
|
||||||
|
return invoke('run_network_instance', { cfg })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectNetworkInfo(instanceId: string) {
|
||||||
|
return await invoke<Api.CollectNetworkInfoResponse>('collect_network_info', { instanceId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setLoggingLevel(level: string) {
|
||||||
|
return await invoke('set_logging_level', { level })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setTunFd(fd: number) {
|
||||||
|
return await invoke('set_tun_fd', { fd })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEasytierVersion() {
|
||||||
|
return await invoke<string>('easytier_version')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listNetworkInstanceIds() {
|
||||||
|
return await invoke<ListNetworkInstanceIdResponse>('list_network_instance_ids')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteNetworkInstance(instanceId: string) {
|
||||||
|
return await invoke('remove_network_instance', { instanceId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateNetworkConfigState(instanceId: string, disabled: boolean) {
|
||||||
|
return await invoke('update_network_config_state', { instanceId, disabled })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveNetworkConfig(cfg: NetworkConfig) {
|
||||||
|
return await invoke('save_network_config', { cfg })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateConfig(cfg: NetworkConfig) {
|
||||||
|
return await invoke<ValidateConfigResponse>('validate_config', { cfg })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getConfig(instanceId: string) {
|
||||||
|
return await invoke<NetworkConfig>('get_config', { instanceId })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendConfigs() {
|
||||||
|
let networkList: NetworkConfig[] = JSON.parse(localStorage.getItem('networkList') || '[]');
|
||||||
|
let autoStartInstIds = getAutoLaunchStatusAsync() ? JSON.parse(localStorage.getItem('autoStartInstIds') || '[]') : []
|
||||||
|
return await invoke('load_configs', { configs: networkList, enabledNetworks: autoStartInstIds })
|
||||||
|
}
|
||||||
51
easytier-gui/src/composables/event.ts
Normal file
51
easytier-gui/src/composables/event.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Event, listen } from "@tauri-apps/api/event";
|
||||||
|
import { type } from "@tauri-apps/plugin-os";
|
||||||
|
import { NetworkTypes } from "easytier-frontend-lib"
|
||||||
|
|
||||||
|
const EVENTS = Object.freeze({
|
||||||
|
SAVE_CONFIGS: 'save_configs',
|
||||||
|
SAVE_ENABLED_NETWORKS: 'save_enabled_networks',
|
||||||
|
PRE_RUN_NETWORK_INSTANCE: 'pre_run_network_instance',
|
||||||
|
POST_RUN_NETWORK_INSTANCE: 'post_run_network_instance',
|
||||||
|
VPN_SERVICE_STOP: 'vpn_service_stop',
|
||||||
|
});
|
||||||
|
|
||||||
|
function onSaveConfigs(event: Event<NetworkTypes.NetworkConfig[]>) {
|
||||||
|
console.log(`Received event '${EVENTS.SAVE_CONFIGS}': ${event.payload}`);
|
||||||
|
localStorage.setItem('networkList', JSON.stringify(event.payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSaveEnabledNetworks(event: Event<string[]>) {
|
||||||
|
console.log(`Received event '${EVENTS.SAVE_ENABLED_NETWORKS}': ${event.payload}`);
|
||||||
|
localStorage.setItem('autoStartInstIds', JSON.stringify(event.payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onPreRunNetworkInstance(event: Event<string>) {
|
||||||
|
if (type() === 'android') {
|
||||||
|
await prepareVpnService(event.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onPostRunNetworkInstance(event: Event<string>) {
|
||||||
|
if (type() === 'android') {
|
||||||
|
await onNetworkInstanceChange(event.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onVpnServiceStop(event: Event<string>) {
|
||||||
|
await onNetworkInstanceChange(event.payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listenGlobalEvents() {
|
||||||
|
const unlisteners = [
|
||||||
|
await listen(EVENTS.SAVE_CONFIGS, onSaveConfigs),
|
||||||
|
await listen(EVENTS.SAVE_ENABLED_NETWORKS, onSaveEnabledNetworks),
|
||||||
|
await listen(EVENTS.PRE_RUN_NETWORK_INSTANCE, onPreRunNetworkInstance),
|
||||||
|
await listen(EVENTS.POST_RUN_NETWORK_INSTANCE, onPostRunNetworkInstance),
|
||||||
|
await listen(EVENTS.VPN_SERVICE_STOP, onVpnServiceStop),
|
||||||
|
];
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisteners.forEach(unlisten => unlisten());
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,8 +5,6 @@ import { prepare_vpn, start_vpn, stop_vpn } from 'tauri-plugin-vpnservice-api'
|
|||||||
|
|
||||||
type Route = NetworkTypes.Route
|
type Route = NetworkTypes.Route
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
|
||||||
|
|
||||||
interface vpnStatus {
|
interface vpnStatus {
|
||||||
running: boolean
|
running: boolean
|
||||||
ipv4Addr: string | null | undefined
|
ipv4Addr: string | null | undefined
|
||||||
@@ -69,7 +67,7 @@ async function onVpnServiceStart(payload: any) {
|
|||||||
console.log('vpn service start', JSON.stringify(payload))
|
console.log('vpn service start', JSON.stringify(payload))
|
||||||
curVpnStatus.running = true
|
curVpnStatus.running = true
|
||||||
if (payload.fd) {
|
if (payload.fd) {
|
||||||
setTunFd(networkStore.networkInstanceIds[0], payload.fd)
|
setTunFd(payload.fd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,20 +114,17 @@ function getRoutesForVpn(routes: Route[], node_config: NetworkTypes.NetworkConfi
|
|||||||
return Array.from(new Set(ret)).sort()
|
return Array.from(new Set(ret)).sort()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onNetworkInstanceChange() {
|
export async function onNetworkInstanceChange(instanceId: string) {
|
||||||
console.error('vpn service watch network instance change ids', JSON.stringify(networkStore.networkInstanceIds))
|
console.error('vpn service network instance change id', instanceId)
|
||||||
const insts = networkStore.networkInstanceIds
|
if (!instanceId) {
|
||||||
const no_tun = networkStore.isNoTunEnabled(insts[0])
|
|
||||||
if (no_tun) {
|
|
||||||
await doStopVpn()
|
await doStopVpn()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!insts) {
|
const config = await getConfig(instanceId)
|
||||||
await doStopVpn()
|
if (config.no_tun) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const curNetworkInfo = (await collectNetworkInfo(instanceId)).info.map[instanceId]
|
||||||
const curNetworkInfo = networkStore.networkInfos[insts[0]]
|
|
||||||
if (!curNetworkInfo || curNetworkInfo?.error_msg?.length) {
|
if (!curNetworkInfo || curNetworkInfo?.error_msg?.length) {
|
||||||
await doStopVpn()
|
await doStopVpn()
|
||||||
return
|
return
|
||||||
@@ -146,7 +141,7 @@ async function onNetworkInstanceChange() {
|
|||||||
network_length = 24
|
network_length = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
const routes = getRoutesForVpn(curNetworkInfo?.routes, networkStore.curNetwork)
|
const routes = getRoutesForVpn(curNetworkInfo?.routes, config)
|
||||||
|
|
||||||
const ipChanged = virtual_ip !== curVpnStatus.ipv4Addr
|
const ipChanged = virtual_ip !== curVpnStatus.ipv4Addr
|
||||||
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
||||||
@@ -164,48 +159,25 @@ async function onNetworkInstanceChange() {
|
|||||||
await doStartVpn(virtual_ip, 24, routes)
|
await doStartVpn(virtual_ip, 24, routes)
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.error('start vpn service failed, clear all network insts.', e)
|
console.error('start vpn service failed, stop all other network insts.', e)
|
||||||
networkStore.clearNetworkInstances()
|
await runNetworkInstance(config);
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function watchNetworkInstance() {
|
async function isNoTunEnabled(instanceId: string | undefined) {
|
||||||
let subscribe_running = false
|
|
||||||
networkStore.$subscribe(async () => {
|
|
||||||
if (subscribe_running) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
subscribe_running = true
|
|
||||||
try {
|
|
||||||
await onNetworkInstanceChange()
|
|
||||||
}
|
|
||||||
catch (_) {
|
|
||||||
}
|
|
||||||
subscribe_running = false
|
|
||||||
})
|
|
||||||
console.error('vpn service watch network instance')
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNoTunEnabled(instanceId: string | undefined) {
|
|
||||||
if (!instanceId) {
|
if (!instanceId) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const no_tun = networkStore.isNoTunEnabled(instanceId)
|
return (await getConfig(instanceId)).no_tun ?? false
|
||||||
if (no_tun) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initMobileVpnService() {
|
export async function initMobileVpnService() {
|
||||||
await registerVpnServiceListener()
|
await registerVpnServiceListener()
|
||||||
await watchNetworkInstance()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareVpnService(instanceId: string) {
|
export async function prepareVpnService(instanceId: string) {
|
||||||
if (isNoTunEnabled(instanceId)) {
|
if (await isNoTunEnabled(instanceId)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('prepare vpn')
|
console.log('prepare vpn')
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import type { NetworkTypes } from 'easytier-frontend-lib'
|
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
|
||||||
|
|
||||||
type NetworkConfig = NetworkTypes.NetworkConfig
|
|
||||||
type NetworkInstanceRunningInfo = NetworkTypes.NetworkInstanceRunningInfo
|
|
||||||
|
|
||||||
export async function parseNetworkConfig(cfg: NetworkConfig) {
|
|
||||||
return invoke<string>('parse_network_config', { cfg })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateNetworkConfig(tomlConfig: string) {
|
|
||||||
return invoke<NetworkConfig>('generate_network_config', { tomlConfig })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runNetworkInstance(cfg: NetworkConfig) {
|
|
||||||
return invoke('run_network_instance', { cfg })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function retainNetworkInstance(instanceIds: string[]) {
|
|
||||||
return invoke('retain_network_instance', { instanceIds })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function collectNetworkInfos() {
|
|
||||||
return await invoke<Record<string, NetworkInstanceRunningInfo>>('collect_network_infos')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getOsHostname() {
|
|
||||||
return await invoke<string>('get_os_hostname')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isAutostart() {
|
|
||||||
return await invoke<boolean>('is_autostart')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setLoggingLevel(level: string) {
|
|
||||||
return await invoke('set_logging_level', { level })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setTunFd(instanceId: string, fd: number) {
|
|
||||||
return await invoke('set_tun_fd', { instanceId, fd })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEasytierVersion() {
|
|
||||||
return await invoke<string>('easytier_version')
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import Aura from '@primeuix/themes/aura';
|
import Aura from '@primeuix/themes/aura';
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config';
|
||||||
import ToastService from 'primevue/toastservice'
|
|
||||||
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router/auto'
|
import EasyTierFrontendLib, { I18nUtils } from 'easytier-frontend-lib';
|
||||||
import { routes } from 'vue-router/auto-routes'
|
import { createRouter, createWebHistory } from 'vue-router/auto';
|
||||||
import App from '~/App.vue'
|
import { routes } from 'vue-router/auto-routes';
|
||||||
import EasyTierFrontendLib, { I18nUtils } from 'easytier-frontend-lib'
|
import App from '~/App.vue';
|
||||||
|
|
||||||
import { getAutoLaunchStatusAsync, loadAutoLaunchStatusAsync } from './modules/auto_launch'
|
import 'easytier-frontend-lib/style.css';
|
||||||
import '~/styles.css'
|
import { ConfirmationService, DialogService, ToastService } from 'primevue';
|
||||||
import 'easytier-frontend-lib/style.css'
|
import '~/styles.css';
|
||||||
|
import { getAutoLaunchStatusAsync, loadAutoLaunchStatusAsync } from './modules/auto_launch';
|
||||||
|
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
document.addEventListener('keydown', (event) => {
|
document.addEventListener('keydown', (event) => {
|
||||||
@@ -55,7 +55,9 @@ async function main() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.use(ToastService as any)
|
app.use(ToastService)
|
||||||
|
app.use(DialogService)
|
||||||
|
app.use(ConfirmationService)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
easytier-gui/src/modules/api.ts
Normal file
44
easytier-gui/src/modules/api.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { type Api, type NetworkTypes } from "easytier-frontend-lib";
|
||||||
|
import * as backend from "~/composables/backend";
|
||||||
|
|
||||||
|
export class GUIRemoteClient implements Api.RemoteClient {
|
||||||
|
async validate_config(config: NetworkTypes.NetworkConfig): Promise<Api.ValidateConfigResponse> {
|
||||||
|
return backend.validateConfig(config);
|
||||||
|
}
|
||||||
|
async run_network(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||||
|
await backend.runNetworkInstance(config);
|
||||||
|
}
|
||||||
|
async get_network_info(inst_id: string): Promise<NetworkTypes.NetworkInstanceRunningInfo | undefined> {
|
||||||
|
return backend.collectNetworkInfo(inst_id).then(infos => infos.info.map[inst_id]);
|
||||||
|
}
|
||||||
|
async list_network_instance_ids(): Promise<Api.ListNetworkInstanceIdResponse> {
|
||||||
|
return backend.listNetworkInstanceIds();
|
||||||
|
}
|
||||||
|
async delete_network(inst_id: string): Promise<undefined> {
|
||||||
|
await backend.deleteNetworkInstance(inst_id);
|
||||||
|
}
|
||||||
|
async update_network_instance_state(inst_id: string, disabled: boolean): Promise<undefined> {
|
||||||
|
await backend.updateNetworkConfigState(inst_id, disabled);
|
||||||
|
}
|
||||||
|
async save_config(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||||
|
await backend.saveNetworkConfig(config);
|
||||||
|
}
|
||||||
|
async get_network_config(inst_id: string): Promise<NetworkTypes.NetworkConfig> {
|
||||||
|
return backend.getConfig(inst_id);
|
||||||
|
}
|
||||||
|
async generate_config(config: NetworkTypes.NetworkConfig): Promise<Api.GenerateConfigResponse> {
|
||||||
|
try {
|
||||||
|
return { toml_config: await backend.parseNetworkConfig(config) };
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e + "" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async parse_config(toml_config: string): Promise<Api.ParseConfigResponse> {
|
||||||
|
try {
|
||||||
|
return { config: await backend.generateNetworkConfig(toml_config) }
|
||||||
|
} catch (e) {
|
||||||
|
return { error: e + "" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,148 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { appLogDir } from '@tauri-apps/api/path'
|
|
||||||
|
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
|
||||||
import { type } from '@tauri-apps/plugin-os'
|
import { type } from '@tauri-apps/plugin-os'
|
||||||
import { exit } from '@tauri-apps/plugin-process'
|
|
||||||
import { open } from '@tauri-apps/plugin-shell'
|
|
||||||
import TieredMenu from 'primevue/tieredmenu'
|
|
||||||
import { useToast } from 'primevue/usetoast'
|
|
||||||
import { NetworkTypes, Config, Status, Utils, I18nUtils, ConfigEditDialog } from 'easytier-frontend-lib'
|
|
||||||
|
|
||||||
import { isAutostart, setLoggingLevel } from '~/composables/network'
|
import { appLogDir } from '@tauri-apps/api/path'
|
||||||
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
||||||
|
import { exit } from '@tauri-apps/plugin-process'
|
||||||
|
import { I18nUtils, RemoteManagement } from "easytier-frontend-lib"
|
||||||
|
import type { MenuItem } from 'primevue/menuitem'
|
||||||
import { useTray } from '~/composables/tray'
|
import { useTray } from '~/composables/tray'
|
||||||
|
import { GUIRemoteClient } from '~/modules/api'
|
||||||
import { getAutoLaunchStatusAsync as getAutoLaunchStatus, loadAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
import { getAutoLaunchStatusAsync as getAutoLaunchStatus, loadAutoLaunchStatusAsync } from '~/modules/auto_launch'
|
||||||
import { getDockVisibilityStatus, loadDockVisibilityAsync } from '~/modules/dock_visibility'
|
import { getDockVisibilityStatus, loadDockVisibilityAsync } from '~/modules/dock_visibility'
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const visible = ref(false)
|
|
||||||
const aboutVisible = ref(false)
|
const aboutVisible = ref(false)
|
||||||
const tomlConfig = ref('')
|
|
||||||
|
|
||||||
useTray(true)
|
useTray(true)
|
||||||
|
|
||||||
const items = ref([
|
const remoteClient = computed(() => new GUIRemoteClient());
|
||||||
{
|
const instanceId = ref<string | undefined>(undefined);
|
||||||
label: () => activeStep.value == "2" ? t('show_config') : t('edit_config'),
|
|
||||||
icon: 'pi pi-file-edit',
|
|
||||||
command: async () => {
|
|
||||||
try {
|
|
||||||
const ret = await parseNetworkConfig(networkStore.curNetwork)
|
|
||||||
tomlConfig.value = ret
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
tomlConfig.value = e
|
|
||||||
}
|
|
||||||
visible.value = true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: () => t('del_cur_network'),
|
|
||||||
icon: 'pi pi-times',
|
|
||||||
command: async () => {
|
|
||||||
networkStore.removeNetworkInstance(networkStore.curNetwork.instance_id)
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
|
||||||
networkStore.delCurNetwork()
|
|
||||||
},
|
|
||||||
disabled: () => networkStore.networkList.length <= 1,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
enum Severity {
|
|
||||||
None = 'none',
|
|
||||||
Success = 'success',
|
|
||||||
Info = 'info',
|
|
||||||
Warn = 'warn',
|
|
||||||
Error = 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
const messageBarSeverity = ref(Severity.None)
|
|
||||||
const messageBarContent = ref('')
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const networkStore = useNetworkStore()
|
|
||||||
|
|
||||||
const curNetworkConfig = computed(() => {
|
|
||||||
if (networkStore.curNetworkId) {
|
|
||||||
// console.log('instanceId', props.instanceId)
|
|
||||||
const c = networkStore.networkList.find(n => n.instance_id === networkStore.curNetworkId)
|
|
||||||
if (c !== undefined)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
return networkStore.curNetwork
|
|
||||||
})
|
|
||||||
|
|
||||||
const curNetworkInst = computed<NetworkTypes.NetworkInstance | null>(() => {
|
|
||||||
let ret = networkStore.networkInstances.find(n => n.instance_id === curNetworkConfig.value.instance_id)
|
|
||||||
console.log('curNetworkInst', ret)
|
|
||||||
if (ret === undefined) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function addNewNetwork() {
|
|
||||||
networkStore.addNewNetwork()
|
|
||||||
networkStore.curNetwork = networkStore.lastNetwork
|
|
||||||
}
|
|
||||||
|
|
||||||
networkStore.$subscribe(async () => {
|
|
||||||
networkStore.saveToLocalStorage()
|
|
||||||
try {
|
|
||||||
await parseNetworkConfig(networkStore.curNetwork)
|
|
||||||
messageBarSeverity.value = Severity.None
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
messageBarContent.value = e
|
|
||||||
messageBarSeverity.value = Severity.Error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function runNetworkCb(cfg: NetworkTypes.NetworkConfig, cb: () => void) {
|
|
||||||
if (type() === 'android') {
|
|
||||||
await prepareVpnService(cfg.instance_id)
|
|
||||||
networkStore.clearNetworkInstances()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
networkStore.removeNetworkInstance(cfg.instance_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
|
||||||
networkStore.addNetworkInstance(cfg.instance_id)
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runNetworkInstance(cfg)
|
|
||||||
networkStore.addAutoStartInstId(cfg.instance_id)
|
|
||||||
}
|
|
||||||
catch (e: any) {
|
|
||||||
// console.error(e)
|
|
||||||
toast.add({ severity: 'info', detail: e })
|
|
||||||
}
|
|
||||||
|
|
||||||
cb()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stopNetworkCb(cfg: NetworkTypes.NetworkConfig, cb: () => void) {
|
|
||||||
// console.log('stopNetworkCb', cfg, cb)
|
|
||||||
cb()
|
|
||||||
networkStore.removeNetworkInstance(cfg.instance_id)
|
|
||||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
|
||||||
networkStore.removeAutoStartInstId(cfg.instance_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateNetworkInfos() {
|
|
||||||
networkStore.updateWithNetworkInfos(await collectNetworkInfos())
|
|
||||||
}
|
|
||||||
|
|
||||||
let intervalId = 0
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
intervalId = window.setInterval(async () => {
|
|
||||||
await updateNetworkInfos()
|
|
||||||
}, 500)
|
|
||||||
|
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
await setTrayMenu([
|
await setTrayMenu([
|
||||||
await MenuItemShow(t('tray.show')),
|
await MenuItemShow(t('tray.show')),
|
||||||
@@ -150,16 +28,47 @@ onMounted(async () => {
|
|||||||
])
|
])
|
||||||
}, 1000)
|
}, 1000)
|
||||||
})
|
})
|
||||||
onUnmounted(() => clearInterval(intervalId))
|
|
||||||
|
|
||||||
const activeStep = computed(() => {
|
|
||||||
return networkStore.networkInstanceIds.includes(networkStore.curNetworkId) ? '2' : '1'
|
|
||||||
})
|
|
||||||
|
|
||||||
let current_log_level = 'off'
|
let current_log_level = 'off'
|
||||||
|
|
||||||
const setting_menu = ref()
|
const log_menu = ref()
|
||||||
const setting_menu_items = ref([
|
const log_menu_items_popup: Ref<MenuItem[]> = ref([
|
||||||
|
...['off', 'warn', 'info', 'debug', 'trace'].map(level => ({
|
||||||
|
label: () => t(`logging_level_${level}`) + (current_log_level === level ? ' ✓' : ''),
|
||||||
|
command: async () => {
|
||||||
|
current_log_level = level
|
||||||
|
await setLoggingLevel(level)
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
separator: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => t('logging_open_dir'),
|
||||||
|
icon: 'pi pi-folder-open',
|
||||||
|
command: async () => {
|
||||||
|
// console.log('open log dir', await appLogDir())
|
||||||
|
await open(await appLogDir())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => t('logging_copy_dir'),
|
||||||
|
icon: 'pi pi-tablet',
|
||||||
|
command: async () => {
|
||||||
|
await writeText(await appLogDir())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
function toggle_log_menu(event: any) {
|
||||||
|
log_menu.value.toggle(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLabel(item: MenuItem) {
|
||||||
|
return typeof item.label === 'function' ? item.label() : item.label
|
||||||
|
}
|
||||||
|
|
||||||
|
const setting_menu_items: Ref<MenuItem[]> = ref([
|
||||||
{
|
{
|
||||||
label: () => t('exchange_language'),
|
label: () => t('exchange_language'),
|
||||||
icon: 'pi pi-language',
|
icon: 'pi pi-language',
|
||||||
@@ -187,40 +96,10 @@ const setting_menu_items = ref([
|
|||||||
visible: () => type() === 'macos',
|
visible: () => type() === 'macos',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'logging_menu',
|
||||||
label: () => t('logging'),
|
label: () => t('logging'),
|
||||||
icon: 'pi pi-file',
|
icon: 'pi pi-file',
|
||||||
items: (function () {
|
items: [], // Keep this to show it's a parent menu
|
||||||
const levels = ['off', 'warn', 'info', 'debug', 'trace']
|
|
||||||
const items = []
|
|
||||||
for (const level of levels) {
|
|
||||||
items.push({
|
|
||||||
label: () => t(`logging_level_${level}`) + (current_log_level === level ? ' ✓' : ''),
|
|
||||||
command: async () => {
|
|
||||||
current_log_level = level
|
|
||||||
await setLoggingLevel(level)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items.push({
|
|
||||||
separator: true,
|
|
||||||
})
|
|
||||||
items.push({
|
|
||||||
label: () => t('logging_open_dir'),
|
|
||||||
icon: 'pi pi-folder-open',
|
|
||||||
command: async () => {
|
|
||||||
// console.log('open log dir', await appLogDir())
|
|
||||||
await open(await appLogDir())
|
|
||||||
},
|
|
||||||
})
|
|
||||||
items.push({
|
|
||||||
label: () => t('logging_copy_dir'),
|
|
||||||
icon: 'pi pi-tablet',
|
|
||||||
command: async () => {
|
|
||||||
await writeText(await appLogDir())
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
})(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () => t('about.title'),
|
label: () => t('about.title'),
|
||||||
@@ -238,25 +117,6 @@ const setting_menu_items = ref([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
function toggle_setting_menu(event: any) {
|
|
||||||
setting_menu.value.toggle(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
networkStore.loadFromLocalStorage()
|
|
||||||
if (type() !== 'android' && getAutoLaunchStatus() && await isAutostart()) {
|
|
||||||
getCurrentWindow().hide()
|
|
||||||
const autoStartIds = networkStore.autoStartInstIds
|
|
||||||
for (const id of autoStartIds) {
|
|
||||||
const cfg = networkStore.networkList.find((item: NetworkTypes.NetworkConfig) => item.instance_id === id)
|
|
||||||
if (cfg) {
|
|
||||||
networkStore.addNetworkInstance(cfg.instance_id)
|
|
||||||
await runNetworkInstance(cfg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (type() === 'android') {
|
if (type() === 'android') {
|
||||||
try {
|
try {
|
||||||
@@ -266,125 +126,37 @@ onMounted(async () => {
|
|||||||
console.error("easytier init vpn service failed", e)
|
console.error("easytier init vpn service failed", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const unlisten = await listenGlobalEvents()
|
||||||
|
await sendConfigs()
|
||||||
|
return () => {
|
||||||
|
unlisten()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function isRunning(id: string) {
|
|
||||||
return networkStore.networkInstanceIds.includes(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveTomlConfig(tomlConfig: string) {
|
|
||||||
const config = await generateNetworkConfig(tomlConfig)
|
|
||||||
networkStore.replaceCurNetwork(config);
|
|
||||||
toast.add({ severity: 'success', detail: t('config_saved'), life: 3000 })
|
|
||||||
visible.value = false
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="root" class="flex flex-col">
|
<div id="root" class="flex flex-col">
|
||||||
<ConfigEditDialog v-model:visible="visible" :cur-network="curNetworkConfig" :readonly="activeStep !== '1'"
|
|
||||||
:save-config="saveTomlConfig" :generate-config="parseNetworkConfig" />
|
|
||||||
|
|
||||||
<Dialog v-model:visible="aboutVisible" modal :header="t('about.title')" :style="{ width: '70%' }">
|
<Dialog v-model:visible="aboutVisible" modal :header="t('about.title')" :style="{ width: '70%' }">
|
||||||
<About />
|
<About />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<Menu ref="log_menu" :model="log_menu_items_popup" :popup="true" />
|
||||||
|
|
||||||
<div class="w-full">
|
<RemoteManagement class="flex-1 overflow-y-auto" :api="remoteClient" v-bind:instance-id="instanceId" />
|
||||||
<div class="flex items-center gap-4 p-4 h-20">
|
|
||||||
<!-- 网络按钮 -->
|
|
||||||
<div class="flex shrink-0 items-center">
|
|
||||||
<Button icon="pi pi-plus" severity="primary" :label="t('add_new_network')" class="hidden md:inline-flex"
|
|
||||||
@click="addNewNetwork" />
|
|
||||||
<Button icon="pi pi-plus" severity="primary" class="md:hidden px-6" @click="addNewNetwork" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 网络选择 - 占据中间剩余空间 -->
|
<Menubar :model="setting_menu_items" breakpoint="560px">
|
||||||
<Select v-model="networkStore.curNetwork" :options="networkStore.networkList" :highlight-on-select="false"
|
<template #item="{ item, props }">
|
||||||
:placeholder="t('select_network')" class="flex-1 h-full min-w-0">
|
<a v-if="item.key === 'logging_menu'" v-bind="props.action" @click="toggle_log_menu">
|
||||||
<template #value="slotProps">
|
<span :class="item.icon" />
|
||||||
<div class="flex items-center content-center min-w-0">
|
<span class="p-menubar-item-label">{{ getLabel(item) }}</span>
|
||||||
<div class="mr-4 flex-col min-w-0 flex-1">
|
<span class="pi pi-angle-down p-menubar-item-icon text-[9px]"></span>
|
||||||
<span class="truncate block"> {{ slotProps.value.network_name }}</span>
|
</a>
|
||||||
</div>
|
<a v-else v-bind="props.action">
|
||||||
<Tag class="my-auto leading-3 shrink-0"
|
<span :class="item.icon" />
|
||||||
:severity="isRunning(slotProps.value.instance_id) ? 'success' : 'info'"
|
<span class="p-menubar-item-label">{{ getLabel(item) }}</span>
|
||||||
:value="t(isRunning(slotProps.value.instance_id) ? 'network_running' : 'network_stopped')" />
|
</a>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</Menubar>
|
||||||
<template #option="slotProps">
|
|
||||||
<div class="flex flex-col items-start content-center max-w-full">
|
|
||||||
<div class="flex items-center min-w-0 w-full">
|
|
||||||
<div class="mr-4 min-w-0 flex-1">
|
|
||||||
<span class="truncate block">{{ t('network_name') }}: {{ slotProps.option.network_name }}</span>
|
|
||||||
</div>
|
|
||||||
<Tag class="my-auto leading-3 shrink-0"
|
|
||||||
:severity="isRunning(slotProps.option.instance_id) ? 'success' : 'info'"
|
|
||||||
:value="t(isRunning(slotProps.option.instance_id) ? 'network_running' : 'network_stopped')" />
|
|
||||||
</div>
|
|
||||||
<div v-if="slotProps.option.networking_method !== NetworkTypes.NetworkingMethod.Standalone"
|
|
||||||
class="max-w-full overflow-hidden text-ellipsis">
|
|
||||||
{{ slotProps.option.networking_method === NetworkTypes.NetworkingMethod.Manual
|
|
||||||
? slotProps.option.peer_urls.join(', ')
|
|
||||||
: slotProps.option.public_server_url }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="isRunning(slotProps.option.instance_id) && networkStore.instances[slotProps.option.instance_id].detail && (!!networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)">
|
|
||||||
{{
|
|
||||||
Utils.ipv4InetToString(networkStore.instances[slotProps.option.instance_id].detail?.my_node_info.virtual_ipv4)
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<!-- 设置按钮 -->
|
|
||||||
<div class="flex items-center shrink-0">
|
|
||||||
<Button icon="pi pi-cog" severity="secondary" aria-haspopup="true" :label="t('settings')"
|
|
||||||
class="hidden md:inline-flex" aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
|
||||||
<Button icon="pi pi-cog" severity="secondary" aria-haspopup="true" class="md:hidden px-6"
|
|
||||||
aria-controls="overlay_setting_menu" @click="toggle_setting_menu" />
|
|
||||||
<TieredMenu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Panel class="h-full overflow-y-auto">
|
|
||||||
<Stepper :value="activeStep">
|
|
||||||
<StepList value="1">
|
|
||||||
<Step value="1">
|
|
||||||
{{ t('config_network') }}
|
|
||||||
</Step>
|
|
||||||
<Step value="2">
|
|
||||||
{{ t('running') }}
|
|
||||||
</Step>
|
|
||||||
</StepList>
|
|
||||||
<StepPanels value="1">
|
|
||||||
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="1">
|
|
||||||
<Config :instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
|
|
||||||
:cur-network="curNetworkConfig" @run-network="runNetworkCb($event, () => activateCallback('2'))" />
|
|
||||||
</StepPanel>
|
|
||||||
<StepPanel v-slot="{ activateCallback = (s: string) => { } } = {}" value="2">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<Status :cur-network-inst="curNetworkInst" />
|
|
||||||
</div>
|
|
||||||
<div class="flex pt-6 justify-center">
|
|
||||||
<Button :label="t('stop_network')" severity="danger" icon="pi pi-arrow-left"
|
|
||||||
@click="stopNetworkCb(networkStore.curNetwork, () => activateCallback('1'))" />
|
|
||||||
</div>
|
|
||||||
</StepPanel>
|
|
||||||
</StepPanels>
|
|
||||||
</Stepper>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Menubar :model="items" breakpoint="300px" />
|
|
||||||
<InlineMessage v-if="messageBarSeverity !== Severity.None" class="absolute bottom-0 right-0" severity="error">
|
|
||||||
{{ messageBarContent }}
|
|
||||||
</InlineMessage>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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<string, NetworkTypes.NetworkInstance>,
|
|
||||||
|
|
||||||
networkInfos: {} as Record<string, NetworkTypes.NetworkInstanceRunningInfo>,
|
|
||||||
|
|
||||||
autoStartInstIds: [] as string[],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
lastNetwork(): NetworkTypes.NetworkConfig {
|
|
||||||
return this.networkList[this.networkList.length - 1]
|
|
||||||
},
|
|
||||||
|
|
||||||
curNetworkId(): string {
|
|
||||||
return this.curNetwork.instance_id
|
|
||||||
},
|
|
||||||
|
|
||||||
networkInstances(): Array<NetworkTypes.NetworkInstance> {
|
|
||||||
return Object.values(this.instances)
|
|
||||||
},
|
|
||||||
|
|
||||||
networkInstanceIds(): Array<string> {
|
|
||||||
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<string, NetworkTypes.NetworkInstanceRunningInfo>) {
|
|
||||||
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))
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Button, ConfirmPopup, Divider, IftaLabel, Menu, Select, useConfirm, useToast } from 'primevue';
|
import { Button, ConfirmPopup, Divider, IftaLabel, Menu, Message, Select, Tag, useConfirm, useToast } from 'primevue';
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, Ref, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import * as Api from '../modules/api';
|
import * as Api from '../modules/api';
|
||||||
import { RemoteClient } from '../modules/api';
|
|
||||||
import * as Utils from '../modules/utils';
|
import * as Utils from '../modules/utils';
|
||||||
import * as NetworkTypes from '../types/network';
|
import * as NetworkTypes from '../types/network';
|
||||||
|
import { type MenuItem } from 'primevue/menuitem';
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
api: RemoteClient;
|
api: Api.RemoteClient;
|
||||||
newConfigGenerator?: () => NetworkTypes.NetworkConfig;
|
newConfigGenerator?: () => NetworkTypes.NetworkConfig;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -27,15 +27,16 @@ const configFile = ref();
|
|||||||
|
|
||||||
const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
|
const curNetworkInfo = ref<NetworkTypes.NetworkInstance | null>(null);
|
||||||
|
|
||||||
const isEditing = ref(false);
|
|
||||||
// const showCreateNetworkDialog = ref(false);
|
|
||||||
const showConfigEditDialog = ref(false);
|
const showConfigEditDialog = ref(false);
|
||||||
const isCreatingNetwork = ref(false); // Flag to indicate if we're in network creation mode
|
const isEditingNetwork = ref(false); // Flag to indicate if we're in network editing mode
|
||||||
const editingNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
|
||||||
const currentNetworkConfig = ref<NetworkTypes.NetworkConfig | undefined>(undefined);
|
const currentNetworkConfig = ref<NetworkTypes.NetworkConfig | undefined>(undefined);
|
||||||
|
|
||||||
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
||||||
|
|
||||||
|
const isRunning = (instanceId: string) => {
|
||||||
|
return listInstanceIdResponse.value?.running_inst_ids.map(Utils.UuidToStr).includes(instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
const instanceIdList = computed(() => {
|
const instanceIdList = computed(() => {
|
||||||
let insts = new Set<string>();
|
let insts = new Set<string>();
|
||||||
let t = listInstanceIdResponse.value;
|
let t = listInstanceIdResponse.value;
|
||||||
@@ -59,7 +60,7 @@ const selectedInstanceId = computed({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
watch(selectedInstanceId, async (newVal, oldVal) => {
|
watch(selectedInstanceId, async (newVal, oldVal) => {
|
||||||
if (newVal?.uuid !== oldVal?.uuid && networkIsDisabled.value) {
|
if (newVal?.uuid !== oldVal?.uuid && (networkIsDisabled.value || isEditingNetwork.value)) {
|
||||||
await loadCurrentNetworkConfig();
|
await loadCurrentNetworkConfig();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -144,12 +145,10 @@ const confirmDeleteNetwork = (event: any) => {
|
|||||||
|
|
||||||
const saveAndRunNewNetwork = async () => {
|
const saveAndRunNewNetwork = async () => {
|
||||||
try {
|
try {
|
||||||
if (isEditing.value) {
|
await props.api.delete_network(instanceId.value!);
|
||||||
await props.api.delete_network(instanceId.value!);
|
let ret = await props.api.run_network(currentNetworkConfig.value!!);
|
||||||
}
|
|
||||||
let ret = await props.api.run_network(editingNetworkConfig.value);
|
|
||||||
console.debug("saveAndRunNewNetwork", ret);
|
console.debug("saveAndRunNewNetwork", ret);
|
||||||
selectedInstanceId.value = { uuid: editingNetworkConfig.value.instance_id };
|
selectedInstanceId.value = { uuid: currentNetworkConfig.value!.instance_id };
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to create network, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to create network, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
||||||
@@ -157,18 +156,26 @@ const saveAndRunNewNetwork = async () => {
|
|||||||
}
|
}
|
||||||
emits('update');
|
emits('update');
|
||||||
// showCreateNetworkDialog.value = false;
|
// showCreateNetworkDialog.value = false;
|
||||||
isCreatingNetwork.value = false; // Exit creation mode after successful network creation
|
isEditingNetwork.value = false; // Exit creation mode after successful network creation
|
||||||
}
|
}
|
||||||
|
|
||||||
const newNetwork = () => {
|
const saveNetworkConfig = async () => {
|
||||||
editingNetworkConfig.value = props.newConfigGenerator?.() ?? NetworkTypes.DEFAULT_NETWORK_CONFIG();
|
if (!currentNetworkConfig.value) {
|
||||||
isEditing.value = false;
|
return;
|
||||||
// showCreateNetworkDialog.value = true; // Old dialog approach
|
}
|
||||||
isCreatingNetwork.value = true; // Switch to creation mode instead
|
await props.api.save_config(currentNetworkConfig.value);
|
||||||
|
toast.add({ severity: 'success', summary: t("web.common.success"), detail: t("web.device_management.config_saved"), life: 2000 });
|
||||||
|
}
|
||||||
|
const newNetwork = async () => {
|
||||||
|
const newNetworkConfig = props.newConfigGenerator?.() ?? NetworkTypes.DEFAULT_NETWORK_CONFIG();
|
||||||
|
await props.api.save_config(newNetworkConfig);
|
||||||
|
selectedInstanceId.value = { uuid: newNetworkConfig.instance_id };
|
||||||
|
currentNetworkConfig.value = newNetworkConfig;
|
||||||
|
await loadNetworkInstanceIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelNetworkCreation = () => {
|
const cancelEditNetwork = () => {
|
||||||
isCreatingNetwork.value = false;
|
isEditingNetwork.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editNetwork = async () => {
|
const editNetwork = async () => {
|
||||||
@@ -177,14 +184,11 @@ const editNetwork = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEditing.value = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let ret = await props.api.get_network_config(instanceId.value!);
|
let ret = await props.api.get_network_config(instanceId.value!);
|
||||||
console.debug("editNetwork", ret);
|
console.debug("editNetwork", ret);
|
||||||
editingNetworkConfig.value = ret;
|
currentNetworkConfig.value = ret;
|
||||||
// showCreateNetworkDialog.value = true; // Old dialog approach
|
isEditingNetwork.value = true; // Switch to editing mode instead
|
||||||
isCreatingNetwork.value = true; // Switch to creation mode instead
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to edit network, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Failed to edit network, error: ' + JSON.stringify(e.response.data), life: 2000 });
|
||||||
@@ -194,7 +198,6 @@ const editNetwork = async () => {
|
|||||||
|
|
||||||
const loadNetworkInstanceIds = async () => {
|
const loadNetworkInstanceIds = async () => {
|
||||||
listInstanceIdResponse.value = await props.api.list_network_instance_ids();
|
listInstanceIdResponse.value = await props.api.list_network_instance_ids();
|
||||||
console.debug("loadNetworkInstanceIds", listInstanceIdResponse.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadCurrentNetworkInfo = async () => {
|
const loadCurrentNetworkInfo = async () => {
|
||||||
@@ -202,13 +205,12 @@ const loadCurrentNetworkInfo = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = await props.api.get_network_info(instanceId.value);
|
let network_info = await props.api.get_network_info(instanceId.value);
|
||||||
let network_info = ret[instanceId.value];
|
|
||||||
|
|
||||||
curNetworkInfo.value = {
|
curNetworkInfo.value = {
|
||||||
instance_id: instanceId.value,
|
instance_id: instanceId.value,
|
||||||
running: network_info.running,
|
running: network_info?.running ?? false,
|
||||||
error_msg: network_info.error_msg,
|
error_msg: network_info?.error_msg ?? '',
|
||||||
detail: network_info,
|
detail: network_info,
|
||||||
} as NetworkTypes.NetworkInstance;
|
} as NetworkTypes.NetworkInstance;
|
||||||
}
|
}
|
||||||
@@ -220,9 +222,8 @@ const exportConfig = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let networkConfig = await props.api.get_network_config(instanceId.value!);
|
const { instance_id, ...networkConfig } = await props.api.get_network_config(instanceId.value!);
|
||||||
delete networkConfig.instance_id;
|
let { toml_config: tomlConfig, error } = await props.api.generate_config(networkConfig as NetworkTypes.NetworkConfig);
|
||||||
let { toml_config: tomlConfig, error } = await props.api.generate_config(networkConfig);
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw { response: { data: error } };
|
throw { response: { data: error } };
|
||||||
}
|
}
|
||||||
@@ -255,9 +256,8 @@ const handleFileUpload = (event: Event) => {
|
|||||||
const config = resp.config;
|
const config = resp.config;
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
config.instance_id = editingNetworkConfig.value?.instance_id ?? config?.instance_id;
|
config.instance_id = currentNetworkConfig.value?.instance_id ?? config?.instance_id;
|
||||||
|
currentNetworkConfig.value = config;
|
||||||
Object.assign(editingNetworkConfig.value, resp.config);
|
|
||||||
toast.add({ severity: 'success', summary: 'Import Success', detail: "Config file import success", life: 2000 });
|
toast.add({ severity: 'success', summary: 'Import Success', detail: "Config file import success", life: 2000 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error: ' + error, life: 2000 });
|
toast.add({ severity: 'error', summary: 'Error', detail: 'Config file parse error: ' + error, life: 2000 });
|
||||||
@@ -288,7 +288,7 @@ const generateConfig = async (config: NetworkTypes.NetworkConfig): Promise<strin
|
|||||||
return tomlConfig ?? '';
|
return tomlConfig ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveConfig = async (tomlConfig: string): Promise<void> => {
|
const syncTomlConfig = async (tomlConfig: string): Promise<void> => {
|
||||||
let resp = await props.api.parse_config(tomlConfig);
|
let resp = await props.api.parse_config(tomlConfig);
|
||||||
if (resp.error) {
|
if (resp.error) {
|
||||||
throw resp.error;
|
throw resp.error;
|
||||||
@@ -298,11 +298,7 @@ const saveConfig = async (tomlConfig: string): Promise<void> => {
|
|||||||
throw new Error("Parsed config is empty");
|
throw new Error("Parsed config is empty");
|
||||||
}
|
}
|
||||||
config.instance_id = currentNetworkConfig.value?.instance_id ?? config?.instance_id;
|
config.instance_id = currentNetworkConfig.value?.instance_id ?? config?.instance_id;
|
||||||
if (networkIsDisabled.value) {
|
currentNetworkConfig.value = config;
|
||||||
currentNetworkConfig.value = config;
|
|
||||||
} else {
|
|
||||||
editingNetworkConfig.value = config;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式屏幕宽度
|
// 响应式屏幕宽度
|
||||||
@@ -313,10 +309,11 @@ const updateScreenWidth = () => {
|
|||||||
|
|
||||||
// 菜单引用和菜单项
|
// 菜单引用和菜单项
|
||||||
const menuRef = ref();
|
const menuRef = ref();
|
||||||
const actionMenu = ref([
|
const actionMenu: Ref<MenuItem[]> = ref([
|
||||||
{
|
{
|
||||||
label: t('web.device_management.edit_network'),
|
label: t('web.device_management.edit_network'),
|
||||||
icon: 'pi pi-pencil',
|
icon: 'pi pi-pencil',
|
||||||
|
visible: () => !(networkIsDisabled.value ?? true),
|
||||||
command: () => editNetwork()
|
command: () => editNetwork()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -370,7 +367,31 @@ onUnmounted(() => {
|
|||||||
<IftaLabel class="w-full">
|
<IftaLabel class="w-full">
|
||||||
<Select v-model="selectedInstanceId" :options="instanceIdList" optionLabel="uuid" class="w-full"
|
<Select v-model="selectedInstanceId" :options="instanceIdList" optionLabel="uuid" class="w-full"
|
||||||
inputId="dd-inst-id" :placeholder="t('web.device_management.select_network')"
|
inputId="dd-inst-id" :placeholder="t('web.device_management.select_network')"
|
||||||
:pt="{ root: { class: 'network-select-container' } }" />
|
:pt="{ root: { class: 'network-select-container' } }">
|
||||||
|
<template #value="slotProps">
|
||||||
|
<div v-if="slotProps.value" class="flex items-center content-center min-w-0">
|
||||||
|
<div class="mr-4 flex-col min-w-0 flex-1">
|
||||||
|
<span class="truncate block"> {{ slotProps.value.uuid }}</span>
|
||||||
|
</div>
|
||||||
|
<Tag class="my-auto leading-3 shrink-0"
|
||||||
|
:severity="isRunning(slotProps.value.uuid) ? 'success' : 'info'"
|
||||||
|
:value="t(isRunning(slotProps.value.uuid) ? 'network_running' : 'network_stopped')" />
|
||||||
|
</div>
|
||||||
|
<span v-else>
|
||||||
|
{{ slotProps.placeholder }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #option="slotProps">
|
||||||
|
<div class="flex items-center content-center min-w-0">
|
||||||
|
<div class="mr-4 flex-col min-w-0 flex-1">
|
||||||
|
<span class="truncate block"> {{ slotProps.option.uuid }}</span>
|
||||||
|
</div>
|
||||||
|
<Tag class="my-auto leading-3 shrink-0"
|
||||||
|
:severity="isRunning(slotProps.option.uuid) ? 'success' : 'info'"
|
||||||
|
:value="t(isRunning(slotProps.option.uuid) ? 'network_running' : 'network_stopped')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Select>
|
||||||
<label class="network-label mr-2 font-medium" for="dd-inst-id">{{
|
<label class="network-label mr-2 font-medium" for="dd-inst-id">{{
|
||||||
t('web.device_management.network') }}</label>
|
t('web.device_management.network') }}</label>
|
||||||
</IftaLabel>
|
</IftaLabel>
|
||||||
@@ -379,23 +400,23 @@ onUnmounted(() => {
|
|||||||
<!-- 简化的按钮区域 - 无论屏幕大小都显示 -->
|
<!-- 简化的按钮区域 - 无论屏幕大小都显示 -->
|
||||||
<div class="flex gap-2 shrink-0 button-container items-center">
|
<div class="flex gap-2 shrink-0 button-container items-center">
|
||||||
<!-- Create/Cancel button based on state -->
|
<!-- Create/Cancel button based on state -->
|
||||||
<Button v-if="!isCreatingNetwork" @click="newNetwork" icon="pi pi-plus"
|
<Button v-if="!isEditingNetwork" @click="newNetwork" icon="pi pi-plus"
|
||||||
:label="screenWidth > 640 ? t('web.device_management.create_new') : undefined"
|
:label="screenWidth > 640 ? t('web.device_management.create_new') : undefined"
|
||||||
:class="['create-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
|
:class="['create-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
|
||||||
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
|
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
|
||||||
:tooltip="screenWidth <= 640 ? t('web.device_management.create_network') : undefined"
|
:tooltip="screenWidth <= 640 ? t('web.device_management.create_network') : undefined"
|
||||||
tooltipOptions="{ position: 'bottom' }" severity="primary" />
|
tooltipOptions="{ position: 'bottom' }" severity="primary" />
|
||||||
|
|
||||||
<Button v-else @click="cancelNetworkCreation" icon="pi pi-times"
|
<Button v-else @click="cancelEditNetwork" icon="pi pi-times"
|
||||||
:label="screenWidth > 640 ? t('web.device_management.cancel_creation') : undefined"
|
:label="screenWidth > 640 ? t('web.device_management.cancel_edit') : undefined"
|
||||||
:class="['cancel-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
|
:class="['cancel-button', screenWidth <= 640 ? 'p-button-icon-only' : '']"
|
||||||
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
|
:style="screenWidth <= 640 ? 'width: 3rem !important; height: 3rem !important; font-size: 1.2rem' : ''"
|
||||||
:tooltip="screenWidth <= 640 ? t('web.device_management.cancel_creation') : undefined"
|
:tooltip="screenWidth <= 640 ? t('web.device_management.cancel_edit') : undefined"
|
||||||
tooltipOptions="{ position: 'bottom' }" severity="secondary" />
|
tooltipOptions="{ position: 'bottom' }" severity="secondary" />
|
||||||
|
|
||||||
<!-- More actions menu -->
|
<!-- More actions menu -->
|
||||||
<Menu ref="menuRef" :model="actionMenu" :popup="true" />
|
<Menu ref="menuRef" :model="actionMenu" :popup="true" />
|
||||||
<Button v-if="!isCreatingNetwork && selectedInstanceId" icon="pi pi-ellipsis-v"
|
<Button v-if="!isEditingNetwork && selectedInstanceId" icon="pi pi-ellipsis-v"
|
||||||
class="p-button-rounded flex items-center justify-center" severity="help"
|
class="p-button-rounded flex items-center justify-center" severity="help"
|
||||||
style="width: 3rem !important; height: 3rem !important; font-size: 1.2rem"
|
style="width: 3rem !important; height: 3rem !important; font-size: 1.2rem"
|
||||||
@click="menuRef.toggle($event)" :aria-label="t('web.device_management.more_actions')"
|
@click="menuRef.toggle($event)" :aria-label="t('web.device_management.more_actions')"
|
||||||
@@ -407,11 +428,10 @@ onUnmounted(() => {
|
|||||||
<!-- Main Content Area -->
|
<!-- Main Content Area -->
|
||||||
<div class="network-content bg-surface-0 p-4 rounded-lg shadow-sm">
|
<div class="network-content bg-surface-0 p-4 rounded-lg shadow-sm">
|
||||||
<!-- Network Creation Form -->
|
<!-- Network Creation Form -->
|
||||||
<div v-if="isCreatingNetwork" class="network-creation-container">
|
<div v-if="isEditingNetwork || networkIsDisabled" class="network-creation-container">
|
||||||
<div class="network-creation-header flex items-center gap-2 mb-3">
|
<div class="network-creation-header flex items-center gap-2 mb-3">
|
||||||
<i class="pi pi-plus-circle text-primary text-xl"></i>
|
<i class="pi pi-plus-circle text-primary text-xl"></i>
|
||||||
<h2 class="text-xl font-medium">{{ isEditing ? t('web.device_management.edit_network') :
|
<h2 class="text-xl font-medium">{{ t('web.device_management.edit_network') }}</h2>
|
||||||
t('web.device_management.create_network') }}</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full flex gap-2 flex-wrap justify-start mb-3">
|
<div class="w-full flex gap-2 flex-wrap justify-start mb-3">
|
||||||
@@ -419,11 +439,13 @@ onUnmounted(() => {
|
|||||||
:label="t('web.device_management.edit_as_file')" iconPos="left" severity="secondary" />
|
:label="t('web.device_management.edit_as_file')" iconPos="left" severity="secondary" />
|
||||||
<Button @click="importConfig" icon="pi pi-upload" :label="t('web.device_management.import_config')"
|
<Button @click="importConfig" icon="pi pi-upload" :label="t('web.device_management.import_config')"
|
||||||
iconPos="left" severity="help" />
|
iconPos="left" severity="help" />
|
||||||
|
<Button v-if="networkIsDisabled" @click="saveNetworkConfig" icon="pi pi-save"
|
||||||
|
:label="t('web.device_management.save_config')" iconPos="left" severity="success" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<Config :cur-network="editingNetworkConfig" @run-network="saveAndRunNewNetwork"></Config>
|
<Config :cur-network="currentNetworkConfig" @run-network="saveAndRunNewNetwork"></Config>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Network Status (for running networks) -->
|
<!-- Network Status (for running networks) -->
|
||||||
@@ -433,7 +455,10 @@ onUnmounted(() => {
|
|||||||
<h2 class="text-xl font-medium">{{ t('web.device_management.network_status') }}</h2>
|
<h2 class="text-xl font-medium">{{ t('web.device_management.network_status') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Status v-bind:cur-network-inst="curNetworkInfo" class="mb-4"></Status>
|
<Status v-if="(curNetworkInfo?.error_msg ?? '') === ''" v-bind:cur-network-inst="curNetworkInfo"
|
||||||
|
class="mb-4">
|
||||||
|
</Status>
|
||||||
|
<Message v-else severity="error" class="mb-4">{{ curNetworkInfo?.error_msg }}</Message>
|
||||||
|
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
<Button @click="updateNetworkState(true)" :label="t('web.device_management.disable_network')"
|
<Button @click="updateNetworkState(true)" :label="t('web.device_management.disable_network')"
|
||||||
@@ -441,23 +466,6 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Network Configuration (for disabled networks) -->
|
|
||||||
<div v-else-if="networkIsDisabled" class="network-config-container">
|
|
||||||
<div class="network-config-header flex items-center gap-2 mb-3">
|
|
||||||
<i class="pi pi-cog text-secondary text-xl"></i>
|
|
||||||
<h2 class="text-xl font-medium">{{ t('web.device_management.network_configuration') }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="currentNetworkConfig" class="mb-4">
|
|
||||||
<Config :cur-network="currentNetworkConfig" @run-network="updateNetworkState(false)" />
|
|
||||||
</div>
|
|
||||||
<div v-else class="network-loading-placeholder text-center py-8">
|
|
||||||
<i class="pi pi-spin pi-spinner text-3xl text-primary mb-3"></i>
|
|
||||||
<div class="text-xl text-secondary">{{ t('web.device_management.loading_network_configuration') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- Empty State -->
|
||||||
<div v-else class="empty-state flex flex-col items-center py-12">
|
<div v-else class="empty-state flex flex-col items-center py-12">
|
||||||
<i class="pi pi-sitemap text-5xl text-secondary mb-4 opacity-50"></i>
|
<i class="pi pi-sitemap text-5xl text-secondary mb-4 opacity-50"></i>
|
||||||
@@ -475,8 +483,8 @@ onUnmounted(() => {
|
|||||||
<!-- <ConfigEditDialog v-if="networkIsDisabled" v-model:visible="showCreateNetworkDialog"
|
<!-- <ConfigEditDialog v-if="networkIsDisabled" v-model:visible="showCreateNetworkDialog"
|
||||||
:cur-network="currentNetworkConfig" :generate-config="generateConfig" :save-config="saveConfig" /> -->
|
:cur-network="currentNetworkConfig" :generate-config="generateConfig" :save-config="saveConfig" /> -->
|
||||||
|
|
||||||
<ConfigEditDialog v-model:visible="showConfigEditDialog" :cur-network="editingNetworkConfig"
|
<ConfigEditDialog v-model:visible="showConfigEditDialog" :cur-network="currentNetworkConfig"
|
||||||
:generate-config="generateConfig" :save-config="saveConfig" />
|
:generate-config="generateConfig" :save-config="syncTomlConfig" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -592,4 +600,5 @@ onUnmounted(() => {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -289,9 +289,11 @@ web:
|
|||||||
network: 网络
|
network: 网络
|
||||||
select_network: 选择网络
|
select_network: 选择网络
|
||||||
create_network: 创建网络
|
create_network: 创建网络
|
||||||
cancel_creation: 取消创建
|
cancel_edit: 取消编辑
|
||||||
more_actions: 更多操作
|
more_actions: 更多操作
|
||||||
edit_as_file: 编辑为文件
|
edit_as_file: 编辑为文件
|
||||||
|
save_config: 保存配置
|
||||||
|
config_saved: 配置已保存
|
||||||
import_config: 导入配置
|
import_config: 导入配置
|
||||||
create_new: 创建新网络
|
create_new: 创建新网络
|
||||||
network_status: 网络状态
|
network_status: 网络状态
|
||||||
|
|||||||
@@ -289,9 +289,11 @@ web:
|
|||||||
network: Network
|
network: Network
|
||||||
select_network: Select Network
|
select_network: Select Network
|
||||||
create_network: Create Network
|
create_network: Create Network
|
||||||
cancel_creation: Cancel Creation
|
cancel_edit: Cancel Edit
|
||||||
more_actions: More Actions
|
more_actions: More Actions
|
||||||
edit_as_file: Edit as File
|
edit_as_file: Edit as File
|
||||||
|
save_config: Save Config
|
||||||
|
config_saved: Config Saved
|
||||||
import_config: Import Config
|
import_config: Import Config
|
||||||
create_new: Create New Network
|
create_new: Create New Network
|
||||||
network_status: Network Status
|
network_status: Network Status
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UUID } from './utils';
|
import { UUID } from './utils';
|
||||||
import { NetworkConfig } from '../types/network';
|
import { NetworkConfig, NetworkInstanceRunningInfo } from '../types/network';
|
||||||
|
|
||||||
export interface ValidateConfigResponse {
|
export interface ValidateConfigResponse {
|
||||||
toml_config: string;
|
toml_config: string;
|
||||||
@@ -20,14 +20,21 @@ export interface ParseConfigResponse {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CollectNetworkInfoResponse {
|
||||||
|
info: {
|
||||||
|
map: Record<string, NetworkInstanceRunningInfo | undefined>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface RemoteClient {
|
export interface RemoteClient {
|
||||||
validate_config(config: any): Promise<ValidateConfigResponse>;
|
validate_config(config: NetworkConfig): Promise<ValidateConfigResponse>;
|
||||||
run_network(config: any): Promise<undefined>;
|
run_network(config: NetworkConfig): Promise<undefined>;
|
||||||
get_network_info(inst_id: string): Promise<any>;
|
get_network_info(inst_id: string): Promise<NetworkInstanceRunningInfo | undefined>;
|
||||||
list_network_instance_ids(): Promise<ListNetworkInstanceIdResponse>;
|
list_network_instance_ids(): Promise<ListNetworkInstanceIdResponse>;
|
||||||
delete_network(inst_id: string): Promise<undefined>;
|
delete_network(inst_id: string): Promise<undefined>;
|
||||||
update_network_instance_state(inst_id: string, disabled: boolean): Promise<undefined>;
|
update_network_instance_state(inst_id: string, disabled: boolean): Promise<undefined>;
|
||||||
get_network_config(inst_id: string): Promise<any>;
|
save_config(config: NetworkConfig): Promise<undefined>;
|
||||||
|
get_network_config(inst_id: string): Promise<NetworkConfig>;
|
||||||
generate_config(config: NetworkConfig): Promise<GenerateConfigResponse>;
|
generate_config(config: NetworkConfig): Promise<GenerateConfigResponse>;
|
||||||
parse_config(toml_config: string): Promise<ParseConfigResponse>;
|
parse_config(toml_config: string): Promise<ParseConfigResponse>;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||||
import { Md5 } from 'ts-md5'
|
import { type Api, type NetworkTypes, Utils } from 'easytier-frontend-lib';
|
||||||
import { Api, Utils } from 'easytier-frontend-lib';
|
import { Md5 } from 'ts-md5';
|
||||||
import { NetworkTypes } from 'easytier-frontend-lib';
|
|
||||||
|
|
||||||
export interface ValidateConfigResponse {
|
export interface ValidateConfigResponse {
|
||||||
toml_config: string;
|
toml_config: string;
|
||||||
@@ -188,20 +187,20 @@ class WebRemoteClient implements Api.RemoteClient {
|
|||||||
this.machine_id = machine_id;
|
this.machine_id = machine_id;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
async validate_config(config: any): Promise<Api.ValidateConfigResponse> {
|
async validate_config(config: NetworkTypes.NetworkConfig): Promise<Api.ValidateConfigResponse> {
|
||||||
const response = await this.client.post<any, ValidateConfigResponse>(`/machines/${this.machine_id}/validate-config`, {
|
const response = await this.client.post<NetworkTypes.NetworkConfig, ValidateConfigResponse>(`/machines/${this.machine_id}/validate-config`, {
|
||||||
config: config,
|
config: config,
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
async run_network(config: any): Promise<undefined> {
|
async run_network(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||||
await this.client.post<string>(`/machines/${this.machine_id}/networks`, {
|
await this.client.post<string>(`/machines/${this.machine_id}/networks`, {
|
||||||
config: config,
|
config: config,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async get_network_info(inst_id: string): Promise<any> {
|
async get_network_info(inst_id: string): Promise<NetworkTypes.NetworkInstanceRunningInfo | undefined> {
|
||||||
const response = await this.client.get<any, Record<string, any>>('/machines/' + this.machine_id + '/networks/info/' + inst_id);
|
const response = await this.client.get<any, Api.CollectNetworkInfoResponse>('/machines/' + this.machine_id + '/networks/info/' + inst_id);
|
||||||
return response.info.map;
|
return response.info.map[inst_id];
|
||||||
}
|
}
|
||||||
async list_network_instance_ids(): Promise<Api.ListNetworkInstanceIdResponse> {
|
async list_network_instance_ids(): Promise<Api.ListNetworkInstanceIdResponse> {
|
||||||
const response = await this.client.get<any, ListNetworkInstanceIdResponse>('/machines/' + this.machine_id + '/networks');
|
const response = await this.client.get<any, ListNetworkInstanceIdResponse>('/machines/' + this.machine_id + '/networks');
|
||||||
@@ -215,8 +214,11 @@ class WebRemoteClient implements Api.RemoteClient {
|
|||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async get_network_config(inst_id: string): Promise<any> {
|
async save_config(config: NetworkTypes.NetworkConfig): Promise<undefined> {
|
||||||
const response = await this.client.get<any, Record<string, any>>('/machines/' + this.machine_id + '/networks/config/' + inst_id);
|
await this.client.put(`/machines/${this.machine_id}/networks/config/${config.instance_id}`, { config });
|
||||||
|
}
|
||||||
|
async get_network_config(inst_id: string): Promise<NetworkTypes.NetworkConfig> {
|
||||||
|
const response = await this.client.get<any, NetworkTypes.NetworkConfig>('/machines/' + this.machine_id + '/networks/config/' + inst_id);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
async generate_config(config: NetworkTypes.NetworkConfig): Promise<Api.GenerateConfigResponse> {
|
async generate_config(config: NetworkTypes.NetworkConfig): Promise<Api.GenerateConfigResponse> {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||||
|
|
||||||
use easytier::rpc_service::remote_client::PersistentConfig;
|
use easytier::{launcher::NetworkConfig, rpc_service::remote_client::PersistentConfig};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -41,11 +41,11 @@ impl Related<super::users::Entity> for Entity {
|
|||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl PersistentConfig for Model {
|
impl PersistentConfig<DbErr> for Model {
|
||||||
fn get_network_inst_id(&self) -> &str {
|
fn get_network_inst_id(&self) -> &str {
|
||||||
&self.network_instance_id
|
&self.network_instance_id
|
||||||
}
|
}
|
||||||
fn get_network_config(&self) -> &str {
|
fn get_network_config(&self) -> Result<NetworkConfig, DbErr> {
|
||||||
&self.network_config
|
serde_json::from_str(&self.network_config).map_err(|e| DbErr::Json(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
pub mod entity;
|
pub mod entity;
|
||||||
|
|
||||||
use easytier::rpc_service::remote_client::{ListNetworkProps, Storage};
|
use easytier::{
|
||||||
|
launcher::NetworkConfig,
|
||||||
|
rpc_service::remote_client::{ListNetworkProps, Storage},
|
||||||
|
};
|
||||||
use entity::user_running_network_configs;
|
use entity::user_running_network_configs;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
||||||
@@ -94,7 +97,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
|||||||
&self,
|
&self,
|
||||||
(user_id, device_id): (UserIdInDb, Uuid),
|
(user_id, device_id): (UserIdInDb, Uuid),
|
||||||
network_inst_id: Uuid,
|
network_inst_id: Uuid,
|
||||||
network_config: impl ToString + Send,
|
network_config: NetworkConfig,
|
||||||
) -> Result<(), DbErr> {
|
) -> Result<(), DbErr> {
|
||||||
let txn = self.orm_db().begin().await?;
|
let txn = self.orm_db().begin().await?;
|
||||||
|
|
||||||
@@ -111,7 +114,9 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
|||||||
user_id: sea_orm::Set(user_id),
|
user_id: sea_orm::Set(user_id),
|
||||||
device_id: sea_orm::Set(device_id.to_string()),
|
device_id: sea_orm::Set(device_id.to_string()),
|
||||||
network_instance_id: sea_orm::Set(network_inst_id.to_string()),
|
network_instance_id: sea_orm::Set(network_inst_id.to_string()),
|
||||||
network_config: sea_orm::Set(network_config.to_string()),
|
network_config: sea_orm::Set(
|
||||||
|
serde_json::to_string(&network_config).map_err(|e| DbErr::Json(e.to_string()))?,
|
||||||
|
),
|
||||||
disabled: sea_orm::Set(false),
|
disabled: sea_orm::Set(false),
|
||||||
create_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
create_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
||||||
update_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
update_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
||||||
@@ -126,16 +131,19 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
|||||||
txn.commit().await
|
txn.commit().await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_network_config(
|
async fn delete_network_configs(
|
||||||
&self,
|
&self,
|
||||||
(user_id, _): (UserIdInDb, Uuid),
|
(user_id, _): (UserIdInDb, Uuid),
|
||||||
network_inst_id: Uuid,
|
network_inst_ids: &[Uuid],
|
||||||
) -> Result<(), DbErr> {
|
) -> Result<(), DbErr> {
|
||||||
use entity::user_running_network_configs as urnc;
|
use entity::user_running_network_configs as urnc;
|
||||||
|
|
||||||
urnc::Entity::delete_many()
|
urnc::Entity::delete_many()
|
||||||
.filter(urnc::Column::UserId.eq(user_id))
|
.filter(urnc::Column::UserId.eq(user_id))
|
||||||
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
|
.filter(
|
||||||
|
urnc::Column::NetworkInstanceId
|
||||||
|
.is_in(network_inst_ids.iter().map(|id| id.to_string())),
|
||||||
|
)
|
||||||
.exec(self.orm_db())
|
.exec(self.orm_db())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -220,7 +228,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use easytier::rpc_service::remote_client::Storage;
|
use easytier::{proto::api::manage::NetworkConfig, rpc_service::remote_client::Storage};
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
||||||
|
|
||||||
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
|
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
|
||||||
@@ -229,7 +237,11 @@ mod tests {
|
|||||||
async fn test_user_network_config_management() {
|
async fn test_user_network_config_management() {
|
||||||
let db = Db::memory_db().await;
|
let db = Db::memory_db().await;
|
||||||
let user_id = 1;
|
let user_id = 1;
|
||||||
let network_config = "test_config";
|
let network_config = NetworkConfig {
|
||||||
|
network_name: Some("test_config".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let network_config_json = serde_json::to_string(&network_config).unwrap();
|
||||||
let inst_id = uuid::Uuid::new_v4();
|
let inst_id = uuid::Uuid::new_v4();
|
||||||
let device_id = uuid::Uuid::new_v4();
|
let device_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
@@ -244,10 +256,14 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("{:?}", result);
|
println!("{:?}", result);
|
||||||
assert_eq!(result.network_config, network_config);
|
assert_eq!(result.network_config, network_config_json);
|
||||||
|
|
||||||
// overwrite the config
|
// overwrite the config
|
||||||
let network_config = "test_config2";
|
let network_config = NetworkConfig {
|
||||||
|
network_name: Some("test_config2".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let network_config_json = serde_json::to_string(&network_config).unwrap();
|
||||||
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
|
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -259,7 +275,7 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("device: {}, {:?}", device_id, result2);
|
println!("device: {}, {:?}", device_id, result2);
|
||||||
assert_eq!(result2.network_config, network_config);
|
assert_eq!(result2.network_config, network_config_json);
|
||||||
|
|
||||||
assert_eq!(result.create_time, result2.create_time);
|
assert_eq!(result.create_time, result2.create_time);
|
||||||
assert_ne!(result.update_time, result2.update_time);
|
assert_ne!(result.update_time, result2.update_time);
|
||||||
@@ -272,7 +288,7 @@ mod tests {
|
|||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
db.delete_network_config((user_id, device_id), inst_id)
|
db.delete_network_configs((user_id, device_id), &[inst_id])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let result3 = user_running_network_configs::Entity::find()
|
let result3 = user_running_network_configs::Entity::find()
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ struct ValidateConfigJsonReq {
|
|||||||
config: NetworkConfig,
|
config: NetworkConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct SaveNetworkJsonReq {
|
||||||
|
config: NetworkConfig,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
struct RunNetworkJsonReq {
|
struct RunNetworkJsonReq {
|
||||||
config: NetworkConfig,
|
config: NetworkConfig,
|
||||||
@@ -177,9 +182,9 @@ impl NetworkApi {
|
|||||||
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
) -> Result<(), HttpHandleError> {
|
) -> Result<(), HttpHandleError> {
|
||||||
client_mgr
|
client_mgr
|
||||||
.handle_remove_network_instance(
|
.handle_remove_network_instances(
|
||||||
(Self::get_user_id(&auth_session)?, machine_id),
|
(Self::get_user_id(&auth_session)?, machine_id),
|
||||||
inst_id,
|
vec![inst_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(convert_error)
|
.map_err(convert_error)
|
||||||
@@ -232,6 +237,28 @@ impl NetworkApi {
|
|||||||
.map_err(convert_error)
|
.map_err(convert_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_save_network_config(
|
||||||
|
auth_session: AuthSession,
|
||||||
|
State(client_mgr): AppState,
|
||||||
|
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||||
|
Json(payload): Json<SaveNetworkJsonReq>,
|
||||||
|
) -> Result<(), HttpHandleError> {
|
||||||
|
if payload.config.instance_id() != inst_id.to_string() {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
other_error("Instance ID mismatch".to_string()).into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
client_mgr
|
||||||
|
.handle_save_network_config(
|
||||||
|
(Self::get_user_id(&auth_session)?, machine_id),
|
||||||
|
inst_id,
|
||||||
|
payload.config,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(convert_error)
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_get_network_config(
|
async fn handle_get_network_config(
|
||||||
auth_session: AuthSession,
|
auth_session: AuthSession,
|
||||||
State(client_mgr): AppState,
|
State(client_mgr): AppState,
|
||||||
@@ -269,7 +296,7 @@ impl NetworkApi {
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/v1/machines/:machine-id/networks/config/:inst-id",
|
"/api/v1/machines/:machine-id/networks/config/:inst-id",
|
||||||
get(Self::handle_get_network_config),
|
get(Self::handle_get_network_config).put(Self::handle_save_network_config),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,17 +474,16 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_ip(&self, name: &str, ip: Option<Ipv4Inet>) -> Result<(), Error> {
|
async fn remove_ip(&self, name: &str, ip: Option<Ipv4Inet>) -> Result<(), Error> {
|
||||||
if ip.is_none() {
|
if let Some(ip) = ip {
|
||||||
|
let prefix_len = Self::get_prefix_len(name, ip.address())?;
|
||||||
|
Self::remove_one_ip(name, ip.address(), prefix_len)?;
|
||||||
|
} else {
|
||||||
let addrs = Self::list_addresses(name)?;
|
let addrs = Self::list_addresses(name)?;
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
if let IpAddr::V4(ipv4) = addr.address() {
|
if let IpAddr::V4(ipv4) = addr.address() {
|
||||||
Self::remove_one_ip(name, ipv4, addr.network_length())?;
|
Self::remove_one_ip(name, ipv4, addr.network_length())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let ip = ip.unwrap();
|
|
||||||
let prefix_len = Self::get_prefix_len(name, ip.address())?;
|
|
||||||
Self::remove_one_ip(name, ip.address(), prefix_len)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -520,7 +519,10 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_ipv6(&self, name: &str, ip: Option<Ipv6Inet>) -> Result<(), Error> {
|
async fn remove_ipv6(&self, name: &str, ip: Option<Ipv6Inet>) -> Result<(), Error> {
|
||||||
if ip.is_none() {
|
if let Some(ipv6) = ip {
|
||||||
|
let prefix_len = Self::get_prefix_len_ipv6(name, ipv6.address())?;
|
||||||
|
Self::remove_one_ipv6(name, ipv6.address(), prefix_len)?;
|
||||||
|
} else {
|
||||||
let addrs = Self::list_addresses(name)?;
|
let addrs = Self::list_addresses(name)?;
|
||||||
for addr in addrs {
|
for addr in addrs {
|
||||||
if let IpAddr::V6(ipv6) = addr.address() {
|
if let IpAddr::V6(ipv6) = addr.address() {
|
||||||
@@ -528,10 +530,6 @@ impl IfConfiguerTrait for NetlinkIfConfiger {
|
|||||||
Self::remove_one_ipv6(name, ipv6, prefix_len)?;
|
Self::remove_one_ipv6(name, ipv6, prefix_len)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let ipv6 = ip.unwrap();
|
|
||||||
let prefix_len = Self::get_prefix_len_ipv6(name, ipv6.address())?;
|
|
||||||
Self::remove_one_ipv6(name, ipv6.address(), prefix_len)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct NetworkInstanceManager {
|
|||||||
instance_map: Arc<DashMap<uuid::Uuid, NetworkInstance>>,
|
instance_map: Arc<DashMap<uuid::Uuid, NetworkInstance>>,
|
||||||
instance_stop_tasks: Arc<DashMap<uuid::Uuid, ScopedTask<()>>>,
|
instance_stop_tasks: Arc<DashMap<uuid::Uuid, ScopedTask<()>>>,
|
||||||
stop_check_notifier: Arc<tokio::sync::Notify>,
|
stop_check_notifier: Arc<tokio::sync::Notify>,
|
||||||
|
instance_error_messages: Arc<DashMap<uuid::Uuid, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NetworkInstanceManager {
|
impl Default for NetworkInstanceManager {
|
||||||
@@ -31,6 +32,7 @@ impl NetworkInstanceManager {
|
|||||||
instance_map: Arc::new(DashMap::new()),
|
instance_map: Arc::new(DashMap::new()),
|
||||||
instance_stop_tasks: Arc::new(DashMap::new()),
|
instance_stop_tasks: Arc::new(DashMap::new()),
|
||||||
stop_check_notifier: Arc::new(tokio::sync::Notify::new()),
|
stop_check_notifier: Arc::new(tokio::sync::Notify::new()),
|
||||||
|
instance_error_messages: Arc::new(DashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ impl NetworkInstanceManager {
|
|||||||
|
|
||||||
let instance_map = self.instance_map.clone();
|
let instance_map = self.instance_map.clone();
|
||||||
let instance_stop_tasks = self.instance_stop_tasks.clone();
|
let instance_stop_tasks = self.instance_stop_tasks.clone();
|
||||||
|
let instance_error_messages = self.instance_error_messages.clone();
|
||||||
|
|
||||||
let stop_check_notifier = self.stop_check_notifier.clone();
|
let stop_check_notifier = self.stop_check_notifier.clone();
|
||||||
self.instance_stop_tasks.insert(
|
self.instance_stop_tasks.insert(
|
||||||
@@ -80,6 +83,7 @@ impl NetworkInstanceManager {
|
|||||||
if let Some(e) = instance.get_latest_error_msg() {
|
if let Some(e) = instance.get_latest_error_msg() {
|
||||||
tracing::error!(?e, ?instance_id, "instance stopped with error");
|
tracing::error!(?e, ?instance_id, "instance stopped with error");
|
||||||
eprintln!("instance {} stopped with error: {}", instance_id, e);
|
eprintln!("instance {} stopped with error: {}", instance_id, e);
|
||||||
|
instance_error_messages.insert(instance_id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stop_check_notifier.notify_one();
|
stop_check_notifier.notify_one();
|
||||||
@@ -114,6 +118,9 @@ impl NetworkInstanceManager {
|
|||||||
) -> Result<Vec<uuid::Uuid>, anyhow::Error> {
|
) -> Result<Vec<uuid::Uuid>, anyhow::Error> {
|
||||||
self.instance_map.retain(|k, _| instance_ids.contains(k));
|
self.instance_map.retain(|k, _| instance_ids.contains(k));
|
||||||
self.instance_map.shrink_to_fit();
|
self.instance_map.shrink_to_fit();
|
||||||
|
self.instance_error_messages
|
||||||
|
.retain(|k, _| instance_ids.contains(k));
|
||||||
|
self.instance_error_messages.shrink_to_fit();
|
||||||
Ok(self.list_network_instance_ids())
|
Ok(self.list_network_instance_ids())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +130,9 @@ impl NetworkInstanceManager {
|
|||||||
) -> Result<Vec<uuid::Uuid>, anyhow::Error> {
|
) -> Result<Vec<uuid::Uuid>, anyhow::Error> {
|
||||||
self.instance_map.retain(|k, _| !instance_ids.contains(k));
|
self.instance_map.retain(|k, _| !instance_ids.contains(k));
|
||||||
self.instance_map.shrink_to_fit();
|
self.instance_map.shrink_to_fit();
|
||||||
|
self.instance_error_messages
|
||||||
|
.retain(|k, _| !instance_ids.contains(k));
|
||||||
|
self.instance_error_messages.shrink_to_fit();
|
||||||
Ok(self.list_network_instance_ids())
|
Ok(self.list_network_instance_ids())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +145,15 @@ impl NetworkInstanceManager {
|
|||||||
ret.insert(*instance.key(), info);
|
ret.insert(*instance.key(), info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for v in self.instance_error_messages.iter() {
|
||||||
|
ret.insert(
|
||||||
|
*v.key(),
|
||||||
|
NetworkInstanceRunningInfo {
|
||||||
|
error_msg: Some(v.value().clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +167,12 @@ impl NetworkInstanceManager {
|
|||||||
&self,
|
&self,
|
||||||
instance_id: &uuid::Uuid,
|
instance_id: &uuid::Uuid,
|
||||||
) -> Option<NetworkInstanceRunningInfo> {
|
) -> Option<NetworkInstanceRunningInfo> {
|
||||||
|
if let Some(err_msg) = self.instance_error_messages.get(instance_id) {
|
||||||
|
return Some(NetworkInstanceRunningInfo {
|
||||||
|
error_msg: Some(err_msg.value().clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
self.instance_map
|
self.instance_map
|
||||||
.get(instance_id)?
|
.get(instance_id)?
|
||||||
.get_running_info()
|
.get_running_info()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl DirectConnectorRpc for DirectConnectorManagerRpcServer {
|
|||||||
.config
|
.config
|
||||||
.get_mapped_listeners()
|
.get_mapped_listeners()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(self.global_ctx.get_running_listeners().into_iter())
|
.chain(self.global_ctx.get_running_listeners())
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
.collect();
|
.collect();
|
||||||
// remove et ipv6 from the interface ipv6 list
|
// remove et ipv6 from the interface ipv6 list
|
||||||
|
|||||||
@@ -27,36 +27,52 @@ use crate::{
|
|||||||
peer_manage::PeerManageRpcService, port_forward_manage::PortForwardManageRpcService,
|
peer_manage::PeerManageRpcService, port_forward_manage::PortForwardManageRpcService,
|
||||||
proxy::TcpProxyRpcService, stats::StatsRpcService, vpn_portal::VpnPortalRpcService,
|
proxy::TcpProxyRpcService, stats::StatsRpcService, vpn_portal::VpnPortalRpcService,
|
||||||
},
|
},
|
||||||
tunnel::tcp::TcpTunnelListener,
|
tunnel::{tcp::TcpTunnelListener, TunnelListener},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ApiRpcServer {
|
pub struct ApiRpcServer<T: TunnelListener + 'static> {
|
||||||
rpc_server: StandAloneServer<TcpTunnelListener>,
|
rpc_server: StandAloneServer<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiRpcServer {
|
impl ApiRpcServer<TcpTunnelListener> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
rpc_portal: Option<String>,
|
rpc_portal: Option<String>,
|
||||||
rpc_portal_whitelist: Option<Vec<IpCidr>>,
|
rpc_portal_whitelist: Option<Vec<IpCidr>>,
|
||||||
instance_manager: Arc<NetworkInstanceManager>,
|
instance_manager: Arc<NetworkInstanceManager>,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let mut rpc_server = StandAloneServer::new(TcpTunnelListener::new(
|
let mut server = Self::from_tunnel(
|
||||||
format!("tcp://{}", parse_rpc_portal(rpc_portal)?)
|
TcpTunnelListener::new(
|
||||||
.parse()
|
format!("tcp://{}", parse_rpc_portal(rpc_portal)?)
|
||||||
.context("failed to parse rpc portal address")?,
|
.parse()
|
||||||
));
|
.context("failed to parse rpc portal address")?,
|
||||||
rpc_server.set_hook(Arc::new(InstanceRpcServerHook::new(rpc_portal_whitelist)));
|
),
|
||||||
register_api_rpc_service(&instance_manager, rpc_server.registry());
|
instance_manager,
|
||||||
Ok(Self { rpc_server })
|
);
|
||||||
}
|
|
||||||
|
|
||||||
|
server
|
||||||
|
.rpc_server
|
||||||
|
.set_hook(Arc::new(InstanceRpcServerHook::new(rpc_portal_whitelist)));
|
||||||
|
|
||||||
|
Ok(server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TunnelListener + 'static> ApiRpcServer<T> {
|
||||||
|
pub fn from_tunnel(tunnel: T, instance_manager: Arc<NetworkInstanceManager>) -> Self {
|
||||||
|
let rpc_server = StandAloneServer::new(tunnel);
|
||||||
|
register_api_rpc_service(&instance_manager, rpc_server.registry());
|
||||||
|
Self { rpc_server }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TunnelListener + 'static> ApiRpcServer<T> {
|
||||||
pub async fn serve(mut self) -> Result<Self, Error> {
|
pub async fn serve(mut self) -> Result<Self, Error> {
|
||||||
self.rpc_server.serve().await?;
|
self.rpc_server.serve().await?;
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ApiRpcServer {
|
impl<T: TunnelListener + 'static> Drop for ApiRpcServer<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.rpc_server.registry().unregister_all();
|
self.rpc_server.registry().unregister_all();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ pub mod instance_manage;
|
|||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod remote_client;
|
pub mod remote_client;
|
||||||
|
|
||||||
pub type ApiRpcServer = self::api::ApiRpcServer;
|
pub type ApiRpcServer<T> = self::api::ApiRpcServer<T>;
|
||||||
|
|
||||||
pub trait InstanceRpcService: Sync + Send {
|
pub trait InstanceRpcService: Sync + Send {
|
||||||
fn get_peer_manage_service(
|
fn get_peer_manage_service(
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use crate::proto::{api::manage::*, rpc_types::controller::BaseController};
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait RemoteClientManager<T, C, E>
|
pub trait RemoteClientManager<T, C, E>
|
||||||
where
|
where
|
||||||
T: Copy + Send + 'static,
|
T: Clone + Send + 'static,
|
||||||
C: PersistentConfig + Send + 'static,
|
C: PersistentConfig<E> + Send + 'static,
|
||||||
E: Send + 'static,
|
E: Send + 'static,
|
||||||
{
|
{
|
||||||
fn get_rpc_client(
|
fn get_rpc_client(
|
||||||
@@ -42,17 +42,14 @@ where
|
|||||||
config: NetworkConfig,
|
config: NetworkConfig,
|
||||||
) -> Result<(), RemoteClientError<E>> {
|
) -> Result<(), RemoteClientError<E>> {
|
||||||
let client = self
|
let client = self
|
||||||
.get_rpc_client(identify)
|
.get_rpc_client(identify.clone())
|
||||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||||
let network_config_json = serde_json::to_string(&config).map_err(|e| {
|
|
||||||
RemoteClientError::Other(format!("Failed to serialize config: {:?}", e))
|
|
||||||
})?;
|
|
||||||
let resp = client
|
let resp = client
|
||||||
.run_network_instance(
|
.run_network_instance(
|
||||||
BaseController::default(),
|
BaseController::default(),
|
||||||
RunNetworkInstanceRequest {
|
RunNetworkInstanceRequest {
|
||||||
inst_id: None,
|
inst_id: None,
|
||||||
config: Some(config),
|
config: Some(config.clone()),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -61,7 +58,7 @@ where
|
|||||||
.insert_or_update_user_network_config(
|
.insert_or_update_user_network_config(
|
||||||
identify,
|
identify,
|
||||||
resp.inst_id.unwrap_or_default().into(),
|
resp.inst_id.unwrap_or_default().into(),
|
||||||
network_config_json,
|
config,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(RemoteClientError::PersistentError)?;
|
.map_err(RemoteClientError::PersistentError)?;
|
||||||
@@ -98,7 +95,7 @@ where
|
|||||||
identify: T,
|
identify: T,
|
||||||
) -> Result<ListNetworkInstanceIdsJsonResp, RemoteClientError<E>> {
|
) -> Result<ListNetworkInstanceIdsJsonResp, RemoteClientError<E>> {
|
||||||
let client = self
|
let client = self
|
||||||
.get_rpc_client(identify)
|
.get_rpc_client(identify.clone())
|
||||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||||
let ret = client
|
let ret = client
|
||||||
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
||||||
@@ -122,16 +119,19 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_remove_network_instance(
|
async fn handle_remove_network_instances(
|
||||||
&self,
|
&self,
|
||||||
identify: T,
|
identify: T,
|
||||||
inst_id: uuid::Uuid,
|
inst_ids: Vec<uuid::Uuid>,
|
||||||
) -> Result<(), RemoteClientError<E>> {
|
) -> Result<(), RemoteClientError<E>> {
|
||||||
|
if inst_ids.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
let client = self
|
let client = self
|
||||||
.get_rpc_client(identify)
|
.get_rpc_client(identify.clone())
|
||||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||||
self.get_storage()
|
self.get_storage()
|
||||||
.delete_network_config(identify, inst_id)
|
.delete_network_configs(identify, &inst_ids)
|
||||||
.await
|
.await
|
||||||
.map_err(RemoteClientError::PersistentError)?;
|
.map_err(RemoteClientError::PersistentError)?;
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ where
|
|||||||
.delete_network_instance(
|
.delete_network_instance(
|
||||||
BaseController::default(),
|
BaseController::default(),
|
||||||
DeleteNetworkInstanceRequest {
|
DeleteNetworkInstanceRequest {
|
||||||
inst_ids: vec![inst_id.into()],
|
inst_ids: inst_ids.into_iter().map(|id| id.into()).collect(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -154,7 +154,7 @@ where
|
|||||||
disabled: bool,
|
disabled: bool,
|
||||||
) -> Result<(), RemoteClientError<E>> {
|
) -> Result<(), RemoteClientError<E>> {
|
||||||
let client = self
|
let client = self
|
||||||
.get_rpc_client(identify)
|
.get_rpc_client(identify.clone())
|
||||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||||
let cfg = self
|
let cfg = self
|
||||||
.get_storage()
|
.get_storage()
|
||||||
@@ -177,14 +177,10 @@ where
|
|||||||
BaseController::default(),
|
BaseController::default(),
|
||||||
RunNetworkInstanceRequest {
|
RunNetworkInstanceRequest {
|
||||||
inst_id: Some(inst_id.into()),
|
inst_id: Some(inst_id.into()),
|
||||||
config: Some(serde_json::from_str(cfg.get_network_config()).map_err(
|
config: Some(
|
||||||
|e| {
|
cfg.get_network_config()
|
||||||
RemoteClientError::Other(format!(
|
.map_err(RemoteClientError::PersistentError)?,
|
||||||
"Failed to parse network config: {:?}",
|
),
|
||||||
e
|
|
||||||
))
|
|
||||||
},
|
|
||||||
)?),
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -193,6 +189,23 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_save_network_config(
|
||||||
|
&self,
|
||||||
|
identify: T,
|
||||||
|
inst_id: uuid::Uuid,
|
||||||
|
config: NetworkConfig,
|
||||||
|
) -> Result<(), RemoteClientError<E>> {
|
||||||
|
self.get_storage()
|
||||||
|
.insert_or_update_user_network_config(identify.clone(), inst_id, config)
|
||||||
|
.await
|
||||||
|
.map_err(RemoteClientError::PersistentError)?;
|
||||||
|
self.get_storage()
|
||||||
|
.update_network_config_state(identify, inst_id, true)
|
||||||
|
.await
|
||||||
|
.map_err(RemoteClientError::PersistentError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_get_network_config(
|
async fn handle_get_network_config(
|
||||||
&self,
|
&self,
|
||||||
identify: T,
|
identify: T,
|
||||||
@@ -210,21 +223,23 @@ where
|
|||||||
inst_id
|
inst_id
|
||||||
)))?;
|
)))?;
|
||||||
|
|
||||||
Ok(
|
Ok(db_row
|
||||||
serde_json::from_str::<NetworkConfig>(db_row.get_network_config()).map_err(|e| {
|
.get_network_config()
|
||||||
RemoteClientError::Other(format!("Failed to parse network config: {:?}", e))
|
.map_err(RemoteClientError::PersistentError)?)
|
||||||
})?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum RemoteClientError<E> {
|
pub enum RemoteClientError<E> {
|
||||||
|
#[error("Client not found")]
|
||||||
ClientNotFound,
|
ClientNotFound,
|
||||||
|
#[error("Not found: {0}")]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RpcError(#[from] crate::proto::rpc_types::error::Error),
|
RpcError(#[from] crate::proto::rpc_types::error::Error),
|
||||||
|
#[error(transparent)]
|
||||||
PersistentError(E),
|
PersistentError(E),
|
||||||
|
#[error("Other error: {0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,24 +255,25 @@ pub struct ListNetworkInstanceIdsJsonResp {
|
|||||||
disabled_inst_ids: Vec<crate::proto::common::Uuid>,
|
disabled_inst_ids: Vec<crate::proto::common::Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PersistentConfig {
|
pub trait PersistentConfig<E> {
|
||||||
fn get_network_inst_id(&self) -> &str;
|
fn get_network_inst_id(&self) -> &str;
|
||||||
fn get_network_config(&self) -> &str;
|
fn get_network_config(&self) -> Result<NetworkConfig, E>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Storage<T, C, E>: Send + Sync
|
pub trait Storage<T, C, E>: Send + Sync
|
||||||
where
|
where
|
||||||
C: PersistentConfig,
|
C: PersistentConfig<E>,
|
||||||
{
|
{
|
||||||
async fn insert_or_update_user_network_config(
|
async fn insert_or_update_user_network_config(
|
||||||
&self,
|
&self,
|
||||||
identify: T,
|
identify: T,
|
||||||
network_inst_id: Uuid,
|
network_inst_id: Uuid,
|
||||||
network_config: impl ToString + Send,
|
network_config: NetworkConfig,
|
||||||
) -> Result<(), E>;
|
) -> Result<(), E>;
|
||||||
|
|
||||||
async fn delete_network_config(&self, identify: T, network_inst_id: Uuid) -> Result<(), E>;
|
async fn delete_network_configs(&self, identify: T, network_inst_ids: &[Uuid])
|
||||||
|
-> Result<(), E>;
|
||||||
|
|
||||||
async fn update_network_config_state(
|
async fn update_network_config_state(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Reference in New Issue
Block a user