173 lines
3.8 KiB
Go
173 lines
3.8 KiB
Go
package exporter
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"io"
|
|
"strings"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
// Exporter handles data export in various formats
|
|
type Exporter struct {
|
|
result *models.AnalysisResult
|
|
}
|
|
|
|
// New creates a new exporter
|
|
func New(result *models.AnalysisResult) *Exporter {
|
|
return &Exporter{result: result}
|
|
}
|
|
|
|
// ExportCSV exports serial numbers to CSV format
|
|
func (e *Exporter) ExportCSV(w io.Writer) error {
|
|
writer := csv.NewWriter(w)
|
|
defer writer.Flush()
|
|
|
|
// Header
|
|
if err := writer.Write([]string{"Component", "Serial Number", "Manufacturer", "Location"}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if e.result == nil {
|
|
return nil
|
|
}
|
|
|
|
// FRU data
|
|
for _, fru := range e.result.FRU {
|
|
if !hasUsableSerial(fru.SerialNumber) {
|
|
continue
|
|
}
|
|
name := fru.ProductName
|
|
if name == "" {
|
|
name = fru.Description
|
|
}
|
|
if err := writer.Write([]string{
|
|
name,
|
|
fru.SerialNumber,
|
|
fru.Manufacturer,
|
|
fru.PartNumber,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Hardware data
|
|
if e.result.Hardware != nil {
|
|
// Board
|
|
if hasUsableSerial(e.result.Hardware.BoardInfo.SerialNumber) {
|
|
if err := writer.Write([]string{
|
|
e.result.Hardware.BoardInfo.ProductName,
|
|
strings.TrimSpace(e.result.Hardware.BoardInfo.SerialNumber),
|
|
e.result.Hardware.BoardInfo.Manufacturer,
|
|
"Board",
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
seenCanonical := make(map[string]struct{})
|
|
for _, dev := range canonicalDevicesForExport(e.result.Hardware) {
|
|
if !hasUsableSerial(dev.SerialNumber) {
|
|
continue
|
|
}
|
|
serial := strings.TrimSpace(dev.SerialNumber)
|
|
seenCanonical[serial] = struct{}{}
|
|
component, manufacturer, location := csvFieldsFromCanonicalDevice(dev)
|
|
if err := writer.Write([]string{component, serial, manufacturer, location}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Legacy network cards
|
|
for _, nic := range e.result.Hardware.NetworkCards {
|
|
if !hasUsableSerial(nic.SerialNumber) {
|
|
continue
|
|
}
|
|
serial := strings.TrimSpace(nic.SerialNumber)
|
|
if _, ok := seenCanonical[serial]; ok {
|
|
continue
|
|
}
|
|
if err := writer.Write([]string{
|
|
nic.Model,
|
|
serial,
|
|
"",
|
|
"Network",
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExportJSON exports all data to JSON format
|
|
func (e *Exporter) ExportJSON(w io.Writer) error {
|
|
encoder := json.NewEncoder(w)
|
|
encoder.SetIndent("", " ")
|
|
return encoder.Encode(e.result)
|
|
}
|
|
|
|
func hasUsableSerial(serial string) bool {
|
|
s := strings.TrimSpace(serial)
|
|
if s == "" {
|
|
return false
|
|
}
|
|
switch strings.ToUpper(s) {
|
|
case "N/A", "NA", "NONE", "NULL", "UNKNOWN", "-":
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func csvFieldsFromCanonicalDevice(dev models.HardwareDevice) (component, manufacturer, location string) {
|
|
component = firstNonEmptyString(
|
|
dev.Model,
|
|
dev.PartNumber,
|
|
dev.DeviceClass,
|
|
dev.Kind,
|
|
)
|
|
manufacturer = firstNonEmptyString(dev.Manufacturer, inferCSVVendor(dev))
|
|
location = firstNonEmptyString(dev.Location, dev.Slot, dev.BDF, dev.Kind)
|
|
|
|
switch dev.Kind {
|
|
case models.DeviceKindCPU:
|
|
if component == "" {
|
|
component = "CPU"
|
|
}
|
|
if location == "" {
|
|
location = "CPU"
|
|
}
|
|
case models.DeviceKindMemory:
|
|
component = firstNonEmptyString(dev.PartNumber, dev.Model, "Memory")
|
|
case models.DeviceKindPCIe, models.DeviceKindGPU, models.DeviceKindNetwork:
|
|
if location == "" {
|
|
location = firstNonEmptyString(dev.Slot, dev.BDF, "PCIe")
|
|
}
|
|
case models.DeviceKindPSU:
|
|
component = firstNonEmptyString(dev.Model, "Power Supply")
|
|
}
|
|
|
|
return component, manufacturer, location
|
|
}
|
|
|
|
func inferCSVVendor(dev models.HardwareDevice) string {
|
|
switch dev.Kind {
|
|
case models.DeviceKindCPU:
|
|
return ""
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func firstNonEmptyString(values ...string) string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return strings.TrimSpace(value)
|
|
}
|
|
}
|
|
return ""
|
|
}
|