Show ZFS pool layout, scrub status and device health in Storage view

/api/storage now runs a single `zpool status -j --json-int -P` (OpenZFS
2.3+) for all pools and returns, per pool:
- top-level vdevs with type, raidz/draid parity, class (data, special,
  dedup, log, cache, spare), state and read/write/checksum errors
- member devices with path, resolved base disk, state, error counters,
  slow I/Os, and the spare-N/replacing-N they sit under while swapping
- scrub/resilver status (progress only while running), the pool's
  status/action text and data-error count
- fragmentation, capacity and dedup ratio from `zpool list`

This replaces the per-pool `zpool status` text loop whose result
(zfs_disks) was never used. Member paths (by-id, by-partlabel, wwn) are
resolved to base disks via sysfs rather than an lsblk call per device.
On ZFS without `-j`, pools report details_available: false and the UI
falls back to the existing basic card.

The ZFS pool card shows a layout summary (e.g. "RAIDZ2 | 5 disks |
Scrub finished ...") and opens a details dialog with the vdev tree,
per-device error counts, scrub/resilver progress, and links from each
member disk to its disk details dialog.

Tested against `zpool status -j` output from PVE 9 / OpenZFS 2.4.4:
mirror, raidz2, striped mirrors, single-disk pool, and a file-backed
degraded raidz1 with log, cache and spare devices.

Tests cover the `zpool status -j` -> Storage view shape against real
output from PVE 9 / OpenZFS 2.4.4 hosts (serials and WWNs replaced):
mirror, raidz2, single-disk pool, slow I/Os, and a degraded raidz1 with
log, cache and spare devices; plus a synthetic resilver with a spare
swapping in. Member path -> base disk resolution is tested against a
stubbed sysfs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DrOwl
2026-10-29 11:55:02 +00:00
co-authored by Claude Opus 5.5
parent 5db3ae9c92
commit 72491b2445
7 changed files with 1308 additions and 19 deletions
+50
View File
@@ -309,6 +309,56 @@
"externalStorageUsb": "External Storage (USB)",
"physicalDisksSmart": "Physical Disks & SMART Status",
"zfsPools": "ZFS Pools",
"zfs": {
"poolDetails": "Pool layout, scrub status and device health",
"detailsUnavailable": "Detailed pool layout requires OpenZFS 2.3 or newer.",
"status": "Status",
"action": "Action",
"capacity": "Capacity",
"fragmentation": "Fragmentation",
"dedupRatio": "Dedup Ratio",
"layout": "Layout",
"singleDisk": "Single disk",
"stripe": "Stripe",
"mirror": "Mirror",
"raidz": "RAIDZ{parity}",
"draid": "dRAID{parity}",
"diskCountOne": "{count} disk",
"diskCountMany": "{count} disks",
"errorsReported": "Errors reported",
"scanTitle": "Scrub / Resilver",
"scrub": "Scrub",
"resilver": "Resilver",
"neverScrubbed": "Never scrubbed",
"scanRunning": "{kind} in progress ({percent}%)",
"scanCanceled": "{kind} canceled",
"scanFinished": "{kind} finished {date}",
"started": "Started",
"ended": "Finished",
"repaired": "Repaired",
"scanErrors": "Scan Errors",
"dataErrors": "{count} data errors",
"noDataErrors": "No known data errors",
"device": "Device",
"disk": "Disk",
"state": "State",
"readErrors": "Read errors",
"writeErrors": "Write errors",
"checksumErrors": "Checksum errors",
"readShort": "R",
"writeShort": "W",
"checksumShort": "CK",
"slowIos": "Slow I/Os",
"via": "via {name}",
"classes": {
"normal": "Data",
"special": "Special",
"dedup": "Dedup",
"log": "Log",
"cache": "Cache",
"spare": "Spare"
}
},
"local": "Local",
"remote": "Remote",
"free": "Free",