Implement full JBOD monitor frontend from design JSX

- Dark/light theme toggle, grid/table view toggle
- Expanded DriveHealthSummary with wwn, firmware, capacity, SMART
  counters, and computed health_status field
- Drive detail modal with identity, WWN, ZFS membership, SMART health
- 30s auto-refresh
This commit is contained in:
2026-03-07 03:41:35 +00:00
parent 284943c185
commit 861f9279c4
3 changed files with 662 additions and 360 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -40,11 +40,18 @@ class DriveHealthSummary(BaseModel):
device: str device: str
model: str | None = None model: str | None = None
serial: str | None = None serial: str | None = None
wwn: str | None = None
firmware: str | None = None
capacity_bytes: int | None = None
smart_healthy: bool | None = None smart_healthy: bool | None = None
smart_supported: bool = True smart_supported: bool = True
temperature_c: int | None = None temperature_c: int | None = None
power_on_hours: int | None = None power_on_hours: int | None = None
reallocated_sectors: int | None = None
pending_sectors: int | None = None
uncorrectable_errors: int | None = None
zfs_pool: str | None = None zfs_pool: str | None = None
health_status: str = "healthy"
class SlotWithDrive(BaseModel): class SlotWithDrive(BaseModel):

View File

@@ -68,15 +68,33 @@ async def get_overview():
if sd.get("uncorrectable_errors") and sd["uncorrectable_errors"] > 0: if sd.get("uncorrectable_errors") and sd["uncorrectable_errors"] > 0:
warnings += 1 warnings += 1
# Compute health_status for frontend
realloc = sd.get("reallocated_sectors") or 0
pending = sd.get("pending_sectors") or 0
unc = sd.get("uncorrectable_errors") or 0
if healthy is False:
health_status = "error"
elif realloc > 0 or pending > 0 or unc > 0 or (healthy is None and sd.get("smart_supported", True)):
health_status = "warning"
else:
health_status = "healthy"
drive_summary = DriveHealthSummary( drive_summary = DriveHealthSummary(
device=sd["device"], device=sd["device"],
model=sd.get("model"), model=sd.get("model"),
serial=sd.get("serial"), serial=sd.get("serial"),
wwn=sd.get("wwn"),
firmware=sd.get("firmware"),
capacity_bytes=sd.get("capacity_bytes"),
smart_healthy=healthy, smart_healthy=healthy,
smart_supported=sd.get("smart_supported", True), smart_supported=sd.get("smart_supported", True),
temperature_c=sd.get("temperature_c"), temperature_c=sd.get("temperature_c"),
power_on_hours=sd.get("power_on_hours"), power_on_hours=sd.get("power_on_hours"),
reallocated_sectors=sd.get("reallocated_sectors"),
pending_sectors=sd.get("pending_sectors"),
uncorrectable_errors=sd.get("uncorrectable_errors"),
zfs_pool=pool_map.get(sd["device"]), zfs_pool=pool_map.get(sd["device"]),
health_status=health_status,
) )
elif s["populated"]: elif s["populated"]:
total_drives += 1 total_drives += 1