sb-vault 0.1.0

S&B Vault // Zero-Trust Desktop Suite & Secret Engine
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();
    // Upsert by name
    if let Some(pos) = servers.iter().position(|s| s.name == new_server.name) {
        servers[pos] = new_server;
    } else {
        servers.push(new_server);
    }
    // Write back to file
    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);
    // Write back
    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)
}