Short status (#419)

Co-authored-by: Martin Cross <martinidc1992@gmail.com>
This commit is contained in:
Martin Cross 2023-07-21 09:22:18 +01:00 committed by GitHub
parent 07f26c1e91
commit dffe6970aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 52 deletions

View file

@ -38,6 +38,10 @@ gui:
expandFocusedSidePanel: false expandFocusedSidePanel: false
# Determines which screen mode will be used on startup # Determines which screen mode will be used on startup
screenMode: "normal" # one of 'normal' | 'half' | 'fullscreen' screenMode: "normal" # one of 'normal' | 'half' | 'fullscreen'
# Determines the style of the container status and container health display in the
# containers panel. "long": full words (default), "short": one or two characters,
# "icon": unicode emoji.
containerStatusHealthStyle: "long"
logs: logs:
timestamps: false timestamps: false
since: '60m' # set to '' to show all logs since: '60m' # set to '' to show all logs

View file

@ -133,6 +133,11 @@ type GuiConfig struct {
// ScreenMode allow user to specify which screen mode will be used on startup // ScreenMode allow user to specify which screen mode will be used on startup
ScreenMode string `yaml:"screenMode,omitempty"` ScreenMode string `yaml:"screenMode,omitempty"`
// Determines the style of the container status and container health display in the
// containers panel. "long": full words (default), "short": one or two characters,
// "icon": unicode emoji.
ContainerStatusHealthStyle string `yaml:"containerStatusHealthStyle"`
} }
// CommandTemplatesConfig determines what commands actually get called when we // CommandTemplatesConfig determines what commands actually get called when we
@ -358,14 +363,15 @@ func GetDefaultConfig() UserConfig {
InactiveBorderColor: []string{"default"}, InactiveBorderColor: []string{"default"},
OptionsTextColor: []string{"blue"}, OptionsTextColor: []string{"blue"},
}, },
ShowAllContainers: false, ShowAllContainers: false,
ReturnImmediately: false, ReturnImmediately: false,
WrapMainPanel: true, WrapMainPanel: true,
LegacySortContainers: false, LegacySortContainers: false,
SidePanelWidth: 0.3333, SidePanelWidth: 0.3333,
ShowBottomLine: true, ShowBottomLine: true,
ExpandFocusedSidePanel: false, ExpandFocusedSidePanel: false,
ScreenMode: "normal", ScreenMode: "normal",
ContainerStatusHealthStyle: "long",
}, },
ConfirmOnQuit: false, ConfirmOnQuit: false,
Logs: LogsConfig{ Logs: LogsConfig{

View file

@ -96,7 +96,9 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
return true return true
}, },
GetTableCells: presentation.GetContainerDisplayStrings, GetTableCells: func(container *commands.Container) []string {
return presentation.GetContainerDisplayStrings(&gui.Config.UserConfig.Gui, container)
},
} }
} }

View file

@ -9,14 +9,15 @@ import (
dockerTypes "github.com/docker/docker/api/types" dockerTypes "github.com/docker/docker/api/types"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo" "github.com/samber/lo"
) )
func GetContainerDisplayStrings(container *commands.Container) []string { func GetContainerDisplayStrings(guiConfig *config.GuiConfig, container *commands.Container) []string {
return []string{ return []string{
getContainerDisplayStatus(container), getContainerDisplayStatus(guiConfig, container),
getContainerDisplaySubstatus(container), getContainerDisplaySubstatus(guiConfig, container),
container.Name, container.Name,
getDisplayCPUPerc(container), getDisplayCPUPerc(container),
utils.ColoredString(displayPorts(container), color.FgYellow), utils.ColoredString(displayPorts(container), color.FgYellow),
@ -52,12 +53,44 @@ func displayPorts(c *commands.Container) string {
} }
// getContainerDisplayStatus returns the colored status of the container // getContainerDisplayStatus returns the colored status of the container
func getContainerDisplayStatus(c *commands.Container) string { func getContainerDisplayStatus(guiConfig *config.GuiConfig, c *commands.Container) string {
return utils.ColoredString(c.Container.State, getContainerColor(c)) 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) // 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(c *commands.Container) string { func getContainerDisplaySubstatus(guiConfig *config.GuiConfig, c *commands.Container) string {
if !c.DetailsLoaded() { if !c.DetailsLoaded() {
return "" return ""
} }
@ -68,13 +101,13 @@ func getContainerDisplaySubstatus(c *commands.Container) string {
fmt.Sprintf("(%s)", strconv.Itoa(c.Details.State.ExitCode)), getContainerColor(c), fmt.Sprintf("(%s)", strconv.Itoa(c.Details.State.ExitCode)), getContainerColor(c),
) )
case "running": case "running":
return getHealthStatus(c) return getHealthStatus(guiConfig, c)
default: default:
return "" return ""
} }
} }
func getHealthStatus(c *commands.Container) string { func getHealthStatus(guiConfig *config.GuiConfig, c *commands.Container) string {
if !c.DetailsLoaded() { if !c.DetailsLoaded() {
return "" return ""
} }
@ -88,8 +121,32 @@ func getHealthStatus(c *commands.Container) string {
if c.Details.State.Health == nil { if c.Details.State.Health == nil {
return "" return ""
} }
healthStatus := c.Details.State.Health.Status
if healthStatusColor, ok := healthStatusColorMap[healthStatus]; ok { 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 utils.ColoredString(fmt.Sprintf("(%s)", healthStatus), healthStatusColor)
} }
return "" return ""

View file

@ -3,13 +3,26 @@ package presentation
import ( import (
"github.com/fatih/color" "github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands" "github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
func GetServiceDisplayStrings(service *commands.Service) []string { func GetServiceDisplayStrings(guiConfig *config.GuiConfig, service *commands.Service) []string {
if service.Container == nil { if service.Container == nil {
var containerState string
switch guiConfig.ContainerStatusHealthStyle {
case "short":
containerState = "n"
case "icon":
containerState = "."
case "long":
fallthrough
default:
containerState = "none"
}
return []string{ return []string{
utils.ColoredString("none", color.FgBlue), utils.ColoredString(containerState, color.FgBlue),
"", "",
service.Name, service.Name,
"", "",
@ -20,8 +33,8 @@ func GetServiceDisplayStrings(service *commands.Service) []string {
container := service.Container container := service.Container
return []string{ return []string{
getContainerDisplayStatus(container), getContainerDisplayStatus(guiConfig, container),
getContainerDisplaySubstatus(container), getContainerDisplaySubstatus(guiConfig, container),
service.Name, service.Name,
getDisplayCPUPerc(container), getDisplayCPUPerc(container),
utils.ColoredString(displayPorts(container), color.FgYellow), utils.ColoredString(displayPorts(container), color.FgYellow),

View file

@ -74,7 +74,9 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
return a.Name < b.Name return a.Name < b.Name
}, },
GetTableCells: presentation.GetServiceDisplayStrings, GetTableCells: func(service *commands.Service) []string {
return presentation.GetServiceDisplayStrings(&gui.Config.UserConfig.Gui, service)
},
Hide: func() bool { Hide: func() bool {
return !gui.DockerCommand.InDockerComposeProject return !gui.DockerCommand.InDockerComposeProject
}, },

View file

@ -14,6 +14,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/gocui" "github.com/jesseduffield/gocui"
"github.com/mattn/go-runewidth"
// "github.com/jesseduffield/yaml" // "github.com/jesseduffield/yaml"
@ -41,10 +42,10 @@ func SplitLines(multilineString string) []string {
// WithPadding pads a string as much as you want // WithPadding pads a string as much as you want
func WithPadding(str string, padding int) string { func WithPadding(str string, padding int) string {
uncoloredStr := Decolorise(str) uncoloredStr := Decolorise(str)
if padding < len(uncoloredStr) { if padding < runewidth.StringWidth(uncoloredStr) {
return str return str
} }
return str + strings.Repeat(" ", padding-len(uncoloredStr)) return str + strings.Repeat(" ", padding-runewidth.StringWidth(uncoloredStr))
} }
// ColoredString takes a string and a colour attribute and returns a colored // ColoredString takes a string and a colour attribute and returns a colored
@ -143,54 +144,52 @@ func Max(x, y int) int {
} }
// RenderTable takes an array of string arrays and returns a table containing the values // RenderTable takes an array of string arrays and returns a table containing the values
func RenderTable(stringArrays [][]string) (string, error) { func RenderTable(rows [][]string) (string, error) {
if len(stringArrays) == 0 { if len(rows) == 0 {
return "", nil return "", nil
} }
if !displayArraysAligned(stringArrays) { if !displayArraysAligned(rows) {
return "", errors.New("Each item must return the same number of strings to display") return "", errors.New("Each item must return the same number of strings to display")
} }
padWidths := getPadWidths(stringArrays) columnPadWidths := getPadWidths(rows)
paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths) paddedDisplayRows := getPaddedDisplayStrings(rows, columnPadWidths)
return strings.Join(paddedDisplayStrings, "\n"), nil return strings.Join(paddedDisplayRows, "\n"), nil
} }
// Decolorise strips a string of color // Decolorise strips a string of color
func Decolorise(str string) string { func Decolorise(str string) string {
re := regexp.MustCompile(`\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]`) re := regexp.MustCompile(`\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mK]`)
return re.ReplaceAllString(str, "") return re.ReplaceAllString(str, "")
} }
func getPadWidths(stringArrays [][]string) []int { func getPadWidths(rows [][]string) []int {
if len(stringArrays[0]) <= 1 { if len(rows[0]) <= 1 {
return []int{} return []int{}
} }
padWidths := make([]int, len(stringArrays[0])-1) columnPadWidths := make([]int, len(rows[0])-1)
for i := range padWidths { for i := range columnPadWidths {
for _, strings := range stringArrays { for _, cells := range rows {
uncoloredString := Decolorise(strings[i]) uncoloredCell := Decolorise(cells[i])
if len(uncoloredString) > padWidths[i] {
padWidths[i] = len(uncoloredString) if runewidth.StringWidth(uncoloredCell) > columnPadWidths[i] {
columnPadWidths[i] = runewidth.StringWidth(uncoloredCell)
} }
} }
} }
return padWidths return columnPadWidths
} }
func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { func getPaddedDisplayStrings(rows [][]string, columnPadWidths []int) []string {
paddedDisplayStrings := make([]string, len(stringArrays)) paddedDisplayRows := make([]string, len(rows))
for i, stringArray := range stringArrays { for i, cells := range rows {
if len(stringArray) == 0 { for j, columnPadWidth := range columnPadWidths {
continue paddedDisplayRows[i] += WithPadding(cells[j], columnPadWidth) + " "
} }
for j, padWidth := range padWidths { paddedDisplayRows[i] += cells[len(columnPadWidths)]
paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
}
paddedDisplayStrings[i] += stringArray[len(padWidths)]
} }
return paddedDisplayStrings return paddedDisplayRows
} }
// displayArraysAligned returns true if every string array returned from our // displayArraysAligned returns true if every string array returned from our