//! Client export modal presentation component. use nx9_wg_core::types::client_profile::{ ConnectionType, DeviceCategory, NatType, ResolvedClientProfile, }; use serde::{Deserialize, Serialize}; /// Client configuration export UI state. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ClientExportState { pub selected_provider: Option, pub selected_device: Option, pub selected_connection: ConnectionType, pub selected_nat: NatType, pub manual_mtu_override: Option, pub resolved_profile: Option, pub is_override_enabled: bool, pub qr_view_active: bool, } impl Default for ClientExportState { fn default() -> Self { Self { selected_provider: None, selected_device: None, selected_connection: ConnectionType::Web, selected_nat: NatType::Unknown, manual_mtu_override: None, resolved_profile: None, is_override_enabled: false, qr_view_active: false, } } } impl ClientExportState { /// Create a new initial export state. pub fn new() -> Self { Self::default() } /// Set connection type and reset manual override if disabled. pub fn set_connection(&mut self, connection: ConnectionType) { self.selected_connection = connection; } /// Set NAT type. pub fn set_nat(&mut self, nat: NatType) { self.selected_nat = nat; } /// Set device category. pub fn set_device(&mut self, device: Option) { self.selected_device = device; } /// Set provider. pub fn set_provider(&mut self, provider: Option) { self.selected_provider = provider; } /// Toggle or set manual MTU override. pub fn set_manual_mtu(&mut self, mtu: Option) { self.manual_mtu_override = mtu; self.is_override_enabled = mtu.is_some(); } /// Get current effective MTU for display. pub fn display_mtu(&self) -> u16 { if let Some(m) = self.manual_mtu_override { m } else if let Some(ref res) = self.resolved_profile { res.mtu } else { 1420 } } /// Determine if an active manual override warning should be displayed. pub fn has_manual_override_warning(&self) -> bool { self.is_override_enabled && self.manual_mtu_override.is_some() } /// Format export query parameters for REST API call. pub fn to_query_string(&self, profile_id: Option<&str>) -> String { let mut params = Vec::new(); if let Some(ref p) = self.selected_provider { params.push(format!("provider={}", urlencoding(p))); } if let Some(ref d) = self.selected_device { params.push(format!("device={}", d.as_str())); } params.push(format!("connection={}", self.selected_connection.as_str())); params.push(format!("nat={}", self.selected_nat.as_str())); if let Some(m) = self.manual_mtu_override { params.push(format!("mtu={m}")); } if let Some(id) = profile_id { params.push(format!("profile={}", urlencoding(id))); } params.join("&") } } fn urlencoding(s: &str) -> String { s.replace(' ', "%20") } #[cfg(test)] mod tests { use super::*; #[test] fn test_client_export_state_lifecycle() { let mut state = ClientExportState::new(); assert_eq!(state.display_mtu(), 1420); assert!(!state.has_manual_override_warning()); state.set_connection(ConnectionType::Mobile); state.set_device(Some(DeviceCategory::Android)); state.set_nat(NatType::Cgnat); state.set_provider(Some("tmobile".to_string())); let query = state.to_query_string(None); assert!(query.contains("connection=mobile")); assert!(query.contains("device=android")); assert!(query.contains("nat=cgnat")); assert!(query.contains("provider=tmobile")); state.set_manual_mtu(Some(1300)); assert_eq!(state.display_mtu(), 1300); assert!(state.has_manual_override_warning()); assert!(state.to_query_string(None).contains("mtu=1300")); } }