Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a3ee3d5ba | |||
| 0e8b149718 | |||
| 2c27d0e1db | |||
| 9f18488752 | |||
| fab6404cca | |||
| c3626cc362 | |||
| d68ecfbc64 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cm-dashboard"
|
name = "cm-dashboard"
|
||||||
version = "0.1.100"
|
version = "0.1.108"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -301,7 +301,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cm-dashboard-agent"
|
name = "cm-dashboard-agent"
|
||||||
version = "0.1.100"
|
version = "0.1.108"
|
||||||
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.100"
|
version = "0.1.108"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard-agent"
|
name = "cm-dashboard-agent"
|
||||||
version = "0.1.101"
|
version = "0.1.109"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -343,6 +343,9 @@ impl DiskCollector {
|
|||||||
storage_pools.push(pool);
|
storage_pools.push(pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: Do not create individual filesystem pools when using auto-discovery
|
||||||
|
// All single disk filesystems should be grouped into physical drive pools above
|
||||||
|
|
||||||
// Process mergerfs pools (these remain as logical pools)
|
// Process mergerfs pools (these remain as logical pools)
|
||||||
for pool_info in &topology.mergerfs_pools {
|
for pool_info in &topology.mergerfs_pools {
|
||||||
if let Ok((total_bytes, used_bytes)) = self.get_filesystem_info(&pool_info.mount_point) {
|
if let Ok((total_bytes, used_bytes)) = self.get_filesystem_info(&pool_info.mount_point) {
|
||||||
@@ -416,13 +419,22 @@ impl DiskCollector {
|
|||||||
// Get the physical drive name for this mount point
|
// Get the physical drive name for this mount point
|
||||||
if let Some(devices) = self.detected_devices.get(&fs.mount_point) {
|
if let Some(devices) = self.detected_devices.get(&fs.mount_point) {
|
||||||
if let Some(device_name) = devices.first() {
|
if let Some(device_name) = devices.first() {
|
||||||
// Extract drive name (e.g., "nvme0n1" from "nvme0n1")
|
// Extract base drive name from detected device
|
||||||
let drive_name = device_name.clone();
|
let drive_name = Self::extract_base_device(device_name)
|
||||||
|
.unwrap_or_else(|| device_name.clone());
|
||||||
|
|
||||||
|
debug!("Grouping filesystem {} (device: {}) under drive: {}",
|
||||||
|
fs.mount_point, device_name, drive_name);
|
||||||
|
|
||||||
grouped.entry(drive_name).or_insert_with(Vec::new).push(fs.clone());
|
grouped.entry(drive_name).or_insert_with(Vec::new).push(fs.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Filesystem grouping result: {} drives with filesystems: {:?}",
|
||||||
|
grouped.len(),
|
||||||
|
grouped.keys().collect::<Vec<_>>());
|
||||||
|
|
||||||
Ok(grouped)
|
Ok(grouped)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -887,6 +899,11 @@ impl DiskCollector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert bytes to gigabytes
|
||||||
|
fn bytes_to_gb(&self, bytes: u64) -> f32 {
|
||||||
|
bytes as f32 / (1024.0 * 1024.0 * 1024.0)
|
||||||
|
}
|
||||||
|
|
||||||
/// Detect device backing a mount point using lsblk (static version for startup)
|
/// Detect device backing a mount point using lsblk (static version for startup)
|
||||||
fn detect_device_for_mount_point_static(mount_point: &str) -> Result<Vec<String>> {
|
fn detect_device_for_mount_point_static(mount_point: &str) -> Result<Vec<String>> {
|
||||||
let output = Command::new("lsblk")
|
let output = Command::new("lsblk")
|
||||||
@@ -1212,6 +1229,79 @@ impl Collector for DiskCollector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Individual filesystem metrics for PhysicalDrive pools
|
||||||
|
if let StoragePoolType::PhysicalDrive { filesystems } = &storage_pool.pool_type {
|
||||||
|
for filesystem_mount in filesystems {
|
||||||
|
if let Ok((total_bytes, used_bytes)) = self.get_filesystem_info(filesystem_mount) {
|
||||||
|
let available_bytes = total_bytes - used_bytes;
|
||||||
|
let usage_percent = if total_bytes > 0 {
|
||||||
|
(used_bytes as f64 / total_bytes as f64) * 100.0
|
||||||
|
} else { 0.0 };
|
||||||
|
|
||||||
|
let filesystem_name = if filesystem_mount == "/" {
|
||||||
|
"root".to_string()
|
||||||
|
} else {
|
||||||
|
filesystem_mount.trim_start_matches('/').replace('/', "_")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate filesystem status based on usage
|
||||||
|
let fs_status = if usage_percent >= self.config.usage_critical_percent as f64 {
|
||||||
|
Status::Critical
|
||||||
|
} else if usage_percent >= self.config.usage_warning_percent as f64 {
|
||||||
|
Status::Warning
|
||||||
|
} else {
|
||||||
|
Status::Ok
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filesystem usage metrics
|
||||||
|
metrics.push(Metric {
|
||||||
|
name: format!("disk_{}_fs_{}_usage_percent", pool_name, filesystem_name),
|
||||||
|
value: MetricValue::Float(usage_percent as f32),
|
||||||
|
unit: Some("%".to_string()),
|
||||||
|
description: Some(format!("{}: {:.0}%", filesystem_mount, usage_percent)),
|
||||||
|
status: fs_status.clone(),
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
metrics.push(Metric {
|
||||||
|
name: format!("disk_{}_fs_{}_used_gb", pool_name, filesystem_name),
|
||||||
|
value: MetricValue::Float(self.bytes_to_gb(used_bytes)),
|
||||||
|
unit: Some("GB".to_string()),
|
||||||
|
description: Some(format!("{}: {}GB used", filesystem_mount, self.bytes_to_human_readable(used_bytes))),
|
||||||
|
status: Status::Ok,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
metrics.push(Metric {
|
||||||
|
name: format!("disk_{}_fs_{}_total_gb", pool_name, filesystem_name),
|
||||||
|
value: MetricValue::Float(self.bytes_to_gb(total_bytes)),
|
||||||
|
unit: Some("GB".to_string()),
|
||||||
|
description: Some(format!("{}: {}GB total", filesystem_mount, self.bytes_to_human_readable(total_bytes))),
|
||||||
|
status: Status::Ok,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
metrics.push(Metric {
|
||||||
|
name: format!("disk_{}_fs_{}_available_gb", pool_name, filesystem_name),
|
||||||
|
value: MetricValue::Float(self.bytes_to_gb(available_bytes)),
|
||||||
|
unit: Some("GB".to_string()),
|
||||||
|
description: Some(format!("{}: {}GB available", filesystem_mount, self.bytes_to_human_readable(available_bytes))),
|
||||||
|
status: Status::Ok,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
metrics.push(Metric {
|
||||||
|
name: format!("disk_{}_fs_{}_mount_point", pool_name, filesystem_name),
|
||||||
|
value: MetricValue::String(filesystem_mount.clone()),
|
||||||
|
unit: None,
|
||||||
|
description: Some(format!("Mount: {}", filesystem_mount)),
|
||||||
|
status: Status::Ok,
|
||||||
|
timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add storage pool count metric
|
// Add storage pool count metric
|
||||||
@@ -1234,5 +1324,4 @@ impl Collector for DiskCollector {
|
|||||||
|
|
||||||
Ok(metrics)
|
Ok(metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard"
|
name = "cm-dashboard"
|
||||||
version = "0.1.101"
|
version = "0.1.109"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ struct StoragePool {
|
|||||||
pool_type: String, // "single", "mergerfs (2+1)", "RAID5 (3+1)", etc.
|
pool_type: String, // "single", "mergerfs (2+1)", "RAID5 (3+1)", etc.
|
||||||
pool_health: Option<String>, // "healthy", "degraded", "critical", "rebuilding"
|
pool_health: Option<String>, // "healthy", "degraded", "critical", "rebuilding"
|
||||||
drives: Vec<StorageDrive>,
|
drives: Vec<StorageDrive>,
|
||||||
|
filesystems: Vec<FileSystem>, // For physical drive pools: individual filesystem children
|
||||||
usage_percent: Option<f32>,
|
usage_percent: Option<f32>,
|
||||||
used_gb: Option<f32>,
|
used_gb: Option<f32>,
|
||||||
total_gb: Option<f32>,
|
total_gb: Option<f32>,
|
||||||
@@ -63,6 +64,16 @@ struct StorageDrive {
|
|||||||
status: Status,
|
status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct FileSystem {
|
||||||
|
mount_point: String,
|
||||||
|
usage_percent: Option<f32>,
|
||||||
|
used_gb: Option<f32>,
|
||||||
|
total_gb: Option<f32>,
|
||||||
|
available_gb: Option<f32>,
|
||||||
|
status: Status,
|
||||||
|
}
|
||||||
|
|
||||||
impl SystemWidget {
|
impl SystemWidget {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -160,6 +171,7 @@ impl SystemWidget {
|
|||||||
pool_type: "single".to_string(), // Default, will be updated
|
pool_type: "single".to_string(), // Default, will be updated
|
||||||
pool_health: None,
|
pool_health: None,
|
||||||
drives: Vec::new(),
|
drives: Vec::new(),
|
||||||
|
filesystems: Vec::new(),
|
||||||
usage_percent: None,
|
usage_percent: None,
|
||||||
used_gb: None,
|
used_gb: None,
|
||||||
total_gb: None,
|
total_gb: None,
|
||||||
@@ -230,6 +242,91 @@ impl SystemWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if metric.name.contains("_fs_") {
|
||||||
|
// Handle filesystem metrics for physical drive pools (disk_{pool}_fs_{fs_name}_{metric})
|
||||||
|
if let (Some(fs_name), Some(metric_type)) = self.extract_filesystem_metric(&metric.name) {
|
||||||
|
// Find or create filesystem entry
|
||||||
|
let fs_exists = pool.filesystems.iter().any(|fs| {
|
||||||
|
let fs_id = if fs.mount_point == "/" {
|
||||||
|
"root".to_string()
|
||||||
|
} else {
|
||||||
|
fs.mount_point.trim_start_matches('/').replace('/', "_")
|
||||||
|
};
|
||||||
|
fs_id == fs_name
|
||||||
|
});
|
||||||
|
|
||||||
|
if !fs_exists {
|
||||||
|
// Create filesystem entry with correct mount point
|
||||||
|
let mount_point = if metric_type == "mount_point" {
|
||||||
|
if let MetricValue::String(mount) = &metric.value {
|
||||||
|
mount.clone()
|
||||||
|
} else {
|
||||||
|
// Fallback: handle special cases
|
||||||
|
if fs_name == "root" {
|
||||||
|
"/".to_string()
|
||||||
|
} else {
|
||||||
|
format!("/{}", fs_name.replace('_', "/"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for non-mount_point metrics: generate mount point from fs_name
|
||||||
|
if fs_name == "root" {
|
||||||
|
"/".to_string()
|
||||||
|
} else {
|
||||||
|
format!("/{}", fs_name.replace('_', "/"))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pool.filesystems.push(FileSystem {
|
||||||
|
mount_point,
|
||||||
|
usage_percent: None,
|
||||||
|
used_gb: None,
|
||||||
|
total_gb: None,
|
||||||
|
available_gb: None,
|
||||||
|
status: Status::Unknown,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the filesystem with the metric value
|
||||||
|
if let Some(filesystem) = pool.filesystems.iter_mut().find(|fs| {
|
||||||
|
let fs_id = if fs.mount_point == "/" {
|
||||||
|
"root".to_string()
|
||||||
|
} else {
|
||||||
|
fs.mount_point.trim_start_matches('/').replace('/', "_")
|
||||||
|
};
|
||||||
|
fs_id == fs_name
|
||||||
|
}) {
|
||||||
|
match metric_type.as_str() {
|
||||||
|
"usage_percent" => {
|
||||||
|
if let MetricValue::Float(usage) = metric.value {
|
||||||
|
filesystem.usage_percent = Some(usage);
|
||||||
|
filesystem.status = metric.status.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"used_gb" => {
|
||||||
|
if let MetricValue::Float(used) = metric.value {
|
||||||
|
filesystem.used_gb = Some(used);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"total_gb" => {
|
||||||
|
if let MetricValue::Float(total) = metric.value {
|
||||||
|
filesystem.total_gb = Some(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"available_gb" => {
|
||||||
|
if let MetricValue::Float(available) = metric.value {
|
||||||
|
filesystem.available_gb = Some(available);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"mount_point" => {
|
||||||
|
if let MetricValue::String(mount) = &metric.value {
|
||||||
|
filesystem.mount_point = mount.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,10 +353,17 @@ impl SystemWidget {
|
|||||||
return Some(metric_name[5..drive_start].to_string()); // Skip "disk_"
|
return Some(metric_name[5..drive_start].to_string()); // Skip "disk_"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Handle filesystem metrics: disk_{pool}_fs_{filesystem}_{metric}
|
||||||
|
else if metric_name.contains("_fs_") {
|
||||||
|
if let Some(fs_pos) = metric_name.find("_fs_") {
|
||||||
|
return Some(metric_name[5..fs_pos].to_string()); // Skip "disk_", extract pool name before "_fs_"
|
||||||
|
}
|
||||||
|
}
|
||||||
// For pool-level metrics (usage_percent, used_gb, total_gb), take everything before the metric suffix
|
// For pool-level metrics (usage_percent, used_gb, total_gb), take everything before the metric suffix
|
||||||
else if let Some(suffix_pos) = metric_name.rfind("_usage_percent")
|
else if let Some(suffix_pos) = metric_name.rfind("_usage_percent")
|
||||||
.or_else(|| metric_name.rfind("_used_gb"))
|
.or_else(|| metric_name.rfind("_used_gb"))
|
||||||
.or_else(|| metric_name.rfind("_total_gb")) {
|
.or_else(|| metric_name.rfind("_total_gb"))
|
||||||
|
.or_else(|| metric_name.rfind("_available_gb")) {
|
||||||
return Some(metric_name[5..suffix_pos].to_string()); // Skip "disk_"
|
return Some(metric_name[5..suffix_pos].to_string()); // Skip "disk_"
|
||||||
}
|
}
|
||||||
// Fallback to old behavior for unknown patterns
|
// Fallback to old behavior for unknown patterns
|
||||||
@@ -272,6 +376,28 @@ impl SystemWidget {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract filesystem name and metric type from filesystem metric names
|
||||||
|
/// Pattern: disk_{pool}_fs_{filesystem_name}_{metric_type}
|
||||||
|
fn extract_filesystem_metric(&self, metric_name: &str) -> (Option<String>, Option<String>) {
|
||||||
|
if metric_name.starts_with("disk_") && metric_name.contains("_fs_") {
|
||||||
|
// Find the _fs_ part
|
||||||
|
if let Some(fs_start) = metric_name.find("_fs_") {
|
||||||
|
let after_fs = &metric_name[fs_start + 4..]; // Skip "_fs_"
|
||||||
|
|
||||||
|
// Look for known metric suffixes (these can contain underscores)
|
||||||
|
let known_suffixes = ["usage_percent", "used_gb", "total_gb", "available_gb", "mount_point"];
|
||||||
|
|
||||||
|
for suffix in known_suffixes {
|
||||||
|
if after_fs.ends_with(suffix) {
|
||||||
|
let fs_name = after_fs[..after_fs.len() - suffix.len() - 1].to_string(); // Remove suffix + underscore
|
||||||
|
return (Some(fs_name), Some(suffix.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract drive name from disk metric name
|
/// Extract drive name from disk metric name
|
||||||
fn extract_drive_name(&self, metric_name: &str) -> Option<String> {
|
fn extract_drive_name(&self, metric_name: &str) -> Option<String> {
|
||||||
// Pattern: disk_{pool_name}_{drive_name}_{metric_type}
|
// Pattern: disk_{pool_name}_{drive_name}_{metric_type}
|
||||||
@@ -337,7 +463,9 @@ impl SystemWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let has_drives = !pool.drives.is_empty();
|
let has_drives = !pool.drives.is_empty();
|
||||||
let tree_symbol = if has_drives { "├─" } else { "└─" };
|
let has_filesystems = !pool.filesystems.is_empty();
|
||||||
|
let has_children = has_drives || has_filesystems;
|
||||||
|
let tree_symbol = if has_children { "├─" } else { "└─" };
|
||||||
let mut usage_spans = vec![
|
let mut usage_spans = vec![
|
||||||
Span::raw(" "),
|
Span::raw(" "),
|
||||||
Span::styled(tree_symbol, Typography::tree()),
|
Span::styled(tree_symbol, Typography::tree()),
|
||||||
@@ -397,6 +525,63 @@ impl SystemWidget {
|
|||||||
self.render_drive_line(&mut lines, drive, tree_symbol);
|
self.render_drive_line(&mut lines, drive, tree_symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if pool.pool_type.starts_with("drive (") {
|
||||||
|
// Physical drive pools: show drive info + filesystem children
|
||||||
|
// First show drive information
|
||||||
|
for drive in &pool.drives {
|
||||||
|
let mut drive_info = Vec::new();
|
||||||
|
if let Some(temp) = drive.temperature {
|
||||||
|
drive_info.push(format!("T: {:.0}°C", temp));
|
||||||
|
}
|
||||||
|
if let Some(wear) = drive.wear_percent {
|
||||||
|
drive_info.push(format!("W: {:.0}%", wear));
|
||||||
|
}
|
||||||
|
let drive_text = if drive_info.is_empty() {
|
||||||
|
format!("Drive: {}", drive.name)
|
||||||
|
} else {
|
||||||
|
format!("Drive: {}", drive_info.join(" "))
|
||||||
|
};
|
||||||
|
|
||||||
|
let has_filesystems = !pool.filesystems.is_empty();
|
||||||
|
let tree_symbol = if has_filesystems { "├─" } else { "└─" };
|
||||||
|
let mut drive_spans = vec![
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(tree_symbol, Typography::tree()),
|
||||||
|
Span::raw(" "),
|
||||||
|
];
|
||||||
|
drive_spans.extend(StatusIcons::create_status_spans(drive.status.clone(), &drive_text));
|
||||||
|
lines.push(Line::from(drive_spans));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then show filesystem children
|
||||||
|
for (i, filesystem) in pool.filesystems.iter().enumerate() {
|
||||||
|
let is_last = i == pool.filesystems.len() - 1;
|
||||||
|
let tree_symbol = if is_last { "└─" } else { "├─" };
|
||||||
|
|
||||||
|
let fs_text = match (filesystem.usage_percent, filesystem.used_gb, filesystem.total_gb) {
|
||||||
|
(Some(pct), Some(used), Some(total)) => {
|
||||||
|
format!("{}: {:.0}% {:.1}GB/{:.1}GB", filesystem.mount_point, pct, used, total)
|
||||||
|
}
|
||||||
|
(Some(pct), _, Some(total)) => {
|
||||||
|
format!("{}: {:.0}% —GB/{:.1}GB", filesystem.mount_point, pct, total)
|
||||||
|
}
|
||||||
|
(Some(pct), _, _) => {
|
||||||
|
format!("{}: {:.0}% —GB/—GB", filesystem.mount_point, pct)
|
||||||
|
}
|
||||||
|
(_, Some(used), Some(total)) => {
|
||||||
|
format!("{}: —% {:.1}GB/{:.1}GB", filesystem.mount_point, used, total)
|
||||||
|
}
|
||||||
|
_ => format!("{}: —% —GB/—GB", filesystem.mount_point),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut fs_spans = vec![
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(tree_symbol, Typography::tree()),
|
||||||
|
Span::raw(" "),
|
||||||
|
];
|
||||||
|
fs_spans.extend(StatusIcons::create_status_spans(filesystem.status.clone(), &fs_text));
|
||||||
|
lines.push(Line::from(fs_spans));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Single drive or simple pools
|
// Single drive or simple pools
|
||||||
for (i, drive) in pool.drives.iter().enumerate() {
|
for (i, drive) in pool.drives.iter().enumerate() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard-shared"
|
name = "cm-dashboard-shared"
|
||||||
version = "0.1.101"
|
version = "0.1.109"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user