Update Inspur parsing and align release docs
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -27,20 +28,22 @@ func ConvertToReanimator(result *models.AnalysisResult) (*ReanimatorExport, erro
|
||||
// Determine target host (optional field)
|
||||
targetHost := inferTargetHost(result.TargetHost, result.Filename)
|
||||
|
||||
collectedAt := formatRFC3339(result.CollectedAt)
|
||||
|
||||
export := &ReanimatorExport{
|
||||
Filename: result.Filename,
|
||||
SourceType: normalizeSourceType(result.SourceType),
|
||||
Protocol: normalizeProtocol(result.Protocol),
|
||||
TargetHost: targetHost,
|
||||
CollectedAt: formatRFC3339(result.CollectedAt),
|
||||
CollectedAt: collectedAt,
|
||||
Hardware: ReanimatorHardware{
|
||||
Board: convertBoard(result.Hardware.BoardInfo),
|
||||
Firmware: convertFirmware(result.Hardware.Firmware),
|
||||
CPUs: convertCPUs(result.Hardware.CPUs),
|
||||
Memory: convertMemory(result.Hardware.Memory),
|
||||
Storage: convertStorage(result.Hardware.Storage),
|
||||
PCIeDevices: convertPCIeDevices(result.Hardware),
|
||||
PowerSupplies: convertPowerSupplies(result.Hardware.PowerSupply),
|
||||
Firmware: dedupeFirmware(convertFirmware(result.Hardware.Firmware)),
|
||||
CPUs: dedupeCPUs(convertCPUs(result.Hardware.CPUs, collectedAt)),
|
||||
Memory: dedupeMemory(convertMemory(result.Hardware.Memory, collectedAt)),
|
||||
Storage: dedupeStorage(convertStorage(result.Hardware.Storage, collectedAt)),
|
||||
PCIeDevices: dedupePCIe(convertPCIeDevices(result.Hardware, collectedAt)),
|
||||
PowerSupplies: dedupePSUs(convertPowerSupplies(result.Hardware.PowerSupply, collectedAt)),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,7 +86,7 @@ func convertFirmware(firmware []models.FirmwareInfo) []ReanimatorFirmware {
|
||||
}
|
||||
|
||||
// convertCPUs converts CPU information to Reanimator format
|
||||
func convertCPUs(cpus []models.CPU) []ReanimatorCPU {
|
||||
func convertCPUs(cpus []models.CPU, collectedAt string) []ReanimatorCPU {
|
||||
if len(cpus) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -92,22 +95,41 @@ func convertCPUs(cpus []models.CPU) []ReanimatorCPU {
|
||||
for _, cpu := range cpus {
|
||||
manufacturer := inferCPUManufacturer(cpu.Model)
|
||||
|
||||
cpuStatus := normalizeStatus(cpu.Status, false)
|
||||
if strings.TrimSpace(cpu.Status) == "" {
|
||||
cpuStatus = "Unknown"
|
||||
}
|
||||
meta := buildStatusMeta(
|
||||
cpuStatus,
|
||||
cpu.StatusCheckedAt,
|
||||
cpu.StatusChangedAt,
|
||||
cpu.StatusAtCollect,
|
||||
cpu.StatusHistory,
|
||||
cpu.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorCPU{
|
||||
Socket: cpu.Socket,
|
||||
Model: cpu.Model,
|
||||
Cores: cpu.Cores,
|
||||
Threads: cpu.Threads,
|
||||
FrequencyMHz: cpu.FrequencyMHz,
|
||||
MaxFrequencyMHz: cpu.MaxFreqMHz,
|
||||
Manufacturer: manufacturer,
|
||||
Status: "Unknown",
|
||||
Socket: cpu.Socket,
|
||||
Model: cpu.Model,
|
||||
Cores: cpu.Cores,
|
||||
Threads: cpu.Threads,
|
||||
FrequencyMHz: cpu.FrequencyMHz,
|
||||
MaxFrequencyMHz: cpu.MaxFreqMHz,
|
||||
Manufacturer: manufacturer,
|
||||
Status: cpuStatus,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// convertMemory converts memory modules to Reanimator format
|
||||
func convertMemory(memory []models.MemoryDIMM) []ReanimatorMemory {
|
||||
func convertMemory(memory []models.MemoryDIMM, collectedAt string) []ReanimatorMemory {
|
||||
if len(memory) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -123,25 +145,40 @@ func convertMemory(memory []models.MemoryDIMM) []ReanimatorMemory {
|
||||
}
|
||||
}
|
||||
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
mem.StatusCheckedAt,
|
||||
mem.StatusChangedAt,
|
||||
mem.StatusAtCollect,
|
||||
mem.StatusHistory,
|
||||
mem.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorMemory{
|
||||
Slot: mem.Slot,
|
||||
Location: mem.Location,
|
||||
Present: mem.Present,
|
||||
SizeMB: mem.SizeMB,
|
||||
Type: mem.Type,
|
||||
MaxSpeedMHz: mem.MaxSpeedMHz,
|
||||
CurrentSpeedMHz: mem.CurrentSpeedMHz,
|
||||
Manufacturer: mem.Manufacturer,
|
||||
SerialNumber: mem.SerialNumber,
|
||||
PartNumber: mem.PartNumber,
|
||||
Status: status,
|
||||
Slot: mem.Slot,
|
||||
Location: mem.Location,
|
||||
Present: mem.Present,
|
||||
SizeMB: mem.SizeMB,
|
||||
Type: mem.Type,
|
||||
MaxSpeedMHz: mem.MaxSpeedMHz,
|
||||
CurrentSpeedMHz: mem.CurrentSpeedMHz,
|
||||
Manufacturer: mem.Manufacturer,
|
||||
SerialNumber: mem.SerialNumber,
|
||||
PartNumber: mem.PartNumber,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// convertStorage converts storage devices to Reanimator format
|
||||
func convertStorage(storage []models.Storage) []ReanimatorStorage {
|
||||
func convertStorage(storage []models.Storage, collectedAt string) []ReanimatorStorage {
|
||||
if len(storage) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -154,29 +191,60 @@ func convertStorage(storage []models.Storage) []ReanimatorStorage {
|
||||
}
|
||||
|
||||
status := inferStorageStatus(stor)
|
||||
if strings.TrimSpace(stor.Status) != "" {
|
||||
status = normalizeStatus(stor.Status, false)
|
||||
}
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
stor.StatusCheckedAt,
|
||||
stor.StatusChangedAt,
|
||||
stor.StatusAtCollect,
|
||||
stor.StatusHistory,
|
||||
stor.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorStorage{
|
||||
Slot: stor.Slot,
|
||||
Type: stor.Type,
|
||||
Model: stor.Model,
|
||||
SizeGB: stor.SizeGB,
|
||||
SerialNumber: stor.SerialNumber,
|
||||
Manufacturer: stor.Manufacturer,
|
||||
Firmware: stor.Firmware,
|
||||
Interface: stor.Interface,
|
||||
Present: stor.Present,
|
||||
Status: status,
|
||||
Slot: stor.Slot,
|
||||
Type: stor.Type,
|
||||
Model: stor.Model,
|
||||
SizeGB: stor.SizeGB,
|
||||
SerialNumber: stor.SerialNumber,
|
||||
Manufacturer: stor.Manufacturer,
|
||||
Firmware: stor.Firmware,
|
||||
Interface: stor.Interface,
|
||||
Present: stor.Present,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// convertPCIeDevices converts PCIe devices, GPUs, and network adapters to Reanimator format
|
||||
func convertPCIeDevices(hw *models.HardwareConfig) []ReanimatorPCIe {
|
||||
func convertPCIeDevices(hw *models.HardwareConfig, collectedAt string) []ReanimatorPCIe {
|
||||
result := make([]ReanimatorPCIe, 0)
|
||||
gpuSlots := make(map[string]struct{}, len(hw.GPUs))
|
||||
for _, gpu := range hw.GPUs {
|
||||
slot := strings.ToLower(strings.TrimSpace(gpu.Slot))
|
||||
if slot != "" {
|
||||
gpuSlots[slot] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert regular PCIe devices
|
||||
for _, pcie := range hw.PCIeDevices {
|
||||
slot := strings.ToLower(strings.TrimSpace(pcie.Slot))
|
||||
if _, isDedicatedGPU := gpuSlots[slot]; isDedicatedGPU || isDisplayClass(pcie.DeviceClass) {
|
||||
// Skip GPU-like PCIe entries to avoid duplicates:
|
||||
// dedicated GPUs are exported from hw.GPUs with richer metadata.
|
||||
continue
|
||||
}
|
||||
|
||||
serialNumber := normalizedSerial(pcie.SerialNumber)
|
||||
|
||||
// Determine model (prefer PartNumber, fallback to DeviceClass)
|
||||
@@ -185,21 +253,37 @@ func convertPCIeDevices(hw *models.HardwareConfig) []ReanimatorPCIe {
|
||||
model = pcie.DeviceClass
|
||||
}
|
||||
|
||||
status := normalizeStatus(pcie.Status, false)
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
pcie.StatusCheckedAt,
|
||||
pcie.StatusChangedAt,
|
||||
pcie.StatusAtCollect,
|
||||
pcie.StatusHistory,
|
||||
pcie.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorPCIe{
|
||||
Slot: pcie.Slot,
|
||||
VendorID: pcie.VendorID,
|
||||
DeviceID: pcie.DeviceID,
|
||||
BDF: pcie.BDF,
|
||||
DeviceClass: pcie.DeviceClass,
|
||||
Manufacturer: pcie.Manufacturer,
|
||||
Model: model,
|
||||
LinkWidth: pcie.LinkWidth,
|
||||
LinkSpeed: pcie.LinkSpeed,
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcie.MaxLinkSpeed,
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: "", // PCIeDevice doesn't have firmware in models
|
||||
Status: "Unknown",
|
||||
Slot: pcie.Slot,
|
||||
VendorID: pcie.VendorID,
|
||||
DeviceID: pcie.DeviceID,
|
||||
BDF: pcie.BDF,
|
||||
DeviceClass: pcie.DeviceClass,
|
||||
Manufacturer: pcie.Manufacturer,
|
||||
Model: model,
|
||||
LinkWidth: pcie.LinkWidth,
|
||||
LinkSpeed: pcie.LinkSpeed,
|
||||
MaxLinkWidth: pcie.MaxLinkWidth,
|
||||
MaxLinkSpeed: pcie.MaxLinkSpeed,
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: "", // PCIeDevice doesn't have firmware in models
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,21 +294,37 @@ func convertPCIeDevices(hw *models.HardwareConfig) []ReanimatorPCIe {
|
||||
// Determine device class
|
||||
deviceClass := "DisplayController"
|
||||
|
||||
status := normalizeStatus(gpu.Status, false)
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
gpu.StatusCheckedAt,
|
||||
gpu.StatusChangedAt,
|
||||
gpu.StatusAtCollect,
|
||||
gpu.StatusHistory,
|
||||
gpu.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorPCIe{
|
||||
Slot: gpu.Slot,
|
||||
VendorID: gpu.VendorID,
|
||||
DeviceID: gpu.DeviceID,
|
||||
BDF: gpu.BDF,
|
||||
DeviceClass: deviceClass,
|
||||
Manufacturer: gpu.Manufacturer,
|
||||
Model: gpu.Model,
|
||||
LinkWidth: gpu.CurrentLinkWidth,
|
||||
LinkSpeed: gpu.CurrentLinkSpeed,
|
||||
MaxLinkWidth: gpu.MaxLinkWidth,
|
||||
MaxLinkSpeed: gpu.MaxLinkSpeed,
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: gpu.Firmware,
|
||||
Status: normalizeStatus(gpu.Status, false),
|
||||
Slot: gpu.Slot,
|
||||
VendorID: gpu.VendorID,
|
||||
DeviceID: gpu.DeviceID,
|
||||
BDF: gpu.BDF,
|
||||
DeviceClass: deviceClass,
|
||||
Manufacturer: gpu.Manufacturer,
|
||||
Model: gpu.Model,
|
||||
LinkWidth: gpu.CurrentLinkWidth,
|
||||
LinkSpeed: gpu.CurrentLinkSpeed,
|
||||
MaxLinkWidth: gpu.MaxLinkWidth,
|
||||
MaxLinkSpeed: gpu.MaxLinkSpeed,
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: gpu.Firmware,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -236,29 +336,52 @@ func convertPCIeDevices(hw *models.HardwareConfig) []ReanimatorPCIe {
|
||||
|
||||
serialNumber := normalizedSerial(nic.SerialNumber)
|
||||
|
||||
status := normalizeStatus(nic.Status, false)
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
nic.StatusCheckedAt,
|
||||
nic.StatusChangedAt,
|
||||
nic.StatusAtCollect,
|
||||
nic.StatusHistory,
|
||||
nic.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorPCIe{
|
||||
Slot: nic.Slot,
|
||||
VendorID: nic.VendorID,
|
||||
DeviceID: nic.DeviceID,
|
||||
BDF: "",
|
||||
DeviceClass: "NetworkController",
|
||||
Manufacturer: nic.Vendor,
|
||||
Model: nic.Model,
|
||||
LinkWidth: 0,
|
||||
LinkSpeed: "",
|
||||
MaxLinkWidth: 0,
|
||||
MaxLinkSpeed: "",
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: nic.Firmware,
|
||||
Status: normalizeStatus(nic.Status, false),
|
||||
Slot: nic.Slot,
|
||||
VendorID: nic.VendorID,
|
||||
DeviceID: nic.DeviceID,
|
||||
BDF: "",
|
||||
DeviceClass: "NetworkController",
|
||||
Manufacturer: nic.Vendor,
|
||||
Model: nic.Model,
|
||||
LinkWidth: 0,
|
||||
LinkSpeed: "",
|
||||
MaxLinkWidth: 0,
|
||||
MaxLinkSpeed: "",
|
||||
SerialNumber: serialNumber,
|
||||
Firmware: nic.Firmware,
|
||||
Status: status,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func isDisplayClass(deviceClass string) bool {
|
||||
class := strings.ToLower(strings.TrimSpace(deviceClass))
|
||||
return strings.Contains(class, "display") ||
|
||||
strings.Contains(class, "vga") ||
|
||||
strings.Contains(class, "3d controller")
|
||||
}
|
||||
|
||||
// convertPowerSupplies converts power supplies to Reanimator format
|
||||
func convertPowerSupplies(psus []models.PSU) []ReanimatorPSU {
|
||||
func convertPowerSupplies(psus []models.PSU, collectedAt string) []ReanimatorPSU {
|
||||
if len(psus) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -271,26 +394,291 @@ func convertPowerSupplies(psus []models.PSU) []ReanimatorPSU {
|
||||
}
|
||||
|
||||
status := normalizeStatus(psu.Status, false)
|
||||
meta := buildStatusMeta(
|
||||
status,
|
||||
psu.StatusCheckedAt,
|
||||
psu.StatusChangedAt,
|
||||
psu.StatusAtCollect,
|
||||
psu.StatusHistory,
|
||||
psu.ErrorDescription,
|
||||
collectedAt,
|
||||
)
|
||||
|
||||
result = append(result, ReanimatorPSU{
|
||||
Slot: psu.Slot,
|
||||
Present: psu.Present,
|
||||
Model: psu.Model,
|
||||
Vendor: psu.Vendor,
|
||||
WattageW: psu.WattageW,
|
||||
SerialNumber: psu.SerialNumber,
|
||||
PartNumber: psu.PartNumber,
|
||||
Firmware: psu.Firmware,
|
||||
Status: status,
|
||||
InputType: psu.InputType,
|
||||
InputPowerW: psu.InputPowerW,
|
||||
OutputPowerW: psu.OutputPowerW,
|
||||
InputVoltage: psu.InputVoltage,
|
||||
Slot: psu.Slot,
|
||||
Present: psu.Present,
|
||||
Model: psu.Model,
|
||||
Vendor: psu.Vendor,
|
||||
WattageW: psu.WattageW,
|
||||
SerialNumber: psu.SerialNumber,
|
||||
PartNumber: psu.PartNumber,
|
||||
Firmware: psu.Firmware,
|
||||
Status: status,
|
||||
InputType: psu.InputType,
|
||||
InputPowerW: psu.InputPowerW,
|
||||
OutputPowerW: psu.OutputPowerW,
|
||||
InputVoltage: psu.InputVoltage,
|
||||
StatusCheckedAt: meta.StatusCheckedAt,
|
||||
StatusChangedAt: meta.StatusChangedAt,
|
||||
StatusAtCollect: meta.StatusAtCollection,
|
||||
StatusHistory: meta.StatusHistory,
|
||||
ErrorDescription: meta.ErrorDescription,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type convertedStatusMeta struct {
|
||||
StatusCheckedAt string
|
||||
StatusChangedAt string
|
||||
StatusAtCollection *ReanimatorStatusAtCollection
|
||||
StatusHistory []ReanimatorStatusHistoryEntry
|
||||
ErrorDescription string
|
||||
}
|
||||
|
||||
func buildStatusMeta(
|
||||
currentStatus string,
|
||||
checkedAt time.Time,
|
||||
changedAt time.Time,
|
||||
statusAtCollection *models.StatusAtCollection,
|
||||
history []models.StatusHistoryEntry,
|
||||
errorDescription string,
|
||||
collectedAt string,
|
||||
) convertedStatusMeta {
|
||||
meta := convertedStatusMeta{
|
||||
StatusCheckedAt: formatOptionalRFC3339(checkedAt),
|
||||
StatusChangedAt: formatOptionalRFC3339(changedAt),
|
||||
ErrorDescription: strings.TrimSpace(errorDescription),
|
||||
}
|
||||
|
||||
convertedHistory := make([]ReanimatorStatusHistoryEntry, 0, len(history))
|
||||
for _, h := range history {
|
||||
changed := formatOptionalRFC3339(h.ChangedAt)
|
||||
if changed == "" {
|
||||
continue
|
||||
}
|
||||
convertedHistory = append(convertedHistory, ReanimatorStatusHistoryEntry{
|
||||
Status: normalizeStatus(h.Status, true),
|
||||
ChangedAt: changed,
|
||||
Details: strings.TrimSpace(h.Details),
|
||||
})
|
||||
}
|
||||
sort.Slice(convertedHistory, func(i, j int) bool {
|
||||
return convertedHistory[i].ChangedAt < convertedHistory[j].ChangedAt
|
||||
})
|
||||
if len(convertedHistory) > 0 {
|
||||
meta.StatusHistory = convertedHistory
|
||||
if meta.StatusChangedAt == "" {
|
||||
meta.StatusChangedAt = convertedHistory[len(convertedHistory)-1].ChangedAt
|
||||
}
|
||||
}
|
||||
|
||||
if statusAtCollection != nil {
|
||||
at := formatOptionalRFC3339(statusAtCollection.At)
|
||||
if at != "" && strings.TrimSpace(statusAtCollection.Status) != "" {
|
||||
meta.StatusAtCollection = &ReanimatorStatusAtCollection{
|
||||
Status: normalizeStatus(statusAtCollection.Status, true),
|
||||
At: at,
|
||||
}
|
||||
}
|
||||
}
|
||||
if meta.StatusAtCollection == nil && strings.TrimSpace(currentStatus) != "" && collectedAt != "" {
|
||||
meta.StatusAtCollection = &ReanimatorStatusAtCollection{
|
||||
Status: currentStatus,
|
||||
At: collectedAt,
|
||||
}
|
||||
}
|
||||
|
||||
if meta.StatusCheckedAt == "" && len(meta.StatusHistory) > 0 {
|
||||
meta.StatusCheckedAt = meta.StatusHistory[len(meta.StatusHistory)-1].ChangedAt
|
||||
}
|
||||
if meta.StatusCheckedAt == "" && strings.TrimSpace(currentStatus) != "" && collectedAt != "" {
|
||||
meta.StatusCheckedAt = collectedAt
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
|
||||
func formatOptionalRFC3339(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func dedupeFirmware(items []ReanimatorFirmware) []ReanimatorFirmware {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
result := make([]ReanimatorFirmware, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := strings.ToLower(strings.TrimSpace(item.DeviceName))
|
||||
if key == "" {
|
||||
key = strings.ToLower(strings.TrimSpace(item.Version))
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dedupeCPUs(items []ReanimatorCPU) []ReanimatorCPU {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
seen := make(map[int]struct{}, len(items))
|
||||
result := make([]ReanimatorCPU, 0, len(items))
|
||||
for _, item := range items {
|
||||
if _, ok := seen[item.Socket]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item.Socket] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dedupeMemory(items []ReanimatorMemory) []ReanimatorMemory {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
result := make([]ReanimatorMemory, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := strings.ToLower(strings.TrimSpace(item.Slot))
|
||||
if key == "" {
|
||||
key = strings.ToLower(strings.TrimSpace(item.Location))
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dedupeStorage(items []ReanimatorStorage) []ReanimatorStorage {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
result := make([]ReanimatorStorage, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := strings.ToLower(strings.TrimSpace(item.SerialNumber))
|
||||
if key == "" {
|
||||
key = "slot:" + strings.ToLower(strings.TrimSpace(item.Slot))
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dedupePSUs(items []ReanimatorPSU) []ReanimatorPSU {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
result := make([]ReanimatorPSU, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := strings.ToLower(strings.TrimSpace(item.SerialNumber))
|
||||
if key == "" {
|
||||
key = "slot:" + strings.ToLower(strings.TrimSpace(item.Slot))
|
||||
}
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result = append(result, item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func dedupePCIe(items []ReanimatorPCIe) []ReanimatorPCIe {
|
||||
if len(items) < 2 {
|
||||
return items
|
||||
}
|
||||
type scored struct {
|
||||
item ReanimatorPCIe
|
||||
score int
|
||||
idx int
|
||||
}
|
||||
byKey := make(map[string]scored, len(items))
|
||||
order := make([]string, 0, len(items))
|
||||
for i, item := range items {
|
||||
key := pcieDedupKey(item)
|
||||
curr := scored{item: item, score: pcieQualityScore(item), idx: i}
|
||||
existing, ok := byKey[key]
|
||||
if !ok {
|
||||
byKey[key] = curr
|
||||
order = append(order, key)
|
||||
continue
|
||||
}
|
||||
if curr.score > existing.score {
|
||||
byKey[key] = curr
|
||||
}
|
||||
}
|
||||
result := make([]ReanimatorPCIe, 0, len(byKey))
|
||||
for _, key := range order {
|
||||
result = append(result, byKey[key].item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func pcieDedupKey(item ReanimatorPCIe) string {
|
||||
slot := strings.ToLower(strings.TrimSpace(item.Slot))
|
||||
serial := strings.ToLower(strings.TrimSpace(item.SerialNumber))
|
||||
bdf := strings.ToLower(strings.TrimSpace(item.BDF))
|
||||
if slot != "" {
|
||||
return "slot:" + slot
|
||||
}
|
||||
if serial != "" {
|
||||
return "sn:" + serial
|
||||
}
|
||||
if bdf != "" {
|
||||
return "bdf:" + bdf
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(item.DeviceClass)) + "|" + strings.ToLower(strings.TrimSpace(item.Model))
|
||||
}
|
||||
|
||||
func pcieQualityScore(item ReanimatorPCIe) int {
|
||||
score := 0
|
||||
if strings.TrimSpace(item.SerialNumber) != "" {
|
||||
score += 4
|
||||
}
|
||||
if strings.TrimSpace(item.Model) != "" && !isGenericPCIeModel(item.Model) {
|
||||
score += 3
|
||||
}
|
||||
status := strings.ToLower(strings.TrimSpace(item.Status))
|
||||
if status == "ok" || status == "warning" || status == "critical" {
|
||||
score += 2
|
||||
}
|
||||
if strings.TrimSpace(item.BDF) != "" {
|
||||
score++
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(item.DeviceClass), "DisplayController") {
|
||||
score++
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func isGenericPCIeModel(model string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(model)) {
|
||||
case "", "unknown", "vga", "3d controller", "display controller":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// inferCPUManufacturer determines CPU manufacturer from model string
|
||||
func inferCPUManufacturer(model string) string {
|
||||
upper := strings.ToUpper(model)
|
||||
|
||||
@@ -210,7 +210,7 @@ func TestConvertCPUs(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertCPUs(cpus)
|
||||
result := convertCPUs(cpus, "2026-02-10T15:30:00Z")
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 CPUs, got %d", len(result))
|
||||
@@ -245,7 +245,7 @@ func TestConvertMemory(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertMemory(memory)
|
||||
result := convertMemory(memory, "2026-02-10T15:30:00Z")
|
||||
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 memory modules, got %d", len(result))
|
||||
@@ -278,7 +278,7 @@ func TestConvertStorage(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertStorage(storage)
|
||||
result := convertStorage(storage, "2026-02-10T15:30:00Z")
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 storage device (skipped one without serial), got %d", len(result))
|
||||
@@ -329,7 +329,7 @@ func TestConvertPCIeDevices(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertPCIeDevices(hw)
|
||||
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
|
||||
|
||||
// Should have: 2 PCIe devices + 1 GPU + 1 NIC = 4 total
|
||||
if len(result) != 4 {
|
||||
@@ -369,7 +369,7 @@ func TestConvertPCIeDevices_NVSwitchWithoutSerialRemainsEmpty(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertPCIeDevices(hw)
|
||||
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 PCIe device, got %d", len(result))
|
||||
@@ -380,6 +380,74 @@ func TestConvertPCIeDevices_NVSwitchWithoutSerialRemainsEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPCIeDevices_SkipsDisplayControllerDuplicates(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{
|
||||
Slot: "#GPU0",
|
||||
DeviceClass: "3D Controller",
|
||||
},
|
||||
},
|
||||
GPUs: []models.GPU{
|
||||
{
|
||||
Slot: "#GPU0",
|
||||
Model: "B200 180GB HBM3e",
|
||||
Manufacturer: "NVIDIA",
|
||||
SerialNumber: "1655024043371",
|
||||
Status: "OK",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected only dedicated GPU record without duplicate display PCIe, got %d", len(result))
|
||||
}
|
||||
if result[0].DeviceClass != "DisplayController" {
|
||||
t.Fatalf("expected GPU record with DisplayController class, got %q", result[0].DeviceClass)
|
||||
}
|
||||
if result[0].Status != "OK" {
|
||||
t.Fatalf("expected GPU status OK, got %q", result[0].Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPCIeDevices_MapsGPUStatusHistory(t *testing.T) {
|
||||
hw := &models.HardwareConfig{
|
||||
GPUs: []models.GPU{
|
||||
{
|
||||
Slot: "#GPU6",
|
||||
Model: "B200 180GB HBM3e",
|
||||
Manufacturer: "NVIDIA",
|
||||
SerialNumber: "1655024043204",
|
||||
Status: "Critical",
|
||||
StatusHistory: []models.StatusHistoryEntry{
|
||||
{
|
||||
Status: "Critical",
|
||||
ChangedAt: time.Date(2026, 1, 12, 15, 5, 18, 0, time.UTC),
|
||||
Details: "BIOS miss F_GPU6",
|
||||
},
|
||||
},
|
||||
ErrorDescription: "BIOS miss F_GPU6",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 converted GPU, got %d", len(result))
|
||||
}
|
||||
|
||||
if len(result[0].StatusHistory) != 1 {
|
||||
t.Fatalf("expected 1 history entry, got %d", len(result[0].StatusHistory))
|
||||
}
|
||||
if result[0].StatusHistory[0].ChangedAt != "2026-01-12T15:05:18Z" {
|
||||
t.Fatalf("unexpected history changed_at: %q", result[0].StatusHistory[0].ChangedAt)
|
||||
}
|
||||
if result[0].StatusAtCollect == nil || result[0].StatusAtCollect.At != "2026-02-10T15:30:00Z" {
|
||||
t.Fatalf("expected status_at_collection to be populated from collected_at")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertPowerSupplies(t *testing.T) {
|
||||
psus := []models.PSU{
|
||||
{
|
||||
@@ -398,7 +466,7 @@ func TestConvertPowerSupplies(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
result := convertPowerSupplies(psus)
|
||||
result := convertPowerSupplies(psus, "2026-02-10T15:30:00Z")
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 PSU (skipped empty), got %d", len(result))
|
||||
@@ -506,3 +574,75 @@ func TestInferTargetHost(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
|
||||
input := &models.AnalysisResult{
|
||||
Filename: "dup-test.json",
|
||||
CollectedAt: time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC),
|
||||
Hardware: &models.HardwareConfig{
|
||||
BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
|
||||
Firmware: []models.FirmwareInfo{
|
||||
{DeviceName: "BMC", Version: "1.0"},
|
||||
{DeviceName: "BMC", Version: "1.1"},
|
||||
},
|
||||
CPUs: []models.CPU{
|
||||
{Socket: 0, Model: "CPU-A"},
|
||||
{Socket: 0, Model: "CPU-A-DUP"},
|
||||
},
|
||||
Memory: []models.MemoryDIMM{
|
||||
{Slot: "DIMM_A1", Present: true, SerialNumber: "MEM-1", Status: "OK"},
|
||||
{Slot: "DIMM_A1", Present: true, SerialNumber: "MEM-1-DUP", Status: "OK"},
|
||||
},
|
||||
Storage: []models.Storage{
|
||||
{Slot: "U.2-1", SerialNumber: "SSD-1", Model: "Disk1", Present: true},
|
||||
{Slot: "U.2-2", SerialNumber: "SSD-1", Model: "Disk1-dup", Present: true},
|
||||
},
|
||||
PCIeDevices: []models.PCIeDevice{
|
||||
{Slot: "#GPU0", DeviceClass: "3D Controller", BDF: "17:00.0"},
|
||||
{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.0"},
|
||||
{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.1"},
|
||||
},
|
||||
GPUs: []models.GPU{
|
||||
{Slot: "#GPU0", Model: "B200 180GB HBM3e", SerialNumber: "GPU-1", Status: "OK"},
|
||||
},
|
||||
PowerSupply: []models.PSU{
|
||||
{Slot: "0", Present: true, SerialNumber: "PSU-1", Status: "OK"},
|
||||
{Slot: "1", Present: true, SerialNumber: "PSU-1", Status: "OK"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
out, err := ConvertToReanimator(input)
|
||||
if err != nil {
|
||||
t.Fatalf("ConvertToReanimator() failed: %v", err)
|
||||
}
|
||||
|
||||
if len(out.Hardware.Firmware) != 1 {
|
||||
t.Fatalf("expected deduped firmware len=1, got %d", len(out.Hardware.Firmware))
|
||||
}
|
||||
if len(out.Hardware.CPUs) != 1 {
|
||||
t.Fatalf("expected deduped cpus len=1, got %d", len(out.Hardware.CPUs))
|
||||
}
|
||||
if len(out.Hardware.Memory) != 1 {
|
||||
t.Fatalf("expected deduped memory len=1, got %d", len(out.Hardware.Memory))
|
||||
}
|
||||
if len(out.Hardware.Storage) != 1 {
|
||||
t.Fatalf("expected deduped storage len=1, got %d", len(out.Hardware.Storage))
|
||||
}
|
||||
if len(out.Hardware.PowerSupplies) != 1 {
|
||||
t.Fatalf("expected deduped psu len=1, got %d", len(out.Hardware.PowerSupplies))
|
||||
}
|
||||
if len(out.Hardware.PCIeDevices) != 2 {
|
||||
t.Fatalf("expected deduped pcie len=2 (gpu+nic), got %d", len(out.Hardware.PCIeDevices))
|
||||
}
|
||||
|
||||
gpuCount := 0
|
||||
for _, dev := range out.Hardware.PCIeDevices {
|
||||
if dev.Slot == "#GPU0" {
|
||||
gpuCount++
|
||||
}
|
||||
}
|
||||
if gpuCount != 1 {
|
||||
t.Fatalf("expected single #GPU0 record, got %d", gpuCount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,78 +36,114 @@ type ReanimatorFirmware struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type ReanimatorStatusAtCollection struct {
|
||||
Status string `json:"status"`
|
||||
At string `json:"at"`
|
||||
}
|
||||
|
||||
type ReanimatorStatusHistoryEntry struct {
|
||||
Status string `json:"status"`
|
||||
ChangedAt string `json:"changed_at"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// ReanimatorCPU represents processor information
|
||||
type ReanimatorCPU struct {
|
||||
Socket int `json:"socket"`
|
||||
Model string `json:"model"`
|
||||
Cores int `json:"cores,omitempty"`
|
||||
Threads int `json:"threads,omitempty"`
|
||||
FrequencyMHz int `json:"frequency_mhz,omitempty"`
|
||||
MaxFrequencyMHz int `json:"max_frequency_mhz,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Socket int `json:"socket"`
|
||||
Model string `json:"model"`
|
||||
Cores int `json:"cores,omitempty"`
|
||||
Threads int `json:"threads,omitempty"`
|
||||
FrequencyMHz int `json:"frequency_mhz,omitempty"`
|
||||
MaxFrequencyMHz int `json:"max_frequency_mhz,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusCheckedAt string `json:"status_checked_at,omitempty"`
|
||||
StatusChangedAt string `json:"status_changed_at,omitempty"`
|
||||
StatusAtCollect *ReanimatorStatusAtCollection `json:"status_at_collection,omitempty"`
|
||||
StatusHistory []ReanimatorStatusHistoryEntry `json:"status_history,omitempty"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
// ReanimatorMemory represents a memory module (DIMM)
|
||||
type ReanimatorMemory struct {
|
||||
Slot string `json:"slot"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Present bool `json:"present"`
|
||||
SizeMB int `json:"size_mb,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
MaxSpeedMHz int `json:"max_speed_mhz,omitempty"`
|
||||
CurrentSpeedMHz int `json:"current_speed_mhz,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
PartNumber string `json:"part_number,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Slot string `json:"slot"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Present bool `json:"present"`
|
||||
SizeMB int `json:"size_mb,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
MaxSpeedMHz int `json:"max_speed_mhz,omitempty"`
|
||||
CurrentSpeedMHz int `json:"current_speed_mhz,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
PartNumber string `json:"part_number,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusCheckedAt string `json:"status_checked_at,omitempty"`
|
||||
StatusChangedAt string `json:"status_changed_at,omitempty"`
|
||||
StatusAtCollect *ReanimatorStatusAtCollection `json:"status_at_collection,omitempty"`
|
||||
StatusHistory []ReanimatorStatusHistoryEntry `json:"status_history,omitempty"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
// ReanimatorStorage represents a storage device
|
||||
type ReanimatorStorage struct {
|
||||
Slot string `json:"slot"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Model string `json:"model"`
|
||||
SizeGB int `json:"size_gb,omitempty"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Interface string `json:"interface,omitempty"`
|
||||
Present bool `json:"present"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Slot string `json:"slot"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Model string `json:"model"`
|
||||
SizeGB int `json:"size_gb,omitempty"`
|
||||
SerialNumber string `json:"serial_number"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Interface string `json:"interface,omitempty"`
|
||||
Present bool `json:"present"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusCheckedAt string `json:"status_checked_at,omitempty"`
|
||||
StatusChangedAt string `json:"status_changed_at,omitempty"`
|
||||
StatusAtCollect *ReanimatorStatusAtCollection `json:"status_at_collection,omitempty"`
|
||||
StatusHistory []ReanimatorStatusHistoryEntry `json:"status_history,omitempty"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
// ReanimatorPCIe represents a PCIe device
|
||||
type ReanimatorPCIe struct {
|
||||
Slot string `json:"slot"`
|
||||
VendorID int `json:"vendor_id,omitempty"`
|
||||
DeviceID int `json:"device_id,omitempty"`
|
||||
BDF string `json:"bdf,omitempty"`
|
||||
DeviceClass string `json:"device_class,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
LinkWidth int `json:"link_width,omitempty"`
|
||||
LinkSpeed string `json:"link_speed,omitempty"`
|
||||
MaxLinkWidth int `json:"max_link_width,omitempty"`
|
||||
MaxLinkSpeed string `json:"max_link_speed,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Slot string `json:"slot"`
|
||||
VendorID int `json:"vendor_id,omitempty"`
|
||||
DeviceID int `json:"device_id,omitempty"`
|
||||
BDF string `json:"bdf,omitempty"`
|
||||
DeviceClass string `json:"device_class,omitempty"`
|
||||
Manufacturer string `json:"manufacturer,omitempty"`
|
||||
Model string `json:"model,omitempty"`
|
||||
LinkWidth int `json:"link_width,omitempty"`
|
||||
LinkSpeed string `json:"link_speed,omitempty"`
|
||||
MaxLinkWidth int `json:"max_link_width,omitempty"`
|
||||
MaxLinkSpeed string `json:"max_link_speed,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
StatusCheckedAt string `json:"status_checked_at,omitempty"`
|
||||
StatusChangedAt string `json:"status_changed_at,omitempty"`
|
||||
StatusAtCollect *ReanimatorStatusAtCollection `json:"status_at_collection,omitempty"`
|
||||
StatusHistory []ReanimatorStatusHistoryEntry `json:"status_history,omitempty"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
// ReanimatorPSU represents a power supply unit
|
||||
type ReanimatorPSU struct {
|
||||
Slot string `json:"slot"`
|
||||
Present bool `json:"present"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
WattageW int `json:"wattage_w,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
PartNumber string `json:"part_number,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
InputType string `json:"input_type,omitempty"`
|
||||
InputPowerW int `json:"input_power_w,omitempty"`
|
||||
OutputPowerW int `json:"output_power_w,omitempty"`
|
||||
InputVoltage float64 `json:"input_voltage,omitempty"`
|
||||
Slot string `json:"slot"`
|
||||
Present bool `json:"present"`
|
||||
Model string `json:"model,omitempty"`
|
||||
Vendor string `json:"vendor,omitempty"`
|
||||
WattageW int `json:"wattage_w,omitempty"`
|
||||
SerialNumber string `json:"serial_number,omitempty"`
|
||||
PartNumber string `json:"part_number,omitempty"`
|
||||
Firmware string `json:"firmware,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
InputType string `json:"input_type,omitempty"`
|
||||
InputPowerW int `json:"input_power_w,omitempty"`
|
||||
OutputPowerW int `json:"output_power_w,omitempty"`
|
||||
InputVoltage float64 `json:"input_voltage,omitempty"`
|
||||
StatusCheckedAt string `json:"status_checked_at,omitempty"`
|
||||
StatusChangedAt string `json:"status_changed_at,omitempty"`
|
||||
StatusAtCollect *ReanimatorStatusAtCollection `json:"status_at_collection,omitempty"`
|
||||
StatusHistory []ReanimatorStatusHistoryEntry `json:"status_history,omitempty"`
|
||||
ErrorDescription string `json:"error_description,omitempty"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user