Skip to content

Commit 82a7dd1

Browse files
authored
feat(admin): show node and disk details in cluster info (#226)
1 parent d189d52 commit 82a7dd1

1 file changed

Lines changed: 164 additions & 7 deletions

File tree

  • crates/cli/src/commands/admin

crates/cli/src/commands/admin/info.rs

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,7 @@ struct ServerOutput {
8989

9090
impl 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

277357
async 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
412534
fn 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

Comments
 (0)