mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-02-20 01:00:22 +08:00
203 lines
5.2 KiB
Go
203 lines
5.2 KiB
Go
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
|
||
}
|
||
}
|