263 lines
5.9 KiB
Go
263 lines
5.9 KiB
Go
// Package renderer provides screen rendering implementations for hdmistat
|
|
//
|
|
//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"
|
|
)
|
|
|
|
// OverviewScreen displays system overview
|
|
type OverviewScreen struct{}
|
|
|
|
// NewOverviewScreen creates a new overview screen renderer
|
|
func NewOverviewScreen() *OverviewScreen {
|
|
return &OverviewScreen{}
|
|
}
|
|
|
|
// Name returns the name of this screen
|
|
func (s *OverviewScreen) Name() string {
|
|
return "System Overview"
|
|
}
|
|
|
|
// Render draws the overview screen to the provided canvas
|
|
func (s *OverviewScreen) Render(
|
|
canvas *layout.Canvas,
|
|
info *statcollector.SystemInfo,
|
|
) error {
|
|
_, _ = canvas.Size()
|
|
|
|
// Colors
|
|
textColor := color.RGBA{255, 255, 255, 255}
|
|
headerColor := color.RGBA{100, 200, 255, 255}
|
|
|
|
// Styles
|
|
titleStyle := layout.TextStyle{Size: 48, Color: headerColor}
|
|
headerStyle := layout.TextStyle{Size: 18, Color: headerColor, Bold: true}
|
|
normalStyle := layout.TextStyle{Size: 18, Color: textColor}
|
|
|
|
y := 120 // Start below header
|
|
|
|
// Get short hostname
|
|
shortHostname := info.Hostname
|
|
if idx := strings.Index(shortHostname, "."); idx > 0 {
|
|
shortHostname = shortHostname[:idx]
|
|
}
|
|
|
|
// Title - left aligned at consistent position
|
|
titleText := fmt.Sprintf("%s: status", shortHostname)
|
|
_ = canvas.DrawText(
|
|
titleText,
|
|
layout.Point{X: 50, Y: y},
|
|
layout.TextStyle{
|
|
Size: 36, // Smaller than before
|
|
Color: titleStyle.Color,
|
|
Alignment: layout.AlignLeft,
|
|
},
|
|
)
|
|
y += 60
|
|
|
|
// Standard bar dimensions
|
|
barWidth := 400
|
|
barHeight := 20
|
|
sectionSpacing := 60
|
|
|
|
// CPU section
|
|
_ = canvas.DrawText("CPU", layout.Point{X: 50, Y: y}, headerStyle)
|
|
y += 30
|
|
|
|
// Calculate average CPU usage
|
|
totalCPU := 0.0
|
|
for _, cpu := range info.CPUPercent {
|
|
totalCPU += cpu
|
|
}
|
|
avgCPU := totalCPU / float64(len(info.CPUPercent))
|
|
|
|
// Draw composite CPU bar below header
|
|
cpuBar := &layout.ProgressBar{
|
|
X: 50, Y: y,
|
|
Width: barWidth, Height: barHeight,
|
|
Value: avgCPU / 100.0,
|
|
Label: fmt.Sprintf(
|
|
"%.1f%% average across %d cores",
|
|
avgCPU,
|
|
len(info.CPUPercent),
|
|
),
|
|
LeftLabel: "0%",
|
|
RightLabel: "100%",
|
|
BarColor: color.RGBA{255, 100, 100, 255},
|
|
}
|
|
cpuBar.Draw(canvas)
|
|
y += sectionSpacing
|
|
|
|
// Memory section
|
|
_ = canvas.DrawText("MEMORY", layout.Point{X: 50, Y: y}, headerStyle)
|
|
y += 30
|
|
|
|
memUsedPercent := float64(info.MemoryUsed) / float64(info.MemoryTotal)
|
|
memoryBar := &layout.ProgressBar{
|
|
X: 50, Y: y,
|
|
Width: barWidth, Height: barHeight,
|
|
Value: memUsedPercent,
|
|
Label: fmt.Sprintf(
|
|
"%s of %s",
|
|
layout.FormatBytes(info.MemoryUsed),
|
|
layout.FormatBytes(info.MemoryTotal),
|
|
),
|
|
LeftLabel: "0B",
|
|
RightLabel: layout.FormatBytes(info.MemoryTotal),
|
|
BarColor: color.RGBA{100, 200, 100, 255},
|
|
}
|
|
memoryBar.Draw(canvas)
|
|
y += sectionSpacing
|
|
|
|
// Temperature section
|
|
if len(info.Temperature) > 0 {
|
|
_ = canvas.DrawText(
|
|
"TEMPERATURE",
|
|
layout.Point{X: 50, Y: y},
|
|
headerStyle,
|
|
)
|
|
y += 30
|
|
|
|
// Find the highest temperature
|
|
maxTemp := 0.0
|
|
maxSensor := ""
|
|
for sensor, temp := range info.Temperature {
|
|
if temp > maxTemp {
|
|
maxTemp = temp
|
|
maxSensor = sensor
|
|
}
|
|
}
|
|
|
|
// Temperature scale from 30°C to 99°C
|
|
tempValue := (maxTemp - 30.0) / (99.0 - 30.0)
|
|
if tempValue < 0 {
|
|
tempValue = 0
|
|
}
|
|
if tempValue > 1 {
|
|
tempValue = 1
|
|
}
|
|
|
|
tempBar := &layout.ProgressBar{
|
|
X: 50, Y: y,
|
|
Width: barWidth, Height: barHeight,
|
|
Value: tempValue,
|
|
Label: fmt.Sprintf("%s: %.1f°C", maxSensor, maxTemp),
|
|
LeftLabel: "30°C",
|
|
RightLabel: "99°C",
|
|
BarColor: color.RGBA{255, 150, 50, 255},
|
|
}
|
|
tempBar.Draw(canvas)
|
|
y += sectionSpacing
|
|
}
|
|
|
|
// Disk usage section
|
|
_ = canvas.DrawText("DISK USAGE", layout.Point{X: 50, Y: y}, headerStyle)
|
|
y += 30
|
|
|
|
for _, disk := range info.DiskUsage {
|
|
// Skip snap disks
|
|
if strings.HasPrefix(disk.Path, "/snap") {
|
|
continue
|
|
}
|
|
|
|
diskBar := &layout.ProgressBar{
|
|
X: 50, Y: y,
|
|
Width: barWidth, Height: barHeight,
|
|
Value: disk.UsedPercent / 100.0,
|
|
Label: fmt.Sprintf(
|
|
"%s: %s of %s",
|
|
disk.Path,
|
|
layout.FormatBytes(disk.Used),
|
|
layout.FormatBytes(disk.Total),
|
|
),
|
|
LeftLabel: "0B",
|
|
RightLabel: layout.FormatBytes(disk.Total),
|
|
BarColor: color.RGBA{200, 200, 100, 255},
|
|
}
|
|
diskBar.Draw(canvas)
|
|
y += 40
|
|
|
|
if y > 700 {
|
|
break // Don't overflow the screen
|
|
}
|
|
}
|
|
|
|
y += sectionSpacing
|
|
|
|
// Network section
|
|
if len(info.Network) > 0 {
|
|
_ = canvas.DrawText(
|
|
"NETWORK",
|
|
layout.Point{X: 50, Y: y},
|
|
headerStyle,
|
|
)
|
|
y += 30
|
|
|
|
for _, net := range info.Network {
|
|
// Network interface info
|
|
interfaceText := net.Name
|
|
if len(net.IPAddresses) > 0 {
|
|
interfaceText = fmt.Sprintf(
|
|
"%s (%s)",
|
|
net.Name,
|
|
net.IPAddresses[0],
|
|
)
|
|
}
|
|
_ = canvas.DrawText(
|
|
interfaceText,
|
|
layout.Point{X: 50, Y: y},
|
|
normalStyle,
|
|
)
|
|
y += 25
|
|
|
|
// Get link speed for scaling (default to 1 Gbps if unknown)
|
|
linkSpeed := net.LinkSpeed
|
|
if linkSpeed == 0 {
|
|
linkSpeed = 1000 * 1000 * 1000 // 1 Gbps in bits
|
|
}
|
|
|
|
// TX rate bar
|
|
txValue := float64(net.BitsSentRate) / float64(linkSpeed)
|
|
txBar := &layout.ProgressBar{
|
|
X: 50, Y: y,
|
|
Width: barWidth/2 - 10, Height: barHeight,
|
|
Value: txValue,
|
|
Label: fmt.Sprintf("↑ %s", net.FormatSentRate()),
|
|
LeftLabel: "0",
|
|
RightLabel: humanize.SI(float64(linkSpeed), "bit/s"),
|
|
BarColor: color.RGBA{100, 255, 100, 255},
|
|
}
|
|
txBar.Draw(canvas)
|
|
|
|
// RX rate bar
|
|
rxValue := float64(net.BitsRecvRate) / float64(linkSpeed)
|
|
rxBar := &layout.ProgressBar{
|
|
X: 50 + barWidth/2 + 10, Y: y,
|
|
Width: barWidth/2 - 10, Height: barHeight,
|
|
Value: rxValue,
|
|
Label: fmt.Sprintf("↓ %s", net.FormatRecvRate()),
|
|
LeftLabel: "0",
|
|
RightLabel: humanize.SI(float64(linkSpeed), "bit/s"),
|
|
BarColor: color.RGBA{100, 100, 255, 255},
|
|
}
|
|
rxBar.Draw(canvas)
|
|
|
|
y += 60
|
|
|
|
if y > 900 {
|
|
break // Don't overflow the screen
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|