feat: add service catalog management with policy integration for domain-based resource access control
Add ServiceCatalogItem type and services CRUD API endpoints (list, create, update, delete). Extend Policy type to include services array with domain, upstream_ip, proxy_ip, and ports metadata. Add ServicesPage component with table view and create/edit modals for managing service definitions. Include service name, domain, proxy, and upstream columns with port parsing logic. Integrate service selection
This commit is contained in:
@@ -1,6 +1,15 @@
|
||||
mod tunnel_manager;
|
||||
|
||||
use std::{fs, io::Cursor, net::TcpListener, path::PathBuf, sync::Mutex};
|
||||
use std::{
|
||||
fs,
|
||||
io::Cursor,
|
||||
net::TcpListener,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use png::{ColorType, Decoder};
|
||||
@@ -22,6 +31,7 @@ const SINGLE_INSTANCE_ADDR: &str = "127.0.0.1:53190";
|
||||
struct AppState {
|
||||
session: Mutex<Option<SessionState>>,
|
||||
tray: Mutex<Option<TrayState>>,
|
||||
tunnel_action_in_progress: AtomicBool,
|
||||
single_instance_lock: TcpListener,
|
||||
}
|
||||
|
||||
@@ -392,14 +402,29 @@ async fn select_access_profile(app: AppHandle, profile_id: String) -> Result<Enr
|
||||
|
||||
#[tauri::command]
|
||||
async fn connect_tunnel(app: AppHandle) -> Result<EnrollmentResult, String> {
|
||||
let session_state = sync_current_session(&app).await?;
|
||||
let state = app.state::<AppState>();
|
||||
state.tunnel_action_in_progress.store(true, Ordering::SeqCst);
|
||||
let session_state = match sync_current_session(&app).await {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
state.tunnel_action_in_progress.store(false, Ordering::SeqCst);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
let app_handle = app.clone();
|
||||
let profile_path = session_state.profile_path.clone();
|
||||
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||
let result = match tauri::async_runtime::spawn_blocking(move || {
|
||||
tunnel_manager::connect(&app_handle, std::path::Path::new(&profile_path))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("Unable to join tunnel connect task: {err}"))?;
|
||||
.map_err(|err| format!("Unable to join tunnel connect task: {err}")) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
state.tunnel_action_in_progress.store(false, Ordering::SeqCst);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
state.tunnel_action_in_progress.store(false, Ordering::SeqCst);
|
||||
refresh_tray_menu(&app);
|
||||
result?;
|
||||
Ok(session_state.enrollment)
|
||||
@@ -407,39 +432,65 @@ async fn connect_tunnel(app: AppHandle) -> Result<EnrollmentResult, String> {
|
||||
|
||||
#[tauri::command]
|
||||
async fn disconnect_tunnel(app: AppHandle, state: State<'_, AppState>) -> Result<(), String> {
|
||||
state.tunnel_action_in_progress.store(true, Ordering::SeqCst);
|
||||
let profile_path = {
|
||||
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
|
||||
let session = session.as_ref().ok_or_else(|| "No active session is available".to_string())?;
|
||||
session.profile_path.clone()
|
||||
};
|
||||
let app_handle = app.clone();
|
||||
let result = tauri::async_runtime::spawn_blocking(move || {
|
||||
let result = match tauri::async_runtime::spawn_blocking(move || {
|
||||
tunnel_manager::disconnect(&app_handle, std::path::Path::new(&profile_path))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("Unable to join tunnel disconnect task: {err}"))?;
|
||||
.map_err(|err| format!("Unable to join tunnel disconnect task: {err}")) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
state.tunnel_action_in_progress.store(false, Ordering::SeqCst);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
state.tunnel_action_in_progress.store(false, Ordering::SeqCst);
|
||||
refresh_tray_menu(&app);
|
||||
result
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn tunnel_status(app: AppHandle, state: State<'_, AppState>) -> Result<bool, String> {
|
||||
async fn tunnel_status(app: AppHandle, state: State<'_, AppState>) -> Result<bool, String> {
|
||||
if state.tunnel_action_in_progress.load(Ordering::SeqCst) {
|
||||
return Ok(false);
|
||||
}
|
||||
let profile_path = {
|
||||
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
|
||||
let session = session.as_ref().ok_or_else(|| "No active session is available".to_string())?;
|
||||
session.profile_path.clone()
|
||||
};
|
||||
tunnel_manager::is_active(&app, std::path::Path::new(&profile_path))
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
tunnel_manager::is_active(&app, std::path::Path::new(&profile_path))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("Unable to join tunnel status task: {err}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn tunnel_metrics(app: AppHandle, state: State<'_, AppState>) -> Result<TunnelMetrics, String> {
|
||||
async fn tunnel_metrics(app: AppHandle, state: State<'_, AppState>) -> Result<TunnelMetrics, String> {
|
||||
if state.tunnel_action_in_progress.load(Ordering::SeqCst) {
|
||||
return Ok(TunnelMetrics {
|
||||
active: false,
|
||||
rx_bytes: 0,
|
||||
tx_bytes: 0,
|
||||
});
|
||||
}
|
||||
let profile_path = {
|
||||
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
|
||||
let session = session.as_ref().ok_or_else(|| "No active session is available".to_string())?;
|
||||
session.profile_path.clone()
|
||||
};
|
||||
let metrics = tunnel_manager::metrics(&app, std::path::Path::new(&profile_path))?;
|
||||
let metrics = tauri::async_runtime::spawn_blocking(move || {
|
||||
tunnel_manager::metrics(&app, std::path::Path::new(&profile_path))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| format!("Unable to join tunnel metrics task: {err}"))??;
|
||||
let mapped = TunnelMetrics {
|
||||
active: metrics.active,
|
||||
rx_bytes: metrics.rx_bytes,
|
||||
@@ -542,6 +593,13 @@ fn format_data_size(bytes: u64) -> String {
|
||||
|
||||
fn current_metrics(app: &AppHandle) -> Result<TunnelMetrics, String> {
|
||||
let state = app.state::<AppState>();
|
||||
if state.tunnel_action_in_progress.load(Ordering::SeqCst) {
|
||||
return Ok(TunnelMetrics {
|
||||
active: false,
|
||||
rx_bytes: 0,
|
||||
tx_bytes: 0,
|
||||
});
|
||||
}
|
||||
let profile_path = {
|
||||
let session = state.session.lock().map_err(|_| "Unable to read client state".to_string())?;
|
||||
let session = session.as_ref().ok_or_else(|| "No active session is available".to_string())?;
|
||||
@@ -923,6 +981,7 @@ pub fn run() {
|
||||
sent_item,
|
||||
toggle_item,
|
||||
})),
|
||||
tunnel_action_in_progress: AtomicBool::new(false),
|
||||
single_instance_lock,
|
||||
});
|
||||
refresh_tray_menu(app.handle());
|
||||
|
||||
Reference in New Issue
Block a user