tftsr-devops_investigation/src-tauri/src/commands/proxmox.rs

236 lines
7.1 KiB
Rust
Raw Normal View History

use crate::proxmox::{ClusterInfo, ClusterType, ProxmoxClient};
use crate::state::AppState;
use chrono::Utc;
use rusqlite::OptionalExtension;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tauri::State;
use tokio::sync::Mutex;
/// Proxmox cluster connection information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterConnection {
pub url: String,
pub port: u16,
}
/// Add a Proxmox cluster
#[tauri::command]
pub async fn add_proxmox_cluster(
id: String,
name: String,
cluster_type: ClusterType,
connection: ClusterConnection,
username: String,
password: &str,
state: State<'_, AppState>,
) -> Result<ClusterInfo, String> {
// Create client and authenticate
let client = ProxmoxClient::new(&connection.url, connection.port, &username);
let ticket = client
.authenticate(password)
.await
.map_err(|e| format!("Authentication failed: {}", e))?;
// Encrypt credentials for storage
let credentials = serde_json::json!({
"ticket": ticket,
"username": username
});
let encrypted_credentials = crate::integrations::auth::encrypt_token(
&serde_json::to_string(&credentials).map_err(|e| e.to_string())?,
)
.map_err(|e| format!("Failed to encrypt credentials: {}", e))?;
// Create cluster info
let cluster = ClusterInfo {
id: id.clone(),
name,
cluster_type,
url: connection.url,
port: connection.port,
username,
created_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
updated_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
};
// Store in database
{
let db = state
.db
.lock()
.map_err(|e| format!("Failed to lock database: {}", e))?;
db.execute(
"INSERT INTO proxmox_clusters (id, name, cluster_type, url, port, auth_method, encrypted_credentials, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
rusqlite::params![
cluster.id,
cluster.name,
match cluster.cluster_type {
ClusterType::VE => "ve",
ClusterType::PBS => "pbs",
},
cluster.url,
cluster.port,
"root",
encrypted_credentials,
cluster.created_at,
cluster.updated_at,
],
)
.map_err(|e| format!("Failed to store cluster: {}", e))?;
}
// Store in memory for quick access
{
let mut clusters = state.proxmox_clusters.lock().await;
clusters.insert(id, Arc::new(Mutex::new(client)));
}
Ok(cluster)
}
/// Remove a Proxmox cluster
#[tauri::command]
pub async fn remove_proxmox_cluster(id: String, state: State<'_, AppState>) -> Result<(), String> {
// Remove from database
{
let db = state
.db
.lock()
.map_err(|e| format!("Failed to lock database: {}", e))?;
db.execute("DELETE FROM proxmox_clusters WHERE id = ?1", [id.clone()])
.map_err(|e| format!("Failed to remove cluster: {}", e))?;
}
// Remove from memory
{
let mut clusters = state.clusters.lock().await;
clusters.remove(&id);
}
Ok(())
}
/// List all Proxmox clusters
#[tauri::command]
pub async fn list_proxmox_clusters(state: State<'_, AppState>) -> Result<Vec<ClusterInfo>, String> {
let clusters = {
let db = state
.db
.lock()
.map_err(|e| format!("Failed to lock database: {}", e))?;
let mut stmt = db
.prepare(
"SELECT id, name, cluster_type, url, port, created_at, updated_at FROM proxmox_clusters",
)
.map_err(|e| format!("Failed to prepare query: {}", e))?;
let cluster_iter = stmt
.query_map([], |row| {
Ok(ClusterInfo {
id: row.get(0)?,
name: row.get(1)?,
cluster_type: match row.get::<_, String>(2)?.as_str() {
"ve" => ClusterType::VE,
"pbs" => ClusterType::PBS,
_ => ClusterType::VE,
},
url: row.get(3)?,
port: row.get(4)?,
username: "".to_string(), // Will be decrypted when needed
created_at: row.get(5)?,
updated_at: row.get(6)?,
})
})
.map_err(|e| format!("Failed to query clusters: {}", e))?;
cluster_iter
.collect::<Result<Vec<_>, _>>()
.map_err(|e| e.to_string())
};
clusters
}
/// Get a specific Proxmox cluster
#[tauri::command]
pub async fn get_proxmox_cluster(
id: String,
state: State<'_, AppState>,
) -> Result<Option<ClusterInfo>, String> {
let cluster = {
let db = state
.db
.lock()
.map_err(|e| format!("Failed to lock database: {}", e))?;
let mut stmt = db
.prepare(
"SELECT id, name, cluster_type, url, port, created_at, updated_at FROM proxmox_clusters WHERE id = ?1",
)
.map_err(|e| format!("Failed to prepare query: {}", e))?;
stmt.query_row([id], |row| {
Ok(ClusterInfo {
id: row.get(0)?,
name: row.get(1)?,
cluster_type: match row.get::<_, String>(2)?.as_str() {
"ve" => ClusterType::VE,
"pbs" => ClusterType::PBS,
_ => ClusterType::VE,
},
url: row.get(3)?,
port: row.get(4)?,
username: "".to_string(),
created_at: row.get(5)?,
updated_at: row.get(6)?,
})
})
.optional()
.map_err(|e| format!("Failed to query cluster: {}", e))?
};
Ok(cluster)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cluster_type_serialization() {
let json = serde_json::to_string(&ClusterType::VE).unwrap();
assert_eq!(json, "\"ve\"");
let ve: ClusterType = serde_json::from_str("\"ve\"").unwrap();
assert_eq!(ve, ClusterType::VE);
let pbs: ClusterType = serde_json::from_str("\"pbs\"").unwrap();
assert_eq!(pbs, ClusterType::PBS);
}
#[test]
fn test_cluster_info_serialization() {
let cluster = ClusterInfo {
id: "proxmox-1".to_string(),
name: "Production".to_string(),
cluster_type: ClusterType::VE,
url: "https://pve.example.com".to_string(),
port: 8006,
username: "root@pam".to_string(),
created_at: "2026-06-10 12:00:00".to_string(),
updated_at: "2026-06-10 12:00:00".to_string(),
};
let json = serde_json::to_string(&cluster).unwrap();
let deserialized: ClusterInfo = serde_json::from_str(&json).unwrap();
assert_eq!(cluster.id, deserialized.id);
assert_eq!(cluster.name, deserialized.name);
}
}