Compare commits
3 Commits
4d066e47fd
...
afe9ac1a3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afe9ac1a3a | ||
|
|
34ddae7eb2 | ||
|
|
a72b69ec34 |
@ -351,11 +351,9 @@ pub async fn chat_message(
|
|||||||
let agent_registry = create_agent_registry();
|
let agent_registry = create_agent_registry();
|
||||||
let devops_agent = agent_registry.get("devops-incident-responder");
|
let devops_agent = agent_registry.get("devops-incident-responder");
|
||||||
|
|
||||||
// CRITICAL: Build messages array with ALL system messages FIRST, then history, then user message
|
|
||||||
// This ensures system messages are always at the beginning as required by most LLM APIs
|
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
|
|
||||||
// 1. Inject devops-incident-responder as primary system prompt (always first)
|
// Inject devops-incident-responder as primary system prompt (always)
|
||||||
if let Some(agent) = devops_agent {
|
if let Some(agent) = devops_agent {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "system".into(),
|
role: "system".into(),
|
||||||
@ -365,7 +363,7 @@ pub async fn chat_message(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Inject domain system prompt if provided (second position)
|
// Inject domain system prompt if provided
|
||||||
if let Some(ref prompt) = system_prompt {
|
if let Some(ref prompt) = system_prompt {
|
||||||
if !prompt.is_empty() {
|
if !prompt.is_empty() {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
@ -377,6 +375,28 @@ pub async fn chat_message(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messages.extend(history);
|
||||||
|
|
||||||
|
// If we found integration content, add it to the conversation context
|
||||||
|
if !integration_context.is_empty() {
|
||||||
|
let context_message = Message {
|
||||||
|
role: "system".into(),
|
||||||
|
content: format!(
|
||||||
|
"INTERNAL DOCUMENTATION SOURCES:\n\n{integration_context}\n\n\
|
||||||
|
Instructions: The above content is from internal company documentation systems \
|
||||||
|
(Confluence, ServiceNow, Azure DevOps). \
|
||||||
|
\n\n**IMPORTANT**: First determine if this documentation is RELEVANT to the user's question:\
|
||||||
|
\n- If the documentation directly addresses the question → Use it and cite sources with URLs\
|
||||||
|
\n- If the documentation is tangentially related but doesn't answer the question → Briefly mention what internal docs exist, then provide a complete answer using general knowledge\
|
||||||
|
\n- If the documentation is completely unrelated → Ignore it and answer using general knowledge\
|
||||||
|
\n\nDo NOT force irrelevant internal documentation into your answer. The user needs accurate information, not forced citations."
|
||||||
|
),
|
||||||
|
tool_call_id: None,
|
||||||
|
tool_calls: None,
|
||||||
|
};
|
||||||
|
messages.push(context_message);
|
||||||
|
}
|
||||||
|
|
||||||
// Tool execution configuration
|
// Tool execution configuration
|
||||||
const MAX_TOOL_ITERATIONS: usize = 20; // Allow sufficient iterations for complex diagnostics
|
const MAX_TOOL_ITERATIONS: usize = 20; // Allow sufficient iterations for complex diagnostics
|
||||||
|
|
||||||
@ -407,7 +427,6 @@ pub async fn chat_message(
|
|||||||
!matches!(kind, "anthropic" | "gemini" | "mistral" | "ollama")
|
!matches!(kind, "anthropic" | "gemini" | "mistral" | "ollama")
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Tool-calling system messages — must come BEFORE history so all system messages are contiguous
|
|
||||||
if tools.is_some() && is_openai_compatible {
|
if tools.is_some() && is_openai_compatible {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "system".into(),
|
role: "system".into(),
|
||||||
@ -416,6 +435,7 @@ pub async fn chat_message(
|
|||||||
tool_calls: None,
|
tool_calls: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add iteration budget awareness
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "system".into(),
|
role: "system".into(),
|
||||||
content: format!(
|
content: format!(
|
||||||
@ -434,34 +454,6 @@ pub async fn chat_message(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Integration context as system message — still before history
|
|
||||||
if !integration_context.is_empty() {
|
|
||||||
messages.push(Message {
|
|
||||||
role: "system".into(),
|
|
||||||
content: format!(
|
|
||||||
"INTERNAL DOCUMENTATION SOURCES:\n\n{integration_context}\n\n\
|
|
||||||
Instructions: The above content is from internal company documentation systems \
|
|
||||||
(Confluence, ServiceNow, Azure DevOps). \
|
|
||||||
\n\n**IMPORTANT**: First determine if this documentation is RELEVANT to the user's question:\
|
|
||||||
\n- If the documentation directly addresses the question → Use it and cite sources with URLs\
|
|
||||||
\n- If the documentation is tangentially related but doesn't answer the question → Briefly mention what internal docs exist, then provide a complete answer using general knowledge\
|
|
||||||
\n- If the documentation is completely unrelated → Ignore it and answer using general knowledge\
|
|
||||||
\n\nDo NOT force irrelevant internal documentation into your answer. The user needs accurate information, not forced citations."
|
|
||||||
),
|
|
||||||
tool_call_id: None,
|
|
||||||
tool_calls: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Filter out any system messages from history to avoid duplicates and maintain order
|
|
||||||
let filtered_history: Vec<Message> = history
|
|
||||||
.into_iter()
|
|
||||||
.filter(|msg| msg.role != "system")
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// 6. Add filtered history (user/assistant messages only) — all system messages are already above
|
|
||||||
messages.extend(filtered_history);
|
|
||||||
|
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "user".into(),
|
role: "user".into(),
|
||||||
content: full_message.clone(),
|
content: full_message.clone(),
|
||||||
@ -479,7 +471,7 @@ pub async fn chat_message(
|
|||||||
// Warn AI when approaching limit
|
// Warn AI when approaching limit
|
||||||
if iteration == MAX_TOOL_ITERATIONS - 2 {
|
if iteration == MAX_TOOL_ITERATIONS - 2 {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "user".into(),
|
role: "system".into(),
|
||||||
content: format!(
|
content: format!(
|
||||||
"WARNING: You are on iteration {iteration}/{MAX_TOOL_ITERATIONS} (2 rounds remaining). \
|
"WARNING: You are on iteration {iteration}/{MAX_TOOL_ITERATIONS} (2 rounds remaining). \
|
||||||
You MUST provide your final answer in the NEXT round. \
|
You MUST provide your final answer in the NEXT round. \
|
||||||
@ -498,7 +490,7 @@ pub async fn chat_message(
|
|||||||
// Add final instruction
|
// Add final instruction
|
||||||
let mut final_messages = sanitized_messages;
|
let mut final_messages = sanitized_messages;
|
||||||
final_messages.push(Message {
|
final_messages.push(Message {
|
||||||
role: "user".into(),
|
role: "system".into(),
|
||||||
content: format!(
|
content: format!(
|
||||||
"CRITICAL: Tool iteration limit reached ({iteration}/{MAX_TOOL_ITERATIONS}). \
|
"CRITICAL: Tool iteration limit reached ({iteration}/{MAX_TOOL_ITERATIONS}). \
|
||||||
TOOLS ARE NOW DISABLED. \
|
TOOLS ARE NOW DISABLED. \
|
||||||
|
|||||||
@ -237,181 +237,16 @@ pub async fn get_proxmox_cluster(
|
|||||||
Ok(cluster)
|
Ok(cluster)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to get or create a Proxmox client for a cluster
|
|
||||||
/// This will:
|
|
||||||
/// 1. Check if client exists in memory pool
|
|
||||||
/// 2. If not, load credentials from database and create/authenticate client
|
|
||||||
async fn get_proxmox_client_for_cluster(
|
|
||||||
cluster_id: &str,
|
|
||||||
state: &State<'_, AppState>,
|
|
||||||
) -> Result<Arc<Mutex<crate::proxmox::ProxmoxClient>>, String> {
|
|
||||||
// First, try to get from in-memory pool
|
|
||||||
{
|
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
|
||||||
if let Some(client) = clusters.get(cluster_id) {
|
|
||||||
return Ok(client.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not in memory - load from database and create client
|
|
||||||
let (url, port, username, encrypted_credentials) = {
|
|
||||||
let db = state
|
|
||||||
.db
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock database: {}", e))?;
|
|
||||||
|
|
||||||
let mut stmt = db
|
|
||||||
.prepare(
|
|
||||||
"SELECT url, port, username, encrypted_credentials FROM proxmox_clusters WHERE id = ?1",
|
|
||||||
)
|
|
||||||
.map_err(|e| format!("Failed to prepare query: {}", e))?;
|
|
||||||
|
|
||||||
stmt.query_row([cluster_id], |row| {
|
|
||||||
Ok((
|
|
||||||
row.get::<_, String>(0)?,
|
|
||||||
row.get::<_, u16>(1)?,
|
|
||||||
row.get::<_, String>(2)?,
|
|
||||||
row.get::<_, String>(3)?,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.map_err(|e| format!("Failed to query cluster: {}", e))?
|
|
||||||
.ok_or_else(|| format!("Cluster {} not found in database", cluster_id))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Decrypt credentials
|
|
||||||
let credentials_json = crate::integrations::auth::decrypt_token(&encrypted_credentials)
|
|
||||||
.map_err(|e| format!("Failed to decrypt credentials: {}", e))?;
|
|
||||||
|
|
||||||
let credentials: serde_json::Value = serde_json::from_str(&credentials_json)
|
|
||||||
.map_err(|e| format!("Failed to parse credentials: {}", e))?;
|
|
||||||
|
|
||||||
let password = credentials
|
|
||||||
.get("password")
|
|
||||||
.and_then(|v| v.as_str())
|
|
||||||
.ok_or_else(|| "Password not found in credentials".to_string())?;
|
|
||||||
|
|
||||||
// Create new client
|
|
||||||
let mut client = crate::proxmox::ProxmoxClient::new(&url, port, &username);
|
|
||||||
|
|
||||||
// Authenticate to get ticket
|
|
||||||
let ticket = client
|
|
||||||
.authenticate(password)
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Failed to authenticate with Proxmox: {}", e))?;
|
|
||||||
|
|
||||||
client.set_ticket(&ticket);
|
|
||||||
|
|
||||||
let client_arc = Arc::new(Mutex::new(client));
|
|
||||||
{
|
|
||||||
let mut clusters = state.proxmox_clusters.lock().await;
|
|
||||||
// Re-check under write lock: a concurrent task may have already created a client
|
|
||||||
// for this cluster between our read-check and here, so prefer the existing one.
|
|
||||||
if let Some(existing) = clusters.get(cluster_id) {
|
|
||||||
return Ok(existing.clone());
|
|
||||||
}
|
|
||||||
clusters.insert(cluster_id.to_string(), client_arc.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(client_arc)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ping a Proxmox cluster — authenticates and calls the version endpoint to verify
|
|
||||||
/// that the API is reachable and credentials are valid.
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn ping_proxmox_cluster(
|
|
||||||
cluster_id: String,
|
|
||||||
state: State<'_, AppState>,
|
|
||||||
) -> Result<serde_json::Value, String> {
|
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
|
||||||
let client_guard = client.lock().await;
|
|
||||||
|
|
||||||
client_guard
|
|
||||||
.get::<serde_json::Value>("version", client_guard.ticket.as_deref())
|
|
||||||
.await
|
|
||||||
.map_err(|e| format!("Connection test failed: {}", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update an existing Proxmox cluster's metadata and credentials atomically.
|
|
||||||
/// Unlike the remove-then-add pattern this is a single SQL UPDATE so there is
|
|
||||||
/// no window where the record is missing.
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn update_proxmox_cluster(
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
cluster_type: ClusterType,
|
|
||||||
connection: ClusterConnection,
|
|
||||||
username: String,
|
|
||||||
password: &str,
|
|
||||||
state: State<'_, AppState>,
|
|
||||||
) -> Result<ClusterInfo, String> {
|
|
||||||
let credentials = serde_json::json!({ "password": password, "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))?;
|
|
||||||
|
|
||||||
let updated_at = Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
||||||
|
|
||||||
{
|
|
||||||
let db = state
|
|
||||||
.db
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| format!("Failed to lock database: {}", e))?;
|
|
||||||
|
|
||||||
let rows = db
|
|
||||||
.execute(
|
|
||||||
"UPDATE proxmox_clusters \
|
|
||||||
SET name=?1, cluster_type=?2, url=?3, port=?4, username=?5, \
|
|
||||||
encrypted_credentials=?6, updated_at=?7 \
|
|
||||||
WHERE id=?8",
|
|
||||||
rusqlite::params![
|
|
||||||
name,
|
|
||||||
match cluster_type {
|
|
||||||
ClusterType::VE => "ve",
|
|
||||||
ClusterType::PBS => "pbs",
|
|
||||||
},
|
|
||||||
connection.url,
|
|
||||||
connection.port,
|
|
||||||
username,
|
|
||||||
encrypted_credentials,
|
|
||||||
updated_at,
|
|
||||||
id,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.map_err(|e| format!("Failed to update cluster: {}", e))?;
|
|
||||||
|
|
||||||
if rows == 0 {
|
|
||||||
return Err(format!("Cluster {} not found", id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evict the stale authenticated client — it will re-authenticate with new credentials
|
|
||||||
// on the next API call.
|
|
||||||
{
|
|
||||||
let mut clusters = state.proxmox_clusters.lock().await;
|
|
||||||
clusters.remove(&id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ClusterInfo {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
cluster_type,
|
|
||||||
url: connection.url,
|
|
||||||
port: connection.port,
|
|
||||||
username,
|
|
||||||
created_at: String::new(),
|
|
||||||
updated_at,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List all Proxmox VMs
|
/// List all Proxmox VMs
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn list_proxmox_vms(
|
pub async fn list_proxmox_vms(
|
||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let vms =
|
let vms =
|
||||||
@ -436,7 +271,10 @@ pub async fn get_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let vm = crate::proxmox::vm::get_vm(
|
let vm = crate::proxmox::vm::get_vm(
|
||||||
@ -459,7 +297,10 @@ pub async fn start_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::vm::start_vm(
|
crate::proxmox::vm::start_vm(
|
||||||
@ -480,7 +321,10 @@ pub async fn stop_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::vm::stop_vm(
|
crate::proxmox::vm::stop_vm(
|
||||||
@ -501,7 +345,10 @@ pub async fn reboot_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::vm::reboot_vm(
|
crate::proxmox::vm::reboot_vm(
|
||||||
@ -522,7 +369,10 @@ pub async fn shutdown_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::vm::shutdown_vm(
|
crate::proxmox::vm::shutdown_vm(
|
||||||
@ -542,7 +392,10 @@ pub async fn list_proxmox_backup_jobs(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let jobs = crate::proxmox::backup::list_backup_jobs(
|
let jobs = crate::proxmox::backup::list_backup_jobs(
|
||||||
@ -569,7 +422,10 @@ pub async fn list_proxmox_datastores(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let datastores = crate::proxmox::backup::list_datastores(
|
let datastores = crate::proxmox::backup::list_datastores(
|
||||||
@ -597,7 +453,10 @@ pub async fn trigger_proxmox_backup_job(
|
|||||||
job_id: u32,
|
job_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::backup::trigger_backup_job(
|
crate::proxmox::backup::trigger_backup_job(
|
||||||
@ -616,7 +475,10 @@ pub async fn list_ceph_pools(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let pools = crate::proxmox::ceph::list_pools(
|
let pools = crate::proxmox::ceph::list_pools(
|
||||||
@ -642,7 +504,10 @@ pub async fn list_ceph_osd(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let osds = crate::proxmox::ceph::list_osds(
|
let osds = crate::proxmox::ceph::list_osds(
|
||||||
@ -668,7 +533,10 @@ pub async fn get_ceph_health(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let health = crate::proxmox::ceph::get_ceph_health(
|
let health = crate::proxmox::ceph::get_ceph_health(
|
||||||
@ -689,7 +557,10 @@ pub async fn list_auth_realms(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let realms = crate::proxmox::auth_realm::list_auth_realms(
|
let realms = crate::proxmox::auth_realm::list_auth_realms(
|
||||||
@ -724,7 +595,10 @@ pub async fn add_ldap_realm(
|
|||||||
certificate: String,
|
certificate: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let config = crate::proxmox::auth_realm::LdapRealmConfig {
|
let config = crate::proxmox::auth_realm::LdapRealmConfig {
|
||||||
@ -766,7 +640,10 @@ pub async fn add_ad_realm(
|
|||||||
certificate: String,
|
certificate: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let config = crate::proxmox::auth_realm::AdRealmConfig {
|
let config = crate::proxmox::auth_realm::AdRealmConfig {
|
||||||
@ -805,7 +682,10 @@ pub async fn add_openid_realm(
|
|||||||
mapping: String,
|
mapping: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let config = crate::proxmox::auth_realm::OpenidRealmConfig {
|
let config = crate::proxmox::auth_realm::OpenidRealmConfig {
|
||||||
@ -833,7 +713,10 @@ pub async fn list_acme_accounts(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let accounts = crate::proxmox::acme::list_acme_accounts(
|
let accounts = crate::proxmox::acme::list_acme_accounts(
|
||||||
@ -859,7 +742,10 @@ pub async fn register_acme_account(
|
|||||||
terms_of_service_agreed: bool,
|
terms_of_service_agreed: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let account = crate::proxmox::acme::register_acme_account(
|
let account = crate::proxmox::acme::register_acme_account(
|
||||||
@ -881,7 +767,10 @@ pub async fn get_acme_challenges(
|
|||||||
domain: String,
|
domain: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let challenges = crate::proxmox::acme::get_acme_challenges(
|
let challenges = crate::proxmox::acme::get_acme_challenges(
|
||||||
@ -909,7 +798,10 @@ pub async fn list_apt_updates(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let updates = crate::proxmox::apt::list_apt_updates(
|
let updates = crate::proxmox::apt::list_apt_updates(
|
||||||
@ -935,7 +827,10 @@ pub async fn update_apt_repos(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::apt::update_apt_repos(
|
crate::proxmox::apt::update_apt_repos(
|
||||||
@ -954,7 +849,10 @@ pub async fn list_apt_repositories(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let repos = crate::proxmox::apt::list_apt_repositories(
|
let repos = crate::proxmox::apt::list_apt_repositories(
|
||||||
@ -980,7 +878,10 @@ pub async fn get_shell_ticket(
|
|||||||
remote: String,
|
remote: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let ticket = crate::proxmox::shell::get_shell_ticket(
|
let ticket = crate::proxmox::shell::get_shell_ticket(
|
||||||
@ -1000,7 +901,10 @@ pub async fn list_views(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let views = crate::proxmox::views::list_views(
|
let views = crate::proxmox::views::list_views(
|
||||||
@ -1031,7 +935,10 @@ pub async fn add_view(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let widgets: Vec<crate::proxmox::views::Widget> = widgets
|
let widgets: Vec<crate::proxmox::views::Widget> = widgets
|
||||||
@ -1074,7 +981,10 @@ pub async fn update_view(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let widgets: Vec<crate::proxmox::views::Widget> = widgets
|
let widgets: Vec<crate::proxmox::views::Widget> = widgets
|
||||||
@ -1112,7 +1022,10 @@ pub async fn delete_view(
|
|||||||
view_id: String,
|
view_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::views::delete_view(
|
crate::proxmox::views::delete_view(
|
||||||
@ -1130,7 +1043,10 @@ pub async fn list_certificates(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let certs = crate::proxmox::certificates::list_certificates(
|
let certs = crate::proxmox::certificates::list_certificates(
|
||||||
@ -1157,7 +1073,10 @@ pub async fn upload_certificate(
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let cert = crate::proxmox::certificates::upload_certificate(
|
let cert = crate::proxmox::certificates::upload_certificate(
|
||||||
@ -1180,7 +1099,10 @@ pub async fn get_certificate(
|
|||||||
cert_id: String,
|
cert_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let cert = crate::proxmox::certificates::get_certificate(
|
let cert = crate::proxmox::certificates::get_certificate(
|
||||||
@ -1204,7 +1126,10 @@ pub async fn list_firewall_rules(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let rules = crate::proxmox::firewall::list_firewall_rules(
|
let rules = crate::proxmox::firewall::list_firewall_rules(
|
||||||
@ -1237,7 +1162,10 @@ pub async fn add_firewall_rule(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let rule = crate::proxmox::firewall::FirewallRule {
|
let rule = crate::proxmox::firewall::FirewallRule {
|
||||||
@ -1268,7 +1196,10 @@ pub async fn delete_firewall_rule(
|
|||||||
rule_num: u32,
|
rule_num: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::firewall::delete_rule(
|
crate::proxmox::firewall::delete_rule(
|
||||||
@ -1288,7 +1219,10 @@ pub async fn list_sdn_controllers(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let controllers = crate::proxmox::sdn::list_evpn_zones(
|
let controllers = crate::proxmox::sdn::list_evpn_zones(
|
||||||
@ -1314,7 +1248,10 @@ pub async fn list_sdn_vnets(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let vnets = crate::proxmox::sdn::list_vnets(
|
let vnets = crate::proxmox::sdn::list_vnets(
|
||||||
@ -1338,7 +1275,10 @@ pub async fn list_sdn_zones(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let zones = crate::proxmox::sdn::list_evpn_zones(
|
let zones = crate::proxmox::sdn::list_evpn_zones(
|
||||||
@ -1365,7 +1305,10 @@ pub async fn list_ceph_clusters(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let ceph_clusters = crate::proxmox::ceph_cluster::list_ceph_clusters(
|
let ceph_clusters = crate::proxmox::ceph_cluster::list_ceph_clusters(
|
||||||
@ -1390,7 +1333,10 @@ pub async fn get_ceph_cluster_status(
|
|||||||
ceph_cluster_id: String,
|
ceph_cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let status = crate::proxmox::ceph_cluster::get_ceph_cluster_status(
|
let status = crate::proxmox::ceph_cluster::get_ceph_cluster_status(
|
||||||
@ -1417,7 +1363,10 @@ pub async fn migrate_vm(
|
|||||||
target_cluster: String,
|
target_cluster: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let task = crate::proxmox::migration::migrate_vm(
|
let task = crate::proxmox::migration::migrate_vm(
|
||||||
@ -1441,7 +1390,10 @@ pub async fn list_migration_status(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let tasks = crate::proxmox::migration::list_migration_status(
|
let tasks = crate::proxmox::migration::list_migration_status(
|
||||||
@ -1467,7 +1419,10 @@ pub async fn list_updates(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let updates = crate::proxmox::updates_ext::list_updates_all_remotes(
|
let updates = crate::proxmox::updates_ext::list_updates_all_remotes(
|
||||||
@ -1488,7 +1443,10 @@ pub async fn list_updates(
|
|||||||
/// Refresh updates
|
/// Refresh updates
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn refresh_updates(cluster_id: String, state: State<'_, AppState>) -> Result<(), String> {
|
pub async fn refresh_updates(cluster_id: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::updates_ext::refresh_updates_all(
|
crate::proxmox::updates_ext::refresh_updates_all(
|
||||||
@ -1506,7 +1464,10 @@ pub async fn install_updates(
|
|||||||
packages: Vec<String>,
|
packages: Vec<String>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let package_refs: Vec<&str> = packages.iter().map(|s| s.as_str()).collect();
|
let package_refs: Vec<&str> = packages.iter().map(|s| s.as_str()).collect();
|
||||||
@ -1527,7 +1488,10 @@ pub async fn list_tasks(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let tasks = crate::proxmox::tasks::list_tasks(
|
let tasks = crate::proxmox::tasks::list_tasks(
|
||||||
@ -1554,7 +1518,10 @@ pub async fn get_task_status(
|
|||||||
task_id: String,
|
task_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let task = crate::proxmox::tasks::get_task_status(
|
let task = crate::proxmox::tasks::get_task_status(
|
||||||
@ -1577,7 +1544,10 @@ pub async fn stop_task(
|
|||||||
task_id: String,
|
task_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::tasks::stop_task(
|
crate::proxmox::tasks::stop_task(
|
||||||
@ -1599,7 +1569,10 @@ pub async fn get_metrics_summary(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let nodes = crate::proxmox::metrics::list_nodes(
|
let nodes = crate::proxmox::metrics::list_nodes(
|
||||||
@ -1624,7 +1597,10 @@ pub async fn list_metric_collections(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let nodes = crate::proxmox::metrics::list_nodes(
|
let nodes = crate::proxmox::metrics::list_nodes(
|
||||||
@ -1650,7 +1626,10 @@ pub async fn list_ha_groups(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let groups = crate::proxmox::ha::list_ha_groups(
|
let groups = crate::proxmox::ha::list_ha_groups(
|
||||||
@ -1676,7 +1655,10 @@ pub async fn create_ha_group(
|
|||||||
max_relocate: u32,
|
max_relocate: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::ha::create_ha_group(
|
crate::proxmox::ha::create_ha_group(
|
||||||
@ -1701,7 +1683,10 @@ pub async fn update_ha_group(
|
|||||||
max_relocate: u32,
|
max_relocate: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::ha::update_ha_group(
|
crate::proxmox::ha::update_ha_group(
|
||||||
@ -1723,7 +1708,10 @@ pub async fn delete_ha_group(
|
|||||||
group: String,
|
group: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::ha::delete_ha_group(
|
crate::proxmox::ha::delete_ha_group(
|
||||||
@ -1741,7 +1729,10 @@ pub async fn list_ha_resources(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let resources = crate::proxmox::ha::list_ha_resources(
|
let resources = crate::proxmox::ha::list_ha_resources(
|
||||||
@ -1764,7 +1755,10 @@ pub async fn enable_ha_resource(
|
|||||||
resource: String,
|
resource: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::ha::enable_ha_resource(
|
crate::proxmox::ha::enable_ha_resource(
|
||||||
@ -1784,7 +1778,10 @@ pub async fn list_acls(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "access/acl";
|
let path = "access/acl";
|
||||||
@ -1806,7 +1803,10 @@ pub async fn list_users(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "access/users";
|
let path = "access/users";
|
||||||
@ -1827,7 +1827,10 @@ pub async fn list_realms(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let realms = crate::proxmox::auth_realm::list_auth_realms(
|
let realms = crate::proxmox::auth_realm::list_auth_realms(
|
||||||
@ -1851,7 +1854,10 @@ pub async fn get_cluster_notes(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "cluster/config";
|
let path = "cluster/config";
|
||||||
@ -1874,7 +1880,10 @@ pub async fn update_cluster_notes(
|
|||||||
notes: String,
|
notes: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "cluster/config";
|
let path = "cluster/config";
|
||||||
@ -1900,7 +1909,10 @@ pub async fn search_proxmox_resources(
|
|||||||
query: String,
|
query: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = format!("cluster/resources?type=vm&search={}", query);
|
let path = format!("cluster/resources?type=vm&search={}", query);
|
||||||
@ -1924,7 +1936,10 @@ pub async fn get_node_status(
|
|||||||
node_id: String,
|
node_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = format!("nodes/{}/status", node_id);
|
let path = format!("nodes/{}/status", node_id);
|
||||||
@ -1946,7 +1961,10 @@ pub async fn get_syslog(
|
|||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let limit_val = limit.unwrap_or(500);
|
let limit_val = limit.unwrap_or(500);
|
||||||
@ -1971,7 +1989,10 @@ pub async fn list_network_interfaces(
|
|||||||
node_id: String,
|
node_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = format!("nodes/{}/network", node_id);
|
let path = format!("nodes/{}/network", node_id);
|
||||||
@ -1994,7 +2015,10 @@ pub async fn list_cluster_views(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let views = crate::proxmox::views::list_views(
|
let views = crate::proxmox::views::list_views(
|
||||||
@ -2018,7 +2042,10 @@ pub async fn create_cluster_view(
|
|||||||
name: String,
|
name: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let view = crate::proxmox::views::DashboardView {
|
let view = crate::proxmox::views::DashboardView {
|
||||||
@ -2048,7 +2075,10 @@ pub async fn delete_cluster_view(
|
|||||||
view_id: String,
|
view_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
crate::proxmox::views::delete_view(
|
crate::proxmox::views::delete_view(
|
||||||
@ -2068,7 +2098,10 @@ pub async fn get_subscription_status(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<serde_json::Value, String> {
|
) -> Result<serde_json::Value, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "nodes/localhost/subscription";
|
let path = "nodes/localhost/subscription";
|
||||||
@ -2089,7 +2122,10 @@ pub async fn list_cluster_tasks(
|
|||||||
limit: Option<u32>,
|
limit: Option<u32>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let limit_val = limit.unwrap_or(50);
|
let limit_val = limit.unwrap_or(50);
|
||||||
@ -2111,7 +2147,10 @@ pub async fn list_proxmox_containers(
|
|||||||
cluster_id: String,
|
cluster_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<serde_json::Value>, String> {
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
let client = get_proxmox_client_for_cluster(&cluster_id, &state).await?;
|
let clusters = state.proxmox_clusters.lock().await;
|
||||||
|
let client = clusters
|
||||||
|
.get(&cluster_id)
|
||||||
|
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||||
let client_guard = client.lock().await;
|
let client_guard = client.lock().await;
|
||||||
|
|
||||||
let path = "cluster/resources?type=lxc";
|
let path = "cluster/resources?type=lxc";
|
||||||
@ -2273,12 +2312,6 @@ mod tests {
|
|||||||
assert_eq!(result.unwrap().len(), 2);
|
assert_eq!(result.unwrap().len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_update_proxmox_cluster_not_found_error() {
|
|
||||||
let err = format!("Cluster {} not found", "missing-id");
|
|
||||||
assert_eq!(err, "Cluster missing-id not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cluster_notes_already_unwrapped_present() {
|
fn test_cluster_notes_already_unwrapped_present() {
|
||||||
let response = serde_json::json!({"notes": "Important info", "name": "pve"});
|
let response = serde_json::json!({"notes": "Important info", "name": "pve"});
|
||||||
@ -2330,12 +2363,4 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ping_proxmox_cluster_error_message_format() {
|
|
||||||
let raw = anyhow::anyhow!("connection refused");
|
|
||||||
let msg = format!("Connection test failed: {}", raw);
|
|
||||||
assert!(msg.starts_with("Connection test failed:"));
|
|
||||||
assert!(msg.contains("connection refused"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,8 +225,6 @@ pub fn run() {
|
|||||||
// Proxmox - Existing
|
// Proxmox - Existing
|
||||||
commands::proxmox::add_proxmox_cluster,
|
commands::proxmox::add_proxmox_cluster,
|
||||||
commands::proxmox::remove_proxmox_cluster,
|
commands::proxmox::remove_proxmox_cluster,
|
||||||
commands::proxmox::update_proxmox_cluster,
|
|
||||||
commands::proxmox::ping_proxmox_cluster,
|
|
||||||
commands::proxmox::connect_proxmox_cluster,
|
commands::proxmox::connect_proxmox_cluster,
|
||||||
commands::proxmox::disconnect_proxmox_cluster,
|
commands::proxmox::disconnect_proxmox_cluster,
|
||||||
commands::proxmox::list_proxmox_clusters,
|
commands::proxmox::list_proxmox_clusters,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/index';
|
||||||
import { Button } from '@/components/ui/index';
|
import { Button } from '@/components/ui/index';
|
||||||
import { MoreHorizontal, Plug, PlugZap } from 'lucide-react';
|
import { MoreHorizontal } from 'lucide-react';
|
||||||
|
|
||||||
interface RemoteInfo {
|
interface RemoteInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@ -25,77 +25,6 @@ interface RemotesListProps {
|
|||||||
onDisconnect?: (remote: RemoteInfo) => void;
|
onDisconnect?: (remote: RemoteInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActionsMenu({
|
|
||||||
remote,
|
|
||||||
onEdit,
|
|
||||||
onDelete,
|
|
||||||
onConnect,
|
|
||||||
onDisconnect,
|
|
||||||
}: {
|
|
||||||
remote: RemoteInfo;
|
|
||||||
onEdit?: (remote: RemoteInfo) => void;
|
|
||||||
onDelete?: (remote: RemoteInfo) => void;
|
|
||||||
onConnect?: (remote: RemoteInfo) => void;
|
|
||||||
onDisconnect?: (remote: RemoteInfo) => void;
|
|
||||||
}) {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (e: MouseEvent) => {
|
|
||||||
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative" ref={menuRef}>
|
|
||||||
<button
|
|
||||||
className="rounded-md p-1 hover:bg-accent"
|
|
||||||
onClick={() => setOpen((v) => !v)}
|
|
||||||
title="More actions"
|
|
||||||
>
|
|
||||||
<MoreHorizontal className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
{open && (
|
|
||||||
<div className="absolute right-0 z-50 mt-1 w-44 rounded-md border bg-background shadow-lg">
|
|
||||||
<div className="py-1">
|
|
||||||
<button
|
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent"
|
|
||||||
onClick={() => { setOpen(false); onEdit?.(remote); }}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm hover:bg-accent"
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
if (remote.status === 'connected') {
|
|
||||||
onDisconnect?.(remote);
|
|
||||||
} else {
|
|
||||||
onConnect?.(remote);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Test Connection
|
|
||||||
</button>
|
|
||||||
<div className="my-1 h-px bg-border" />
|
|
||||||
<button
|
|
||||||
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-destructive hover:bg-destructive/10"
|
|
||||||
onClick={() => { setOpen(false); onDelete?.(remote); }}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RemotesList({
|
export function RemotesList({
|
||||||
remotes,
|
remotes,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
@ -171,31 +100,44 @@ export function RemotesList({
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{remote.lastConnected || '-'}</TableCell>
|
<TableCell>{remote.lastConnected || '-'}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<div className="flex items-center justify-end space-x-1">
|
<div className="flex items-center justify-end space-x-2">
|
||||||
|
<button
|
||||||
|
className="rounded-md p-1 hover:bg-accent"
|
||||||
|
onClick={() => onEdit?.(remote)}
|
||||||
|
title="Edit"
|
||||||
|
>
|
||||||
|
<span className="h-4 w-4 text-xs">✏️</span>
|
||||||
|
</button>
|
||||||
{remote.status === 'connected' ? (
|
{remote.status === 'connected' ? (
|
||||||
<button
|
<button
|
||||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600 text-green-600"
|
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||||
onClick={() => onDisconnect?.(remote)}
|
onClick={() => onDisconnect?.(remote)}
|
||||||
title="Disconnect"
|
title="Disconnect"
|
||||||
>
|
>
|
||||||
<PlugZap className="h-4 w-4" />
|
<span className="h-4 w-4 text-xs">🔌</span>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="rounded-md p-1 hover:bg-green-100 hover:text-green-600 text-muted-foreground"
|
className="rounded-md p-1 hover:bg-green-100 hover:text-green-600"
|
||||||
onClick={() => onConnect?.(remote)}
|
onClick={() => onConnect?.(remote)}
|
||||||
title="Test connection"
|
title="Connect"
|
||||||
>
|
>
|
||||||
<Plug className="h-4 w-4" />
|
<span className="h-4 w-4 text-xs">🔌</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<ActionsMenu
|
<button
|
||||||
remote={remote}
|
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
||||||
onEdit={onEdit}
|
onClick={() => onDelete?.(remote)}
|
||||||
onDelete={onDelete}
|
title="Delete"
|
||||||
onConnect={onConnect}
|
>
|
||||||
onDisconnect={onDisconnect}
|
<span className="h-4 w-4 text-xs">🗑️</span>
|
||||||
/>
|
</button>
|
||||||
|
<button
|
||||||
|
className="rounded-md p-1 hover:bg-accent"
|
||||||
|
title="More"
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@ -40,36 +40,6 @@ export async function removeProxmoxCluster(id: string): Promise<void> {
|
|||||||
await invoke("remove_proxmox_cluster", { id });
|
await invoke("remove_proxmox_cluster", { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing Proxmox cluster's metadata and credentials atomically.
|
|
||||||
* Uses a single SQL UPDATE so there is no window where the record is missing.
|
|
||||||
*/
|
|
||||||
export async function updateProxmoxCluster(
|
|
||||||
id: string,
|
|
||||||
name: string,
|
|
||||||
clusterType: ClusterType,
|
|
||||||
connection: { url: string; port: number },
|
|
||||||
username: string,
|
|
||||||
password: string
|
|
||||||
): Promise<ClusterInfo> {
|
|
||||||
return await invoke<ClusterInfo>("update_proxmox_cluster", {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
clusterType,
|
|
||||||
connection,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ping a Proxmox cluster — authenticates and calls the version endpoint to verify
|
|
||||||
* the API is reachable and credentials are valid.
|
|
||||||
*/
|
|
||||||
export async function pingProxmoxCluster(clusterId: string): Promise<unknown> {
|
|
||||||
return await invoke("ping_proxmox_cluster", { clusterId });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect (or re-connect) to a cluster stored in the DB.
|
* Connect (or re-connect) to a cluster stored in the DB.
|
||||||
* Authenticates against the Proxmox API and populates the in-memory pool.
|
* Authenticates against the Proxmox API and populates the in-memory pool.
|
||||||
|
|||||||
@ -1,111 +1,10 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||||
import { Button } from '@/components/ui/index';
|
import { Button } from '@/components/ui/index';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
import { PoolList, OSDList, CephHealthWidget, MonitorList } from '@/components/Proxmox';
|
import { PoolList, OSDList, CephHealthWidget, MonitorList } from '@/components/Proxmox';
|
||||||
import { listProxmoxClusters, listCephPools, listCephOsd, getCephHealth } from '@/lib/proxmoxClient';
|
|
||||||
import { toast } from 'sonner';
|
|
||||||
|
|
||||||
export function ProxmoxCephPage() {
|
export function ProxmoxCephPage() {
|
||||||
const [clusterId, setClusterId] = useState<string>('');
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const [health, setHealth] = useState<any>(null);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const [pools, setPools] = useState<any[]>([]);
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const [osds, setOsds] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [isCephEnabled, setIsCephEnabled] = useState<boolean | null>(null);
|
|
||||||
|
|
||||||
const loadData = useCallback(async (cId: string) => {
|
|
||||||
if (!cId) return;
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
|
|
||||||
// Check Ceph availability by fetching health first
|
|
||||||
let cephAvailable = false;
|
|
||||||
try {
|
|
||||||
const h = await getCephHealth(cId);
|
|
||||||
setHealth(h);
|
|
||||||
cephAvailable = true;
|
|
||||||
} catch {
|
|
||||||
setIsCephEnabled(false);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cephAvailable) {
|
|
||||||
setIsCephEnabled(true);
|
|
||||||
const [poolsResult, osdsResult] = await Promise.allSettled([
|
|
||||||
listCephPools(cId),
|
|
||||||
listCephOsd(cId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (poolsResult.status === 'fulfilled') {
|
|
||||||
setPools(poolsResult.value);
|
|
||||||
} else {
|
|
||||||
toast.error('Failed to load Ceph pools');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (osdsResult.status === 'fulfilled') {
|
|
||||||
setOsds(osdsResult.value);
|
|
||||||
} else {
|
|
||||||
toast.error('Failed to load Ceph OSDs');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
listProxmoxClusters()
|
|
||||||
.then((cls) => {
|
|
||||||
if (cls.length > 0) {
|
|
||||||
setClusterId(cls[0].id);
|
|
||||||
loadData(cls[0].id);
|
|
||||||
} else {
|
|
||||||
setIsCephEnabled(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Failed to load clusters:', err);
|
|
||||||
setError('Failed to load clusters');
|
|
||||||
setIsCephEnabled(false);
|
|
||||||
});
|
|
||||||
}, [loadData]);
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
if (clusterId) loadData(clusterId);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isCephEnabled === false) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold">Ceph Storage</h1>
|
|
||||||
<p className="text-muted-foreground">Manage Ceph clusters and storage</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="py-12 text-center text-muted-foreground">
|
|
||||||
{error ? (
|
|
||||||
<p>{error}</p>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p className="text-base font-medium">Ceph is not configured on this cluster</p>
|
|
||||||
<p className="text-sm mt-1">
|
|
||||||
Ceph storage requires a dedicated Ceph cluster deployment on the Proxmox nodes.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@ -114,8 +13,8 @@ export function ProxmoxCephPage() {
|
|||||||
<p className="text-muted-foreground">Manage Ceph clusters and storage</p>
|
<p className="text-muted-foreground">Manage Ceph clusters and storage</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={loading}>
|
<Button variant="outline" size="sm">
|
||||||
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -127,11 +26,9 @@ export function ProxmoxCephPage() {
|
|||||||
<CardTitle>Ceph Health</CardTitle>
|
<CardTitle>Ceph Health</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{health ? (
|
<CephHealthWidget
|
||||||
<CephHealthWidget health={health} />
|
health={{ status: 'HEALTH_OK', summary: 'Cluster healthy', details: [] }}
|
||||||
) : (
|
/>
|
||||||
<p className="text-sm text-muted-foreground">Loading health data...</p>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@ -143,8 +40,8 @@ export function ProxmoxCephPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<PoolList
|
<PoolList
|
||||||
pools={pools}
|
pools={[]}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={() => {}}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -155,8 +52,8 @@ export function ProxmoxCephPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<OSDList
|
<OSDList
|
||||||
osds={osds}
|
osds={[]}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={() => {}}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -169,7 +66,7 @@ export function ProxmoxCephPage() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<MonitorList
|
<MonitorList
|
||||||
monitors={[]}
|
monitors={[]}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={() => {}}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { AddRemoteForm } from '@/components/Proxmox';
|
|||||||
import { EditRemoteForm } from '@/components/Proxmox';
|
import { EditRemoteForm } from '@/components/Proxmox';
|
||||||
import { RemoveRemoteDialog } from '@/components/Proxmox';
|
import { RemoveRemoteDialog } from '@/components/Proxmox';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/index';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/index';
|
||||||
import { listProxmoxClusters, addProxmoxCluster, removeProxmoxCluster, updateProxmoxCluster, connectProxmoxCluster, disconnectProxmoxCluster } from '@/lib/proxmoxClient';
|
import { listProxmoxClusters, addProxmoxCluster, removeProxmoxCluster, connectProxmoxCluster, disconnectProxmoxCluster } from '@/lib/proxmoxClient';
|
||||||
import { ClusterType } from '@/lib/domain';
|
import { ClusterType } from '@/lib/domain';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ export function ProxmoxRemotesPage() {
|
|||||||
url: c.url,
|
url: c.url,
|
||||||
username: c.username,
|
username: c.username,
|
||||||
type: c.clusterType === 've' ? 'pve' : 'pbs',
|
type: c.clusterType === 've' ? 'pve' : 'pbs',
|
||||||
status: (c.connected ? 'connected' : 'disconnected') as RemoteInfo['status'],
|
status: 'connected' as const, // Placeholder - actual status requires connection test
|
||||||
}));
|
}));
|
||||||
setRemotes(remotesList);
|
setRemotes(remotesList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -102,7 +102,11 @@ export function ProxmoxRemotesPage() {
|
|||||||
const clusterType = config.type === 'pve' ? 've' : 'pbs';
|
const clusterType = config.type === 'pve' ? 've' : 'pbs';
|
||||||
const { hostname, port } = parseRemoteUrl(config.url, config.type);
|
const { hostname, port } = parseRemoteUrl(config.url, config.type);
|
||||||
|
|
||||||
await updateProxmoxCluster(
|
// Edit operation requires remove-then-add since backend doesn't support update.
|
||||||
|
// If add fails after remove, the remote will be lost - this is a known limitation
|
||||||
|
// until backend supports atomic update operations.
|
||||||
|
await removeProxmoxCluster(config.id);
|
||||||
|
await addProxmoxCluster(
|
||||||
config.id,
|
config.id,
|
||||||
config.name,
|
config.name,
|
||||||
clusterType as ClusterType,
|
clusterType as ClusterType,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user