syncable_cli/wizard/
cluster_selection.rs

1//! Cluster selection step for deployment wizard
2
3use crate::platform::api::types::ClusterSummary;
4use crate::wizard::render::{display_step_header, status_indicator, wizard_render_config};
5use colored::Colorize;
6use inquire::{InquireError, Select};
7
8/// Result of cluster selection step
9#[derive(Debug, Clone)]
10pub enum ClusterSelectionResult {
11    /// User selected a cluster
12    Selected(ClusterSummary),
13    /// User wants to go back
14    Back,
15    /// User cancelled the wizard
16    Cancelled,
17}
18
19/// Display cluster selection for Kubernetes deployments
20pub fn select_cluster(clusters: &[ClusterSummary]) -> ClusterSelectionResult {
21    display_step_header(
22        3,
23        "Select Cluster",
24        "Choose which Kubernetes cluster to deploy to.",
25    );
26
27    // Filter to only healthy clusters
28    let healthy_clusters: Vec<&ClusterSummary> = clusters.iter().filter(|c| c.is_healthy).collect();
29
30    if healthy_clusters.is_empty() {
31        println!(
32            "\n{}",
33            "No healthy clusters available. Provision a cluster in platform settings.".red()
34        );
35        return ClusterSelectionResult::Cancelled;
36    }
37
38    // Build options with status and region
39    let mut options: Vec<String> = healthy_clusters
40        .iter()
41        .map(|c| {
42            format!(
43                "{} {}  {}",
44                status_indicator(c.is_healthy),
45                c.name.cyan(),
46                c.region.dimmed()
47            )
48        })
49        .collect();
50
51    // Add back option
52    options.push("← Back to target selection".dimmed().to_string());
53
54    let selection = Select::new("Select cluster:", options.clone())
55        .with_render_config(wizard_render_config())
56        .with_help_message("↑↓ to move, Enter to select, Esc to cancel")
57        .with_page_size(6)
58        .prompt();
59
60    match selection {
61        Ok(answer) => {
62            if answer.contains("Back") {
63                return ClusterSelectionResult::Back;
64            }
65
66            // Find selected cluster by name
67            let selected = healthy_clusters
68                .iter()
69                .find(|c| answer.contains(&c.name))
70                .copied();
71
72            match selected {
73                Some(cluster) => {
74                    println!(
75                        "\n{} Selected cluster: {} ({})",
76                        "✓".green(),
77                        cluster.name,
78                        cluster.region
79                    );
80                    ClusterSelectionResult::Selected(cluster.clone())
81                }
82                None => ClusterSelectionResult::Cancelled,
83            }
84        }
85        Err(InquireError::OperationCanceled) | Err(InquireError::OperationInterrupted) => {
86            println!("\n{}", "Wizard cancelled.".dimmed());
87            ClusterSelectionResult::Cancelled
88        }
89        Err(_) => ClusterSelectionResult::Cancelled,
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_cluster_selection_result_variants() {
99        let cluster = ClusterSummary {
100            id: "c1".to_string(),
101            name: "prod".to_string(),
102            region: "us-central1".to_string(),
103            is_healthy: true,
104        };
105        let _ = ClusterSelectionResult::Selected(cluster);
106        let _ = ClusterSelectionResult::Back;
107        let _ = ClusterSelectionResult::Cancelled;
108    }
109}