Jesse Duffield 826f0293f1 Fix types
2025-11-14 21:20:28 +11:00

203 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package presentation
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
func GetContainerDisplayStrings(guiConfig *config.GuiConfig, container *commands.Container) []string {
return []string{
getContainerDisplayStatus(guiConfig, container),
getContainerDisplaySubstatus(guiConfig, container),
container.Name,
getDisplayCPUPerc(container),
utils.ColoredString(displayPorts(container), color.FgYellow),
utils.ColoredString(displayContainerImage(container), color.FgMagenta),
}
}
func displayContainerImage(container *commands.Container) string {
return strings.TrimPrefix(container.Container.Image, "sha256:")
}
func displayPorts(c *commands.Container) string {
portStrings := lo.Map(c.Container.Ports, func(port container.Port, _ int) string {
if port.PublicPort == 0 {
return fmt.Sprintf("%d/%s", port.PrivatePort, port.Type)
}
// docker ps will show '0.0.0.0:80->80/tcp' but we'll show
// '80->80/tcp' instead to save space (unless the IP is something other than
// 0.0.0.0)
ipString := ""
if port.IP != "0.0.0.0" {
ipString = port.IP + ":"
}
return fmt.Sprintf("%s%d->%d/%s", ipString, port.PublicPort, port.PrivatePort, port.Type)
})
// sorting because the order of the ports is not deterministic
// and we don't want to have them constantly swapping
sort.Strings(portStrings)
return strings.Join(portStrings, ", ")
}
// getContainerDisplayStatus returns the colored status of the container
func getContainerDisplayStatus(guiConfig *config.GuiConfig, c *commands.Container) string {
shortStatusMap := map[string]string{
"paused": "P",
"exited": "X",
"created": "C",
"removing": "RM",
"restarting": "RS",
"running": "R",
"dead": "D",
}
iconStatusMap := map[string]rune{
"paused": '◫',
"exited": '',
"created": '+',
"removing": '',
"restarting": '⟳',
"running": '▶',
"dead": '!',
}
var containerState string
switch guiConfig.ContainerStatusHealthStyle {
case "short":
containerState = shortStatusMap[c.Container.State]
case "icon":
containerState = string(iconStatusMap[c.Container.State])
case "long":
fallthrough
default:
containerState = c.Container.State
}
return utils.ColoredString(containerState, getContainerColor(c))
}
// GetDisplayStatus returns the exit code if the container has exited, and the health status if the container is running (and has a health check)
func getContainerDisplaySubstatus(guiConfig *config.GuiConfig, c *commands.Container) string {
if !c.DetailsLoaded() {
return ""
}
switch c.Container.State {
case "exited":
return utils.ColoredString(
fmt.Sprintf("(%s)", strconv.Itoa(c.Details.State.ExitCode)), getContainerColor(c),
)
case "running":
return getHealthStatus(guiConfig, c)
default:
return ""
}
}
func getHealthStatus(guiConfig *config.GuiConfig, c *commands.Container) string {
if !c.DetailsLoaded() {
return ""
}
healthStatusColorMap := map[string]color.Attribute{
"healthy": color.FgGreen,
"unhealthy": color.FgRed,
"starting": color.FgYellow,
}
if c.Details.State.Health == nil {
return ""
}
shortHealthStatusMap := map[string]string{
"healthy": "H",
"unhealthy": "U",
"starting": "S",
}
iconHealthStatusMap := map[string]rune{
"healthy": '✔',
"unhealthy": '?',
"starting": '…',
}
var healthStatus string
switch guiConfig.ContainerStatusHealthStyle {
case "short":
healthStatus = shortHealthStatusMap[c.Details.State.Health.Status]
case "icon":
healthStatus = string(iconHealthStatusMap[c.Details.State.Health.Status])
case "long":
fallthrough
default:
healthStatus = c.Details.State.Health.Status
}
if healthStatusColor, ok := healthStatusColorMap[c.Details.State.Health.Status]; ok {
return utils.ColoredString(fmt.Sprintf("(%s)", healthStatus), healthStatusColor)
}
return ""
}
// getDisplayCPUPerc colors the cpu percentage based on how extreme it is
func getDisplayCPUPerc(c *commands.Container) string {
stats, ok := c.GetLastStats()
if !ok {
return ""
}
percentage := stats.DerivedStats.CPUPercentage
formattedPercentage := fmt.Sprintf("%.2f%%", stats.DerivedStats.CPUPercentage)
var clr color.Attribute
if percentage > 90 {
clr = color.FgRed
} else if percentage > 50 {
clr = color.FgYellow
} else {
clr = color.FgWhite
}
return utils.ColoredString(formattedPercentage, clr)
}
// getContainerColor Container color
func getContainerColor(c *commands.Container) color.Attribute {
switch c.Container.State {
case "exited":
// This means the colour may be briefly yellow and then switch to red upon starting
// Not sure what a better alternative is.
if !c.DetailsLoaded() || c.Details.State.ExitCode == 0 {
return color.FgYellow
}
return color.FgRed
case "created":
return color.FgCyan
case "running":
return color.FgGreen
case "paused":
return color.FgYellow
case "dead":
return color.FgRed
case "restarting":
return color.FgBlue
case "removing":
return color.FgMagenta
default:
return color.FgWhite
}
}