Rust · 5365 bytes Raw Blame History
1 use clap::Args;
2 use anyhow::{Context, Result};
3 use humansize::{format_size, BINARY};
4 use std::time::Duration;
5 use tracing::info;
6
7 use crate::config::Config;
8 use crate::client::ZephyrClient;
9 use super::Command;
10
11 #[derive(Debug, Args)]
12 pub struct StatusCommand {
13 /// Show detailed status information
14 #[arg(short, long)]
15 detailed: bool,
16
17 /// Output format (table, json)
18 #[arg(long, default_value = "table")]
19 format: String,
20
21 /// Refresh interval in seconds (0 for single check)
22 #[arg(short, long, default_value = "0")]
23 watch: u64,
24 }
25
26 #[async_trait::async_trait]
27 impl Command for StatusCommand {
28 async fn execute(&self, config: &Config) -> Result<()> {
29 info!("Getting ZephyrFS node status");
30
31 if self.watch > 0 {
32 self.watch_status(config).await
33 } else {
34 self.show_status(config).await
35 }
36 }
37 }
38
39 impl StatusCommand {
40 async fn show_status(&self, config: &Config) -> Result<()> {
41 let client = ZephyrClient::new(config);
42
43 // Get node status
44 let node_info = client.get_node_status().await
45 .context("Failed to get node status. Is the node running?")?;
46
47 match self.format.as_str() {
48 "table" => self.print_table_status(&node_info, config),
49 "json" => self.print_json_status(&node_info)?,
50 _ => anyhow::bail!("Invalid format: {}. Valid options: table, json", self.format),
51 }
52
53 Ok(())
54 }
55
56 async fn watch_status(&self, config: &Config) -> Result<()> {
57 println!("Watching node status (refresh every {} seconds)...", self.watch);
58 println!("Press Ctrl+C to stop\n");
59
60 loop {
61 // Clear screen
62 print!("\x1B[2J\x1B[1;1H");
63
64 match self.show_status(config).await {
65 Ok(()) => {},
66 Err(e) => println!("Error getting status: {}", e),
67 }
68
69 tokio::time::sleep(Duration::from_secs(self.watch)).await;
70 }
71 }
72
73 fn print_table_status(&self, node_info: &crate::client::NodeInfo, config: &Config) {
74 println!("ZephyrFS Node Status");
75 println!("{}", "=".repeat(50));
76 println!();
77
78 // Basic info
79 println!("Node Information:");
80 println!(" ID: {}", node_info.id);
81 println!(" Name: {}", node_info.name);
82 println!(" Status: {}", self.format_status(&node_info.status));
83 println!();
84
85 // Network info
86 println!("Network:");
87 println!(" Connected peers: {}", node_info.peers_connected);
88 println!(" Listen port: {}", config.node.listen_port);
89 println!(" Coordinator: {}", config.coordinator.endpoint);
90 println!();
91
92 // Storage info
93 let used_gb = node_info.storage_used_gb;
94 let available_gb = node_info.storage_available_gb;
95 let total_gb = used_gb + available_gb;
96 let usage_percent = if total_gb > 0.0 { (used_gb / total_gb) * 100.0 } else { 0.0 };
97
98 println!("Storage:");
99 println!(" Used: {} ({:.1}%)", format_size((used_gb * 1e9) as u64, BINARY), usage_percent);
100 println!(" Available: {}", format_size((available_gb * 1e9) as u64, BINARY));
101 println!(" Total allocated: {}", format_size((total_gb * 1e9) as u64, BINARY));
102 println!(" Max allocation: {} GB", config.storage.max_storage_gb);
103 println!();
104
105 // Runtime info
106 let uptime = Duration::from_secs(node_info.uptime_seconds);
107 println!("Runtime:");
108 println!(" Uptime: {}", self.format_duration(uptime));
109 println!(" Data directory: {:?}", config.node.data_dir);
110
111 if self.detailed {
112 println!();
113 println!("Configuration:");
114 println!(" Chunk size: {} MB", config.storage.chunk_size_mb);
115 println!(" Replication factor: {}", config.storage.replication_factor);
116 println!(" Max connections: {}", config.network.max_connections);
117 println!(" Connection timeout: {}s", config.network.connection_timeout);
118 }
119 }
120
121 fn print_json_status(&self, node_info: &crate::client::NodeInfo) -> Result<()> {
122 let json = serde_json::to_string_pretty(node_info)
123 .context("Failed to serialize node info to JSON")?;
124 println!("{}", json);
125 Ok(())
126 }
127
128 fn format_status(&self, status: &str) -> String {
129 match status.to_lowercase().as_str() {
130 "running" => "🟢 Running".to_string(),
131 "starting" => "🟡 Starting".to_string(),
132 "stopping" => "🟡 Stopping".to_string(),
133 "stopped" => "🔴 Stopped".to_string(),
134 "error" => "❌ Error".to_string(),
135 _ => format!("❓ {}", status),
136 }
137 }
138
139 fn format_duration(&self, duration: Duration) -> String {
140 let secs = duration.as_secs();
141
142 if secs < 60 {
143 format!("{}s", secs)
144 } else if secs < 3600 {
145 format!("{}m {}s", secs / 60, secs % 60)
146 } else if secs < 86400 {
147 let hours = secs / 3600;
148 let mins = (secs % 3600) / 60;
149 format!("{}h {}m", hours, mins)
150 } else {
151 let days = secs / 86400;
152 let hours = (secs % 86400) / 3600;
153 format!("{}d {}h", days, hours)
154 }
155 }
156 }