Initial Commit

This commit is contained in:
2026-09-23 09:16:09 -04:00
commit 0cd2a516bb
49 changed files with 10134 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration;
use ping_async::IcmpEchoRequestor;
use serde::Deserialize;
use tauri::{AppHandle, Emitter};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use crate::models::monitor::MonitorEntry;
struct MonitorState {
entries: HashMap<IpAddr, MonitorEntry>,
task: Option<JoinHandle<()>>,
running: bool,
}
static MONITOR: std::sync::OnceLock<Arc<Mutex<MonitorState>>> = std::sync::OnceLock::new();
fn get_monitor() -> Arc<Mutex<MonitorState>> {
MONITOR
.get_or_init(|| {
Arc::new(Mutex::new(MonitorState {
entries: HashMap::new(),
task: None,
running: false,
}))
})
.clone()
}
#[derive(Debug, Deserialize)]
pub struct MonitorHost {
pub ip: String,
pub label: Option<String>,
}
#[tauri::command]
pub async fn monitor_add_hosts(hosts: Vec<MonitorHost>) -> Result<(), String> {
let state = get_monitor();
let mut guard = state.lock().await;
for host in hosts {
let ip: IpAddr = host
.ip
.parse()
.map_err(|e| format!("Invalid IP '{}': {}", host.ip, e))?;
guard
.entries
.entry(ip)
.or_insert_with(|| MonitorEntry::new(ip, host.label));
}
Ok(())
}
#[tauri::command]
pub async fn monitor_remove_host(ip: String) -> Result<(), String> {
let parsed: IpAddr = ip.parse().map_err(|e| format!("Invalid IP: {}", e))?;
let state = get_monitor();
let mut guard = state.lock().await;
guard.entries.remove(&parsed);
Ok(())
}
#[tauri::command]
pub async fn monitor_clear() -> Result<(), String> {
let state = get_monitor();
let mut guard = state.lock().await;
guard.entries.clear();
Ok(())
}
#[tauri::command]
pub async fn monitor_get_entries() -> Result<Vec<MonitorEntry>, String> {
let state = get_monitor();
let guard = state.lock().await;
let mut entries: Vec<MonitorEntry> = guard.entries.values().cloned().collect();
entries.sort_by(|a, b| a.ip.to_string().cmp(&b.ip.to_string()));
Ok(entries)
}
#[tauri::command]
pub async fn monitor_start(app: AppHandle, interval_ms: Option<u64>) -> Result<(), String> {
let state = get_monitor();
let mut guard = state.lock().await;
if guard.running {
return Err("Monitor already running".to_string());
}
if guard.entries.is_empty() {
return Err("No hosts to monitor".to_string());
}
guard.running = true;
let interval = Duration::from_millis(interval_ms.unwrap_or(2000));
let state2 = get_monitor();
let task = tokio::spawn(async move {
loop {
{
let guard = state2.lock().await;
if !guard.running {
break;
}
}
// Collect IPs to ping
let ips: Vec<IpAddr> = {
let guard = state2.lock().await;
guard.entries.keys().cloned().collect()
};
// Ping all hosts concurrently
let mut join_set = tokio::task::JoinSet::new();
for ip in ips {
join_set.spawn(async move {
let timeout = Duration::from_millis(1500);
let requestor = match IcmpEchoRequestor::new(ip, None, None, Some(timeout)) {
Ok(r) => r,
Err(_) => return (ip, None),
};
match requestor.send().await {
Ok(reply) => match reply.status() {
ping_async::IcmpEchoStatus::Success => {
let rtt = reply.round_trip_time().as_secs_f64() * 1000.0;
(ip, Some(rtt))
}
_ => (ip, None),
},
Err(_) => (ip, None),
}
});
}
// Process results
while let Some(result) = join_set.join_next().await {
if let Ok((ip, rtt)) = result {
let mut guard = state2.lock().await;
if let Some(entry) = guard.entries.get_mut(&ip) {
let old_status = entry.status.clone();
match rtt {
Some(ms) => entry.record_success(ms),
None => entry.record_failure(),
}
// Emit state change event
if entry.status != old_status {
let _ = app.emit(
"monitor:state-change",
serde_json::json!({
"ip": ip.to_string(),
"old": format!("{:?}", old_status),
"new": format!("{:?}", entry.status),
}),
);
}
let _ = app.emit("monitor:update", &entry.clone());
}
}
}
tokio::time::sleep(interval).await;
}
});
guard.task = Some(task);
Ok(())
}
#[tauri::command]
pub async fn monitor_stop() -> Result<(), String> {
let state = get_monitor();
let mut guard = state.lock().await;
guard.running = false;
if let Some(task) = guard.task.take() {
task.abort();
}
Ok(())
}