package exporter import ( "encoding/csv" "encoding/json" "fmt" "io" "text/tabwriter" "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 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 { // Memory for _, mem := range e.result.Hardware.Memory { if mem.SerialNumber == "" { continue } location := mem.Location if location == "" { location = mem.Slot } if err := writer.Write([]string{ mem.PartNumber, mem.SerialNumber, mem.Manufacturer, location, }); err != nil { return err } } // Storage for _, stor := range e.result.Hardware.Storage { if stor.SerialNumber == "" { continue } if err := writer.Write([]string{ stor.Model, stor.SerialNumber, stor.Manufacturer, stor.Slot, }); err != nil { return err } } // PCIe devices for _, pcie := range e.result.Hardware.PCIeDevices { if pcie.SerialNumber == "" { continue } if err := writer.Write([]string{ pcie.DeviceClass, pcie.SerialNumber, pcie.Manufacturer, pcie.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) } // ExportTXT exports a human-readable text report func (e *Exporter) ExportTXT(w io.Writer) error { fmt.Fprintln(w, "LOGPile Analysis Report - mchus.pro") fmt.Fprintln(w, "====================================") fmt.Fprintln(w) if e.result == nil { fmt.Fprintln(w, "No data loaded.") return nil } fmt.Fprintf(w, "File:\t%s\n", e.result.Filename) fmt.Fprintf(w, "Source:\t%s\n", e.result.SourceType) fmt.Fprintf(w, "Protocol:\t%s\n", e.result.Protocol) fmt.Fprintf(w, "Target:\t%s\n", e.result.TargetHost) fmt.Fprintln(w) // Server model and serial number if e.result.Hardware != nil && e.result.Hardware.BoardInfo.ProductName != "" { fmt.Fprintf(w, "Server Model:\t%s\n", e.result.Hardware.BoardInfo.ProductName) fmt.Fprintf(w, "Serial Number:\t%s\n", e.result.Hardware.BoardInfo.SerialNumber) } fmt.Fprintln(w) // Hardware summary if e.result.Hardware != nil { hw := e.result.Hardware // Firmware tab if len(hw.Firmware) > 0 { fmt.Fprintln(w, "FIRMWARE VERSIONS") fmt.Fprintln(w, "-----------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Component\tVersion\tBuild Time") for _, fw := range hw.Firmware { fmt.Fprintf(tw, "%s\t%s\t%s\n", fw.DeviceName, fw.Version, fw.BuildTime) } _ = tw.Flush() fmt.Fprintln(w) } // CPU tab if len(hw.CPUs) > 0 { fmt.Fprintln(w, "PROCESSORS") fmt.Fprintln(w, "----------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Socket\tModel\tCores\tThreads\tFreq MHz\tTurbo MHz\tTDP W\tPPIN/SN") for _, cpu := range hw.CPUs { id := cpu.SerialNumber if id == "" { id = cpu.PPIN } fmt.Fprintf(tw, "CPU%d\t%s\t%d\t%d\t%d\t%d\t%d\t%s\n", cpu.Socket, cpu.Model, cpu.Cores, cpu.Threads, cpu.FrequencyMHz, cpu.MaxFreqMHz, cpu.TDP, id) } _ = tw.Flush() fmt.Fprintln(w) } // Memory tab if len(hw.Memory) > 0 { fmt.Fprintln(w, "MEMORY") fmt.Fprintln(w, "------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tPresent\tSize MB\tType\tSpeed MHz\tVendor\tModel/PN\tSerial\tStatus") for _, mem := range hw.Memory { location := mem.Location if location == "" { location = mem.Slot } fmt.Fprintf(tw, "%s\t%t\t%d\t%s\t%d\t%s\t%s\t%s\t%s\n", location, mem.Present, mem.SizeMB, mem.Type, mem.CurrentSpeedMHz, mem.Manufacturer, mem.PartNumber, mem.SerialNumber, mem.Status) } _ = tw.Flush() fmt.Fprintln(w) } // Power tab if len(hw.PowerSupply) > 0 { fmt.Fprintln(w, "POWER SUPPLIES") fmt.Fprintln(w, "--------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tPresent\tVendor\tModel\tWattage W\tInput W\tOutput W\tInput V\tTemp C\tStatus\tSerial") for _, psu := range hw.PowerSupply { fmt.Fprintf(tw, "%s\t%t\t%s\t%s\t%d\t%d\t%d\t%.0f\t%d\t%s\t%s\n", psu.Slot, psu.Present, psu.Vendor, psu.Model, psu.WattageW, psu.InputPowerW, psu.OutputPowerW, psu.InputVoltage, psu.TemperatureC, psu.Status, psu.SerialNumber) } _ = tw.Flush() fmt.Fprintln(w) } // Storage tab if len(hw.Storage) > 0 { fmt.Fprintln(w, "STORAGE") fmt.Fprintln(w, "-------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tPresent\tType\tInterface\tModel\tSize GB\tVendor\tFirmware\tSerial") for _, stor := range hw.Storage { fmt.Fprintf(tw, "%s\t%t\t%s\t%s\t%s\t%d\t%s\t%s\t%s\n", stor.Slot, stor.Present, stor.Type, stor.Interface, stor.Model, stor.SizeGB, stor.Manufacturer, stor.Firmware, stor.SerialNumber) } _ = tw.Flush() fmt.Fprintln(w) } // GPU tab if len(hw.GPUs) > 0 { fmt.Fprintln(w, "GPUS") fmt.Fprintln(w, "----") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tModel\tVendor\tBDF\tPCIe\tSerial\tStatus") for _, gpu := range hw.GPUs { link := fmt.Sprintf("x%d %s", gpu.CurrentLinkWidth, gpu.CurrentLinkSpeed) if gpu.MaxLinkWidth > 0 || gpu.MaxLinkSpeed != "" { link = fmt.Sprintf("%s / x%d %s", link, gpu.MaxLinkWidth, gpu.MaxLinkSpeed) } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", gpu.Slot, gpu.Model, gpu.Manufacturer, gpu.BDF, link, gpu.SerialNumber, gpu.Status) } _ = tw.Flush() fmt.Fprintln(w) } // Network tab if len(hw.NetworkAdapters) > 0 { fmt.Fprintln(w, "NETWORK ADAPTERS") fmt.Fprintln(w, "----------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tLocation\tModel\tVendor\tPorts\tType\tStatus\tSerial") for _, nic := range hw.NetworkAdapters { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%d\t%s\t%s\t%s\n", nic.Slot, nic.Location, nic.Model, nic.Vendor, nic.PortCount, nic.PortType, nic.Status, nic.SerialNumber) } _ = tw.Flush() fmt.Fprintln(w) } // Device inventory tab if len(hw.PCIeDevices) > 0 { fmt.Fprintln(w, "PCIE DEVICES") fmt.Fprintln(w, "------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Slot\tBDF\tClass\tVendor\tVID:DID\tLink\tSerial") for _, pcie := range hw.PCIeDevices { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%04x:%04x\tx%d %s / x%d %s\t%s\n", pcie.Slot, pcie.BDF, pcie.DeviceClass, pcie.Manufacturer, pcie.VendorID, pcie.DeviceID, pcie.LinkWidth, pcie.LinkSpeed, pcie.MaxLinkWidth, pcie.MaxLinkSpeed, pcie.SerialNumber) } _ = tw.Flush() fmt.Fprintln(w) } } // Sensors tab if len(e.result.Sensors) > 0 { fmt.Fprintln(w, "SENSOR READINGS") fmt.Fprintln(w, "---------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Type\tName\tValue\tUnit\tRaw\tStatus") for _, s := range e.result.Sensors { fmt.Fprintf(tw, "%s\t%s\t%.0f\t%s\t%s\t%s\n", s.Type, s.Name, s.Value, s.Unit, s.RawValue, s.Status) } _ = tw.Flush() fmt.Fprintln(w) } // Serials/FRU tab if len(e.result.FRU) > 0 { fmt.Fprintln(w, "FRU COMPONENTS") fmt.Fprintln(w, "--------------") tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Description\tManufacturer\tProduct\tSerial\tPart Number") for _, fru := range e.result.FRU { name := fru.ProductName if name == "" { name = fru.Description } fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", fru.Description, fru.Manufacturer, name, fru.SerialNumber, fru.PartNumber) } _ = tw.Flush() fmt.Fprintln(w) } // Events tab fmt.Fprintf(w, "EVENTS: %d total\n", len(e.result.Events)) if len(e.result.Events) > 0 { tw := tabwriter.NewWriter(w, 0, 0, 2, ' ', 0) fmt.Fprintln(tw, "Time\tSeverity\tSource\tType\tName\tDescription") for _, ev := range e.result.Events { fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", ev.Timestamp.Format("2006-01-02 15:04:05"), ev.Severity, ev.Source, ev.SensorType, ev.SensorName, ev.Description) } _ = tw.Flush() } var critical, warning, info int for _, ev := range e.result.Events { switch ev.Severity { case models.SeverityCritical: critical++ case models.SeverityWarning: warning++ case models.SeverityInfo: info++ } } fmt.Fprintf(w, " Critical: %d\n", critical) fmt.Fprintf(w, " Warning: %d\n", warning) fmt.Fprintf(w, " Info: %d\n", info) // Footer fmt.Fprintln(w) fmt.Fprintln(w, "------------------------------------") fmt.Fprintln(w, "Generated by LOGPile - mchus.pro") fmt.Fprintln(w, "https://git.mchus.pro/mchus/logpile") return nil }