use color_eyre::Result;
use std::time::Duration;
use super::CloudProvider;
#[cfg(feature = "remote-providers")]
use blueprint_remote_providers::{CloudProvisioner, DeploymentTracker, HealthMonitor};
#[derive(Debug)]
struct DeploymentStatus {
id: String,
provider: String,
region: String,
status: String,
health: Option<String>,
ip: String,
uptime: String,
ttl: String,
}
pub async fn show_status(deployment_id: Option<String>, watch: bool) -> Result<()> {
if watch {
loop {
print!("\x1B[2J\x1B[1;1H"); display_status(&deployment_id).await?;
tokio::time::sleep(Duration::from_secs(5)).await;
}
} else {
display_status(&deployment_id).await
}
}
async fn display_status(deployment_id: &Option<String>) -> Result<()> {
println!("📊 Cloud Deployment Status\n");
if let Some(id) = deployment_id {
show_deployment_details(id).await?;
} else {
show_all_deployments().await?;
}
Ok(())
}
async fn show_deployment_details(id: &str) -> Result<()> {
println!("Deployment: {}", id);
println!("{}", "─".repeat(50));
println!("Provider: AWS");
println!("Region: us-east-1");
println!("Instance Type: t3.xlarge");
println!("Status: 🟢 Running");
println!("Public IP: 54.123.45.67");
println!("Private IP: 10.0.1.42");
println!("Created: 2024-01-15 10:30:00");
println!("Uptime: 2h 45m");
println!("TTL: 21h 15m remaining");
println!();
println!("Resources:");
println!(" CPU: 4 cores (23% usage)");
println!(" Memory: 16 GB (8.2 GB used)");
println!(" Storage: 100 GB (12 GB used)");
println!(" Network In: 1.2 GB");
println!(" Network Out: 3.4 GB");
println!();
println!("Blueprint:");
println!(" ID: 123");
println!(" Name: my-blueprint");
println!(" Version: 0.1.0");
println!(" Jobs Executed: 42");
println!(" Last Job: 5 minutes ago");
println!();
println!("Health Checks:");
println!(" HTTP /health: ✅ 200 OK (32ms)");
println!(" TCP port 8080: ✅ Open");
println!(" Process: ✅ Running (PID 1234)");
println!();
println!("Logs (last 5 lines):");
println!(" [10:45:23] INFO Starting job execution");
println!(" [10:45:24] INFO Job 42 completed successfully");
println!(" [10:45:25] DEBUG Metrics updated");
println!(" [10:45:30] INFO Health check passed");
println!(" [10:45:35] INFO Waiting for next job");
Ok(())
}
async fn show_all_deployments() -> Result<()> {
#[cfg(feature = "remote-providers")]
let deployments = {
match load_real_deployments().await {
Ok(deployments) => deployments,
Err(e) => {
println!("⚠️ Failed to load deployments from tracker: {}", e);
get_mock_deployments()
}
}
};
#[cfg(not(feature = "remote-providers"))]
let deployments = get_mock_deployments();
if deployments.is_empty() {
println!("No active deployments.");
println!("\nDeploy a blueprint with:");
println!(" cargo tangle cloud deploy --provider aws");
} else {
println!(
"{:<15} {:<15} {:<12} {:<15} {:<15} {:<10} {:<10}",
"ID", "Provider", "Region", "Status", "IP", "Uptime", "TTL"
);
println!("{}", "-".repeat(92));
for dep in &deployments {
println!(
"{:<15} {:<15} {:<12} {:<15} {:<15} {:<10} {:<10}",
dep.id, dep.provider, dep.region, dep.status, dep.ip, dep.uptime, dep.ttl
);
}
println!("\nSummary:");
let running = deployments
.iter()
.filter(|d| d.status.contains("Running"))
.count();
let total = deployments.len();
println!(" {} running, {} total deployments", running, total);
let total_cost = running as f32 * 0.42;
println!(" Estimated cost: ${:.2}/hour", total_cost);
println!("\nCommands:");
println!(" View details: cargo tangle cloud status --deployment-id <id>");
println!(" Watch status: cargo tangle cloud status --watch");
println!(" Terminate: cargo tangle cloud terminate --deployment-id <id>");
}
Ok(())
}
pub async fn terminate(deployment_id: Option<String>, all: bool, yes: bool) -> Result<()> {
println!("🛑 Terminating Cloud Deployments\n");
if all {
if !yes {
use dialoguer::Confirm;
if !Confirm::new()
.with_prompt("Are you sure you want to terminate ALL deployments?")
.default(false)
.interact()?
{
println!("Termination cancelled.");
return Ok(());
}
}
println!("Terminating all deployments...");
let pb = indicatif::ProgressBar::new(4);
pb.set_style(
indicatif::ProgressStyle::default_bar().template("[{bar:40}] {pos}/{len} {msg}")?,
);
for i in 0..4 {
pb.set_message(format!("Terminating dep-{}", i));
tokio::time::sleep(Duration::from_millis(500)).await;
pb.inc(1);
}
pb.finish_with_message("All deployments terminated");
} else if let Some(id) = deployment_id {
if !yes {
use dialoguer::Confirm;
if !Confirm::new()
.with_prompt(format!("Terminate deployment {}?", id))
.default(true)
.interact()?
{
println!("Termination cancelled.");
return Ok(());
}
}
println!("Terminating {}...", id);
let spinner = indicatif::ProgressBar::new_spinner();
spinner.set_style(indicatif::ProgressStyle::default_spinner().template("{spinner} {msg}")?);
#[cfg(feature = "remote-providers")]
{
spinner.set_message("Initializing cloud provisioner...");
match CloudProvisioner::new().await {
Ok(provisioner) => {
spinner.set_message("Terminating instance...");
if let Err(e) = terminate_real_deployment(&provisioner, &id).await {
spinner
.finish_with_message(format!("❌ Failed to terminate {}: {}", id, e));
return Ok(());
}
}
Err(e) => {
spinner
.finish_with_message(format!("❌ Failed to initialize provisioner: {}", e));
return Ok(());
}
}
}
#[cfg(not(feature = "remote-providers"))]
{
spinner.set_message("Stopping services...");
tokio::time::sleep(Duration::from_secs(1)).await;
spinner.set_message("Deallocating resources...");
tokio::time::sleep(Duration::from_secs(1)).await;
spinner.set_message("Cleaning up...");
tokio::time::sleep(Duration::from_millis(500)).await;
}
spinner.finish_with_message(format!("✅ {} terminated", id));
} else {
println!("No deployment specified.");
println!("\nUsage:");
println!(" Terminate one: cargo tangle cloud terminate --deployment-id <id>");
println!(" Terminate all: cargo tangle cloud terminate --all");
}
Ok(())
}
#[cfg(feature = "remote-providers")]
async fn load_real_deployments() -> Result<Vec<DeploymentStatus>> {
use std::path::PathBuf;
let tracker_path = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".tangle")
.join("remote_deployments");
if !tracker_path.exists() {
return Ok(Vec::new());
}
let tracker = DeploymentTracker::new(&tracker_path)
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to initialize deployment tracker: {}", e))?;
let provisioner = std::sync::Arc::new(
CloudProvisioner::new()
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to initialize provisioner: {}", e))?,
);
let tracker_arc = std::sync::Arc::new(tracker);
let health_monitor = HealthMonitor::new(provisioner, tracker_arc.clone());
let mut deployments = Vec::new();
let all_deployments = tracker_arc
.list_all()
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to load deployments: {}", e))?;
for deployment in all_deployments {
let health_status = match health_monitor.is_healthy(&deployment.id).await {
Ok(true) => Some("💚 Healthy".to_string()),
Ok(false) => Some("❤️ Unhealthy".to_string()),
Err(_) => Some("❓ Unknown".to_string()),
};
use blueprint_remote_providers::deployment::tracker::DeploymentStatus as DS;
let status_icon = match deployment.status {
DS::Active => "🟢",
DS::Terminating => "🟡",
DS::Terminated => "🔴",
DS::Failed => "🔴",
DS::Unknown => "⚪",
};
let status_str = match deployment.status {
DS::Active => "active",
DS::Terminating => "terminating",
DS::Terminated => "terminated",
DS::Failed => "failed",
DS::Unknown => "unknown",
};
let duration = chrono::Utc::now().signed_duration_since(deployment.deployed_at);
let uptime = format!("{}h {}m", duration.num_hours(), duration.num_minutes() % 60);
deployments.push(DeploymentStatus {
id: deployment.id.clone(),
provider: format!("{:?}", deployment.provider),
region: deployment
.region
.clone()
.unwrap_or_else(|| "unknown".to_string()),
status: format!("{} {}", status_icon, status_str),
health: health_status,
ip: deployment
.metadata
.get("public_ip")
.cloned()
.unwrap_or_else(|| "Pending".to_string()),
uptime,
ttl: deployment
.expires_at
.map(|expires| {
let remaining = expires.signed_duration_since(chrono::Utc::now());
if remaining.num_seconds() > 0 {
format!(
"{}h {}m",
remaining.num_hours(),
(remaining.num_minutes() % 60)
)
} else {
"Expired".to_string()
}
})
.unwrap_or_else(|| "Never".to_string()),
});
}
Ok(deployments)
}
#[cfg(feature = "remote-providers")]
async fn terminate_real_deployment(
provisioner: &CloudProvisioner,
instance_id: &str,
) -> Result<()> {
use std::path::PathBuf;
let tracker_path = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".tangle")
.join("remote_deployments");
if !tracker_path.exists() {
return Err(color_eyre::eyre::eyre!("No deployment tracker found"));
}
let tracker = DeploymentTracker::new(&tracker_path)
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to initialize deployment tracker: {}", e))?;
let deployment = tracker
.get_by_instance_id(instance_id)
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to find deployment: {}", e))?
.ok_or_else(|| color_eyre::eyre::eyre!("Deployment {} not found", instance_id))?;
if let Some(provider) = deployment.provider {
let cloud_id = deployment
.resource_ids
.get("instance_id")
.map(|s| s.as_str())
.unwrap_or(instance_id);
provisioner
.terminate(provider, cloud_id)
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to terminate instance: {}", e))?;
}
tracker
.remove_by_instance_id(instance_id)
.await
.map_err(|e| color_eyre::eyre::eyre!("Failed to remove from tracker: {}", e))?;
Ok(())
}
fn get_mock_deployments() -> Vec<DeploymentStatus> {
vec![
DeploymentStatus {
id: "dep-abc123".to_string(),
provider: "AWS".to_string(),
region: "us-east-1".to_string(),
status: "🟢 Running".to_string(),
health: Some("💚 Healthy".to_string()),
ip: "54.123.45.67".to_string(),
uptime: "2h 45m".to_string(),
ttl: "21h 15m".to_string(),
},
DeploymentStatus {
id: "dep-def456".to_string(),
provider: "GCP".to_string(),
region: "us-central1".to_string(),
status: "🟢 Running".to_string(),
health: Some("💚 Healthy".to_string()),
ip: "35.222.33.44".to_string(),
uptime: "5d 3h".to_string(),
ttl: "Never".to_string(),
},
DeploymentStatus {
id: "dep-ghi789".to_string(),
provider: "DigitalOcean".to_string(),
region: "nyc3".to_string(),
status: "🟡 Starting".to_string(),
health: Some("❓ Unknown".to_string()),
ip: "Pending".to_string(),
uptime: "0m".to_string(),
ttl: "24h".to_string(),
},
DeploymentStatus {
id: "dep-jkl012".to_string(),
provider: "Vultr".to_string(),
region: "ewr".to_string(),
status: "🔴 Stopped".to_string(),
health: Some("❤️ Unhealthy".to_string()),
ip: "N/A".to_string(),
uptime: "N/A".to_string(),
ttl: "Expired".to_string(),
},
]
}