From a25ce4ae21d9c0d4caa28a1415e7e7b4e62f9ff1 Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 7 Mar 2026 04:44:31 +0000 Subject: [PATCH] Add ZFS drive state (ONLINE/FAULTED/DEGRADED) to UI --- frontend/src/App.jsx | 30 +++++++++++++++++++++++------- models/schemas.py | 2 ++ routers/drives.py | 1 + routers/overview.py | 1 + services/zfs.py | 2 ++ 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 56bf2a0..1c08824 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -264,7 +264,7 @@ function TableView({ enclosure, onSelect, selectedSerial, t }) { - {["Slot", "Device", "Model", "Serial", "WWN", "FW", "Capacity", "Pool", "Vdev", "Temp", "Hours", "Health"].map((h) => ( + {["Slot", "Device", "Model", "Serial", "WWN", "FW", "Capacity", "Pool", "Vdev", "ZFS State", "Temp", "Hours", "Health"].map((h) => ( ))} @@ -314,6 +314,17 @@ function TableView({ enclosure, onSelect, selectedSerial, t }) { {"\u2014"} )} + @@ -431,12 +442,17 @@ function DriveDetail({ slot, onClose, t }) { / {d.zfs_vdev} )} - - MEMBER - + {d.zfs_state && ( + + {d.zfs_state} + + )} ) : ( <> diff --git a/models/schemas.py b/models/schemas.py index 814a699..4928632 100644 --- a/models/schemas.py +++ b/models/schemas.py @@ -34,6 +34,7 @@ class DriveDetail(BaseModel): wear_leveling_percent: int | None = None zfs_pool: str | None = None zfs_vdev: str | None = None + zfs_state: str | None = None smart_attributes: list[dict] = [] @@ -53,6 +54,7 @@ class DriveHealthSummary(BaseModel): uncorrectable_errors: int | None = None zfs_pool: str | None = None zfs_vdev: str | None = None + zfs_state: str | None = None health_status: str = "healthy" diff --git a/routers/drives.py b/routers/drives.py index 08d77c5..24be49e 100644 --- a/routers/drives.py +++ b/routers/drives.py @@ -23,5 +23,6 @@ async def get_drive_detail(device: str): if zfs_info: data["zfs_pool"] = zfs_info["pool"] data["zfs_vdev"] = zfs_info["vdev"] + data["zfs_state"] = zfs_info.get("state") return DriveDetail(**data) diff --git a/routers/overview.py b/routers/overview.py index 54ed7fa..ef4debf 100644 --- a/routers/overview.py +++ b/routers/overview.py @@ -95,6 +95,7 @@ async def get_overview(): uncorrectable_errors=sd.get("uncorrectable_errors"), zfs_pool=pool_map.get(sd["device"], {}).get("pool"), zfs_vdev=pool_map.get(sd["device"], {}).get("vdev"), + zfs_state=pool_map.get(sd["device"], {}).get("state"), health_status=health_status, ) elif s["populated"]: diff --git a/services/zfs.py b/services/zfs.py index d758021..47d313a 100644 --- a/services/zfs.py +++ b/services/zfs.py @@ -71,9 +71,11 @@ async def get_zfs_pool_map() -> dict[str, dict]: parts = stripped.split() dev_path = parts[0] try: + dev_state = parts[1] if len(parts) > 1 else None info = { "pool": current_pool, "vdev": current_vdev or current_pool, + "state": dev_state, } # Resolve symlink and map the device real = os.path.realpath(dev_path)
{h}
+ {d.zfs_state ? ( + {d.zfs_state} + ) : ( + {"\u2014"} + )} + = 40 ? t.health.warning.text : t.text }}>{d.temperature_c != null ? `${d.temperature_c}\u00B0C` : "\u2014"} {formatHours(d.power_on_hours)}