mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 02:42:08 +00:00
Each disk in /api/hardware now carries a controller_slot, resolved from the disk's sysfs device path: the deepest PCI address in that path is the controller (bridges sit above it), normalised to lspci's slot format so it matches pci_devices[].slot directly. NVMe falls back to the controller's own sysfs link when the namespace resolves to a subsystem with no PCI parent; zvols and other virtual devices get None. Hardware page: - The disk dialog shows its storage controller (click through to the controller), and the PCI device dialog lists the disks attached to it. - Storage controllers in the PCI Devices list get a badge with the number of attached disks. Storage view: - /api/storage returns controller_slot on each physical disk plus a controllers map (slot -> class, vendor, device, subsystem name, driver), built from the cached `lspci -vmm` output so the Storage view doesn't need the heavier /api/hardware enumeration. - "Physical Disks & SMART Status" can group disks under their storage controller, with a header showing the controller name, slot, driver and disk count. Groups follow PCI slot order; disks with no resolvable controller are listed last. USB disks keep their own section. - The disk details dialog shows the storage controller on the Overview tab. - Grouping is opt-in via a new Storage Display card in Settings, stored per browser (localStorage) and synced across open views, following the existing nav-tab-order preference pattern. Off by default. Also: - Treat "IDE interface" as a storage controller, so SATA controllers left in BIOS IDE mode (e.g. Intel 7 Series, ata_piix) appear in pci_devices and disks behind them can be mapped. - lspci prints "Device xxxx" when it has no subsystem name; that placeholder is ignored so the chip name is shown instead. - Add the missing sdevice field to the PCIDevice type. Tests cover the sysfs path -> controller slot rule against real paths from two PVE 9 hosts (SAS HBA, AHCI SATA, SATA in BIOS IDE mode, USB behind a hub, NVMe behind chipset bridges), the non-zero PCI domain case (Intel VMD), the NVMe multipath fallback, and get_storage_controllers' lspci parsing including the "Device xxxx" placeholder and driver lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
import { useEffect, useState, useCallback } from "react"
|
|
|
|
// Per-browser display preference for the Storage view: whether the
|
|
// "Physical Disks & SMART Status" card groups disks under the storage
|
|
// controller they hang off. Off by default so the layout is unchanged
|
|
// until the user opts in from Settings.
|
|
|
|
const STORAGE_KEY = "proxmenux-group-disks-by-controller"
|
|
const CHANGE_EVENT = "proxmenux-group-disks-by-controller-changed"
|
|
|
|
export function readGroupDisksByController(): boolean {
|
|
if (typeof window === "undefined") return false
|
|
try {
|
|
return window.localStorage.getItem(STORAGE_KEY) === "true"
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export function writeGroupDisksByController(enabled: boolean): void {
|
|
if (typeof window === "undefined") return
|
|
try {
|
|
window.localStorage.setItem(STORAGE_KEY, String(enabled))
|
|
window.dispatchEvent(new CustomEvent(CHANGE_EVENT))
|
|
} catch {
|
|
// Storage full / disabled; the in-memory state still updates.
|
|
}
|
|
}
|
|
|
|
// Hook that keeps the Settings card and the Storage view in sync in the
|
|
// same tab (custom event) and across tabs (storage event).
|
|
export function useGroupDisksByController(): [boolean, (enabled: boolean) => void] {
|
|
const [enabled, setEnabledState] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setEnabledState(readGroupDisksByController())
|
|
const onChange = () => setEnabledState(readGroupDisksByController())
|
|
const onStorage = (e: StorageEvent) => {
|
|
if (e.key === STORAGE_KEY) onChange()
|
|
}
|
|
window.addEventListener(CHANGE_EVENT, onChange)
|
|
window.addEventListener("storage", onStorage)
|
|
return () => {
|
|
window.removeEventListener(CHANGE_EVENT, onChange)
|
|
window.removeEventListener("storage", onStorage)
|
|
}
|
|
}, [])
|
|
|
|
const setEnabled = useCallback((next: boolean) => {
|
|
writeGroupDisksByController(next)
|
|
setEnabledState(next)
|
|
}, [])
|
|
|
|
return [enabled, setEnabled]
|
|
}
|