Integrate SnapRAID parity drives into mergerfs pools
All checks were successful
Build and Release / build-and-release (push) Successful in 1m19s

- Add SnapRAID parity drive detection to mergerfs discovery
- Remove Pool Status health line as discussed
- Update drive display to always show wear data when available
- Include /mnt/parity drives as part of mergerfs pool structure
This commit is contained in:
Christoffer Martinsson 2025-11-23 18:05:19 +01:00
parent 43fb838c9b
commit 192eea6e0c
6 changed files with 25 additions and 28 deletions

6
Cargo.lock generated
View File

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

View File

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

View File

@ -210,7 +210,7 @@ impl DiskCollector {
.collect(); .collect();
// Convert numeric references to actual mount points if needed // Convert numeric references to actual mount points if needed
let member_paths = if raw_paths.iter().any(|path| !path.starts_with('/')) { let mut member_paths = if raw_paths.iter().any(|path| !path.starts_with('/')) {
// Handle numeric format like "1:2" by finding corresponding /mnt/disk* paths // Handle numeric format like "1:2" by finding corresponding /mnt/disk* paths
self.resolve_numeric_mergerfs_paths(&raw_paths)? self.resolve_numeric_mergerfs_paths(&raw_paths)?
} else { } else {
@ -218,6 +218,10 @@ impl DiskCollector {
raw_paths raw_paths
}; };
// For SnapRAID setups, also include parity drives as part of the pool
let snapraid_parity_paths = self.discover_snapraid_parity_drives()?;
member_paths.extend(snapraid_parity_paths);
// Categorize as data vs parity drives // Categorize as data vs parity drives
let (data_drives, parity_drives) = match self.categorize_pool_drives(&member_paths) { let (data_drives, parity_drives) = match self.categorize_pool_drives(&member_paths) {
Ok(drives) => drives, Ok(drives) => drives,
@ -240,6 +244,16 @@ impl DiskCollector {
Ok(pools) Ok(pools)
} }
/// Discover SnapRAID parity drives
fn discover_snapraid_parity_drives(&self) -> Result<Vec<String>> {
let mount_devices = self.get_mount_devices()?;
let parity_paths: Vec<String> = mount_devices.keys()
.filter(|path| path.contains("parity"))
.cloned()
.collect();
Ok(parity_paths)
}
/// Categorize pool member drives as data vs parity /// Categorize pool member drives as data vs parity
fn categorize_pool_drives(&self, member_paths: &[String]) -> Result<(Vec<DriveInfo>, Vec<DriveInfo>)> { fn categorize_pool_drives(&self, member_paths: &[String]) -> Result<(Vec<DriveInfo>, Vec<DriveInfo>)> {
let mut data_drives = Vec::new(); let mut data_drives = Vec::new();

View File

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

View File

@ -470,26 +470,7 @@ impl SystemWidget {
); );
lines.push(Line::from(pool_spans)); lines.push(Line::from(pool_spans));
// Pool health line (for multi-disk pools) // Skip pool health line as discussed - removed
if pool.pool_type != "single" {
if let Some(health) = &pool.pool_health {
let health_text = match health.as_str() {
"healthy" => format!("Pool Status: {} Healthy",
if pool.drives.len() > 1 { format!("({} drives)", pool.drives.len()) } else { String::new() }),
"degraded" => "Pool Status: ⚠ Degraded".to_string(),
"critical" => "Pool Status: ✗ Critical".to_string(),
"rebuilding" => "Pool Status: ⟳ Rebuilding".to_string(),
_ => format!("Pool Status: ? {}", health),
};
let mut health_spans = vec![
Span::raw(" "),
Span::styled("├─ ", Typography::tree()),
];
health_spans.extend(StatusIcons::create_status_spans(pool.health_status.clone(), &health_text));
lines.push(Line::from(health_spans));
}
}
// Total usage line (always show for pools) // Total usage line (always show for pools)
let usage_text = match (pool.usage_percent, pool.used_gb, pool.total_gb) { let usage_text = match (pool.usage_percent, pool.used_gb, pool.total_gb) {
@ -641,10 +622,12 @@ impl SystemWidget {
if let Some(wear) = drive.wear_percent { if let Some(wear) = drive.wear_percent {
drive_info.push(format!("W: {:.0}%", wear)); drive_info.push(format!("W: {:.0}%", wear));
} }
// Always show drive name with info, or just name if no info available
let drive_text = if drive_info.is_empty() { let drive_text = if drive_info.is_empty() {
drive.name.clone() drive.name.clone()
} else { } else {
format!("{} {}", drive.name, drive_info.join(" ")) format!("{} {}", drive.name, drive_info.join(" "))
}; };
let mut drive_spans = vec![ let mut drive_spans = vec![

View File

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