Merge pull request 'fix: Proxmox v1.2.2 — client retrieval, AI message ordering, Remotes UX, Ceph false positive' (#124) from fix/proxmox-v1.2.2-consolidated into beta
Some checks failed
Release Beta / autotag (push) Successful in 10s
Release Beta / changelog (push) Successful in 1m35s
Test / frontend-tests (push) Successful in 1m45s
Test / frontend-typecheck (push) Successful in 1m57s
Release Beta / build-linux-amd64 (push) Successful in 12m7s
Release Beta / build-windows-amd64 (push) Successful in 12m28s
Release Beta / build-linux-arm64 (push) Successful in 13m48s
Release Beta / build-macos-arm64 (push) Failing after 21m38s
Test / rust-fmt-check (push) Successful in 23m18s
Test / rust-clippy (push) Successful in 24m34s
Test / rust-tests (push) Successful in 26m42s
Some checks failed
Release Beta / autotag (push) Successful in 10s
Release Beta / changelog (push) Successful in 1m35s
Test / frontend-tests (push) Successful in 1m45s
Test / frontend-typecheck (push) Successful in 1m57s
Release Beta / build-linux-amd64 (push) Successful in 12m7s
Release Beta / build-windows-amd64 (push) Successful in 12m28s
Release Beta / build-linux-arm64 (push) Successful in 13m48s
Release Beta / build-macos-arm64 (push) Failing after 21m38s
Test / rust-fmt-check (push) Successful in 23m18s
Test / rust-clippy (push) Successful in 24m34s
Test / rust-tests (push) Successful in 26m42s
Reviewed-on: #124
This commit is contained in:
commit
cb770661d7
@ -351,9 +351,11 @@ 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();
|
||||||
|
|
||||||
// Inject devops-incident-responder as primary system prompt (always)
|
// 1. Inject devops-incident-responder as primary system prompt (always first)
|
||||||
if let Some(agent) = devops_agent {
|
if let Some(agent) = devops_agent {
|
||||||
messages.push(Message {
|
messages.push(Message {
|
||||||
role: "system".into(),
|
role: "system".into(),
|
||||||
@ -363,7 +365,7 @@ pub async fn chat_message(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject domain system prompt if provided
|
// 2. Inject domain system prompt if provided (second position)
|
||||||
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 {
|
||||||
@ -375,28 +377,6 @@ 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
|
||||||
|
|
||||||
@ -427,6 +407,7 @@ 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(),
|
||||||
@ -435,7 +416,6 @@ 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!(
|
||||||
@ -454,6 +434,34 @@ 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(),
|
||||||
@ -471,7 +479,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: "system".into(),
|
role: "user".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. \
|
||||||
@ -490,7 +498,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: "system".into(),
|
role: "user".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. \
|
||||||
|
|||||||
@ -232,16 +232,181 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 =
|
||||||
@ -266,10 +431,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -292,10 +454,7 @@ pub async fn start_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -316,10 +475,7 @@ pub async fn stop_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -340,10 +496,7 @@ pub async fn reboot_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -364,10 +517,7 @@ pub async fn shutdown_proxmox_vm(
|
|||||||
vm_id: u32,
|
vm_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -387,10 +537,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -417,10 +564,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -448,10 +592,7 @@ pub async fn trigger_proxmox_backup_job(
|
|||||||
job_id: u32,
|
job_id: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -470,10 +611,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -499,10 +637,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -528,10 +663,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -552,10 +684,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -590,10 +719,7 @@ pub async fn add_ldap_realm(
|
|||||||
certificate: String,
|
certificate: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 {
|
||||||
@ -635,10 +761,7 @@ pub async fn add_ad_realm(
|
|||||||
certificate: String,
|
certificate: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 {
|
||||||
@ -677,10 +800,7 @@ pub async fn add_openid_realm(
|
|||||||
mapping: String,
|
mapping: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 {
|
||||||
@ -708,10 +828,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -737,10 +854,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -762,10 +876,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -793,10 +904,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -822,10 +930,7 @@ pub async fn update_apt_repos(
|
|||||||
node: String,
|
node: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -844,10 +949,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -873,10 +975,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -896,10 +995,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -930,10 +1026,7 @@ pub async fn add_view(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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
|
||||||
@ -976,10 +1069,7 @@ pub async fn update_view(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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
|
||||||
@ -1017,10 +1107,7 @@ pub async fn delete_view(
|
|||||||
view_id: String,
|
view_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1038,10 +1125,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1068,10 +1152,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1094,10 +1175,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1121,10 +1199,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1157,10 +1232,7 @@ pub async fn add_firewall_rule(
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 {
|
||||||
@ -1191,10 +1263,7 @@ pub async fn delete_firewall_rule(
|
|||||||
rule_num: u32,
|
rule_num: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1214,10 +1283,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1243,10 +1309,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1270,10 +1333,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1300,10 +1360,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1328,10 +1385,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1358,10 +1412,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1385,10 +1436,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1414,10 +1462,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1438,10 +1483,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1459,10 +1501,7 @@ pub async fn install_updates(
|
|||||||
packages: Vec<String>,
|
packages: Vec<String>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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();
|
||||||
@ -1483,10 +1522,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1513,10 +1549,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1539,10 +1572,7 @@ pub async fn stop_task(
|
|||||||
task_id: String,
|
task_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1564,10 +1594,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1592,10 +1619,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1621,10 +1645,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1650,10 +1671,7 @@ pub async fn create_ha_group(
|
|||||||
max_relocate: u32,
|
max_relocate: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1678,10 +1696,7 @@ pub async fn update_ha_group(
|
|||||||
max_relocate: u32,
|
max_relocate: u32,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1703,10 +1718,7 @@ pub async fn delete_ha_group(
|
|||||||
group: String,
|
group: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1724,10 +1736,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1750,10 +1759,7 @@ pub async fn enable_ha_resource(
|
|||||||
resource: String,
|
resource: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1773,10 +1779,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -1798,10 +1801,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -1823,10 +1823,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -1850,10 +1847,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -1877,10 +1871,7 @@ pub async fn update_cluster_notes(
|
|||||||
notes: String,
|
notes: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -1906,10 +1897,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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);
|
||||||
@ -1934,10 +1922,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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);
|
||||||
@ -1962,10 +1947,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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);
|
||||||
@ -1991,10 +1973,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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);
|
||||||
@ -2018,10 +1997,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -2045,10 +2021,7 @@ pub async fn create_cluster_view(
|
|||||||
name: String,
|
name: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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 {
|
||||||
@ -2078,10 +2051,7 @@ pub async fn delete_cluster_view(
|
|||||||
view_id: String,
|
view_id: String,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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(
|
||||||
@ -2101,10 +2071,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -2128,10 +2095,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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);
|
||||||
@ -2154,10 +2118,7 @@ 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 clusters = state.proxmox_clusters.lock().await;
|
let client = get_proxmox_client_for_cluster(&cluster_id, &state).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";
|
||||||
@ -2243,4 +2204,41 @@ mod tests {
|
|||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
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]
|
||||||
|
fn test_update_proxmox_cluster_rows_zero_means_not_found() {
|
||||||
|
let rows: usize = 0;
|
||||||
|
let result: Result<(), String> = if rows == 0 {
|
||||||
|
Err(format!("Cluster {} not found", "ghost-id"))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(result.unwrap_err().contains("ghost-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_proxmox_cluster_rows_nonzero_succeeds() {
|
||||||
|
let rows: usize = 1;
|
||||||
|
let result: Result<(), String> = if rows == 0 {
|
||||||
|
Err(format!("Cluster {} not found", "real-id"))
|
||||||
|
} else {
|
||||||
|
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,6 +225,8 @@ 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::list_proxmox_clusters,
|
commands::proxmox::list_proxmox_clusters,
|
||||||
commands::proxmox::get_proxmox_cluster,
|
commands::proxmox::get_proxmox_cluster,
|
||||||
commands::proxmox::list_proxmox_vms,
|
commands::proxmox::list_proxmox_vms,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { useState, useRef, useEffect } 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 } from 'lucide-react';
|
import { MoreHorizontal, Plug, PlugZap } from 'lucide-react';
|
||||||
|
|
||||||
interface RemoteInfo {
|
interface RemoteInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@ -25,6 +25,77 @@ 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,
|
||||||
@ -100,44 +171,31 @@ 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-2">
|
<div className="flex items-center justify-end space-x-1">
|
||||||
<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"
|
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600 text-green-600"
|
||||||
onClick={() => onDisconnect?.(remote)}
|
onClick={() => onDisconnect?.(remote)}
|
||||||
title="Disconnect"
|
title="Disconnect"
|
||||||
>
|
>
|
||||||
<span className="h-4 w-4 text-xs">🔌</span>
|
<PlugZap className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="rounded-md p-1 hover:bg-green-100 hover:text-green-600"
|
className="rounded-md p-1 hover:bg-green-100 hover:text-green-600 text-muted-foreground"
|
||||||
onClick={() => onConnect?.(remote)}
|
onClick={() => onConnect?.(remote)}
|
||||||
title="Connect"
|
title="Test connection"
|
||||||
>
|
>
|
||||||
<span className="h-4 w-4 text-xs">🔌</span>
|
<Plug className="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<ActionsMenu
|
||||||
className="rounded-md p-1 hover:bg-red-100 hover:text-red-600"
|
remote={remote}
|
||||||
onClick={() => onDelete?.(remote)}
|
onEdit={onEdit}
|
||||||
title="Delete"
|
onDelete={onDelete}
|
||||||
>
|
onConnect={onConnect}
|
||||||
<span className="h-4 w-4 text-xs">🗑️</span>
|
onDisconnect={onDisconnect}
|
||||||
</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,6 +40,36 @@ 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<any> {
|
||||||
|
return await invoke<any>("ping_proxmox_cluster", { clusterId });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all Proxmox clusters
|
* List all Proxmox clusters
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,10 +1,111 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect, useCallback } 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">
|
||||||
@ -13,8 +114,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">
|
<Button variant="outline" size="sm" onClick={handleRefresh} disabled={loading}>
|
||||||
<RefreshCw className="mr-2 h-4 w-4" />
|
<RefreshCw className={`mr-2 h-4 w-4 ${loading ? 'animate-spin' : ''}`} />
|
||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -26,9 +127,11 @@ export function ProxmoxCephPage() {
|
|||||||
<CardTitle>Ceph Health</CardTitle>
|
<CardTitle>Ceph Health</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<CephHealthWidget
|
{health ? (
|
||||||
health={{ status: 'HEALTH_OK', summary: 'Cluster healthy', details: [] }}
|
<CephHealthWidget health={health} />
|
||||||
/>
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">Loading health data...</p>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@ -40,8 +143,8 @@ export function ProxmoxCephPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<PoolList
|
<PoolList
|
||||||
pools={[]}
|
pools={pools}
|
||||||
onRefresh={() => {}}
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -52,8 +155,8 @@ export function ProxmoxCephPage() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<OSDList
|
<OSDList
|
||||||
osds={[]}
|
osds={osds}
|
||||||
onRefresh={() => {}}
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -66,7 +169,7 @@ export function ProxmoxCephPage() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<MonitorList
|
<MonitorList
|
||||||
monitors={[]}
|
monitors={[]}
|
||||||
onRefresh={() => {}}
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
</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 } from '@/lib/proxmoxClient';
|
import { listProxmoxClusters, addProxmoxCluster, removeProxmoxCluster, updateProxmoxCluster, pingProxmoxCluster } 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: 'connected' as const, // Placeholder - actual status requires connection test
|
status: (c.connected ? 'connected' : 'disconnected') as RemoteInfo['status'],
|
||||||
}));
|
}));
|
||||||
setRemotes(remotesList);
|
setRemotes(remotesList);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -102,11 +102,7 @@ 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);
|
||||||
|
|
||||||
// Edit operation requires remove-then-add since backend doesn't support update.
|
await updateProxmoxCluster(
|
||||||
// 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,
|
||||||
@ -136,6 +132,30 @@ export function ProxmoxRemotesPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConnectRemote = async (remote: RemoteInfo) => {
|
||||||
|
try {
|
||||||
|
toast.info(`Testing connection to ${remote.name}...`);
|
||||||
|
await pingProxmoxCluster(remote.id);
|
||||||
|
toast.success(`Connected to ${remote.name}`);
|
||||||
|
setRemotes((prev) =>
|
||||||
|
prev.map((r) => (r.id === remote.id ? { ...r, status: 'connected' } : r))
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to connect remote:', err);
|
||||||
|
toast.error('Connection failed: ' + String(err));
|
||||||
|
setRemotes((prev) =>
|
||||||
|
prev.map((r) => (r.id === remote.id ? { ...r, status: 'error' } : r))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisconnectRemote = (remote: RemoteInfo) => {
|
||||||
|
setRemotes((prev) =>
|
||||||
|
prev.map((r) => (r.id === remote.id ? { ...r, status: 'disconnected' } : r))
|
||||||
|
);
|
||||||
|
toast.info(`Disconnected from ${remote.name}`);
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
@ -164,6 +184,12 @@ export function ProxmoxRemotesPage() {
|
|||||||
onDelete={(remote) => {
|
onDelete={(remote) => {
|
||||||
setRemovingRemote(remote as RemoteInfo | null);
|
setRemovingRemote(remote as RemoteInfo | null);
|
||||||
}}
|
}}
|
||||||
|
onConnect={(remote) => {
|
||||||
|
void handleConnectRemote(remote as RemoteInfo);
|
||||||
|
}}
|
||||||
|
onDisconnect={(remote) => {
|
||||||
|
handleDisconnectRemote(remote as RemoteInfo);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{showAddDialog && (
|
{showAddDialog && (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user