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 } } // CPUs for _, cpu := range e.result.Hardware.CPUs { if !hasUsableSerial(cpu.SerialNumber) { continue } if err := writer.Write([]string{ cpu.Model, strings.TrimSpace(cpu.SerialNumber), "", "CPU", }); err != nil { return err } } // Memory for _, mem := range e.result.Hardware.Memory { if !hasUsableSerial(mem.SerialNumber) { continue } location := mem.Location if location == "" { location = mem.Slot } if err := writer.Write([]string{ mem.PartNumber, strings.TrimSpace(mem.SerialNumber), mem.Manufacturer, location, }); err != nil { return err } } // Storage for _, stor := range e.result.Hardware.Storage { if !hasUsableSerial(stor.SerialNumber) { continue } if err := writer.Write([]string{ stor.Model, strings.TrimSpace(stor.SerialNumber), stor.Manufacturer, stor.Slot, }); err != nil { return err } } // GPUs for _, gpu := range e.result.Hardware.GPUs { if !hasUsableSerial(gpu.SerialNumber) { continue } component := gpu.Model if component == "" { component = "GPU" } if err := writer.Write([]string{ component, strings.TrimSpace(gpu.SerialNumber), gpu.Manufacturer, gpu.Slot, }); err != nil { return err } } // PCIe devices for _, pcie := range e.result.Hardware.PCIeDevices { if !hasUsableSerial(pcie.SerialNumber) { continue } if err := writer.Write([]string{ pcie.DeviceClass, strings.TrimSpace(pcie.SerialNumber), pcie.Manufacturer, pcie.Slot, }); err != nil { return err } } // Network adapters for _, nic := range e.result.Hardware.NetworkAdapters { if !hasUsableSerial(nic.SerialNumber) { continue } location := nic.Location if location == "" { location = nic.Slot } if err := writer.Write([]string{ nic.Model, strings.TrimSpace(nic.SerialNumber), nic.Vendor, location, }); err != nil { return err } } // Legacy network cards for _, nic := range e.result.Hardware.NetworkCards { if !hasUsableSerial(nic.SerialNumber) { continue } if err := writer.Write([]string{ nic.Model, strings.TrimSpace(nic.SerialNumber), "", "Network", }); err != nil { return err } } // Power supplies for _, psu := range e.result.Hardware.PowerSupply { if !hasUsableSerial(psu.SerialNumber) { continue } if err := writer.Write([]string{ psu.Model, strings.TrimSpace(psu.SerialNumber), psu.Vendor, psu.Slot, }); 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 } }