use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ServerEntry {
pub name: String,
pub host: String,
pub port: u16,
pub user: String,
pub tags: Vec<String>,
pub identity_file: Option<String>,
pub description: Option<String>,
pub last_connected: Option<String>,
pub jump_host: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ServersConfig {
pub servers: Vec<ServerEntry>,
}
pub fn get_sb_ssh_dir() -> PathBuf {
if let Some(home) = dirs::home_dir() {
home.join(".sb-ssh")
} else {
PathBuf::from(".sb-ssh")
}
}
pub fn load_servers() -> Vec<ServerEntry> {
let path = get_sb_ssh_dir().join("servers.toml");
if !path.exists() {
return Vec::new();
}
let content = std::fs::read_to_string(&path).unwrap_or_default();
let config: ServersConfig = toml::from_str(&content).unwrap_or_default();
config.servers
}
#[tauri::command]
pub fn get_servers() -> Result<Vec<ServerEntry>, String> {
Ok(load_servers())
}
#[tauri::command]
pub async fn ping_server(host: String, port: u16) -> Result<f32, String> {
let addr = format!("{}:{}", host, port);
let start = std::time::Instant::now();
match tokio::time::timeout(
std::time::Duration::from_secs(3),
tokio::net::TcpStream::connect(&addr),
).await {
Ok(Ok(_)) => {
let latency_ms = start.elapsed().as_secs_f32() * 1000.0;
Ok(latency_ms)
}
Ok(Err(e)) => Err(format!("Verbindung verweigert: {}", e)),
Err(_) => Err("Timeout (3s)".to_string()),
}
}
#[tauri::command]
pub async fn open_ssh_terminal(
target: String,
state: tauri::State<'_, crate::vault::VaultState>,
) -> Result<(), String> {
crate::vault::ensure_unlocked(&state)?;
let sb_ssh = which::which("sb-ssh")
.or_else(|_| {
dirs::home_dir().map(|h| h.join(".cargo").join("bin").join(if cfg!(windows) { "sb-ssh.exe" } else { "sb-ssh" }))
.filter(|p| p.exists())
.ok_or("Nicht gefunden")
})
.map_err(|_| "sb-ssh Binary wurde nicht im System gefunden".to_string())?;
#[cfg(windows)]
{
tokio::process::Command::new("cmd")
.args(["/C", "start", "wt", "new-tab", &sb_ssh.to_string_lossy(), "connect", &target])
.spawn()
.or_else(|_| {
tokio::process::Command::new("cmd")
.args(["/C", "start", "cmd", "/K", &sb_ssh.to_string_lossy(), "connect", &target])
.spawn()
})
.map_err(|e| format!("Terminal konnte nicht geöffnet werden: {}", e))?;
}
#[cfg(unix)]
{
tokio::process::Command::new("x-terminal-emulator")
.args(["-e", &format!("{} connect {}", sb_ssh.to_string_lossy(), target)])
.spawn()
.map_err(|e| format!("Terminal konnte nicht geöffnet werden: {}", e))?;
}
Ok(())
}
use std::fs::OpenOptions;
use std::io::Write;
#[tauri::command]
pub fn save_server(new_server: ServerEntry) -> Result<Vec<ServerEntry>, String> {
let mut servers = load_servers();
if let Some(pos) = servers.iter().position(|s| s.name == new_server.name) {
servers[pos] = new_server;
} else {
servers.push(new_server);
}
let path = get_sb_ssh_dir().join("servers.toml");
let config = ServersConfig { servers: servers.clone() };
let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.map_err(|e| e.to_string())?;
file.write_all(toml_str.as_bytes()).map_err(|e| e.to_string())?;
Ok(servers)
}
#[tauri::command]
pub fn delete_server(name: String) -> Result<Vec<ServerEntry>, String> {
let mut servers = load_servers();
servers.retain(|s| s.name != name);
let path = get_sb_ssh_dir().join("servers.toml");
let config = ServersConfig { servers: servers.clone() };
let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&path)
.map_err(|e| e.to_string())?;
file.write_all(toml_str.as_bytes()).map_err(|e| e.to_string())?;
Ok(servers)
}