Improve NVMe serial parsing and restructure MergerFS display
All checks were successful
Build and Release / build-and-release (push) Successful in 1m25s

- Fix NVMe serial number parsing to handle whitespace variations
- Move mount point to MergerFS header, remove drive count
- Restructure data drives to same level as parity with Data_1, Data_2 labels
- Remove "Total:" label from pool usage line
- Update parity to use closing tree symbol as last item
This commit is contained in:
Christoffer Martinsson 2025-11-25 11:28:54 +01:00
parent dc1105eefe
commit 267654fda4
6 changed files with 40 additions and 33 deletions

6
Cargo.lock generated
View File

@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "cm-dashboard"
version = "0.1.155"
version = "0.1.156"
dependencies = [
"anyhow",
"chrono",
@ -301,7 +301,7 @@ dependencies = [
[[package]]
name = "cm-dashboard-agent"
version = "0.1.155"
version = "0.1.156"
dependencies = [
"anyhow",
"async-trait",
@ -324,7 +324,7 @@ dependencies = [
[[package]]
name = "cm-dashboard-shared"
version = "0.1.155"
version = "0.1.156"
dependencies = [
"chrono",
"serde",

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-agent"
version = "0.1.155"
version = "0.1.156"
edition = "2021"
[dependencies]

View File

@ -457,11 +457,15 @@ impl DiskCollector {
}
}
// Serial number parsing
if line.starts_with("Serial Number:") {
// Serial number parsing (both SATA and NVMe)
if line.contains("Serial Number:") {
if let Some(serial_part) = line.split("Serial Number:").nth(1) {
if let Some(serial_str) = serial_part.split_whitespace().next() {
serial_number = Some(serial_str.to_string());
let serial_str = serial_part.trim();
if !serial_str.is_empty() {
// Take first whitespace-separated token
if let Some(serial) = serial_str.split_whitespace().next() {
serial_number = Some(serial.to_string());
}
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard"
version = "0.1.155"
version = "0.1.156"
edition = "2021"
[dependencies]

View File

@ -391,8 +391,8 @@ impl SystemWidget {
pool.name.clone()
}
} else {
// For mergerfs pools, show pool name with format like "mergerfs (2+1):"
format!("{}:", pool.pool_type)
// For mergerfs pools, show pool type with mount point
format!("mergerfs {}:", pool.mount_point)
};
let pool_spans = StatusIcons::create_status_spans(pool.status.clone(), &pool_label);
@ -431,7 +431,7 @@ impl SystemWidget {
// └─ Mount: /srv/media
// Pool total usage
let total_text = format!("Total: {:.0}% {:.1}GB/{:.1}GB",
let total_text = format!("{:.0}% {:.1}GB/{:.1}GB",
pool.usage_percent.unwrap_or(0.0),
pool.used_gb.unwrap_or(0.0),
pool.total_gb.unwrap_or(0.0)
@ -442,20 +442,30 @@ impl SystemWidget {
total_spans.extend(StatusIcons::create_status_spans(Status::Ok, &total_text));
lines.push(Line::from(total_spans));
// Data Disks section
if !pool.data_drives.is_empty() {
lines.push(Line::from(vec![
Span::styled(" ├─ ", Typography::tree()),
Span::styled("Data Disks:", Typography::secondary())
]));
for (i, drive) in pool.data_drives.iter().enumerate() {
let is_last = i == pool.data_drives.len() - 1;
let tree_symbol = if is_last { " │ └─ " } else { " │ ├─ " };
render_mergerfs_drive(drive, tree_symbol, &mut lines);
// Data drives - at same level as parity
for (i, drive) in pool.data_drives.iter().enumerate() {
let mut drive_details = Vec::new();
if let Some(temp) = drive.temperature {
drive_details.push(format!("T: {}°C", temp as i32));
}
if let Some(wear) = drive.wear_percent {
drive_details.push(format!("W: {}%", wear as i32));
}
let drive_text = if !drive_details.is_empty() {
format!("Data_{}: {} {}", i + 1, drive.name, drive_details.join(" "))
} else {
format!("Data_{}: {}", i + 1, drive.name)
};
let mut data_spans = vec![
Span::styled(" ├─ ", Typography::tree()),
];
data_spans.extend(StatusIcons::create_status_spans(drive.status.clone(), &drive_text));
lines.push(Line::from(data_spans));
}
// Parity section
// Parity drives - last item
if !pool.parity_drives.is_empty() {
for drive in &pool.parity_drives {
let mut drive_details = Vec::new();
@ -465,7 +475,7 @@ impl SystemWidget {
if let Some(wear) = drive.wear_percent {
drive_details.push(format!("W: {}%", wear as i32));
}
let drive_text = if !drive_details.is_empty() {
format!("Parity: {} {}", drive.name, drive_details.join(" "))
} else {
@ -473,19 +483,12 @@ impl SystemWidget {
};
let mut parity_spans = vec![
Span::styled(" ", Typography::tree()),
Span::styled(" ", Typography::tree()),
];
parity_spans.extend(StatusIcons::create_status_spans(drive.status.clone(), &drive_text));
lines.push(Line::from(parity_spans));
}
}
// Mount point
lines.push(Line::from(vec![
Span::styled(" └─ ", Typography::tree()),
Span::styled("Mount: ", Typography::secondary()),
Span::styled(&pool.mount_point, Typography::secondary())
]));
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "cm-dashboard-shared"
version = "0.1.155"
version = "0.1.156"
edition = "2021"
[dependencies]