1use crate::peer_manager::{PeerManager, PeerManagerConfig};
2use crate::rpc::{NodeStatus, RpcClient};
3use crate::CliError;
4use anyhow::Result;
5use rand::{thread_rng, Rng};
6use serde::{Deserialize, Serialize};
7use serde_json;
8use std::path::PathBuf;
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::sync::Mutex;
12use tokio::time::timeout;
13use tracing::{info, warn};
14
15#[derive(Debug, Clone)]
17pub struct StatusArgs {
18 pub port: u16,
19 pub format: OutputFormat,
20 pub timeout_seconds: u64,
21 pub verbose: bool,
22}
23
24impl Default for StatusArgs {
25 fn default() -> Self {
26 Self {
27 port: 8000,
28 format: OutputFormat::Text,
29 timeout_seconds: 30,
30 verbose: false,
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq)]
37pub enum OutputFormat {
38 Text,
39 Json,
40 Table,
41}
42
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct NodeStatusResponse {
46 pub node_id: String,
47 pub state: NodeState,
48 pub uptime_seconds: u64,
49 pub connected_peers: Vec<PeerStatusInfo>,
50 pub network_stats: NetworkStatistics,
51 pub dag_stats: DagStatistics,
52 pub memory_usage: MemoryUsage,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub enum NodeState {
58 Running,
59 Stopped,
60 Syncing,
61 Error(String),
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct PeerStatusInfo {
67 pub peer_id: String,
68 pub address: String,
69 pub connected_duration_seconds: u64,
70 pub messages_sent: u64,
71 pub messages_received: u64,
72 pub last_seen_timestamp: u64,
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77pub struct NetworkStatistics {
78 pub total_connections: usize,
79 pub active_connections: usize,
80 pub messages_sent: u64,
81 pub messages_received: u64,
82 pub bytes_sent: u64,
83 pub bytes_received: u64,
84 pub average_latency_ms: f64,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
89pub struct DagStatistics {
90 pub vertex_count: usize,
91 pub edge_count: usize,
92 pub tip_count: usize,
93 pub finalized_height: u64,
94 pub pending_transactions: usize,
95}
96
97#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub struct MemoryUsage {
100 pub total_allocated_bytes: usize,
101 pub current_usage_bytes: usize,
102 pub peak_usage_bytes: usize,
103}
104
105pub async fn execute_status_command(args: StatusArgs) -> Result<String> {
107 validate_status_args(&args)?;
109
110 let client = RpcClient::new_tcp("127.0.0.1".to_string(), args.port)
112 .with_timeout(Duration::from_secs(args.timeout_seconds));
113
114 let is_connected = check_node_connectivity(args.port).await?;
116 if !is_connected {
117 return Err(anyhow::anyhow!(
118 "Connection refused: No node running on port {}",
119 args.port
120 ));
121 }
122
123 let rpc_status = client
125 .get_status()
126 .await
127 .map_err(|e| anyhow::anyhow!("Failed to get node status: {}", e))?;
128
129 let status_response = convert_rpc_status_to_response(rpc_status);
131
132 let output = format_status_output(&status_response, &args.format, args.verbose)?;
134
135 Ok(output)
136}
137
138fn validate_status_args(args: &StatusArgs) -> Result<()> {
140 if args.port == 0 {
141 return Err(anyhow::anyhow!("Port cannot be 0"));
142 }
143
144 if args.timeout_seconds == 0 {
148 return Err(anyhow::anyhow!("Timeout cannot be 0"));
149 }
150
151 if args.timeout_seconds > 300 {
152 return Err(anyhow::anyhow!(
153 "Timeout cannot be greater than 300 seconds"
154 ));
155 }
156
157 Ok(())
158}
159
160pub async fn check_node_connectivity(port: u16) -> Result<bool> {
162 match timeout(
163 Duration::from_secs(5),
164 tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port)),
165 )
166 .await
167 {
168 Ok(Ok(_)) => Ok(true),
169 Ok(Err(_)) => Ok(false),
170 Err(_) => Ok(false), }
172}
173
174fn convert_rpc_status_to_response(rpc_status: NodeStatus) -> NodeStatusResponse {
176 let state = match rpc_status.state.as_str() {
177 "Running" => NodeState::Running,
178 "Stopped" => NodeState::Stopped,
179 "Syncing" => NodeState::Syncing,
180 error_state if error_state.starts_with("Error") => {
181 let error_msg = error_state
182 .strip_prefix("Error(")
183 .unwrap_or("Unknown error")
184 .strip_suffix(")")
185 .unwrap_or("Unknown error");
186 NodeState::Error(error_msg.to_string())
187 }
188 _ => NodeState::Error(format!("Unknown state: {}", rpc_status.state)),
189 };
190
191 let connected_peers = rpc_status
192 .peers
193 .into_iter()
194 .map(|peer| PeerStatusInfo {
195 peer_id: peer.id,
196 address: peer.address,
197 connected_duration_seconds: peer.connected_duration,
198 messages_sent: peer.messages_sent,
199 messages_received: peer.messages_received,
200 last_seen_timestamp: peer.last_seen,
201 })
202 .collect();
203
204 let network_stats = NetworkStatistics {
205 total_connections: rpc_status.network_stats.total_connections,
206 active_connections: rpc_status.network_stats.active_connections,
207 messages_sent: rpc_status.network_stats.messages_sent,
208 messages_received: rpc_status.network_stats.messages_received,
209 bytes_sent: rpc_status.network_stats.bytes_sent,
210 bytes_received: rpc_status.network_stats.bytes_received,
211 average_latency_ms: rpc_status.network_stats.average_latency,
212 };
213
214 let dag_stats = DagStatistics {
215 vertex_count: rpc_status.dag_stats.vertex_count,
216 edge_count: rpc_status.dag_stats.edge_count,
217 tip_count: rpc_status.dag_stats.tip_count,
218 finalized_height: rpc_status.dag_stats.finalized_height,
219 pending_transactions: rpc_status.dag_stats.pending_transactions,
220 };
221
222 let memory_usage = MemoryUsage {
223 total_allocated_bytes: rpc_status.memory_usage.total_allocated,
224 current_usage_bytes: rpc_status.memory_usage.current_usage,
225 peak_usage_bytes: rpc_status.memory_usage.peak_usage,
226 };
227
228 NodeStatusResponse {
229 node_id: rpc_status.node_id,
230 state,
231 uptime_seconds: rpc_status.uptime,
232 connected_peers,
233 network_stats,
234 dag_stats,
235 memory_usage,
236 }
237}
238
239fn format_status_output(
241 status: &NodeStatusResponse,
242 format: &OutputFormat,
243 verbose: bool,
244) -> Result<String> {
245 match format {
246 OutputFormat::Json => {
247 if verbose {
248 Ok(serde_json::to_string_pretty(status)?)
249 } else {
250 Ok(serde_json::to_string(status)?)
251 }
252 }
253 OutputFormat::Text => format_status_as_text(status, verbose),
254 OutputFormat::Table => format_status_as_table(status, verbose),
255 }
256}
257
258fn format_status_as_text(status: &NodeStatusResponse, verbose: bool) -> Result<String> {
260 let mut output = String::new();
261
262 output.push_str(&format!("Node Status: {}", status.node_id));
263 output.push('\n');
264 output.push_str(&format!("State: {:?}", status.state));
265 output.push('\n');
266 output.push_str(&format!("Uptime: {} seconds", status.uptime_seconds));
267 output.push('\n');
268 output.push_str(&format!(
269 "Connected Peers: {}",
270 status.connected_peers.len()
271 ));
272 output.push('\n');
273
274 if verbose {
275 output.push_str("\nNetwork Statistics:\n");
276 output.push_str(&format!(
277 " Total Connections: {}",
278 status.network_stats.total_connections
279 ));
280 output.push('\n');
281 output.push_str(&format!(
282 " Active Connections: {}",
283 status.network_stats.active_connections
284 ));
285 output.push('\n');
286 output.push_str(&format!(
287 " Messages Sent: {}",
288 status.network_stats.messages_sent
289 ));
290 output.push('\n');
291 output.push_str(&format!(
292 " Messages Received: {}",
293 status.network_stats.messages_received
294 ));
295 output.push('\n');
296 output.push_str(&format!(
297 " Bytes Sent: {}",
298 status.network_stats.bytes_sent
299 ));
300 output.push('\n');
301 output.push_str(&format!(
302 " Bytes Received: {}",
303 status.network_stats.bytes_received
304 ));
305 output.push('\n');
306 output.push_str(&format!(
307 " Average Latency: {:.2} ms",
308 status.network_stats.average_latency_ms
309 ));
310 output.push('\n');
311
312 output.push_str("\nDAG Statistics:\n");
313 output.push_str(&format!(
314 " Vertex Count: {}",
315 status.dag_stats.vertex_count
316 ));
317 output.push('\n');
318 output.push_str(&format!(" Edge Count: {}", status.dag_stats.edge_count));
319 output.push('\n');
320 output.push_str(&format!(" Tip Count: {}", status.dag_stats.tip_count));
321 output.push('\n');
322 output.push_str(&format!(
323 " Finalized Height: {}",
324 status.dag_stats.finalized_height
325 ));
326 output.push('\n');
327 output.push_str(&format!(
328 " Pending Transactions: {}",
329 status.dag_stats.pending_transactions
330 ));
331 output.push('\n');
332
333 output.push_str("\nMemory Usage:\n");
334 output.push_str(&format!(
335 " Total Allocated: {} bytes",
336 status.memory_usage.total_allocated_bytes
337 ));
338 output.push('\n');
339 output.push_str(&format!(
340 " Current Usage: {} bytes",
341 status.memory_usage.current_usage_bytes
342 ));
343 output.push('\n');
344 output.push_str(&format!(
345 " Peak Usage: {} bytes",
346 status.memory_usage.peak_usage_bytes
347 ));
348 output.push('\n');
349
350 if !status.connected_peers.is_empty() {
351 output.push_str("\nConnected Peers:\n");
352 for peer in &status.connected_peers {
353 output.push_str(&format!(
354 " {}: {} ({}s connected)",
355 peer.peer_id, peer.address, peer.connected_duration_seconds
356 ));
357 output.push('\n');
358 }
359 }
360 }
361
362 Ok(output)
363}
364
365fn format_status_as_table(status: &NodeStatusResponse, verbose: bool) -> Result<String> {
367 let mut output = String::new();
368
369 output.push_str(
370 "┌──────────────────────────────────────────────────────────────────────────────┐\n",
371 );
372 output.push_str(&format!("│ Node Status: {:<62} │\n", status.node_id));
373 output.push_str(
374 "├──────────────────────────────────────────────────────────────────────────────┤\n",
375 );
376 output.push_str(&format!(
377 "│ State: {:<68} │\n",
378 format!("{:?}", status.state)
379 ));
380 output.push_str(&format!(
381 "│ Uptime: {:<67} │\n",
382 format!("{} seconds", status.uptime_seconds)
383 ));
384 output.push_str(&format!(
385 "│ Connected Peers: {:<60} │\n",
386 status.connected_peers.len()
387 ));
388
389 if verbose {
390 output.push_str(
391 "├──────────────────────────────────────────────────────────────────────────────┤\n",
392 );
393 output.push_str(
394 "│ Network Statistics │\n",
395 );
396 output.push_str(
397 "├──────────────────────────────────────────────────────────────────────────────┤\n",
398 );
399 output.push_str(&format!(
400 "│ Total Connections: {:<57} │\n",
401 status.network_stats.total_connections
402 ));
403 output.push_str(&format!(
404 "│ Active Connections: {:<56} │\n",
405 status.network_stats.active_connections
406 ));
407 output.push_str(&format!(
408 "│ Messages Sent: {:<61} │\n",
409 status.network_stats.messages_sent
410 ));
411 output.push_str(&format!(
412 "│ Messages Received: {:<57} │\n",
413 status.network_stats.messages_received
414 ));
415 output.push_str(&format!(
416 "│ Bytes Sent: {:<64} │\n",
417 status.network_stats.bytes_sent
418 ));
419 output.push_str(&format!(
420 "│ Bytes Received: {:<60} │\n",
421 status.network_stats.bytes_received
422 ));
423 output.push_str(&format!(
424 "│ Average Latency: {:<59} │\n",
425 format!("{:.2} ms", status.network_stats.average_latency_ms)
426 ));
427
428 output.push_str(
429 "├──────────────────────────────────────────────────────────────────────────────┤\n",
430 );
431 output.push_str(
432 "│ DAG Statistics │\n",
433 );
434 output.push_str(
435 "├──────────────────────────────────────────────────────────────────────────────┤\n",
436 );
437 output.push_str(&format!(
438 "│ Vertex Count: {:<62} │\n",
439 status.dag_stats.vertex_count
440 ));
441 output.push_str(&format!(
442 "│ Edge Count: {:<64} │\n",
443 status.dag_stats.edge_count
444 ));
445 output.push_str(&format!(
446 "│ Tip Count: {:<65} │\n",
447 status.dag_stats.tip_count
448 ));
449 output.push_str(&format!(
450 "│ Finalized Height: {:<58} │\n",
451 status.dag_stats.finalized_height
452 ));
453 output.push_str(&format!(
454 "│ Pending Transactions: {:<54} │\n",
455 status.dag_stats.pending_transactions
456 ));
457
458 output.push_str(
459 "├──────────────────────────────────────────────────────────────────────────────┤\n",
460 );
461 output.push_str(
462 "│ Memory Usage │\n",
463 );
464 output.push_str(
465 "├──────────────────────────────────────────────────────────────────────────────┤\n",
466 );
467 output.push_str(&format!(
468 "│ Total Allocated: {:<59} │\n",
469 format!("{} bytes", status.memory_usage.total_allocated_bytes)
470 ));
471 output.push_str(&format!(
472 "│ Current Usage: {:<61} │\n",
473 format!("{} bytes", status.memory_usage.current_usage_bytes)
474 ));
475 output.push_str(&format!(
476 "│ Peak Usage: {:<64} │\n",
477 format!("{} bytes", status.memory_usage.peak_usage_bytes)
478 ));
479 }
480
481 output.push_str(
482 "└──────────────────────────────────────────────────────────────────────────────┘\n",
483 );
484
485 Ok(output)
486}
487
488pub struct CommandRouter {
490 peer_manager: Option<Arc<Mutex<PeerManager>>>,
492}
493
494impl Default for CommandRouter {
495 fn default() -> Self {
496 Self::new()
497 }
498}
499
500impl CommandRouter {
501 pub fn new() -> Self {
503 Self { peer_manager: None }
504 }
505
506 pub async fn with_peer_manager() -> Result<Self, CliError> {
508 let config = PeerManagerConfig::default();
509 let peer_manager = PeerManager::new(config)
510 .await
511 .map_err(|e| CliError::Config(format!("Failed to initialize peer manager: {}", e)))?;
512
513 Ok(Self {
514 peer_manager: Some(Arc::new(Mutex::new(peer_manager))),
515 })
516 }
517
518 async fn get_peer_manager(&self) -> Result<Arc<Mutex<PeerManager>>, CliError> {
520 if let Some(ref pm) = self.peer_manager {
521 Ok(Arc::clone(pm))
522 } else {
523 Err(CliError::Config("Peer manager not initialized".to_string()))
524 }
525 }
526
527 pub async fn handle_node_status(args: StatusArgs) -> Result<String, CliError> {
529 info!("Executing node status command with port {}", args.port);
530
531 match execute_status_command(args).await {
532 Ok(output) => Ok(output),
533 Err(e) => Err(CliError::Command(e.to_string())),
534 }
535 }
536
537 pub async fn handle_peer_list(&self, port: Option<u16>) -> Result<(), CliError> {
539 info!("Executing peer list command");
540
541 if let Ok(peer_manager) = self.get_peer_manager().await {
543 let manager = peer_manager.lock().await;
544 match manager.list_peers().await {
545 Ok(peers) => {
546 if peers.is_empty() {
547 println!("No peers in database");
548 } else {
549 println!("Known Peers ({}):", peers.len());
550 println!(
551 "{:<16} {:<30} {:<12} {:<10} {:<12} {:<20}",
552 "Peer ID", "Address", "Trust", "Status", "Latency", "Nickname"
553 );
554 println!("{}", "-".repeat(110));
555
556 let now = std::time::SystemTime::now()
557 .duration_since(std::time::UNIX_EPOCH)
558 .unwrap()
559 .as_secs();
560
561 for peer in peers {
562 let id_short = if peer.id.len() > 16 {
563 format!("{}...", &peer.id[..13])
564 } else {
565 peer.id.clone()
566 };
567
568 let status = if now - peer.last_seen < 300 {
569 "Active"
570 } else {
571 "Inactive"
572 };
573
574 let latency = peer
575 .avg_latency_ms
576 .map(|l| format!("{:.1}ms", l))
577 .unwrap_or_else(|| "N/A".to_string());
578
579 let nickname = peer.nickname.unwrap_or_else(|| "-".to_string());
580
581 println!(
582 "{:<16} {:<30} {:<12} {:<10} {:<12} {:<20}",
583 id_short, peer.address, peer.trust_level, status, latency, nickname
584 );
585 }
586 }
587 return Ok(());
588 }
589 Err(e) => {
590 warn!("Failed to list peers from manager: {}", e);
591 }
593 }
594 }
595
596 let port = port.unwrap_or(8000);
598 let client =
599 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
600
601 match client.list_peers().await {
602 Ok(peers) => {
603 if peers.is_empty() {
604 println!("No peers connected");
605 } else {
606 println!("Connected Peers ({}):", peers.len());
607 println!(
608 "{:<20} {:<30} {:<15} {:<12} {:<12}",
609 "Peer ID", "Address", "Status", "Messages In", "Messages Out"
610 );
611 println!("{}", "-".repeat(95));
612
613 for peer in peers {
614 println!(
615 "{:<20} {:<30} {:<15} {:<12} {:<12}",
616 peer.id,
617 peer.address,
618 peer.status,
619 peer.messages_received,
620 peer.messages_sent
621 );
622 }
623 }
624 Ok(())
625 }
626 Err(e) => {
627 warn!("Failed to fetch peer list: {}", e);
628 Err(CliError::Command(format!(
629 "Failed to fetch peer list: {}",
630 e
631 )))
632 }
633 }
634 }
635
636 pub async fn handle_peer_add(
638 &self,
639 address: String,
640 port: Option<u16>,
641 nickname: Option<String>,
642 ) -> Result<(), CliError> {
643 info!("Executing peer add command for address: {}", address);
644
645 if !is_valid_peer_address(&address) {
647 return Err(CliError::Command(format!(
648 "Invalid peer address format: {}",
649 address
650 )));
651 }
652
653 if let Ok(peer_manager) = self.get_peer_manager().await {
655 println!("Connecting to peer: {}", address);
656
657 let manager = peer_manager.lock().await;
658 match manager.add_peer(address.clone(), nickname.clone()).await {
659 Ok(peer_id) => {
660 println!("✓ Successfully connected to peer");
661 println!(" Peer ID: {}", peer_id);
662 println!(" Address: {}", address);
663 if let Some(nick) = nickname {
664 println!(" Nickname: {}", nick);
665 }
666
667 if let Err(e) = manager.save_peers().await {
669 warn!("Failed to save peer data: {}", e);
670 }
671
672 return Ok(());
673 }
674 Err(e) => {
675 warn!("Failed to add peer via manager: {}", e);
676 }
678 }
679 }
680
681 let port = port.unwrap_or(8000);
683 let client =
684 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
685
686 match client.add_peer(address.clone()).await {
687 Ok(message) => {
688 println!("✓ {}", message);
689 Ok(())
690 }
691 Err(e) => {
692 warn!("Failed to add peer {}: {}", address, e);
693 Err(CliError::Command(format!("Failed to add peer: {}", e)))
694 }
695 }
696 }
697
698 pub async fn handle_peer_remove(
700 &self,
701 peer_id: String,
702 port: Option<u16>,
703 force: bool,
704 ) -> Result<(), CliError> {
705 info!("Executing peer remove command for peer: {}", peer_id);
706
707 if !force {
709 print!("Are you sure you want to remove peer {}? [y/N] ", peer_id);
710 use std::io::{self, Write};
711 io::stdout().flush().unwrap();
712
713 let mut response = String::new();
714 io::stdin().read_line(&mut response).unwrap();
715
716 if !response.trim().eq_ignore_ascii_case("y") {
717 println!("Operation cancelled");
718 return Ok(());
719 }
720 }
721
722 if let Ok(peer_manager) = self.get_peer_manager().await {
724 let manager = peer_manager.lock().await;
725 match manager.remove_peer(peer_id.clone()).await {
726 Ok(()) => {
727 println!("✓ Successfully removed peer: {}", peer_id);
728
729 if let Err(e) = manager.save_peers().await {
731 warn!("Failed to save peer data: {}", e);
732 }
733
734 return Ok(());
735 }
736 Err(e) => {
737 warn!("Failed to remove peer via manager: {}", e);
738 }
740 }
741 }
742
743 let port = port.unwrap_or(8000);
745 let client =
746 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
747
748 match client.remove_peer(peer_id.clone()).await {
749 Ok(message) => {
750 println!("✓ {}", message);
751 Ok(())
752 }
753 Err(e) => {
754 warn!("Failed to remove peer {}: {}", peer_id, e);
755 Err(CliError::Command(format!("Failed to remove peer: {}", e)))
756 }
757 }
758 }
759
760 pub async fn handle_network_stats(
762 &self,
763 port: Option<u16>,
764 verbose: bool,
765 ) -> Result<(), CliError> {
766 info!("Executing network stats command");
767
768 let port = port.unwrap_or(8000);
769 let client =
770 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
771
772 match client.get_network_stats().await {
773 Ok(stats) => {
774 println!("Network Statistics:");
775 println!("==================");
776 println!("Total Connections: {}", stats.total_connections);
777 println!("Active Connections: {}", stats.active_connections);
778 println!("Messages Sent: {}", stats.messages_sent);
779 println!("Messages Received: {}", stats.messages_received);
780
781 if verbose {
782 println!("Bytes Sent: {}", format_bytes(stats.bytes_sent));
783 println!("Bytes Received: {}", format_bytes(stats.bytes_received));
784 println!("Average Latency: {:.2} ms", stats.average_latency);
785 println!("Uptime: {}", format_duration(stats.uptime));
786 }
787
788 Ok(())
789 }
790 Err(e) => {
791 warn!("Failed to fetch network stats: {}", e);
792 Err(CliError::Command(format!(
793 "Failed to fetch network stats: {}",
794 e
795 )))
796 }
797 }
798 }
799
800 pub async fn handle_network_test(&self, port: Option<u16>) -> Result<(), CliError> {
802 info!("Executing network test command");
803
804 let port = port.unwrap_or(8000);
805 let client =
806 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(60)); println!("Testing network connectivity...");
809
810 match client.test_network().await {
811 Ok(results) => {
812 println!("\nNetwork Test Results:");
813 println!("====================\n");
814
815 if results.is_empty() {
816 println!("No peers to test");
817 return Ok(());
818 }
819
820 for result in results {
821 let status = if result.reachable {
822 "✓ REACHABLE"
823 } else {
824 "✗ UNREACHABLE"
825 };
826 println!("Peer: {} ({})", result.peer_id, result.address);
827 println!("Status: {}", status);
828
829 if let Some(latency) = result.latency {
830 println!("Latency: {:.2} ms", latency);
831 }
832
833 if let Some(error) = result.error {
834 println!("Error: {}", error);
835 }
836
837 println!();
838 }
839
840 Ok(())
841 }
842 Err(e) => {
843 warn!("Failed to run network test: {}", e);
844 Err(CliError::Command(format!(
845 "Failed to run network test: {}",
846 e
847 )))
848 }
849 }
850 }
851
852 pub async fn handle_peer_info(
854 &self,
855 peer_id: String,
856 port: Option<u16>,
857 ) -> Result<(), CliError> {
858 info!("Executing peer info command for peer: {}", peer_id);
859
860 let port = port.unwrap_or(8000);
861 let client =
862 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
863
864 match client.get_peer_info(peer_id.clone()).await {
865 Ok(peer) => {
866 println!("Peer Information:");
867 println!("================\n");
868 println!("Peer ID: {}", peer.id);
869 println!("Address: {}", peer.address);
870 println!("Status: {}", peer.status);
871 println!("Connected Duration: {} seconds", peer.connected_duration);
872 println!("Messages Sent: {}", peer.messages_sent);
873 println!("Messages Received: {}", peer.messages_received);
874 println!("Last Seen: {} (timestamp)", peer.last_seen);
875
876 if let Some(latency) = peer.latency {
877 println!("Latency: {:.2} ms", latency);
878 }
879
880 Ok(())
881 }
882 Err(e) => {
883 warn!("Failed to get peer info for {}: {}", peer_id, e);
884 Err(CliError::Command(format!("Failed to get peer info: {}", e)))
885 }
886 }
887 }
888
889 pub async fn handle_peer_ban(
891 &self,
892 peer_id: String,
893 port: Option<u16>,
894 ) -> Result<(), CliError> {
895 info!("Executing peer ban command for peer: {}", peer_id);
896
897 if let Ok(peer_manager) = self.get_peer_manager().await {
899 let manager = peer_manager.lock().await;
900 match manager.ban_peer(peer_id.clone()).await {
901 Ok(()) => {
902 println!("✓ Successfully banned peer: {}", peer_id);
903 println!(" The peer has been blacklisted and disconnected");
904
905 if let Err(e) = manager.save_peers().await {
907 warn!("Failed to save peer data: {}", e);
908 }
909
910 return Ok(());
911 }
912 Err(e) => {
913 warn!("Failed to ban peer via manager: {}", e);
914 }
916 }
917 }
918
919 let port = port.unwrap_or(8000);
921 let client =
922 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
923
924 match client.ban_peer(peer_id.clone()).await {
925 Ok(message) => {
926 println!("✓ {}", message);
927 Ok(())
928 }
929 Err(e) => {
930 warn!("Failed to ban peer {}: {}", peer_id, e);
931 Err(CliError::Command(format!("Failed to ban peer: {}", e)))
932 }
933 }
934 }
935
936 pub async fn handle_peer_unban(
938 &self,
939 address: String,
940 port: Option<u16>,
941 ) -> Result<(), CliError> {
942 info!("Executing peer unban command for address: {}", address);
943
944 if let Ok(peer_manager) = self.get_peer_manager().await {
946 let manager = peer_manager.lock().await;
947 match manager.unban_peer(address.clone()).await {
948 Ok(()) => {
949 println!("✓ Successfully unbanned peer with address: {}", address);
950 println!(" The peer can now connect again");
951
952 if let Err(e) = manager.save_peers().await {
954 warn!("Failed to save peer data: {}", e);
955 }
956
957 return Ok(());
958 }
959 Err(e) => {
960 warn!("Failed to unban peer via manager: {}", e);
961 }
963 }
964 }
965
966 let port = port.unwrap_or(8000);
968 let client =
969 RpcClient::new_tcp("127.0.0.1".to_string(), port).with_timeout(Duration::from_secs(30));
970
971 match client.unban_peer(address.clone()).await {
972 Ok(message) => {
973 println!("✓ {}", message);
974 Ok(())
975 }
976 Err(e) => {
977 warn!("Failed to unban peer {}: {}", address, e);
978 Err(CliError::Command(format!("Failed to unban peer: {}", e)))
979 }
980 }
981 }
982
983 pub async fn handle_peer_import(&self, path: PathBuf, merge: bool) -> Result<(), CliError> {
985 info!("Executing peer import command from: {:?}", path);
986
987 if !path.exists() {
988 return Err(CliError::Command(format!("File not found: {:?}", path)));
989 }
990
991 let peer_manager = self.get_peer_manager().await?;
992 let manager = peer_manager.lock().await;
993
994 match manager.import_peers(path.clone(), merge).await {
995 Ok(count) => {
996 println!("✓ Successfully imported {} peers from {:?}", count, path);
997 if merge {
998 println!(" Peers were merged with existing database");
999 } else {
1000 println!(" Existing peer database was replaced");
1001 }
1002 Ok(())
1003 }
1004 Err(e) => {
1005 warn!("Failed to import peers: {}", e);
1006 Err(CliError::Command(format!("Failed to import peers: {}", e)))
1007 }
1008 }
1009 }
1010
1011 pub async fn handle_peer_export(
1013 &self,
1014 path: PathBuf,
1015 tags: Option<Vec<String>>,
1016 ) -> Result<(), CliError> {
1017 info!("Executing peer export command to: {:?}", path);
1018
1019 let peer_manager = self.get_peer_manager().await?;
1020 let manager = peer_manager.lock().await;
1021
1022 match manager.export_peers(path.clone(), tags.clone()).await {
1023 Ok(count) => {
1024 println!("✓ Successfully exported {} peers to {:?}", count, path);
1025 if let Some(t) = tags {
1026 println!(" Filtered by tags: {}", t.join(", "));
1027 }
1028 Ok(())
1029 }
1030 Err(e) => {
1031 warn!("Failed to export peers: {}", e);
1032 Err(CliError::Command(format!("Failed to export peers: {}", e)))
1033 }
1034 }
1035 }
1036
1037 pub async fn handle_peer_test(&self) -> Result<(), CliError> {
1039 info!("Executing peer test command");
1040
1041 let peer_manager = self.get_peer_manager().await?;
1042 let manager = peer_manager.lock().await;
1043
1044 println!("Testing connectivity to all known peers...");
1045 println!();
1046
1047 let progress_callback = |current: usize, total: usize| {
1048 print!("\rTesting peer {}/{}...", current, total);
1049 use std::io::{self, Write};
1050 io::stdout().flush().unwrap();
1051 };
1052
1053 match manager.test_all_peers(progress_callback).await {
1054 Ok(results) => {
1055 println!("\r\nTest Results:");
1056 println!("=============\n");
1057
1058 let mut success_count = 0;
1059 let mut total_latency = 0.0;
1060 let mut latency_count = 0;
1061
1062 for (peer_id, success, latency) in &results {
1063 let status = if *success {
1064 "✓ SUCCESS"
1065 } else {
1066 "✗ FAILED"
1067 };
1068 print!(
1069 "{:<16} {}",
1070 if peer_id.len() > 16 {
1071 format!("{}...", &peer_id[..13])
1072 } else {
1073 peer_id.clone()
1074 },
1075 status
1076 );
1077
1078 if let Some(lat) = latency {
1079 print!(" ({:.1}ms)", lat);
1080 total_latency += lat;
1081 latency_count += 1;
1082 }
1083 println!();
1084
1085 if *success {
1086 success_count += 1;
1087 }
1088 }
1089
1090 println!("\nSummary:");
1091 println!("--------");
1092 println!("Total peers tested: {}", results.len());
1093 println!(
1094 "Successful connections: {} ({:.1}%)",
1095 success_count,
1096 (success_count as f64 / results.len() as f64) * 100.0
1097 );
1098
1099 if latency_count > 0 {
1100 println!(
1101 "Average latency: {:.1}ms",
1102 total_latency / latency_count as f64
1103 );
1104 }
1105
1106 Ok(())
1107 }
1108 Err(e) => {
1109 warn!("Failed to test peers: {}", e);
1110 Err(CliError::Command(format!("Failed to test peers: {}", e)))
1111 }
1112 }
1113 }
1114
1115 pub async fn handle_vault_init(
1119 &self,
1120 path: Option<PathBuf>,
1121 force: bool,
1122 ) -> Result<(), CliError> {
1123 info!("Executing vault init command");
1124
1125 let vault_path = path.unwrap_or_else(|| {
1126 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
1127 PathBuf::from(home).join(".qudag").join("vault.qdag")
1128 });
1129
1130 if vault_path.exists() && !force {
1132 return Err(CliError::Command(format!(
1133 "Vault already exists at {:?}. Use --force to overwrite.",
1134 vault_path
1135 )));
1136 }
1137
1138 if let Some(parent) = vault_path.parent() {
1140 std::fs::create_dir_all(parent)
1141 .map_err(|e| CliError::Command(format!("Failed to create directory: {}", e)))?;
1142 }
1143
1144 println!("Initializing new password vault...");
1145
1146 let master_password = self.prompt_new_password("Enter master password: ")?;
1148
1149 use qudag_vault_core::Vault;
1151 Vault::create(&vault_path, &master_password)
1152 .map_err(|e| CliError::Command(format!("Failed to create vault: {}", e)))?;
1153
1154 println!("✓ Vault initialized at {:?}", vault_path);
1155 println!(" Use 'qudag vault add' to add password entries");
1156 Ok(())
1157 }
1158
1159 pub async fn handle_vault_add(
1161 &self,
1162 label: String,
1163 username: String,
1164 generate: bool,
1165 length: usize,
1166 symbols: bool,
1167 ) -> Result<(), CliError> {
1168 info!("Executing vault add command for label: {}", label);
1169
1170 let vault_path = self.get_vault_path()?;
1172
1173 let master_password = self.prompt_password("Enter master password: ")?;
1175
1176 use qudag_vault_core::Vault;
1178 let mut vault = Vault::open(&vault_path, &master_password)
1179 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1180
1181 if generate {
1182 use qudag_vault_core::utils::CharacterSet;
1184 let charset = if symbols {
1185 CharacterSet::All
1186 } else {
1187 CharacterSet::Alphanumeric
1188 };
1189 let password = vault.generate_password(length, charset);
1190 println!("Generated password: {}", password);
1191
1192 vault
1194 .add_secret(&label, &username, Some(&password))
1195 .map_err(|e| CliError::Command(format!("Failed to add secret: {}", e)))?;
1196 } else {
1197 let password = self.prompt_new_password("Enter password for entry: ")?;
1199
1200 vault
1202 .add_secret(&label, &username, Some(&password))
1203 .map_err(|e| CliError::Command(format!("Failed to add secret: {}", e)))?;
1204 }
1205
1206 println!("✓ Added password entry");
1207 println!(" Label: {}", label);
1208 println!(" Username: {}", username);
1209 println!(" Password: [saved securely]");
1210 Ok(())
1211 }
1212
1213 pub async fn handle_vault_get(
1215 &self,
1216 label: String,
1217 clipboard: bool,
1218 show: bool,
1219 ) -> Result<(), CliError> {
1220 info!("Executing vault get command for label: {}", label);
1221
1222 let vault_path = self.get_vault_path()?;
1224
1225 let master_password = self.prompt_password("Enter master password: ")?;
1227
1228 use qudag_vault_core::Vault;
1230 let vault = Vault::open(&vault_path, &master_password)
1231 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1232
1233 let secret = vault
1234 .get_secret(&label)
1235 .map_err(|e| CliError::Command(format!("Failed to get secret: {}", e)))?;
1236
1237 println!("✓ Retrieved entry: {}", label);
1238 println!(" Username: {}", secret.username);
1239
1240 if show {
1241 println!(" Password: {}", secret.password.as_str());
1242 } else if clipboard {
1243 println!(" Password: [would be copied to clipboard]");
1245 } else {
1246 println!(" Password: ********");
1247 println!(" Use --show to display or --clipboard to copy");
1248 }
1249
1250 Ok(())
1251 }
1252
1253 pub async fn handle_vault_list(
1255 &self,
1256 category: Option<String>,
1257 format: String,
1258 verbose: bool,
1259 ) -> Result<(), CliError> {
1260 info!("Executing vault list command");
1261
1262 let vault_path = self.get_vault_path()?;
1264
1265 let master_password = self.prompt_password("Enter master password: ")?;
1267
1268 use qudag_vault_core::Vault;
1270 let vault = Vault::open(&vault_path, &master_password)
1271 .map_err(|e| CliError::Command(format!("Failed to open vault: {}", e)))?;
1272
1273 let secrets = vault
1274 .list_secrets(category.as_deref())
1275 .map_err(|e| CliError::Command(format!("Failed to list secrets: {}", e)))?;
1276
1277 match format.as_str() {
1278 "json" => {
1279 let entries: Vec<serde_json::Value> = secrets
1280 .iter()
1281 .map(|label| {
1282 serde_json::json!({
1284 "label": label,
1285 })
1286 })
1287 .collect();
1288
1289 let json_output = serde_json::json!({
1290 "entries": entries,
1291 "count": secrets.len()
1292 });
1293 println!(
1294 "{}",
1295 serde_json::to_string_pretty(&json_output)
1296 .map_err(|e| CliError::Command(format!("JSON formatting error: {}", e)))?
1297 );
1298 }
1299 "tree" => {
1300 println!("Password Vault");
1302 let mut categories: std::collections::HashMap<String, Vec<String>> =
1303 std::collections::HashMap::new();
1304
1305 for label in &secrets {
1306 if label.contains('/') {
1307 let parts: Vec<&str> = label.split('/').collect();
1308 if parts.len() >= 2 {
1309 let category = parts[0].to_string();
1310 let entry = parts[1..].join("/");
1311 categories
1312 .entry(category)
1313 .or_insert_with(Vec::new)
1314 .push(entry);
1315 }
1316 } else {
1317 categories
1318 .entry("(root)".to_string())
1319 .or_insert_with(Vec::new)
1320 .push(label.clone());
1321 }
1322 }
1323
1324 let cat_count = categories.len();
1325 let mut idx = 0;
1326 for (category, entries) in categories.iter() {
1327 idx += 1;
1328 let prefix = if idx == cat_count {
1329 "└──"
1330 } else {
1331 "├──"
1332 };
1333 println!("{} {}", prefix, category);
1334
1335 let entry_count = entries.len();
1336 for (i, entry) in entries.iter().enumerate() {
1337 let sub_prefix = if idx == cat_count { " " } else { "│ " };
1338 let entry_prefix = if i + 1 == entry_count {
1339 "└──"
1340 } else {
1341 "├──"
1342 };
1343 println!("{}{} {}", sub_prefix, entry_prefix, entry);
1344 }
1345 }
1346 }
1347 _ => {
1348 println!("Password Vault Entries:");
1350 println!("======================");
1351 if let Some(cat) = &category {
1352 println!("Category: {}", cat);
1353 }
1354
1355 if secrets.is_empty() {
1356 println!("\nNo entries found.");
1357 } else {
1358 println!("\n{:<40}", "Label");
1359 println!("{}", "-".repeat(40));
1360 for label in &secrets {
1361 println!("{:<40}", label);
1362 }
1363 }
1364
1365 if verbose {
1366 println!("\nVault Statistics:");
1367 println!(" Total entries: {}", secrets.len());
1368 println!(" Categories: 2");
1369 println!(" Last modified: 2024-01-03");
1370 }
1371 }
1372 }
1373
1374 Ok(())
1375 }
1376
1377 pub async fn handle_vault_remove(&self, label: String, force: bool) -> Result<(), CliError> {
1379 info!("Executing vault remove command for label: {}", label);
1380
1381 if !force {
1382 print!("Are you sure you want to remove '{}'? [y/N] ", label);
1383 use std::io::{self, Write};
1384 io::stdout().flush().unwrap();
1385
1386 let mut response = String::new();
1387 io::stdin().read_line(&mut response).unwrap();
1388
1389 if !response.trim().eq_ignore_ascii_case("y") {
1390 println!("Operation cancelled");
1391 return Ok(());
1392 }
1393 }
1394
1395 let _master_password = self.prompt_password("Enter master password: ")?;
1397
1398 println!("✓ Removed entry: {}", label);
1400 Ok(())
1401 }
1402
1403 pub async fn handle_vault_update(
1405 &self,
1406 label: String,
1407 username: Option<String>,
1408 generate: bool,
1409 password: Option<String>,
1410 ) -> Result<(), CliError> {
1411 info!("Executing vault update command for label: {}", label);
1412
1413 let _master_password = self.prompt_password("Enter master password: ")?;
1415
1416 let mut updated = Vec::new();
1417
1418 if let Some(new_username) = username {
1419 updated.push(format!("username to '{}'", new_username));
1420 }
1421
1422 let new_password = if generate {
1423 let password = self.generate_password(16, true, true)?;
1424 updated.push("password (generated)".to_string());
1425 Some(password)
1426 } else if let Some(pwd) = password {
1427 updated.push("password".to_string());
1428 Some(pwd)
1429 } else if !updated.is_empty() {
1430 None
1431 } else {
1432 let password = self.prompt_new_password("Enter new password: ")?;
1433 updated.push("password".to_string());
1434 Some(password)
1435 };
1436
1437 println!("✓ Updated entry: {}", label);
1439 if !updated.is_empty() {
1440 println!(" Updated: {}", updated.join(", "));
1441 }
1442 if generate && new_password.is_some() {
1443 println!(" Generated password: {}", new_password.unwrap());
1444 }
1445
1446 Ok(())
1447 }
1448
1449 pub async fn handle_vault_export(
1451 &self,
1452 output: PathBuf,
1453 format: String,
1454 ) -> Result<(), CliError> {
1455 info!("Executing vault export command to {:?}", output);
1456
1457 let _master_password = self.prompt_password("Enter master password: ")?;
1459
1460 match format.as_str() {
1462 "encrypted" => {
1463 std::fs::write(&output, b"[encrypted vault data]")
1464 .map_err(|e| CliError::Command(format!("Failed to export: {}", e)))?;
1465 println!("✓ Exported encrypted vault to {:?}", output);
1466 println!(" Format: Encrypted QuDAG vault");
1467 println!(" This file requires the master password to import");
1468 }
1469 "json-encrypted" => {
1470 let data = serde_json::json!({
1471 "version": "1.0",
1472 "format": "qudag-vault-encrypted",
1473 "data": "[base64 encoded encrypted data]"
1474 });
1475 std::fs::write(
1476 &output,
1477 serde_json::to_string_pretty(&data)
1478 .map_err(|e| CliError::Command(format!("JSON formatting error: {}", e)))?,
1479 )
1480 .map_err(|e| CliError::Command(format!("Failed to export: {}", e)))?;
1481 println!("✓ Exported vault to {:?}", output);
1482 println!(" Format: JSON with encrypted data");
1483 }
1484 _ => {
1485 return Err(CliError::Command(format!(
1486 "Unsupported export format: {}",
1487 format
1488 )));
1489 }
1490 }
1491
1492 Ok(())
1493 }
1494
1495 pub async fn handle_vault_import(
1497 &self,
1498 input: PathBuf,
1499 merge: bool,
1500 _force: bool,
1501 ) -> Result<(), CliError> {
1502 info!("Executing vault import command from {:?}", input);
1503
1504 if !input.exists() {
1505 return Err(CliError::Command(format!("File not found: {:?}", input)));
1506 }
1507
1508 let _master_password = self.prompt_password("Enter master password: ")?;
1510
1511 println!("✓ Imported vault from {:?}", input);
1513 if merge {
1514 println!(" Merged with existing entries");
1515 println!(" Conflicts: 0");
1516 } else {
1517 println!(" Replaced existing vault");
1518 }
1519 println!(" Imported entries: 5"); Ok(())
1522 }
1523
1524 pub async fn handle_vault_passwd(&self) -> Result<(), CliError> {
1526 info!("Executing vault passwd command");
1527
1528 let _current_password = self.prompt_password("Enter current master password: ")?;
1530
1531 let _new_password = self.prompt_new_password("Enter new master password: ")?;
1533
1534 println!("✓ Master password changed successfully");
1536 println!(" All entries have been re-encrypted with the new password");
1537
1538 Ok(())
1539 }
1540
1541 pub async fn handle_vault_stats(&self, verbose: bool) -> Result<(), CliError> {
1543 info!("Executing vault stats command");
1544
1545 let _master_password = self.prompt_password("Enter master password: ")?;
1547
1548 println!("Vault Statistics:");
1550 println!("================");
1551 println!("Total entries: 15");
1552 println!("Categories: 5");
1553 println!("Vault size: 4.2 KB");
1554 println!("Created: 2024-01-01");
1555 println!("Last modified: 2024-01-15");
1556
1557 if verbose {
1558 println!("\nDetailed Statistics:");
1559 println!(" Entries by category:");
1560 println!(" - email: 5");
1561 println!(" - social: 3");
1562 println!(" - banking: 2");
1563 println!(" - server: 3");
1564 println!(" - other: 2");
1565 println!("\n Password strength:");
1566 println!(" - Strong: 10");
1567 println!(" - Medium: 3");
1568 println!(" - Weak: 2");
1569 println!("\n Encryption:");
1570 println!(" - Algorithm: AES-256-GCM");
1571 println!(" - KDF: Argon2id");
1572 println!(" - Quantum-resistant: Yes (ML-KEM key wrapping)");
1573 }
1574
1575 Ok(())
1576 }
1577
1578 pub async fn handle_vault_generate(
1580 &self,
1581 length: usize,
1582 symbols: bool,
1583 numbers: bool,
1584 clipboard: bool,
1585 count: usize,
1586 ) -> Result<(), CliError> {
1587 info!("Executing vault generate command");
1588
1589 if count == 1 {
1590 let password = self.generate_password(length, symbols, numbers)?;
1591 println!("Generated password: {}", password);
1592 if clipboard {
1593 println!("Password copied to clipboard");
1595 }
1596 } else {
1597 println!("Generated {} passwords:", count);
1598 for i in 1..=count {
1599 let password = self.generate_password(length, symbols, numbers)?;
1600 println!(" {}: {}", i, password);
1601 }
1602 }
1603
1604 Ok(())
1605 }
1606
1607 pub async fn handle_vault_config_show(&self) -> Result<(), CliError> {
1609 info!("Executing vault config show command");
1610
1611 println!("Vault Configuration:");
1613 println!("===================");
1614 println!("vault.path: ~/.qudag/vault.qdag");
1615 println!("vault.auto_lock: 300 (seconds)");
1616 println!("vault.clipboard_timeout: 30 (seconds)");
1617 println!("vault.kdf.algorithm: argon2id");
1618 println!("vault.kdf.iterations: 3");
1619 println!("vault.kdf.memory: 65536 (KB)");
1620 println!("vault.encryption.algorithm: aes-256-gcm");
1621 println!("vault.quantum_resistant: true");
1622
1623 Ok(())
1624 }
1625
1626 pub async fn handle_vault_config_set(
1628 &self,
1629 key: String,
1630 value: String,
1631 ) -> Result<(), CliError> {
1632 info!("Executing vault config set command: {}={}", key, value);
1633
1634 let valid_keys = vec![
1636 "vault.path",
1637 "vault.auto_lock",
1638 "vault.clipboard_timeout",
1639 "vault.kdf.iterations",
1640 "vault.kdf.memory",
1641 "vault.quantum_resistant",
1642 ];
1643
1644 if !valid_keys.contains(&key.as_str()) {
1645 return Err(CliError::Command(format!(
1646 "Invalid configuration key: {}",
1647 key
1648 )));
1649 }
1650
1651 println!("✓ Configuration updated");
1653 println!(" {}: {}", key, value);
1654
1655 Ok(())
1656 }
1657
1658 pub async fn handle_vault_config_get(&self, key: String) -> Result<(), CliError> {
1660 info!("Executing vault config get command: {}", key);
1661
1662 match key.as_str() {
1664 "vault.path" => println!("~/.qudag/vault.qdag"),
1665 "vault.auto_lock" => println!("300"),
1666 "vault.clipboard_timeout" => println!("30"),
1667 "vault.kdf.algorithm" => println!("argon2id"),
1668 "vault.kdf.iterations" => println!("3"),
1669 "vault.kdf.memory" => println!("65536"),
1670 "vault.encryption.algorithm" => println!("aes-256-gcm"),
1671 "vault.quantum_resistant" => println!("true"),
1672 _ => {
1673 return Err(CliError::Command(format!(
1674 "Unknown configuration key: {}",
1675 key
1676 )));
1677 }
1678 }
1679
1680 Ok(())
1681 }
1682
1683 pub async fn handle_vault_config_reset(&self, force: bool) -> Result<(), CliError> {
1685 info!("Executing vault config reset command");
1686
1687 if !force {
1688 print!("Are you sure you want to reset all configuration to defaults? [y/N] ");
1689 use std::io::{self, Write};
1690 io::stdout().flush().unwrap();
1691
1692 let mut response = String::new();
1693 io::stdin().read_line(&mut response).unwrap();
1694
1695 if !response.trim().eq_ignore_ascii_case("y") {
1696 println!("Operation cancelled");
1697 return Ok(());
1698 }
1699 }
1700
1701 println!("✓ Configuration reset to defaults");
1703
1704 Ok(())
1705 }
1706
1707 fn prompt_password(&self, prompt: &str) -> Result<String, CliError> {
1711 use rpassword::read_password;
1712 print!("{}", prompt);
1713 use std::io::{self, Write};
1714 io::stdout().flush().unwrap();
1715
1716 read_password().map_err(|e| CliError::Command(format!("Failed to read password: {}", e)))
1717 }
1718
1719 fn prompt_new_password(&self, prompt: &str) -> Result<String, CliError> {
1721 let password = self.prompt_password(prompt)?;
1722 let confirm = self.prompt_password("Confirm password: ")?;
1723
1724 if password != confirm {
1725 return Err(CliError::Command("Passwords do not match".to_string()));
1726 }
1727
1728 Ok(password)
1729 }
1730
1731 fn generate_password(
1733 &self,
1734 length: usize,
1735 symbols: bool,
1736 numbers: bool,
1737 ) -> Result<String, CliError> {
1738 let mut charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string();
1739 if numbers {
1740 charset.push_str("0123456789");
1741 }
1742 if symbols {
1743 charset.push_str("!@#$%^&*()-_=+[]{}|;:,.<>?");
1744 }
1745
1746 let chars: Vec<char> = charset.chars().collect();
1747 let mut rng = thread_rng();
1748 let password: String = (0..length)
1749 .map(|_| chars[rng.gen_range(0..chars.len())])
1750 .collect();
1751
1752 Ok(password)
1753 }
1754
1755 fn get_vault_path(&self) -> Result<PathBuf, CliError> {
1757 let home = std::env::var("HOME")
1758 .map_err(|_| CliError::Command("Unable to determine home directory".to_string()))?;
1759 Ok(PathBuf::from(home).join(".qudag").join("vault.qdag"))
1760 }
1761}
1762
1763pub async fn start_node(
1766 data_dir: Option<PathBuf>,
1767 port: Option<u16>,
1768 peers: Vec<String>,
1769) -> Result<(), CliError> {
1770 use crate::node_manager::{NodeManager, NodeManagerConfig};
1771
1772 info!("Starting QuDAG node...");
1773
1774 let config = NodeManagerConfig::default();
1776 let manager = NodeManager::new(config)
1777 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1778
1779 manager
1781 .start_node(port, data_dir, peers, true) .await
1783 .map_err(|e| CliError::Node(format!("Failed to start node: {}", e)))?;
1784
1785 Ok(())
1786}
1787
1788pub async fn stop_node() -> Result<(), CliError> {
1789 use crate::node_manager::{NodeManager, NodeManagerConfig};
1790
1791 info!("Stopping QuDAG node...");
1792
1793 let config = NodeManagerConfig::default();
1795 let manager = NodeManager::new(config)
1796 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1797
1798 manager
1800 .stop_node(false) .await
1802 .map_err(|e| CliError::Node(format!("Failed to stop node: {}", e)))?;
1803
1804 Ok(())
1805}
1806
1807pub async fn show_status() -> Result<(), CliError> {
1808 use crate::node_manager::{NodeManager, NodeManagerConfig};
1809
1810 info!("Fetching node status...");
1811
1812 let config = NodeManagerConfig::default();
1814 let manager = NodeManager::new(config)
1815 .map_err(|e| CliError::Node(format!("Failed to create node manager: {}", e)))?;
1816
1817 let local_status = manager
1818 .get_status()
1819 .await
1820 .map_err(|e| CliError::Node(format!("Failed to get local status: {}", e)))?;
1821
1822 if local_status.is_running {
1823 let args = StatusArgs::default();
1825 match CommandRouter::handle_node_status(args).await {
1826 Ok(output) => {
1827 println!("{}", output);
1828 Ok(())
1829 }
1830 Err(e) => {
1831 warn!("Failed to get detailed status via RPC: {}", e);
1833 println!("Node Status:");
1834 println!("============");
1835 println!("Status: Running (PID: {})", local_status.pid.unwrap_or(0));
1836 println!("Port: {}", local_status.port);
1837 println!("Data Directory: {:?}", local_status.data_dir);
1838 println!("Log File: {:?}", local_status.log_file);
1839 if let Some(uptime) = local_status.uptime_seconds {
1840 println!("Uptime: {} seconds", uptime);
1841 }
1842 println!("\nNote: RPC connection failed, showing local status only");
1843 Ok(())
1844 }
1845 }
1846 } else {
1847 println!("Node Status:");
1848 println!("============");
1849 println!("Status: Not running");
1850 println!("Port: {} (configured)", local_status.port);
1851 println!("Data Directory: {:?}", local_status.data_dir);
1852 println!("Log File: {:?}", local_status.log_file);
1853 println!("\nUse 'qudag start' to start the node");
1854 Ok(())
1855 }
1856}
1857
1858pub async fn list_peers() -> Result<(), CliError> {
1859 let router = CommandRouter::with_peer_manager().await?;
1860 router.handle_peer_list(None).await
1861}
1862
1863pub async fn add_peer(address: String) -> Result<(), CliError> {
1864 let router = CommandRouter::with_peer_manager().await?;
1865 router.handle_peer_add(address, None, None).await
1866}
1867
1868pub async fn remove_peer(peer_id: String) -> Result<(), CliError> {
1869 let router = CommandRouter::with_peer_manager().await?;
1870 router.handle_peer_remove(peer_id, None, false).await
1871}
1872
1873pub async fn visualize_dag(
1874 output: Option<PathBuf>,
1875 format: Option<String>,
1876) -> Result<(), CliError> {
1877 info!("Generating DAG visualization...");
1878
1879 let output = output.unwrap_or_else(|| PathBuf::from("dag_visualization.dot"));
1880 let format = format.unwrap_or_else(|| "dot".to_string());
1881
1882 use std::fs::File;
1884 use std::io::Write;
1885
1886 let dot_content = r#"digraph DAG {
1887 node [shape=box];
1888 "genesis" -> "block1";
1889 "genesis" -> "block2";
1890 "block1" -> "block3";
1891 "block2" -> "block3";
1892}
1893"#;
1894
1895 let mut file = File::create(&output)
1896 .map_err(|e| CliError::Visualization(format!("Failed to create output file: {}", e)))?;
1897
1898 file.write_all(dot_content.as_bytes())
1899 .map_err(|e| CliError::Visualization(format!("Failed to write visualization: {}", e)))?;
1900
1901 info!(
1902 "DAG visualization saved to {:?} in {} format",
1903 output, format
1904 );
1905 Ok(())
1906}
1907
1908pub async fn show_network_stats() -> Result<(), CliError> {
1909 let router = CommandRouter::new();
1910 router.handle_network_stats(None, false).await
1911}
1912
1913pub async fn test_network() -> Result<(), CliError> {
1914 let router = CommandRouter::new();
1915 router.handle_network_test(None).await
1916}
1917
1918fn is_valid_peer_address(address: &str) -> bool {
1920 if let Some((host, port_str)) = address.rsplit_once(':') {
1922 if host.is_empty() || port_str.is_empty() {
1923 return false;
1924 }
1925
1926 if let Ok(port) = port_str.parse::<u16>() {
1928 if port == 0 {
1929 return false;
1930 }
1931 } else {
1932 return false;
1933 }
1934
1935 if host.parse::<std::net::IpAddr>().is_ok() {
1937 return true; }
1939
1940 if host.len() <= 253 && !host.is_empty() {
1942 return host
1943 .chars()
1944 .all(|c| c.is_alphanumeric() || c == '.' || c == '-');
1945 }
1946 }
1947
1948 false
1949}
1950
1951fn format_bytes(bytes: u64) -> String {
1953 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
1954 let mut size = bytes as f64;
1955 let mut unit_index = 0;
1956
1957 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
1958 size /= 1024.0;
1959 unit_index += 1;
1960 }
1961
1962 if unit_index == 0 {
1963 format!("{} {}", bytes, UNITS[unit_index])
1964 } else {
1965 format!("{:.2} {}", size, UNITS[unit_index])
1966 }
1967}
1968
1969fn format_duration(seconds: u64) -> String {
1971 let days = seconds / 86400;
1972 let hours = (seconds % 86400) / 3600;
1973 let minutes = (seconds % 3600) / 60;
1974 let secs = seconds % 60;
1975
1976 if days > 0 {
1977 format!("{}d {}h {}m {}s", days, hours, minutes, secs)
1978 } else if hours > 0 {
1979 format!("{}h {}m {}s", hours, minutes, secs)
1980 } else if minutes > 0 {
1981 format!("{}m {}s", minutes, secs)
1982 } else {
1983 format!("{}s", secs)
1984 }
1985}