Files
logpile/internal/exporter/exporter.go
Michael Chus c7422e95aa v1.1.0: Parser versioning, server info, auto-browser, section overviews
- Add parser versioning with Version() method and version display on main screen
- Add server model and serial number to Configuration tab and TXT export
- Add auto-browser opening on startup with --no-browser flag
- Add Restart and Exit buttons with graceful shutdown
- Add section overview stats (CPU, Power, Storage, GPU, Network)
- Change PCIe Link display to "x16 PCIe Gen4" format
- Add Location column to Serials section
- Extract BoardInfo from FRU and PlatformId from ThermalConfig

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 13:49:43 +03:00

277 lines
6.5 KiB
Go

package exporter
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"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: %s\n", e.result.Filename)
// Server model and serial number
if e.result.Hardware != nil && e.result.Hardware.BoardInfo.ProductName != "" {
fmt.Fprintln(w)
fmt.Fprintf(w, "Server Model: %s\n", e.result.Hardware.BoardInfo.ProductName)
fmt.Fprintf(w, "Serial Number: %s\n", e.result.Hardware.BoardInfo.SerialNumber)
}
fmt.Fprintln(w)
// Hardware summary
if e.result.Hardware != nil {
hw := e.result.Hardware
// Firmware
if len(hw.Firmware) > 0 {
fmt.Fprintln(w, "FIRMWARE VERSIONS")
fmt.Fprintln(w, "-----------------")
for _, fw := range hw.Firmware {
fmt.Fprintf(w, " %s: %s\n", fw.DeviceName, fw.Version)
}
fmt.Fprintln(w)
}
// CPUs
if len(hw.CPUs) > 0 {
fmt.Fprintln(w, "PROCESSORS")
fmt.Fprintln(w, "----------")
for _, cpu := range hw.CPUs {
fmt.Fprintf(w, " Socket %d: %s\n", cpu.Socket, cpu.Model)
fmt.Fprintf(w, " Cores: %d, Threads: %d, Freq: %d MHz (Turbo: %d MHz)\n",
cpu.Cores, cpu.Threads, cpu.FrequencyMHz, cpu.MaxFreqMHz)
fmt.Fprintf(w, " TDP: %dW, L3 Cache: %d KB\n", cpu.TDP, cpu.L3CacheKB)
}
fmt.Fprintln(w)
}
// Memory
if len(hw.Memory) > 0 {
fmt.Fprintln(w, "MEMORY")
fmt.Fprintln(w, "------")
totalMB := 0
for _, mem := range hw.Memory {
totalMB += mem.SizeMB
}
fmt.Fprintf(w, " Total: %d GB (%d DIMMs)\n", totalMB/1024, len(hw.Memory))
fmt.Fprintf(w, " Type: %s @ %d MHz\n", hw.Memory[0].Type, hw.Memory[0].CurrentSpeedMHz)
fmt.Fprintf(w, " Manufacturer: %s\n", hw.Memory[0].Manufacturer)
fmt.Fprintln(w)
}
// Storage
if len(hw.Storage) > 0 {
fmt.Fprintln(w, "STORAGE")
fmt.Fprintln(w, "-------")
for _, stor := range hw.Storage {
fmt.Fprintf(w, " %s: %s (%d GB) - S/N: %s\n",
stor.Slot, stor.Model, stor.SizeGB, stor.SerialNumber)
}
fmt.Fprintln(w)
}
// PCIe
if len(hw.PCIeDevices) > 0 {
fmt.Fprintln(w, "PCIE DEVICES")
fmt.Fprintln(w, "------------")
for _, pcie := range hw.PCIeDevices {
fmt.Fprintf(w, " %s: %s (x%d %s)\n",
pcie.Slot, pcie.DeviceClass, pcie.LinkWidth, pcie.LinkSpeed)
if pcie.SerialNumber != "" {
fmt.Fprintf(w, " S/N: %s\n", pcie.SerialNumber)
}
if len(pcie.MACAddresses) > 0 {
fmt.Fprintf(w, " MACs: %v\n", pcie.MACAddresses)
}
}
fmt.Fprintln(w)
}
}
// Sensors summary
if len(e.result.Sensors) > 0 {
fmt.Fprintln(w, "SENSOR READINGS")
fmt.Fprintln(w, "---------------")
// Group by type
byType := make(map[string][]models.SensorReading)
for _, s := range e.result.Sensors {
byType[s.Type] = append(byType[s.Type], s)
}
for stype, sensors := range byType {
fmt.Fprintf(w, "\n %s:\n", stype)
for _, s := range sensors {
if s.Value != 0 {
fmt.Fprintf(w, " %s: %.0f %s [%s]\n", s.Name, s.Value, s.Unit, s.Status)
} else if s.RawValue != "" {
fmt.Fprintf(w, " %s: %s [%s]\n", s.Name, s.RawValue, s.Status)
}
}
}
fmt.Fprintln(w)
}
// FRU summary
if len(e.result.FRU) > 0 {
fmt.Fprintln(w, "FRU COMPONENTS")
fmt.Fprintln(w, "--------------")
for _, fru := range e.result.FRU {
name := fru.ProductName
if name == "" {
name = fru.Description
}
fmt.Fprintf(w, " %s\n", name)
if fru.SerialNumber != "" {
fmt.Fprintf(w, " Serial: %s\n", fru.SerialNumber)
}
if fru.Manufacturer != "" {
fmt.Fprintf(w, " Manufacturer: %s\n", fru.Manufacturer)
}
}
fmt.Fprintln(w)
}
// Events summary
fmt.Fprintf(w, "EVENTS: %d total\n", len(e.result.Events))
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
}