@@ -89,11 +89,7 @@ struct ServerOutput {
8989
9090impl From < & ServerInfo > for ServerOutput {
9191 fn from ( server : & ServerInfo ) -> Self {
92- let online = server
93- . disks
94- . iter ( )
95- . filter ( |d| d. state == "online" || d. state == "ok" )
96- . count ( ) ;
92+ let online = count_online_disks ( & server. disks ) ;
9793 let offline = server. disks . iter ( ) . filter ( |d| d. state == "offline" ) . count ( ) ;
9894
9995 Self {
@@ -272,6 +268,90 @@ fn print_cluster_info(info: &ClusterInfo, formatter: &Formatter) {
272268 formatter. println ( & format ! ( " EC Parity: {}" , parity) ) ;
273269 }
274270 }
271+
272+ if let Some ( ref servers) = info. servers {
273+ print_cluster_node_details ( servers, formatter) ;
274+ print_cluster_disk_details ( servers, formatter) ;
275+ }
276+ }
277+
278+ fn print_cluster_node_details ( servers : & [ ServerInfo ] , formatter : & Formatter ) {
279+ if servers. is_empty ( ) {
280+ return ;
281+ }
282+
283+ formatter. println ( "" ) ;
284+ formatter. println ( & formatter. style_name ( "Nodes:" ) ) ;
285+
286+ for server in servers {
287+ let state_icon = if is_online_state ( & server. state ) {
288+ formatter. style_size ( "●" )
289+ } else {
290+ formatter. style_date ( "○" )
291+ } ;
292+ let endpoint = value_or_unknown ( & server. endpoint ) ;
293+ let version = value_or_unknown ( & server. version ) ;
294+ let online_disks = count_online_disks ( & server. disks ) ;
295+ let total_disks = server. disks . len ( ) ;
296+
297+ formatter. println ( & format ! (
298+ " {} {}" ,
299+ state_icon,
300+ formatter. style_name( endpoint)
301+ ) ) ;
302+ formatter. println ( & format ! ( " Uptime: {}" , format_duration( server. uptime) ) ) ;
303+ formatter. println ( & format ! ( " Version: {}" , formatter. style_date( version) ) ) ;
304+ if !server. network . is_empty ( ) {
305+ formatter. println ( & format ! (
306+ " Network: {}/{} OK" ,
307+ count_online_networks( server) ,
308+ server. network. len( )
309+ ) ) ;
310+ }
311+ formatter. println ( & format ! (
312+ " Drives: {}/{} OK" ,
313+ online_disks, total_disks
314+ ) ) ;
315+ if server. pool_number > 0 {
316+ formatter. println ( & format ! ( " Pool: {}" , server. pool_number) ) ;
317+ }
318+ }
319+ }
320+
321+ fn print_cluster_disk_details ( servers : & [ ServerInfo ] , formatter : & Formatter ) {
322+ let disks: Vec < & DiskInfo > = servers. iter ( ) . flat_map ( |server| & server. disks ) . collect ( ) ;
323+ if disks. is_empty ( ) {
324+ return ;
325+ }
326+
327+ formatter. println ( "" ) ;
328+ formatter. println ( & formatter. style_name ( "Disks:" ) ) ;
329+
330+ for disk in disks {
331+ let state_icon = if is_online_state ( & disk. state ) {
332+ formatter. style_size ( "●" )
333+ } else {
334+ formatter. style_date ( "○" )
335+ } ;
336+ let path = value_or_unknown ( & disk. drive_path ) ;
337+ let state = value_or_unknown ( & disk. state ) ;
338+ let endpoint = value_or_unknown ( & disk. endpoint ) ;
339+
340+ formatter. println ( & format ! (
341+ " {} {} [{}] {}" ,
342+ state_icon,
343+ formatter. style_name( path) ,
344+ state,
345+ endpoint
346+ ) ) ;
347+ if disk. total_space > 0 {
348+ formatter. println ( & format ! ( " Capacity: {}" , disk_capacity_summary( disk) ) ) ;
349+ }
350+ formatter. println ( & format ! (
351+ " Location: pool:{} set:{} disk:{}" ,
352+ disk. pool_index, disk. set_index, disk. disk_index
353+ ) ) ;
354+ }
275355}
276356
277357async fn execute_server ( args : ServerArgs , formatter : & Formatter ) -> ExitCode {
@@ -300,7 +380,7 @@ async fn execute_server(args: ServerArgs, formatter: &Formatter) -> ExitCode {
300380 formatter. println ( "" ) ;
301381
302382 for server in & servers {
303- let state_icon = if server. state == "online" {
383+ let state_icon = if is_online_state ( & server. state ) {
304384 formatter. style_size ( "●" )
305385 } else {
306386 formatter. style_date ( "○" )
@@ -365,7 +445,7 @@ async fn execute_disk(args: DiskArgs, formatter: &Formatter) -> ExitCode {
365445
366446 for disk in disks {
367447 let state_icon = match disk. state . as_str ( ) {
368- "online" | "ok" => formatter. style_size ( "●" ) ,
448+ state if is_online_state ( state ) => formatter. style_size ( "●" ) ,
369449 "offline" => formatter. style_date ( "○" ) ,
370450 _ => formatter. style_date ( "?" ) ,
371451 } ;
@@ -408,6 +488,48 @@ async fn execute_disk(args: DiskArgs, formatter: &Formatter) -> ExitCode {
408488 }
409489}
410490
491+ fn is_online_state ( state : & str ) -> bool {
492+ state. eq_ignore_ascii_case ( "online" ) || state. eq_ignore_ascii_case ( "ok" )
493+ }
494+
495+ fn count_online_disks ( disks : & [ DiskInfo ] ) -> usize {
496+ disks
497+ . iter ( )
498+ . filter ( |disk| is_online_state ( & disk. state ) )
499+ . count ( )
500+ }
501+
502+ fn count_online_networks ( server : & ServerInfo ) -> usize {
503+ server
504+ . network
505+ . values ( )
506+ . filter ( |state| is_online_state ( state) )
507+ . count ( )
508+ }
509+
510+ fn value_or_unknown ( value : & str ) -> & str {
511+ if value. is_empty ( ) { "unknown" } else { value }
512+ }
513+
514+ fn disk_capacity_summary ( disk : & DiskInfo ) -> String {
515+ if disk. total_space == 0 {
516+ return format ! (
517+ "{} / 0 B (0%, available: {})" ,
518+ format_bytes( disk. used_space) ,
519+ format_bytes( disk. available_space)
520+ ) ;
521+ }
522+
523+ let usage_pct = ( disk. used_space as f64 / disk. total_space as f64 * 100.0 ) as u8 ;
524+ format ! (
525+ "{} / {} ({}%, available: {})" ,
526+ format_bytes( disk. used_space) ,
527+ format_bytes( disk. total_space) ,
528+ usage_pct,
529+ format_bytes( disk. available_space)
530+ )
531+ }
532+
411533/// Format bytes into human-readable form
412534fn format_bytes ( bytes : u64 ) -> String {
413535 const KB : u64 = 1024 ;
@@ -544,4 +666,39 @@ mod tests {
544666 assert_eq ! ( output. set_index, 1 ) ;
545667 assert_eq ! ( output. disk_index, 2 ) ;
546668 }
669+
670+ #[ test]
671+ fn test_count_online_disks_accepts_ok_state ( ) {
672+ let disks = vec ! [
673+ DiskInfo {
674+ state: "online" . to_string( ) ,
675+ ..Default :: default ( )
676+ } ,
677+ DiskInfo {
678+ state: "ok" . to_string( ) ,
679+ ..Default :: default ( )
680+ } ,
681+ DiskInfo {
682+ state: "offline" . to_string( ) ,
683+ ..Default :: default ( )
684+ } ,
685+ ] ;
686+
687+ assert_eq ! ( count_online_disks( & disks) , 2 ) ;
688+ }
689+
690+ #[ test]
691+ fn test_disk_capacity_summary_includes_available_space ( ) {
692+ let disk = DiskInfo {
693+ total_space : 1024 * 1024 * 1024 ,
694+ used_space : 512 * 1024 * 1024 ,
695+ available_space : 512 * 1024 * 1024 ,
696+ ..Default :: default ( )
697+ } ;
698+
699+ assert_eq ! (
700+ disk_capacity_summary( & disk) ,
701+ "512.00 MiB / 1.00 GiB (50%, available: 512.00 MiB)"
702+ ) ;
703+ }
547704}
0 commit comments