//nolint:mnd package renderer import ( "fmt" "image/color" "strings" "git.eeqj.de/sneak/hdmistat/internal/layout" "git.eeqj.de/sneak/hdmistat/internal/statcollector" "github.com/dustin/go-humanize" ) // StatusScreen displays system status overview type StatusScreen struct{} // NewStatusScreen creates a new status screen func NewStatusScreen() *StatusScreen { return &StatusScreen{} } // Name returns the screen name func (s *StatusScreen) Name() string { return "System Status" } // Render renders the status screen func (s *StatusScreen) Render( canvas *layout.Canvas, info *statcollector.SystemInfo, ) error { // Use consistent font size for entire screen const fontSize = 16 // Colors textColor := color.RGBA{255, 255, 255, 255} dimColor := color.RGBA{150, 150, 150, 255} // Styles normalStyle := layout.TextStyle{Size: fontSize, Color: textColor} dimStyle := layout.TextStyle{Size: fontSize, Color: dimColor} // Get short hostname shortHostname := info.Hostname if idx := strings.Index(shortHostname, "."); idx > 0 { shortHostname = shortHostname[:idx] } // Starting Y position (after header) y := 150 // Title titleText := fmt.Sprintf("%s: system status", shortHostname) _ = canvas.DrawText(titleText, layout.Point{X: 16, Y: y}, normalStyle) y += 40 // CPU section cpuLabel := fmt.Sprintf( "CPU: %.1f%% average across %d cores", getAverageCPU(info.CPUPercent), len(info.CPUPercent), ) _ = canvas.DrawText(cpuLabel, layout.Point{X: 16, Y: y}, normalStyle) y += 25 // CPU progress bar _ = canvas.DrawText("0%", layout.Point{X: 100, Y: y}, dimStyle) drawProgressBar( canvas, 130, y-10, getAverageCPU(info.CPUPercent)/100.0, textColor, ) _ = canvas.DrawText("100%", layout.Point{X: 985, Y: y}, dimStyle) y += 40 // Memory section memUsedPercent := float64( info.MemoryUsed, ) / float64( info.MemoryTotal, ) * 100.0 memLabel := fmt.Sprintf( "MEMORY: %s of %s (%.1f%%)", layout.FormatBytes(info.MemoryUsed), layout.FormatBytes(info.MemoryTotal), memUsedPercent, ) _ = canvas.DrawText(memLabel, layout.Point{X: 16, Y: y}, normalStyle) y += 25 // Memory progress bar _ = canvas.DrawText("0B", layout.Point{X: 100, Y: y}, dimStyle) drawProgressBar( canvas, 130, y-10, float64(info.MemoryUsed)/float64(info.MemoryTotal), textColor, ) _ = canvas.DrawText( layout.FormatBytes(info.MemoryTotal), layout.Point{X: 985, Y: y}, dimStyle, ) y += 40 // Temperature section if len(info.Temperature) > 0 { maxTemp, maxSensor := getMaxTemperature(info.Temperature) tempLabel := fmt.Sprintf( "TEMPERATURE: %.1f°C (%s)", maxTemp, maxSensor, ) _ = canvas.DrawText( tempLabel, layout.Point{X: 16, Y: y}, normalStyle, ) y += 25 // Temperature progress bar (30-99°C scale) _ = canvas.DrawText("30°C", layout.Point{X: 90, Y: y}, dimStyle) tempValue := (maxTemp - 30.0) / (99.0 - 30.0) if tempValue < 0 { tempValue = 0 } if tempValue > 1 { tempValue = 1 } drawProgressBar(canvas, 130, y-10, tempValue, textColor) _ = canvas.DrawText("99°C", layout.Point{X: 985, Y: y}, dimStyle) y += 40 } // Disk usage section _ = canvas.DrawText( "DISK USAGE:", layout.Point{X: 16, Y: y}, normalStyle, ) y += 25 for _, disk := range info.DiskUsage { // Skip snap disks if strings.HasPrefix(disk.Path, "/snap") { continue } diskLabel := fmt.Sprintf(" * %-12s %s of %s (%.1f%%)", disk.Path, layout.FormatBytes(disk.Used), layout.FormatBytes(disk.Total), disk.UsedPercent) _ = canvas.DrawText( diskLabel, layout.Point{X: 16, Y: y}, normalStyle, ) // Disk progress bar _ = canvas.DrawText("0B", layout.Point{X: 470, Y: y}, dimStyle) drawDiskProgressBar( canvas, 500, y-10, disk.UsedPercent/100.0, textColor, ) _ = canvas.DrawText( layout.FormatBytes(disk.Total), layout.Point{X: 985, Y: y}, dimStyle, ) y += 30 if y > 700 { break // Don't overflow } } // Network section if len(info.Network) > 0 { y += 15 _ = canvas.DrawText( "NETWORK:", layout.Point{X: 16, Y: y}, normalStyle, ) y += 25 for _, net := range info.Network { // Interface header interfaceText := fmt.Sprintf(" * %s", net.Name) if len(net.IPAddresses) > 0 { interfaceText = fmt.Sprintf( " * %s (%s):", net.Name, net.IPAddresses[0], ) } _ = canvas.DrawText( interfaceText, layout.Point{X: 16, Y: y}, normalStyle, ) y += 25 // Get link speed for scaling (default to 1 Gbps if unknown) linkSpeed := net.LinkSpeed linkSpeedText := "" if linkSpeed == 0 { linkSpeed = 1000 * 1000 * 1000 // 1 Gbps in bits linkSpeedText = "1G link" } else { linkSpeedText = fmt.Sprintf("%s link", humanize.SI(float64(linkSpeed), "bit/s")) } // Upload rate upLabel := fmt.Sprintf( " ↑ %7s (%s)", net.FormatSentRate(), linkSpeedText, ) _ = canvas.DrawText( upLabel, layout.Point{X: 16, Y: y}, normalStyle, ) _ = canvas.DrawText( "0 bit/s", layout.Point{X: 400, Y: y}, dimStyle, ) drawNetworkProgressBar( canvas, 500, y-10, float64(net.BitsSentRate)/float64(linkSpeed), textColor, ) _ = canvas.DrawText( humanize.SI(float64(linkSpeed), "bit/s"), layout.Point{X: 960, Y: y}, dimStyle, ) y += 25 // Download rate downLabel := fmt.Sprintf(" ↓ %7s", net.FormatRecvRate()) _ = canvas.DrawText( downLabel, layout.Point{X: 16, Y: y}, normalStyle, ) _ = canvas.DrawText( "0 bit/s", layout.Point{X: 400, Y: y}, dimStyle, ) drawNetworkProgressBar( canvas, 500, y-10, float64(net.BitsRecvRate)/float64(linkSpeed), textColor, ) _ = canvas.DrawText( humanize.SI(float64(linkSpeed), "bit/s"), layout.Point{X: 960, Y: y}, dimStyle, ) y += 35 if y > 900 { break } } } return nil } // drawProgressBar draws a progress bar matching the mockup style func drawProgressBar( canvas *layout.Canvas, x, y int, value float64, color color.Color, ) { const barWidth = 850 // Draw opening bracket _ = canvas.DrawText( "[", layout.Point{X: x, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) // Calculate fill fillChars := int(value * 80) emptyChars := 80 - fillChars // Draw bar content barContent := strings.Repeat( "█", fillChars, ) + strings.Repeat( "▒", emptyChars, ) _ = canvas.DrawText( barContent, layout.Point{X: x + 10, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) // Draw closing bracket _ = canvas.DrawText( "]", layout.Point{X: x + barWidth - 10, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) } // drawDiskProgressBar draws a smaller progress bar for disk usage func drawDiskProgressBar( canvas *layout.Canvas, x, y int, value float64, color color.Color, ) { const barWidth = 480 // Draw opening bracket _ = canvas.DrawText( "[", layout.Point{X: x, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) // Calculate fill (50 chars total) fillChars := int(value * 50) emptyChars := 50 - fillChars // Draw bar content barContent := strings.Repeat( "█", fillChars, ) + strings.Repeat( "▒", emptyChars, ) _ = canvas.DrawText( barContent, layout.Point{X: x + 10, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) // Draw closing bracket _ = canvas.DrawText( "]", layout.Point{X: x + barWidth - 10, Y: y + 15}, layout.TextStyle{Size: 16, Color: color}, ) } // drawNetworkProgressBar draws a progress bar for network rates func drawNetworkProgressBar( canvas *layout.Canvas, x, y int, value float64, color color.Color, ) { // Same as disk progress bar drawDiskProgressBar(canvas, x, y, value, color) } // getAverageCPU calculates average CPU usage across all cores func getAverageCPU(cpuPercents []float64) float64 { if len(cpuPercents) == 0 { return 0 } total := 0.0 for _, cpu := range cpuPercents { total += cpu } return total / float64(len(cpuPercents)) } // getMaxTemperature finds the highest temperature and its sensor name func getMaxTemperature(temps map[string]float64) (float64, string) { maxTemp := 0.0 maxSensor := "" for sensor, temp := range temps { if temp > maxTemp { maxTemp = temp maxSensor = sensor } } return maxTemp, maxSensor }