mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
move things into presentation package
This commit is contained in:
parent
fc592b5806
commit
a87b698761
24 changed files with 311 additions and 254 deletions
|
|
@ -4,18 +4,14 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/samber/lo"
|
|
||||||
"github.com/sasha-s/go-deadlock"
|
"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/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/i18n"
|
"github.com/jesseduffield/lazydocker/pkg/i18n"
|
||||||
|
|
@ -34,13 +30,13 @@ type Container struct {
|
||||||
OneOff bool
|
OneOff bool
|
||||||
ProjectName string
|
ProjectName string
|
||||||
ID string
|
ID string
|
||||||
Container types.Container
|
Container dockerTypes.Container
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
OSCommand *OSCommand
|
OSCommand *OSCommand
|
||||||
Config *config.AppConfig
|
Config *config.AppConfig
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
StatHistory []*RecordedStats
|
StatHistory []*RecordedStats
|
||||||
Details types.ContainerJSON
|
Details dockerTypes.ContainerJSON
|
||||||
MonitoringStats bool
|
MonitoringStats bool
|
||||||
DockerCommand LimitedDockerCommand
|
DockerCommand LimitedDockerCommand
|
||||||
Tr *i18n.TranslationSet
|
Tr *i18n.TranslationSet
|
||||||
|
|
@ -48,126 +44,8 @@ type Container struct {
|
||||||
StatsMutex deadlock.Mutex
|
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
|
// 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))
|
c.Log.Warn(fmt.Sprintf("removing container %s", c.Name))
|
||||||
if err := c.Client.ContainerRemove(context.Background(), c.ID, options); err != nil {
|
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") {
|
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
|
// 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)
|
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
|
// RenderStats returns a string containing the rendered stats of the container
|
||||||
func (c *Container) RenderStats(viewWidth int) (string, error) {
|
func (c *Container) RenderStats(viewWidth int) (string, error) {
|
||||||
stats, ok := c.getLastStats()
|
stats, ok := c.GetLastStats()
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", nil
|
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()
|
c.StatsMutex.Lock()
|
||||||
defer c.StatsMutex.Unlock()
|
defer c.StatsMutex.Unlock()
|
||||||
history := c.StatHistory
|
history := c.StatHistory
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands/ssh"
|
"github.com/jesseduffield/lazydocker/pkg/commands/ssh"
|
||||||
|
|
@ -189,7 +189,7 @@ func (c *DockerCommand) GetContainers(existingContainers []*Container) ([]*Conta
|
||||||
c.ContainerMutex.Lock()
|
c.ContainerMutex.Lock()
|
||||||
defer c.ContainerMutex.Unlock()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
"github.com/samber/lo"
|
"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/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
|
@ -20,7 +20,7 @@ type Image struct {
|
||||||
Name string
|
Name string
|
||||||
Tag string
|
Tag string
|
||||||
ID string
|
ID string
|
||||||
Image types.ImageSummary
|
Image dockerTypes.ImageSummary
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
OSCommand *OSCommand
|
OSCommand *OSCommand
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
|
|
@ -28,7 +28,7 @@ type Image struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the image
|
// 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 {
|
if _, err := i.Client.ImageRemove(context.Background(), i.ID, options); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -94,7 +94,7 @@ func (i *Image) RenderHistory() (string, error) {
|
||||||
|
|
||||||
// RefreshImages returns a slice of docker images
|
// RefreshImages returns a slice of docker images
|
||||||
func (c *DockerCommand) RefreshImages() ([]*Image, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"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/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
@ -21,7 +21,7 @@ type Service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the service's containers
|
// 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)
|
return s.Container.Remove(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package commands
|
||||||
import (
|
import (
|
||||||
"context"
|
"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/api/types/filters"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
// Volume : A docker Volume
|
// Volume : A docker Volume
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
Name string
|
Name string
|
||||||
Volume *types.Volume
|
Volume *dockerTypes.Volume
|
||||||
Client *client.Client
|
Client *client.Client
|
||||||
OSCommand *OSCommand
|
OSCommand *OSCommand
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"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 {
|
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,
|
ShowStdout: true,
|
||||||
ShowStderr: true,
|
ShowStderr: true,
|
||||||
Timestamps: gui.Config.UserConfig.Logs.Timestamps,
|
Timestamps: gui.Config.UserConfig.Logs.Timestamps,
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
@ -88,18 +90,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
GetDisplayStrings: func(container *commands.Container) []string {
|
GetDisplayStrings: presentation.GetContainerDisplayStrings,
|
||||||
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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,7 +309,7 @@ func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMenuPress := func(configOptions types.ContainerRemoveOptions) error {
|
handleMenuPress := func(configOptions dockerTypes.ContainerRemoveOptions) error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
||||||
if err := container.Remove(configOptions); err != nil {
|
if err := container.Remove(configOptions); err != nil {
|
||||||
if commands.HasErrorCode(err, commands.MustStopContainer) {
|
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]},
|
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]},
|
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.createConfirmationPanel(gui.Tr.Confirm, gui.Tr.ConfirmRemoveContainers, func(g *gocui.Gui, v *gocui.View) error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
||||||
for _, container := range gui.Panels.Containers.List.GetAllItems() {
|
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)
|
gui.Log.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,13 @@ 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/config"
|
||||||
|
"github.com/jesseduffield/lazydocker/pkg/gui/types"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject, title string, waitingStatus string) error {
|
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)
|
resolvedCommand := utils.ApplyTemplate(command.Command, commandObject)
|
||||||
|
|
||||||
onPress := func() error {
|
onPress := func() error {
|
||||||
|
|
@ -31,7 +32,7 @@ func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, command
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: []string{
|
LabelColumns: []string{
|
||||||
command.Name,
|
command.Name,
|
||||||
utils.ColoredString(utils.WithShortSha(resolvedCommand), color.FgCyan),
|
utils.ColoredString(utils.WithShortSha(resolvedCommand), color.FgCyan),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/i18n"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/tasks"
|
"github.com/jesseduffield/lazydocker/pkg/tasks"
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
|
|
@ -55,7 +56,7 @@ type Panels struct {
|
||||||
Containers *panels.SideListPanel[*commands.Container]
|
Containers *panels.SideListPanel[*commands.Container]
|
||||||
Images *panels.SideListPanel[*commands.Image]
|
Images *panels.SideListPanel[*commands.Image]
|
||||||
Volumes *panels.SideListPanel[*commands.Volume]
|
Volumes *panels.SideListPanel[*commands.Volume]
|
||||||
Menu *panels.SideListPanel[*MenuItem]
|
Menu *panels.SideListPanel[*types.MenuItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutexes struct {
|
type Mutexes struct {
|
||||||
|
|
@ -309,7 +310,7 @@ func (gui *Gui) listenForEvents(ctx context.Context, refresh func()) {
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
for {
|
for {
|
||||||
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), types.EventsOptions{})
|
messageChan, errChan := gui.DockerCommand.Client.Events(context.Background(), dockerTypes.EventsOptions{})
|
||||||
|
|
||||||
if errorCount > 0 {
|
if errorCount > 0 {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
@ -60,13 +62,7 @@ func (gui *Gui) getImagesPanel() *panels.SideListPanel[*commands.Image] {
|
||||||
|
|
||||||
return a.ID < b.ID
|
return a.ID < b.ID
|
||||||
},
|
},
|
||||||
GetDisplayStrings: func(image *commands.Image) []string {
|
GetDisplayStrings: presentation.GetImageDisplayStrings,
|
||||||
return []string{
|
|
||||||
image.Name,
|
|
||||||
image.Tag,
|
|
||||||
utils.FormatDecimalBytes(int(image.Image.Size)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +122,7 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||||
type removeImageOption struct {
|
type removeImageOption struct {
|
||||||
description string
|
description string
|
||||||
command string
|
command string
|
||||||
configOptions types.ImageRemoveOptions
|
configOptions dockerTypes.ImageRemoveOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
image, err := gui.Panels.Images.GetSelectedItem()
|
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,
|
description: gui.Tr.Remove,
|
||||||
command: "docker image rm " + shortSha,
|
command: "docker image rm " + shortSha,
|
||||||
configOptions: types.ImageRemoveOptions{PruneChildren: true, Force: false},
|
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: true, Force: false},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: gui.Tr.RemoveWithoutPrune,
|
description: gui.Tr.RemoveWithoutPrune,
|
||||||
command: "docker image rm --no-prune " + shortSha,
|
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,
|
description: gui.Tr.RemoveWithForce,
|
||||||
command: "docker image rm --force " + shortSha,
|
command: "docker image rm --force " + shortSha,
|
||||||
configOptions: types.ImageRemoveOptions{PruneChildren: true, Force: true},
|
configOptions: dockerTypes.ImageRemoveOptions{PruneChildren: true, Force: true},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: gui.Tr.RemoveWithoutPruneWithForce,
|
description: gui.Tr.RemoveWithoutPruneWithForce,
|
||||||
command: "docker image rm --no-prune --force " + shortSha,
|
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 {
|
menuItems := lo.Map(options, func(option *removeImageOption, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: []string{
|
LabelColumns: []string{
|
||||||
option.description,
|
option.description,
|
||||||
color.New(color.FgRed).Sprint(option.command),
|
color.New(color.FgRed).Sprint(option.command),
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,28 @@ package gui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/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 {
|
type CreateMenuOptions struct {
|
||||||
Title string
|
Title string
|
||||||
Items []*MenuItem
|
Items []*types.MenuItem
|
||||||
HideCancel bool
|
HideCancel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) getMenuPanel() *panels.SideListPanel[*MenuItem] {
|
func (gui *Gui) getMenuPanel() *panels.SideListPanel[*types.MenuItem] {
|
||||||
return &panels.SideListPanel[*MenuItem]{
|
return &panels.SideListPanel[*types.MenuItem]{
|
||||||
ListPanel: panels.ListPanel[*MenuItem]{
|
ListPanel: panels.ListPanel[*types.MenuItem]{
|
||||||
List: panels.NewFilteredList[*MenuItem](),
|
List: panels.NewFilteredList[*types.MenuItem](),
|
||||||
View: gui.Views.Menu,
|
View: gui.Views.Menu,
|
||||||
},
|
},
|
||||||
NoItemsMessage: "",
|
NoItemsMessage: "",
|
||||||
Gui: gui.intoInterface(),
|
Gui: gui.intoInterface(),
|
||||||
OnClick: gui.onMenuPress,
|
OnClick: gui.onMenuPress,
|
||||||
Sort: nil,
|
Sort: nil,
|
||||||
GetDisplayStrings: func(menuItem *MenuItem) []string {
|
GetDisplayStrings: presentation.GetMenuItemDisplayStrings,
|
||||||
return menuItem.LabelColumns
|
|
||||||
},
|
|
||||||
OnRerender: func() error {
|
OnRerender: func() error {
|
||||||
return gui.resizePopupPanel(gui.Views.Menu)
|
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 {
|
if err := gui.handleMenuClose(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +59,7 @@ func (gui *Gui) handleMenuPress() error {
|
||||||
func (gui *Gui) Menu(opts CreateMenuOptions) error {
|
func (gui *Gui) Menu(opts CreateMenuOptions) error {
|
||||||
if !opts.HideCancel {
|
if !opts.HideCancel {
|
||||||
// this is mutative but I'm okay with that for now
|
// 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},
|
LabelColumns: []string{gui.Tr.Cancel},
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
"github.com/jesseduffield/gocui"
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazydocker/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems := lo.Map(gui.getBindings(v), func(binding *Binding, _ int) *MenuItem {
|
menuItems := lo.Map(gui.getBindings(v), func(binding *Binding, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: []string{binding.GetKey(), binding.Description},
|
LabelColumns: []string{binding.GetKey(), binding.Description},
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
if binding.Key == nil {
|
if binding.Key == nil {
|
||||||
|
|
|
||||||
143
pkg/gui/presentation/containers.go
Normal file
143
pkg/gui/presentation/containers.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
14
pkg/gui/presentation/images.go
Normal file
14
pkg/gui/presentation/images.go
Normal file
|
|
@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
7
pkg/gui/presentation/menu_items.go
Normal file
7
pkg/gui/presentation/menu_items.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package presentation
|
||||||
|
|
||||||
|
import "github.com/jesseduffield/lazydocker/pkg/gui/types"
|
||||||
|
|
||||||
|
func GetMenuItemDisplayStrings(menuItem *types.MenuItem) []string {
|
||||||
|
return menuItem.LabelColumns
|
||||||
|
}
|
||||||
7
pkg/gui/presentation/projects.go
Normal file
7
pkg/gui/presentation/projects.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package presentation
|
||||||
|
|
||||||
|
import "github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
|
|
||||||
|
func GetProjectDisplayStrings(project *commands.Project) []string {
|
||||||
|
return []string{project.Name}
|
||||||
|
}
|
||||||
28
pkg/gui/presentation/services.go
Normal file
28
pkg/gui/presentation/services.go
Normal file
|
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
7
pkg/gui/presentation/volumes.go
Normal file
7
pkg/gui/presentation/volumes.go
Normal file
|
|
@ -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/gocui"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
||||||
|
"github.com/jesseduffield/lazydocker/pkg/gui/presentation"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/utils"
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/jesseduffield/yaml"
|
"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 {
|
Sort: func(a *commands.Project, b *commands.Project) bool {
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
GetDisplayStrings: func(project *commands.Project) []string {
|
GetDisplayStrings: presentation.GetProjectDisplayStrings,
|
||||||
return []string{project.Name}
|
|
||||||
},
|
|
||||||
// It doesn't make sense to filter a list of only one item.
|
// It doesn't make sense to filter a list of only one item.
|
||||||
DisableFilter: true,
|
DisableFilter: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
@ -70,26 +72,7 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
|
||||||
|
|
||||||
return a.Name < b.Name
|
return a.Name < b.Name
|
||||||
},
|
},
|
||||||
GetDisplayStrings: func(service *commands.Service) []string {
|
GetDisplayStrings: presentation.GetServiceDisplayStrings,
|
||||||
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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Hide: func() bool {
|
Hide: func() bool {
|
||||||
return !gui.DockerCommand.InDockerComposeProject
|
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 {
|
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: option.getDisplayStrings(),
|
LabelColumns: option.getDisplayStrings(),
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, 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 {
|
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: option.getDisplayStrings(),
|
LabelColumns: option.getDisplayStrings(),
|
||||||
OnPress: option.onPress,
|
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 {
|
menuItems := lo.Map(options, func(option *commandOption, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: option.getDisplayStrings(),
|
LabelColumns: option.getDisplayStrings(),
|
||||||
OnPress: option.onPress,
|
OnPress: option.onPress,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/commands"
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
@ -14,28 +14,28 @@ func sampleContainers() []*commands.Container {
|
||||||
{
|
{
|
||||||
ID: "1",
|
ID: "1",
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "exited",
|
State: "exited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "2",
|
ID: "2",
|
||||||
Name: "2",
|
Name: "2",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "3",
|
ID: "3",
|
||||||
Name: "3",
|
Name: "3",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "4",
|
ID: "4",
|
||||||
Name: "4",
|
Name: "4",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "created",
|
State: "created",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -47,28 +47,28 @@ func expectedPerStatusContainers() []*commands.Container {
|
||||||
{
|
{
|
||||||
ID: "2",
|
ID: "2",
|
||||||
Name: "2",
|
Name: "2",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "3",
|
ID: "3",
|
||||||
Name: "3",
|
Name: "3",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "1",
|
ID: "1",
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "exited",
|
State: "exited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "4",
|
ID: "4",
|
||||||
Name: "4",
|
Name: "4",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "created",
|
State: "created",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -80,28 +80,28 @@ func expectedLegacySortedContainers() []*commands.Container {
|
||||||
{
|
{
|
||||||
ID: "1",
|
ID: "1",
|
||||||
Name: "1",
|
Name: "1",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "exited",
|
State: "exited",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "2",
|
ID: "2",
|
||||||
Name: "2",
|
Name: "2",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "3",
|
ID: "3",
|
||||||
Name: "3",
|
Name: "3",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "running",
|
State: "running",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "4",
|
ID: "4",
|
||||||
Name: "4",
|
Name: "4",
|
||||||
Container: types.Container{
|
Container: dockerTypes.Container{
|
||||||
State: "created",
|
State: "created",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
13
pkg/gui/types/types.go
Normal file
13
pkg/gui/types/types.go
Normal file
|
|
@ -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/commands"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/config"
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
||||||
"github.com/jesseduffield/lazydocker/pkg/gui/panels"
|
"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/jesseduffield/lazydocker/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
@ -46,9 +48,7 @@ func (gui *Gui) getVolumesPanel() *panels.SideListPanel[*commands.Volume] {
|
||||||
}
|
}
|
||||||
return a.Name < b.Name
|
return a.Name < b.Name
|
||||||
},
|
},
|
||||||
GetDisplayStrings: func(volume *commands.Volume) []string {
|
GetDisplayStrings: presentation.GetVolumeDisplayStrings,
|
||||||
return []string{volume.Volume.Driver, volume.Name}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,8 +130,8 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
menuItems := lo.Map(options, func(option *removeVolumeOption, _ int) *MenuItem {
|
menuItems := lo.Map(options, func(option *removeVolumeOption, _ int) *types.MenuItem {
|
||||||
return &MenuItem{
|
return &types.MenuItem{
|
||||||
LabelColumns: []string{option.description, color.New(color.FgRed).Sprint(option.command)},
|
LabelColumns: []string{option.description, color.New(color.FgRed).Sprint(option.command)},
|
||||||
OnPress: func() error {
|
OnPress: func() error {
|
||||||
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue