use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PortInfo {
pub port: u16,
pub is_occupied: bool,
pub pid: Option<u32>,
pub process_name: Option<String>,
pub local_address: Option<String>,
}
#[cfg(windows)]
fn get_process_names_windows() -> HashMap<u32, String> {
let mut map = HashMap::new();
if let Ok(output) = std::process::Command::new("tasklist")
.args(["/FO", "CSV", "/NH"])
.output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 2 {
let name = parts[0].trim_matches('"').trim().to_string();
let pid_str = parts[1].trim_matches('"').trim();
if let Ok(pid) = pid_str.parse::<u32>() {
map.insert(pid, name);
}
}
}
}
map
}
#[tauri::command]
pub async fn scan_ports(ports_to_check: Option<Vec<u16>>) -> Result<Vec<PortInfo>, String> {
let default_ports = vec![3000, 3001, 5173, 5174, 8000, 8080, 5432, 6379, 27017, 3306];
let ports = ports_to_check.unwrap_or(default_ports);
#[cfg(windows)]
{
let output = tokio::process::Command::new("netstat")
.args(["-ano", "-p", "tcp"])
.output()
.await
.map_err(|e| format!("Fehler beim Ausführen von netstat: {}", e))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let proc_map = get_process_names_windows();
let mut occupied_map: HashMap<u16, (u32, String)> = HashMap::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 && parts[0].eq_ignore_ascii_case("TCP") && parts[3].eq_ignore_ascii_case("LISTENING") {
let local_addr = parts[1];
let pid_str = parts[4];
if let Some(colon_idx) = local_addr.rfind(':') {
if let Ok(port) = local_addr[colon_idx + 1..].parse::<u16>() {
if let Ok(pid) = pid_str.parse::<u32>() {
occupied_map.insert(port, (pid, local_addr.to_string()));
}
}
}
}
}
let mut results = Vec::new();
for port in ports {
if let Some((pid, addr)) = occupied_map.get(&port) {
let proc_name = proc_map.get(pid).cloned();
results.push(PortInfo {
port,
is_occupied: true,
pid: Some(*pid),
process_name: proc_name,
local_address: Some(addr.clone()),
});
} else {
results.push(PortInfo {
port,
is_occupied: false,
pid: None,
process_name: None,
local_address: None,
});
}
}
Ok(results)
}
#[cfg(unix)]
{
let mut results = Vec::new();
for port in ports {
let output = tokio::process::Command::new("lsof")
.args(["-i", &format!(":{}", port), "-sTCP:LISTEN", "-t"])
.output()
.await;
match output {
Ok(out) if out.status.success() && !out.stdout.is_empty() => {
let pid_str = String::from_utf8_lossy(&out.stdout).trim().to_string();
let pid = pid_str.lines().next().and_then(|l| l.parse::<u32>().ok());
results.push(PortInfo {
port,
is_occupied: true,
pid,
process_name: None,
local_address: Some(format!("0.0.0.0:{}", port)),
});
}
_ => {
results.push(PortInfo {
port,
is_occupied: false,
pid: None,
process_name: None,
local_address: None,
});
}
}
}
Ok(results)
}
}
#[tauri::command]
pub async fn kill_process_by_pid(pid: u32) -> Result<String, String> {
if pid == 0 || pid == 4 {
return Err("System-Prozesse (PID 0, 4) können aus Sicherheitsgründen nicht beendet werden.".to_string());
}
#[cfg(windows)]
{
let output = tokio::process::Command::new("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.output()
.await
.map_err(|e| format!("Fehler beim Ausführen von taskkill: {}", e))?;
if output.status.success() {
Ok(format!("Prozess mit PID {} wurde erfolgreich beendet.", pid))
} else {
let err = String::from_utf8_lossy(&output.stderr);
Err(format!("Prozess konnte nicht beendet werden: {}", err.trim()))
}
}
#[cfg(unix)]
{
let output = tokio::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output()
.await
.map_err(|e| format!("Fehler beim Beenden: {}", e))?;
if output.status.success() {
Ok(format!("Prozess mit PID {} wurde erfolgreich beendet.", pid))
} else {
let err = String::from_utf8_lossy(&output.stderr);
Err(format!("Prozess konnte nicht beendet werden: {}", err.trim()))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_scan_ports_runs() {
let res = scan_ports(Some(vec![80, 443, 65530])).await;
assert!(res.is_ok());
let list = res.unwrap();
assert_eq!(list.len(), 3);
}
}