move things into presentation package

pull/392/head
Jesse Duffield 2 years ago
parent fc592b5806
commit a87b698761

@ -4,18 +4,14 @@ import (
"context"
"fmt"
"os/exec"
"sort"
"strconv"
"strings"
"github.com/docker/docker/api/types/container"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/fatih/color"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/i18n"
@ -34,13 +30,13 @@ type Container struct {
OneOff bool
ProjectName string
ID string
Container types.Container
Container dockerTypes.Container
Client *client.Client
OSCommand *OSCommand
Config *config.AppConfig
Log *logrus.Entry
StatHistory []*RecordedStats
Details types.ContainerJSON
Details dockerTypes.ContainerJSON
MonitoringStats bool
DockerCommand LimitedDockerCommand
Tr *i18n.TranslationSet
@ -48,126 +44,8 @@ type Container struct {
StatsMutex deadlock.Mutex
}
// TODO: move this stuff into a presentation layer
func (c *Container) DisplayPorts() string {
portStrings := lo.Map(c.Container.Ports, func(port types.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, ", ")
}
// GetDisplayStatus returns the colored status of the container
func (c *Container) GetDisplayStatus() string {
return utils.ColoredString(c.Container.State, c.GetColor())
}
// 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 (c *Container) GetDisplaySubstatus() string {
if !c.DetailsLoaded() {
return ""
}
switch c.Container.State {
case "exited":
return utils.ColoredString(
fmt.Sprintf("(%s)", strconv.Itoa(c.Details.State.ExitCode)), c.GetColor(),
)
case "running":
return c.getHealthStatus()
default:
return ""
}
}
func (c *Container) getHealthStatus() 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 ""
}
healthStatus := c.Details.State.Health.Status
if healthStatusColor, ok := healthStatusColorMap[healthStatus]; ok {
return utils.ColoredString(fmt.Sprintf("(%s)", healthStatus), healthStatusColor)
}
return ""
}
// GetDisplayCPUPerc colors the cpu percentage based on how extreme it is
func (c *Container) GetDisplayCPUPerc() 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)
}
// GetColor Container color
func (c *Container) GetColor() 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
}
}
// Remove removes the container
func (c *Container) Remove(options types.ContainerRemoveOptions) error {
func (c *Container) Remove(options dockerTypes.ContainerRemoveOptions) error {
c.Log.Warn(fmt.Sprintf("removing container %s", c.Name))
if err := c.Client.ContainerRemove(context.Background(), c.ID, options); err != nil {
if strings.Contains(err.Error(), "Stop the container before attempting removal or force remove") {
@ -250,7 +128,7 @@ func (c *DockerCommand) PruneContainers() error {
}
// Inspect returns details about the container
func (c *Container) Inspect() (types.ContainerJSON, error) {
func (c *Container) Inspect() (dockerTypes.ContainerJSON, error) {
return c.Client.ContainerInspect(context.Background(), c.ID)
}

@ -168,7 +168,7 @@ func (s *ContainerStats) CalculateContainerMemoryUsage() float64 {
// RenderStats returns a string containing the rendered stats of the container
func (c *Container) RenderStats(viewWidth int) (string, error) {
stats, ok := c.getLastStats()
stats, ok := c.GetLastStats()
if !ok {
return "", nil
}
@ -225,7 +225,7 @@ func (c *Container) eraseOldHistory() {
}
}
func (c *Container) getLastStats() (*RecordedStats, bool) {
func (c *Container) GetLastStats() (*RecordedStats, bool) {
c.StatsMutex.Lock()
defer c.StatsMutex.Unlock()
history := c.StatHistory

@ -11,7 +11,7 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/imdario/mergo"
"github.com/jesseduffield/lazydocker/pkg/commands/ssh"
@ -189,7 +189,7 @@ func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Conta
c.ContainerMutex.Lock()
defer c.ContainerMutex.Unlock()
containers, err := c.Client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
containers, err := c.Client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{All: true})
if err != nil {
return nil, err
}

@ -7,7 +7,7 @@ import (
"github.com/docker/docker/api/types/image"
"github.com/samber/lo"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/fatih/color"
@ -20,7 +20,7 @@ type Image struct {
Name string
Tag string
ID string
Image types.ImageSummary
Image dockerTypes.ImageSummary
Client *client.Client
OSCommand *OSCommand
Log *logrus.Entry
@ -28,7 +28,7 @@ type Image struct {
}
// Remove removes the image
func (i *Image) Remove(options types.ImageRemoveOptions) error {
func (i *Image) Remove(options dockerTypes.ImageRemoveOptions) error {
if _, err := i.Client.ImageRemove(context.Background(), i.ID, options); err != nil {
return err
}
@ -94,7 +94,7 @@ func (i *Image) RenderHistory() (string, error) {
// RefreshImages returns a slice of docker images
func (c *DockerCommand) RefreshImages() ([]*Image, error) {
images, err := c.Client.ImageList(context.Background(), types.ImageListOptions{})
images, err := c.Client.ImageList(context.Background(), dockerTypes.ImageListOptions{})
if err != nil {
return nil, err
}

@ -5,7 +5,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/sirupsen/logrus"
)
@ -21,7 +21,7 @@ type Service struct {
}
// Remove removes the service's containers
func (s *Service) Remove(options types.ContainerRemoveOptions) error {
func (s *Service) Remove(options dockerTypes.ContainerRemoveOptions) error {
return s.Container.Remove(options)
}

@ -3,7 +3,7 @@ package commands
import (
"context"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
@ -12,7 +12,7 @@ import (
// Volume : A docker Volume
type Volume struct {
Name string
Volume *types.Volume
Volume *dockerTypes.Volume
Client *client.Client
OSCommand *OSCommand
Log *logrus.Entry

@ -8,7 +8,7 @@ import (
"os/signal"
"time"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stdcopy"
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
@ -107,7 +107,7 @@ func (gui *Gui) promptToReturn() {
}
func (gui *Gui) writeContainerLogs(container *commands.Container, ctx context.Context, writer io.Writer) error {
readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{
readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, container.ID, dockerTypes.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Timestamps: gui.Config.UserConfig.Logs.Timestamps,

@ -6,12 +6,14 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
@ -88,18 +90,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
return true
},
GetDisplayStrings: func(container *commands.Container) []string {
image := strings.TrimPrefix(container.Container.Image, "sha256:")
return []string{
container.GetDisplayStatus(),
container.GetDisplaySubstatus(),
container.Name,
container.GetDisplayCPUPerc(),
utils.ColoredString(image, color.FgMagenta),
utils.ColoredString(container.DisplayPorts(), color.FgYellow),
}
},
GetDisplayStrings: presentation.GetContainerDisplayStrings,
}
}
@ -318,7 +309,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return nil
}
handleMenuPress := func(configOptions types.ContainerRemoveOptions) error {
handleMenuPress := func(configOptions dockerTypes.ContainerRemoveOptions) error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
if err := container.Remove(configOptions); err != nil {
if commands.HasErrorCode(err, commands.MustStopContainer) {
@ -335,14 +326,14 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
})
}
menuItems := []*MenuItem{
menuItems := []*types.MenuItem{
{
LabelColumns: []string{gui.Tr.Remove, "docker rm " + container.ID[1:10]},
OnPress: func() error { return handleMenuPress(types.ContainerRemoveOptions{}) },
OnPress: func() error { return handleMenuPress(dockerTypes.ContainerRemoveOptions{}) },
},
{
LabelColumns: []string{gui.Tr.RemoveWithVolumes, "docker rm --volumes " + container.ID[1:10]},
OnPress: func() error { return handleMenuPress(types.ContainerRemoveOptions{RemoveVolumes: true}) },
OnPress: func() error { return handleMenuPress(dockerTypes.ContainerRemoveOptions{RemoveVolumes: true}) },
},
}
@ -500,7 +491,7 @@ func (gui *Gui) handleRemoveContainers() error {
return gui.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
for _, container := range gui.Panels.Containers.List.GetAllItems() {
if err := container.Remove(types.ContainerRemoveOptions{Force: true}); err != nil {
if err := container.Remove(dockerTypes.ContainerRemoveOptions{Force: true}); err != nil {
gui.Log.Error(err)
}
}

@ -4,12 +4,13 @@ import (
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject, title string, waitingStatus string) error {
menuItems := lo.Map(customCommands, func(command config.CustomCommand, _ int) *MenuItem {
menuItems := lo.Map(customCommands, func(command config.CustomCommand, _ int) *types.MenuItem {
resolvedCommand := utils.ApplyTemplate(command.Command, commandObject)
onPress := func() error {
@ -31,7 +32,7 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command
})
}
return &MenuItem{
return &types.MenuItem{
LabelColumns: []string{
command.Name,
utils.ColoredString(utils.WithShortSha(resolvedCommand), color.FgCyan),

@ -6,7 +6,7 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/go-errors/errors"
@ -16,6 +16,7 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/i18n"
"github.com/jesseduffield/lazydocker/pkg/tasks"
"github.com/sasha-s/go-deadlock"
@ -55,7 +56,7 @@ type Panels struct {
Containers *panels.SideListPanel[*commands.Container]
Images *panels.SideListPanel[*commands.Image]
Volumes *panels.SideListPanel[*commands.Volume]
Menu *panels.SideListPanel[*MenuItem]
Menu *panels.SideListPanel[*types.MenuItem]
}
type Mutexes struct {
@ -309,7 +310,7 @@ func (gui *Gui) listenForEvents(ctx context.Context, refresh func()) {
outer:
for {
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), types.EventsOptions{})
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), dockerTypes.EventsOptions{})
if errorCount > 0 {
select {

@ -5,12 +5,14 @@ import (
"strings"
"time"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/fatih/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
@ -60,13 +62,7 @@ func (gui *Gui) getImagesPanel() *panels.SideListPanel[*commands.Image] {
return a.ID < b.ID
},
GetDisplayStrings: func(image *commands.Image) []string {
return []string{
image.Name,
image.Tag,
utils.FormatDecimalBytes(int(image.Image.Size)),
}
},
GetDisplayStrings: presentation.GetImageDisplayStrings,
}
}
@ -126,7 +122,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
type removeImageOption struct {
description string
command string
configOptions types.ImageRemoveOptions
configOptions dockerTypes.ImageRemoveOptions
}
image, err := gui.Panels.Images.GetSelectedItem()
@ -141,27 +137,27 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
{
description: gui.Tr.Remove,
command: "docker image rm " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: true, Force: false},
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: true, Force: false},
},
{
description: gui.Tr.RemoveWithoutPrune,
command: "docker image rm --no-prune " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: false, Force: false},
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: false, Force: false},
},
{
description: gui.Tr.RemoveWithForce,
command: "docker image rm --force " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: true, Force: true},
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: true, Force: true},
},
{
description: gui.Tr.RemoveWithoutPruneWithForce,
command: "docker image rm --no-prune --force " + shortSha,
configOptions: types.ImageRemoveOptions{PruneChildren: false, Force: true},
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: false, Force: true},
},
}
menuItems := lo.Map(options, func(option *removeImageOption, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(options, func(option *removeImageOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: []string{
option.description,
color.New(color.FgRed).Sprint(option.command),

@ -2,40 +2,28 @@ package gui
import (
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
type MenuItem struct {
Label string
// alternative to Label. Allows specifying columns which will be auto-aligned
LabelColumns []string
OnPress func() error
// Only applies when Label is used
OpensMenu bool
}
type CreateMenuOptions struct {
Title string
Items []*MenuItem
Items []*types.MenuItem
HideCancel bool
}
func (gui *Gui) getMenuPanel() *panels.SideListPanel[*MenuItem] {
return &panels.SideListPanel[*MenuItem]{
ListPanel: panels.ListPanel[*MenuItem]{
List: panels.NewFilteredList[*MenuItem](),
func (gui *Gui) getMenuPanel() *panels.SideListPanel[*types.MenuItem] {
return &panels.SideListPanel[*types.MenuItem]{
ListPanel: panels.ListPanel[*types.MenuItem]{
List: panels.NewFilteredList[*types.MenuItem](),
View: gui.Views.Menu,
},
NoItemsMessage: "",
Gui: gui.intoInterface(),
OnClick: gui.onMenuPress,
Sort: nil,
GetDisplayStrings: func(menuItem *MenuItem) []string {
return menuItem.LabelColumns
},
NoItemsMessage: "",
Gui: gui.intoInterface(),
OnClick: gui.onMenuPress,
Sort: nil,
GetDisplayStrings: presentation.GetMenuItemDisplayStrings,
OnRerender: func() error {
return gui.resizePopupPanel(gui.Views.Menu)
},
@ -47,7 +35,7 @@ func (gui *Gui) getMenuPanel() *panels.SideListPanel[*MenuItem] {
}
}
func (gui *Gui) onMenuPress(menuItem *MenuItem) error {
func (gui *Gui) onMenuPress(menuItem *types.MenuItem) error {
if err := gui.handleMenuClose(); err != nil {
return err
}
@ -71,7 +59,7 @@ func (gui *Gui) handleMenuPress() error {
func (gui *Gui) Menu(opts CreateMenuOptions) error {
if !opts.HideCancel {
// this is mutative but I'm okay with that for now
opts.Items = append(opts.Items, &MenuItem{
opts.Items = append(opts.Items, &types.MenuItem{
LabelColumns: []string{gui.Tr.Cancel},
OnPress: func() error {
return nil

@ -4,6 +4,7 @@ import (
"github.com/samber/lo"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
)
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
@ -55,8 +56,8 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
return nil
}
menuItems := lo.Map(gui.getBindings(v), func(binding *Binding, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(gui.getBindings(v), func(binding *Binding, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: []string{binding.GetKey(), binding.Description},
OnPress: func() error {
if binding.Key == nil {

@ -0,0 +1,143 @@
package presentation
import (
"fmt"
"sort"
"strconv"
"strings"
dockerTypes "github.com/docker/docker/api/types"
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
func GetContainerDisplayStrings(container *commands.Container) []string {
image := strings.TrimPrefix(container.Container.Image, "sha256:")
return []string{
getContainerDisplayStatus(container),
getContainerDisplaySubstatus(container),
container.Name,
getDisplayCPUPerc(container),
utils.ColoredString(image, color.FgMagenta),
utils.ColoredString(displayPorts(container), color.FgYellow),
}
}
func displayPorts(c *commands.Container) string {
portStrings := lo.Map(c.Container.Ports, func(port dockerTypes.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(c *commands.Container) string {
return utils.ColoredString(c.Container.State, 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(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(c)
default:
return ""
}
}
func getHealthStatus(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 ""
}
healthStatus := c.Details.State.Health.Status
if healthStatusColor, ok := healthStatusColorMap[healthStatus]; 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
}
}

@ -0,0 +1,14 @@
package presentation
import (
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
func GetImageDisplayStrings(image *commands.Image) []string {
return []string{
image.Name,
image.Tag,
utils.FormatDecimalBytes(int(image.Image.Size)),
}
}

@ -0,0 +1,7 @@
package presentation
import "github.com/jesseduffield/lazydocker/pkg/gui/types"
func GetMenuItemDisplayStrings(menuItem *types.MenuItem) []string {
return menuItem.LabelColumns
}

@ -0,0 +1,7 @@
package presentation
import "github.com/jesseduffield/lazydocker/pkg/commands"
func GetProjectDisplayStrings(project *commands.Project) []string {
return []string{project.Name}
}

@ -0,0 +1,28 @@
package presentation
import (
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/utils"
)
func GetServiceDisplayStrings(service *commands.Service) []string {
if service.Container == nil {
return []string{
utils.ColoredString("none", color.FgBlue),
"",
service.Name,
"",
"",
}
}
container := service.Container
return []string{
getContainerDisplayStatus(container),
getContainerDisplaySubstatus(container),
service.Name,
getDisplayCPUPerc(container),
utils.ColoredString(displayPorts(container), color.FgYellow),
}
}

@ -0,0 +1,7 @@
package presentation
import "github.com/jesseduffield/lazydocker/pkg/commands"
func GetVolumeDisplayStrings(volume *commands.Volume) []string {
return []string{volume.Volume.Driver, volume.Name}
}

@ -9,6 +9,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/jesseduffield/yaml"
)
@ -63,9 +64,7 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
Sort: func(a *commands.Project, b *commands.Project) bool {
return false
},
GetDisplayStrings: func(project *commands.Project) []string {
return []string{project.Name}
},
GetDisplayStrings: presentation.GetProjectDisplayStrings,
// It doesn't make sense to filter a list of only one item.
DisableFilter: true,
}

@ -9,6 +9,8 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
@ -70,26 +72,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
return a.Name < b.Name
},
GetDisplayStrings: func(service *commands.Service) []string {
if service.Container == nil {
return []string{
utils.ColoredString("none", color.FgBlue),
"",
service.Name,
"",
"",
}
}
cont := service.Container
return []string{
cont.GetDisplayStatus(),
cont.GetDisplaySubstatus(),
service.Name,
cont.GetDisplayCPUPerc(),
utils.ColoredString(cont.DisplayPorts(), color.FgYellow),
}
},
GetDisplayStrings: presentation.GetServiceDisplayStrings,
Hide: func() bool {
return !gui.DockerCommand.InDockerComposeProject
},
@ -174,8 +157,8 @@ func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error {
},
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: func() error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
@ -355,8 +338,8 @@ func (gui *Gui) handleProjectDown(g *gocui.Gui, v *gocui.View) error {
},
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}
@ -427,8 +410,8 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
},
}
menuItems := lo.Map(options, func(option *commandOption, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: option.getDisplayStrings(),
OnPress: option.onPress,
}

@ -4,7 +4,7 @@ import (
"sort"
"testing"
"github.com/docker/docker/api/types"
dockerTypes "github.com/docker/docker/api/types"
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/stretchr/testify/assert"
)
@ -14,28 +14,28 @@ func sampleContainers() []*commands.Container {
{
ID: "1",
Name: "1",
Container: types.Container{
Container: dockerTypes.Container{
State: "exited",
},
},
{
ID: "2",
Name: "2",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "3",
Name: "3",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "4",
Name: "4",
Container: types.Container{
Container: dockerTypes.Container{
State: "created",
},
},
@ -47,28 +47,28 @@ func expectedPerStatusContainers() []*commands.Container {
{
ID: "2",
Name: "2",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "3",
Name: "3",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "1",
Name: "1",
Container: types.Container{
Container: dockerTypes.Container{
State: "exited",
},
},
{
ID: "4",
Name: "4",
Container: types.Container{
Container: dockerTypes.Container{
State: "created",
},
},
@ -80,28 +80,28 @@ func expectedLegacySortedContainers() []*commands.Container {
{
ID: "1",
Name: "1",
Container: types.Container{
Container: dockerTypes.Container{
State: "exited",
},
},
{
ID: "2",
Name: "2",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "3",
Name: "3",
Container: types.Container{
Container: dockerTypes.Container{
State: "running",
},
},
{
ID: "4",
Name: "4",
Container: types.Container{
Container: dockerTypes.Container{
State: "created",
},
},

@ -0,0 +1,13 @@
package types
type MenuItem struct {
Label string
// alternative to Label. Allows specifying columns which will be auto-aligned
LabelColumns []string
OnPress func() error
// Only applies when Label is used
OpensMenu bool
}

@ -8,6 +8,8 @@ import (
"github.com/jesseduffield/lazydocker/pkg/commands"
"github.com/jesseduffield/lazydocker/pkg/config"
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
"github.com/jesseduffield/lazydocker/pkg/utils"
"github.com/samber/lo"
)
@ -46,9 +48,7 @@ func (gui *Gui) getVolumesPanel() *panels.SideListPanel[*commands.Volume] {
}
return a.Name < b.Name
},
GetDisplayStrings: func(volume *commands.Volume) []string {
return []string{volume.Volume.Driver, volume.Name}
},
GetDisplayStrings: presentation.GetVolumeDisplayStrings,
}
}
@ -130,8 +130,8 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
},
}
menuItems := lo.Map(options, func(option *removeVolumeOption, _ int) *MenuItem {
return &MenuItem{
menuItems := lo.Map(options, func(option *removeVolumeOption, _ int) *types.MenuItem {
return &types.MenuItem{
LabelColumns: []string{option.description, color.New(color.FgRed).Sprint(option.command)},
OnPress: func() error {
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {

Loading…
Cancel
Save