Enumerate physical drives behind RAID via smartctl megaraid passthrough

This commit is contained in:
2026-03-07 05:32:08 +00:00
parent 98e435674c
commit 1dd40a1181
5 changed files with 171 additions and 55 deletions

View File

@@ -595,8 +595,71 @@ const driveTypeBadge = (type, t) => {
);
};
function HostDriveRow({ d, onSelect, t, indent }) {
const healthStatus = d.health_status || "healthy";
const c = t.health[healthStatus] || t.health.healthy;
return (
<button
onClick={() => onSelect({ slot: d.megaraid_id || "-", device: d.device, populated: true, drive: d })}
style={{
display: "flex", alignItems: "center", gap: 16,
padding: indent ? "10px 16px 10px 44px" : "12px 16px", borderRadius: 12,
background: c.bg, border: `1px solid ${c.border}`,
cursor: "pointer", width: "100%", textAlign: "left",
transition: "all 0.18s",
}}
onMouseEnter={(e) => { e.currentTarget.style.transform = "scale(1.01)"; e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; }}
onMouseLeave={(e) => { e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.boxShadow = "none"; }}
>
<span style={{ width: 9, height: 9, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
<span style={{
fontSize: indent ? 13 : 14, fontWeight: 700, color: t.text,
fontFamily: "'JetBrains Mono', monospace", minWidth: 80,
}}>
{indent && d.megaraid_id != null ? `disk ${d.megaraid_id}` : d.device}
</span>
{driveTypeBadge(d.drive_type, t)}
<span style={{
fontSize: 13, color: t.text, flex: 1,
overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
}}>
{d.model || "\u2014"}
</span>
<span style={{
fontSize: 12, color: t.textSecondary,
fontFamily: "'JetBrains Mono', monospace",
}}>
{formatCapacity(d.capacity_bytes)}
</span>
{d.temperature_c != null && (
<span style={{
fontSize: 12, fontWeight: 600,
color: d.temperature_c >= 40 ? t.health.warning.text : t.text,
}}>
{d.temperature_c}&deg;C
</span>
)}
{d.power_on_hours != null && indent && (
<span style={{ fontSize: 11, color: t.textSecondary }}>
{formatHours(d.power_on_hours)}
</span>
)}
{d.zfs_pool && (
<span style={{
fontSize: 11, fontWeight: 700, color: t.accent,
fontFamily: "'JetBrains Mono', monospace",
}}>
{d.zfs_pool}
</span>
)}
<StatusPill status={healthStatus} t={t} />
</button>
);
}
function HostDrivesCard({ drives, onSelect, t }) {
if (!drives || drives.length === 0) return null;
const totalCount = drives.reduce((n, d) => n + 1 + (d.physical_drives?.length || 0), 0);
return (
<div style={{
background: t.cardBg, borderRadius: 16,
@@ -612,69 +675,32 @@ function HostDrivesCard({ drives, onSelect, t }) {
<div>
<div style={{ fontSize: 16, fontWeight: 700, color: t.text }}>Host Drives</div>
<div style={{ fontSize: 12, color: t.textSecondary, marginTop: 2 }}>
{drives.length} drive{drives.length !== 1 ? "s" : ""} &middot; non-enclosure
{totalCount} drive{totalCount !== 1 ? "s" : ""} &middot; non-enclosure
</div>
</div>
</div>
<div style={{ padding: 16 }}>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{drives.map((d) => {
const healthStatus = d.health_status || "healthy";
const c = t.health[healthStatus] || t.health.healthy;
return (
<button
key={d.device}
onClick={() => onSelect({ slot: "-", device: d.device, populated: true, drive: d })}
style={{
display: "flex", alignItems: "center", gap: 16,
padding: "12px 16px", borderRadius: 12,
background: c.bg, border: `1px solid ${c.border}`,
cursor: "pointer", width: "100%", textAlign: "left",
transition: "all 0.18s",
}}
onMouseEnter={(e) => { e.currentTarget.style.transform = "scale(1.01)"; e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)"; }}
onMouseLeave={(e) => { e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.boxShadow = "none"; }}
>
<span style={{ width: 9, height: 9, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
<span style={{
fontSize: 14, fontWeight: 700, color: t.text,
fontFamily: "'JetBrains Mono', monospace", minWidth: 80,
}}>
{d.device}
</span>
{driveTypeBadge(d.drive_type, t)}
<span style={{
fontSize: 13, color: t.text, flex: 1,
overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
}}>
{d.model || "\u2014"}
</span>
<span style={{
fontSize: 12, color: t.textSecondary,
fontFamily: "'JetBrains Mono', monospace",
}}>
{formatCapacity(d.capacity_bytes)}
</span>
{d.temperature_c != null && (
{drives.map((d) => (
<React.Fragment key={d.device}>
<div style={{ position: "relative" }}>
<HostDriveRow d={d} onSelect={onSelect} t={t} />
{d.physical_drives?.length > 0 && (
<span style={{
fontSize: 12, fontWeight: 600,
color: d.temperature_c >= 40 ? t.health.warning.text : t.text,
position: "absolute", top: 8, right: 8,
fontSize: 10, fontWeight: 700, color: t.textMuted,
background: t.health.empty.bg, border: `1px solid ${t.cardBorder}`,
padding: "2px 6px", borderRadius: 4,
}}>
{d.temperature_c}&deg;C
{d.physical_drives.length} disks
</span>
)}
{d.zfs_pool && (
<span style={{
fontSize: 11, fontWeight: 700, color: t.accent,
fontFamily: "'JetBrains Mono', monospace",
}}>
{d.zfs_pool}
</span>
)}
<StatusPill status={healthStatus} t={t} />
</button>
);
})}
</div>
{d.physical_drives?.map((pd, i) => (
<HostDriveRow key={pd.serial || i} d={pd} onSelect={onSelect} t={t} indent />
))}
</React.Fragment>
))}
</div>
</div>
</div>