feat(gui): GUI add support to connect to config server (#1596)

This commit is contained in:
Mg Pig
2025-12-04 23:05:36 +08:00
committed by GitHub
parent 53f279f5ff
commit 0a718163fd
15 changed files with 458 additions and 105 deletions

View File

@@ -1154,6 +1154,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
cli.machine_id.clone(),
cli.network_options.hostname.clone(),
manager.clone(),
None,
)
.await
.inspect(|_| {

View File

@@ -28,6 +28,7 @@ use crate::{
proxy::TcpProxyRpcService, stats::StatsRpcService, vpn_portal::VpnPortalRpcService,
},
tunnel::{tcp::TcpTunnelListener, TunnelListener},
web_client::DefaultHooks,
};
pub struct ApiRpcServer<T: TunnelListener + 'static> {
@@ -142,7 +143,10 @@ fn register_api_rpc_service(
);
registry.register(
WebClientServiceServer::new(InstanceManageRpcService::new(instance_manager.clone())),
WebClientServiceServer::new(InstanceManageRpcService::new(
instance_manager.clone(),
Arc::new(DefaultHooks),
)),
"",
);
}

View File

@@ -7,16 +7,18 @@ use crate::{
api::{config::GetConfigRequest, manage::*},
rpc_types::{self, controller::BaseController},
},
web_client::WebClientHooks,
};
#[derive(Clone)]
pub struct InstanceManageRpcService {
manager: Arc<NetworkInstanceManager>,
hooks: Arc<dyn WebClientHooks>,
}
impl InstanceManageRpcService {
pub fn new(manager: Arc<NetworkInstanceManager>) -> Self {
Self { manager }
pub fn new(manager: Arc<NetworkInstanceManager>, hooks: Arc<dyn WebClientHooks>) -> Self {
Self { manager, hooks }
}
}
@@ -51,7 +53,14 @@ impl WebClientService for InstanceManageRpcService {
};
let mut control = if let Some(control) = self.manager.get_instance_config_control(&id) {
if !req.overwrite {
let error_msg = self
.manager
.get_network_info(&id)
.await
.and_then(|i| i.error_msg)
.unwrap_or_default();
if !req.overwrite && error_msg.is_empty() {
return Ok(resp);
}
if control.is_read_only() {
@@ -96,8 +105,17 @@ impl WebClientService for InstanceManageRpcService {
}
}
if let Err(e) = self.hooks.pre_run_network_instance(&cfg).await {
return Err(anyhow::anyhow!("pre-run hook failed: {}", e).into());
}
self.manager.run_network_instance(cfg, true, control)?;
println!("instance {} started", id);
if let Err(e) = self.hooks.post_run_network_instance(&id).await {
tracing::warn!("post-run hook failed: {}", e);
}
Ok(resp)
}
@@ -173,6 +191,9 @@ impl WebClientService for InstanceManageRpcService {
req: DeleteNetworkInstanceRequest,
) -> Result<DeleteNetworkInstanceResponse, rpc_types::error::Error> {
let inst_ids: HashSet<uuid::Uuid> = req.inst_ids.into_iter().map(Into::into).collect();
let hook_ids: Vec<uuid::Uuid> = inst_ids.iter().cloned().collect();
let inst_ids = self
.manager
.iter()
@@ -190,6 +211,11 @@ impl WebClientService for InstanceManageRpcService {
.collect::<Vec<_>>();
let remain_inst_ids = self.manager.delete_network_instance(inst_ids)?;
println!("instance {:?} retained", remain_inst_ids);
if let Err(e) = self.hooks.post_remove_network_instances(&hook_ids).await {
tracing::warn!("post-remove hook failed: {}", e);
}
for config_file in config_files {
if let Err(e) = std::fs::remove_file(&config_file) {
tracing::warn!(

View File

@@ -110,11 +110,12 @@ where
// collect networks that are disabled
let disabled_inst_ids = self
.get_storage()
.list_network_configs(identify, ListNetworkProps::DisabledOnly)
.list_network_configs(identify, ListNetworkProps::All)
.await
.map_err(RemoteClientError::PersistentError)?
.iter()
.map(|x| Into::<crate::proto::common::Uuid>::into(x.get_network_inst_id().to_string()))
.filter(|id| !ret.inst_ids.contains(id))
.collect::<Vec<_>>();
Ok(ListNetworkInstanceIdsJsonResp {

View File

@@ -2,21 +2,28 @@ use std::sync::Arc;
use crate::{
instance_manager::NetworkInstanceManager,
rpc_service::instance_manage::InstanceManageRpcService,
rpc_service::instance_manage::InstanceManageRpcService, web_client::WebClientHooks,
};
pub struct Controller {
token: String,
hostname: String,
manager: Arc<NetworkInstanceManager>,
hooks: Arc<dyn WebClientHooks>,
}
impl Controller {
pub fn new(token: String, hostname: String, manager: Arc<NetworkInstanceManager>) -> Self {
pub fn new(
token: String,
hostname: String,
manager: Arc<NetworkInstanceManager>,
hooks: Arc<dyn WebClientHooks>,
) -> Self {
Controller {
token,
hostname,
manager,
hooks,
}
}
@@ -33,7 +40,7 @@ impl Controller {
}
pub fn get_rpc_service(&self) -> InstanceManageRpcService {
InstanceManageRpcService::new(self.manager.clone())
InstanceManageRpcService::new(self.manager.clone(), self.hooks.clone())
}
pub(super) fn notify_manager_stopping(&self) {

View File

@@ -11,15 +11,40 @@ use crate::{
tunnel::{IpVersion, TunnelConnector},
};
use anyhow::{Context as _, Result};
use async_trait::async_trait;
use url::Url;
use uuid::Uuid;
#[async_trait]
pub trait WebClientHooks: Send + Sync {
async fn pre_run_network_instance(&self, _cfg: &TomlConfigLoader) -> Result<(), String> {
Ok(())
}
async fn post_run_network_instance(&self, _id: &Uuid) -> Result<(), String> {
Ok(())
}
async fn post_remove_network_instances(&self, _ids: &[Uuid]) -> Result<(), String> {
Ok(())
}
}
pub struct DefaultHooks;
#[async_trait]
impl WebClientHooks for DefaultHooks {}
pub mod controller;
pub mod session;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct WebClient {
controller: Arc<controller::Controller>,
tasks: ScopedTask<()>,
manager_guard: DaemonGuard,
connected: Arc<AtomicBool>,
}
impl WebClient {
@@ -28,28 +53,35 @@ impl WebClient {
token: S,
hostname: H,
manager: Arc<NetworkInstanceManager>,
hooks: Option<Arc<dyn WebClientHooks>>,
) -> Self {
let manager_guard = manager.register_daemon();
let hooks = hooks.unwrap_or_else(|| Arc::new(DefaultHooks));
let controller = Arc::new(controller::Controller::new(
token.to_string(),
hostname.to_string(),
manager,
hooks,
));
let connected = Arc::new(AtomicBool::new(false));
let controller_clone = controller.clone();
let connected_clone = connected.clone();
let tasks = ScopedTask::from(tokio::spawn(async move {
Self::routine(controller_clone, Box::new(connector)).await;
Self::routine(controller_clone, connected_clone, Box::new(connector)).await;
}));
WebClient {
controller,
tasks,
manager_guard,
connected,
}
}
async fn routine(
controller: Arc<controller::Controller>,
connected: Arc<AtomicBool>,
mut connector: Box<dyn TunnelConnector>,
) {
loop {
@@ -65,12 +97,18 @@ impl WebClient {
}
};
connected.store(true, Ordering::Release);
println!("Successfully connected to {:?}", conn.info());
let mut session = session::Session::new(conn, controller.clone());
session.wait().await;
connected.store(false, Ordering::Release);
}
}
pub fn is_connected(&self) -> bool {
self.connected.load(Ordering::Acquire)
}
}
pub async fn run_web_client(
@@ -78,6 +116,7 @@ pub async fn run_web_client(
machine_id: Option<String>,
hostname: Option<String>,
manager: Arc<NetworkInstanceManager>,
hooks: Option<Arc<dyn WebClientHooks>>,
) -> Result<WebClient> {
set_default_machine_id(machine_id);
let config_server_url = match Url::parse(config_server_url_s) {
@@ -87,7 +126,7 @@ pub async fn run_web_client(
config_server_url_s
)
.parse()
.unwrap(),
.with_context(|| "failed to parse config server URL")?,
};
let mut c_url = config_server_url.clone();
@@ -122,6 +161,7 @@ pub async fn run_web_client(
token.to_string(),
hostname,
manager.clone(),
hooks,
))
}
@@ -139,6 +179,7 @@ mod tests {
None,
None,
manager.clone(),
None,
)
.await
.unwrap();