Add VPN configuration tools and agent documentation
Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection and enhanced agent documentation framework. VPN Configuration (PST-NW-VPN): - Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS - Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24) - Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment - Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2 - Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic - Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes) - Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper - vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts - OpenVPN config files (Windows-compatible, abandoned for L2TP) Key VPN Implementation Details: - L2TP creates PPP adapter with connection name as interface description - UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24 - Split-tunnel enabled (only remote traffic through VPN) - All-user connection for pre-login auto-connect via scheduled task - Authentication: CHAP + MSChapv2 for UniFi compatibility Agent Documentation: - AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents - documentation-squire.md: Documentation and task management specialist agent - Updated all agent markdown files with standardized formatting Project Organization: - Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs) - Cleaned up old session JSONL files from projects/msp-tools/ - Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows) - Added guru-rmm server components and deployment configs Technical Notes: - VPN IP pool: 192.168.4.x (client gets 192.168.4.6) - Remote network: 192.168.0.0/24 (router at 192.168.0.10) - PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7 - Credentials: pst-admin / 24Hearts$ Files: 15 VPN scripts, 2 agent docs, conversation log reorganization, guru-connect/guru-rmm infrastructure additions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
197
projects/msp-tools/guru-connect/agent/src/tray/mod.rs
Normal file
197
projects/msp-tools/guru-connect/agent/src/tray/mod.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! System tray icon and menu for the agent
|
||||
//!
|
||||
//! Provides a tray icon with menu options:
|
||||
//! - Connection status
|
||||
//! - Machine name
|
||||
//! - End session
|
||||
|
||||
use anyhow::Result;
|
||||
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use tray_icon::{Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[cfg(windows)]
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE,
|
||||
};
|
||||
|
||||
/// Events that can be triggered from the tray menu
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TrayAction {
|
||||
EndSession,
|
||||
ShowDetails,
|
||||
ShowDebugWindow,
|
||||
}
|
||||
|
||||
/// Tray icon controller
|
||||
pub struct TrayController {
|
||||
_tray_icon: TrayIcon,
|
||||
menu: Menu,
|
||||
end_session_item: MenuItem,
|
||||
debug_item: MenuItem,
|
||||
status_item: MenuItem,
|
||||
exit_requested: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl TrayController {
|
||||
/// Create a new tray controller
|
||||
/// `allow_end_session` - If true, show "End Session" menu item (only for support sessions)
|
||||
pub fn new(machine_name: &str, support_code: Option<&str>, allow_end_session: bool) -> Result<Self> {
|
||||
// Create menu items
|
||||
let status_text = if let Some(code) = support_code {
|
||||
format!("Support Session: {}", code)
|
||||
} else {
|
||||
"Persistent Agent".to_string()
|
||||
};
|
||||
|
||||
let status_item = MenuItem::new(&status_text, false, None);
|
||||
let machine_item = MenuItem::new(format!("Machine: {}", machine_name), false, None);
|
||||
let separator = PredefinedMenuItem::separator();
|
||||
|
||||
// Only show "End Session" for support sessions
|
||||
// Persistent agents can only be removed by admin
|
||||
let end_session_item = if allow_end_session {
|
||||
MenuItem::new("End Session", true, None)
|
||||
} else {
|
||||
MenuItem::new("Managed by Administrator", false, None)
|
||||
};
|
||||
|
||||
// Debug window option (always available)
|
||||
let debug_item = MenuItem::new("Show Debug Window", true, None);
|
||||
|
||||
// Build menu
|
||||
let menu = Menu::new();
|
||||
menu.append(&status_item)?;
|
||||
menu.append(&machine_item)?;
|
||||
menu.append(&separator)?;
|
||||
menu.append(&debug_item)?;
|
||||
menu.append(&end_session_item)?;
|
||||
|
||||
// Create tray icon
|
||||
let icon = create_default_icon()?;
|
||||
|
||||
let tray_icon = TrayIconBuilder::new()
|
||||
.with_menu(Box::new(menu.clone()))
|
||||
.with_tooltip(format!("GuruConnect - {}", machine_name))
|
||||
.with_icon(icon)
|
||||
.build()?;
|
||||
|
||||
let exit_requested = Arc::new(AtomicBool::new(false));
|
||||
|
||||
Ok(Self {
|
||||
_tray_icon: tray_icon,
|
||||
menu,
|
||||
end_session_item,
|
||||
debug_item,
|
||||
status_item,
|
||||
exit_requested,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if exit has been requested
|
||||
pub fn exit_requested(&self) -> bool {
|
||||
self.exit_requested.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Update the connection status display
|
||||
pub fn update_status(&self, status: &str) {
|
||||
self.status_item.set_text(status);
|
||||
}
|
||||
|
||||
/// Process pending menu events (call this from the main loop)
|
||||
pub fn process_events(&self) -> Option<TrayAction> {
|
||||
// Pump Windows message queue to process tray icon events
|
||||
#[cfg(windows)]
|
||||
pump_windows_messages();
|
||||
|
||||
// Check for menu events
|
||||
if let Ok(event) = MenuEvent::receiver().try_recv() {
|
||||
if event.id == self.end_session_item.id() {
|
||||
info!("End session requested from tray menu");
|
||||
self.exit_requested.store(true, Ordering::SeqCst);
|
||||
return Some(TrayAction::EndSession);
|
||||
}
|
||||
if event.id == self.debug_item.id() {
|
||||
info!("Debug window requested from tray menu");
|
||||
return Some(TrayAction::ShowDebugWindow);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tray icon events (like double-click)
|
||||
if let Ok(event) = TrayIconEvent::receiver().try_recv() {
|
||||
match event {
|
||||
TrayIconEvent::DoubleClick { .. } => {
|
||||
info!("Tray icon double-clicked");
|
||||
return Some(TrayAction::ShowDetails);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Pump the Windows message queue to process tray icon events
|
||||
#[cfg(windows)]
|
||||
fn pump_windows_messages() {
|
||||
unsafe {
|
||||
let mut msg = MSG::default();
|
||||
// Process all pending messages
|
||||
while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
|
||||
let _ = TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a simple default icon (green circle for connected)
|
||||
fn create_default_icon() -> Result<Icon> {
|
||||
// Create a simple 32x32 green icon
|
||||
let size = 32u32;
|
||||
let mut rgba = vec![0u8; (size * size * 4) as usize];
|
||||
|
||||
let center = size as f32 / 2.0;
|
||||
let radius = size as f32 / 2.0 - 2.0;
|
||||
|
||||
for y in 0..size {
|
||||
for x in 0..size {
|
||||
let dx = x as f32 - center;
|
||||
let dy = y as f32 - center;
|
||||
let dist = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
let idx = ((y * size + x) * 4) as usize;
|
||||
|
||||
if dist <= radius {
|
||||
// Green circle
|
||||
rgba[idx] = 76; // R
|
||||
rgba[idx + 1] = 175; // G
|
||||
rgba[idx + 2] = 80; // B
|
||||
rgba[idx + 3] = 255; // A
|
||||
} else if dist <= radius + 1.0 {
|
||||
// Anti-aliased edge
|
||||
let alpha = ((radius + 1.0 - dist) * 255.0) as u8;
|
||||
rgba[idx] = 76;
|
||||
rgba[idx + 1] = 175;
|
||||
rgba[idx + 2] = 80;
|
||||
rgba[idx + 3] = alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let icon = Icon::from_rgba(rgba, size, size)?;
|
||||
Ok(icon)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_icon() {
|
||||
let icon = create_default_icon();
|
||||
assert!(icon.is_ok());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user