Initial Commit
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user