Merge pull request #107 from jesseduffield/bulk-commands

Support bulk commands in each of the panels
This commit is contained in:
Jesse Duffield 2019-07-29 19:01:20 +10:00 committed by GitHub
commit 4dc8f76083
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 272 additions and 74 deletions

View file

@ -6,8 +6,6 @@ A simple terminal UI for both docker and docker-compose, written in Go with the
[![CircleCI](https://circleci.com/gh/jesseduffield/lazydocker.svg?style=svg)](https://circleci.com/gh/jesseduffield/lazydocker) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazydocker)](https://goreportcard.com/report/github.com/jesseduffield/lazydocker) [![GolangCI](https://golangci.com/badges/github.com/jesseduffield/lazydocker.svg)](https://golangci.com) [![GoDoc](https://godoc.org/github.com/jesseduffield/lazydocker?status.svg)](http://godoc.org/github.com/jesseduffield/lazydocker) [![GitHub tag](https://img.shields.io/github/tag/jesseduffield/lazydocker.svg)]() [![CircleCI](https://circleci.com/gh/jesseduffield/lazydocker.svg?style=svg)](https://circleci.com/gh/jesseduffield/lazydocker) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazydocker)](https://goreportcard.com/report/github.com/jesseduffield/lazydocker) [![GolangCI](https://golangci.com/badges/github.com/jesseduffield/lazydocker.svg)](https://golangci.com) [![GoDoc](https://godoc.org/github.com/jesseduffield/lazydocker?status.svg)](http://godoc.org/github.com/jesseduffield/lazydocker) [![GitHub tag](https://img.shields.io/github/tag/jesseduffield/lazydocker.svg)]()
![Gif](/docs/resources/demo3.gif) ![Gif](/docs/resources/demo3.gif)
[Demo](https://youtu.be/NICqQPxwJWw) [Demo](https://youtu.be/NICqQPxwJWw)
@ -31,7 +29,7 @@ Memorising docker commands is hard. Memorising aliases is slightly less hard. Ke
## Requirements ## Requirements
- Docker >= **1.8** (API >= **1.20**) - Docker >= **1.8** (API >= **1.25**)
- Docker-Compose >= **1.23.2** (optional) - Docker-Compose >= **1.23.2** (optional)
## Installation ## Installation
@ -197,18 +195,23 @@ If you want to see what I (Jesse) am up to in terms of development, follow me on
## FAQ ## FAQ
### How do I edit my config? ### How do I edit my config?
By opening lazydocker, clicking on the 'project' panel in the top left, and pressing 'o' (or 'e' if your editor is vim). See [Config Docs](/docs/Config.md) By opening lazydocker, clicking on the 'project' panel in the top left, and pressing 'o' (or 'e' if your editor is vim). See [Config Docs](/docs/Config.md)
### How do I get text to wrap in my main panel? ### How do I get text to wrap in my main panel?
In the future I want to make this the default, but for now there are some CPU issues that arise with wrapping. If you want to enable wrapping, use `gui.wrapMainPanel: true` In the future I want to make this the default, but for now there are some CPU issues that arise with wrapping. If you want to enable wrapping, use `gui.wrapMainPanel: true`
### How do you select text? ### How do you select text?
Because we support mouse events, you will need to hold option while dragging the mouse to indicate you're trying to select text rather than click on something. Alternatively you can disable mouse events via the `gui.ignoreMouseEvents` config value Because we support mouse events, you will need to hold option while dragging the mouse to indicate you're trying to select text rather than click on something. Alternatively you can disable mouse events via the `gui.ignoreMouseEvents` config value
### Does this work with Windows? ### Does this work with Windows?
Currently not unless you use WSL. Instructions for setting up docker for WSL can be found here [here](https://nickjanetakis.com/blog/setting-up-docker-for-windows-and-wsl-to-work-flawlessly) Currently not unless you use WSL. Instructions for setting up docker for WSL can be found here [here](https://nickjanetakis.com/blog/setting-up-docker-for-windows-and-wsl-to-work-flawlessly)
### Why can't I see my container's logs? ### Why can't I see my container's logs?
By default we only show logs from the last hour, so that we're not putting too much strain on the machine. This may be why you can't see logs when you first start lazydocker. This can be overwritten in the config's `commandTemplates` By default we only show logs from the last hour, so that we're not putting too much strain on the machine. This may be why you can't see logs when you first start lazydocker. This can be overwritten in the config's `commandTemplates`
## Alternatives ## Alternatives

View file

@ -2,12 +2,14 @@ package commands
import ( import (
"context" "context"
"github.com/docker/docker/api/types/container" "fmt"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types" "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"
@ -317,6 +319,7 @@ func (c *Container) GetColor() color.Attribute {
// Remove removes the container // Remove removes the container
func (c *Container) Remove(options types.ContainerRemoveOptions) error { func (c *Container) Remove(options types.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 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") {
return ComplexError{ return ComplexError{
@ -333,16 +336,20 @@ func (c *Container) Remove(options types.ContainerRemoveOptions) error {
// Stop stops the container // Stop stops the container
func (c *Container) Stop() error { func (c *Container) Stop() error {
c.Log.Warn(fmt.Sprintf("stopping container %s", c.Name))
return c.Client.ContainerStop(context.Background(), c.ID, nil) return c.Client.ContainerStop(context.Background(), c.ID, nil)
} }
// Restart restarts the container // Restart restarts the container
func (c *Container) Restart() error { func (c *Container) Restart() error {
c.Log.Warn(fmt.Sprintf("restarting container %s", c.Name))
return c.Client.ContainerRestart(context.Background(), c.ID, nil) return c.Client.ContainerRestart(context.Background(), c.ID, nil)
} }
// Attach attaches the container // Attach attaches the container
func (c *Container) Attach() (*exec.Cmd, error) { func (c *Container) Attach() (*exec.Cmd, error) {
c.Log.Warn(fmt.Sprintf("attaching to container %s", c.Name))
// verify that we can in fact attach to this container // verify that we can in fact attach to this container
if !c.Details.Config.OpenStdin { if !c.Details.Config.OpenStdin {
return nil, errors.New(c.Tr.UnattachableContainerError) return nil, errors.New(c.Tr.UnattachableContainerError)

View file

@ -22,7 +22,7 @@ import (
) )
const ( const (
APIVersion = "1.20" APIVersion = "1.25"
) )
// DockerCommand is our main docker interface // DockerCommand is our main docker interface

View file

@ -50,6 +50,10 @@ type UserConfig struct {
// those are found in the commands package // those are found in the commands package
CustomCommands CustomCommands `yaml:"customCommands,omitempty"` CustomCommands CustomCommands `yaml:"customCommands,omitempty"`
// BulkCommands are commands that apply to all items in a panel e.g.
// killing all containers, stopping all services, or pruning all images
BulkCommands CustomCommands `yaml:"bulkCommands,omitempty"`
// OS determines what defaults are set for opening files and links // OS determines what defaults are set for opening files and links
OS OSConfig `yaml:"oS,omitempty"` OS OSConfig `yaml:"oS,omitempty"`
@ -260,10 +264,10 @@ type CustomCommands struct {
// Services contains the custom commands for services // Services contains the custom commands for services
Services []CustomCommand `yaml:"services,omitempty"` Services []CustomCommand `yaml:"services,omitempty"`
// Services contains the custom commands for services // Images contains the custom commands for images
Images []CustomCommand `yaml:"images,omitempty"` Images []CustomCommand `yaml:"images,omitempty"`
// Services contains the custom commands for services // Volumes contains the custom commands for volumes
Volumes []CustomCommand `yaml:"volumes,omitempty"` Volumes []CustomCommand `yaml:"volumes,omitempty"`
} }
@ -289,6 +293,9 @@ type CustomCommand struct {
// field has no effect on customcommands under the 'communications' part of // field has no effect on customcommands under the 'communications' part of
// the customCommand config. // the customCommand config.
ServiceNames []string `yaml:"serviceNames"` ServiceNames []string `yaml:"serviceNames"`
// InternalFunction is the name of a function inside lazydocker that we want to run, as opposed to a command-line command. This is only used internally and can't be configured by the user
InternalFunction func() error `yaml:"internalFunction"`
} }
// GetDefaultConfig returns the application default configuration NOTE (to // GetDefaultConfig returns the application default configuration NOTE (to
@ -345,6 +352,52 @@ func GetDefaultConfig() UserConfig {
Images: []CustomCommand{}, Images: []CustomCommand{},
Volumes: []CustomCommand{}, Volumes: []CustomCommand{},
}, },
BulkCommands: CustomCommands{
Services: []CustomCommand{
{
Name: "up",
Command: "{{ .DockerCompose }} up -d",
},
{
Name: "up (attached)",
Command: "{{ .DockerCompose }} up",
Attach: true,
},
{
Name: "stop",
Command: "{{ .DockerCompose }} stop",
},
{
Name: "pull",
Command: "{{ .DockerCompose }} pull",
Attach: true,
},
{
Name: "build",
Command: "{{ .DockerCompose }} build --parallel --force-rm",
Attach: true,
},
{
Name: "down",
Command: "{{ .DockerCompose }} down",
},
{
Name: "down with volumes",
Command: "{{ .DockerCompose }} down --volumes",
},
{
Name: "down with images",
Command: "{{ .DockerCompose }} down --rmi all",
},
{
Name: "down with volumes and images",
Command: "{{ .DockerCompose }} down --volumes --rmi all",
},
},
Containers: []CustomCommand{},
Images: []CustomCommand{},
Volumes: []CustomCommand{},
},
OS: GetPlatformDefaultConfig(), OS: GetPlatformDefaultConfig(),
Update: UpdateConfig{ Update: UpdateConfig{
DockerRefreshInterval: time.Millisecond * 100, DockerRefreshInterval: time.Millisecond * 100,

View file

@ -11,6 +11,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"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/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
@ -476,8 +477,8 @@ func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
func (gui *Gui) handlePruneContainers(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneContainers() error {
return gui.createConfirmationPanel(gui.g, v, gui.Tr.Confirm, gui.Tr.ConfirmPruneContainers, func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.g, gui.getContainersView(), gui.Tr.Confirm, gui.Tr.ConfirmPruneContainers, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error {
err := gui.DockerCommand.PruneContainers() err := gui.DockerCommand.PruneContainers()
if err != nil { if err != nil {
@ -517,3 +518,51 @@ func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error
return gui.createCustomCommandMenu(customCommands, commandObject) return gui.createCustomCommandMenu(customCommands, commandObject)
} }
func (gui *Gui) handleStopContainers() error {
return gui.createConfirmationPanel(gui.g, gui.getContainersView(), gui.Tr.Confirm, gui.Tr.ConfirmStopContainers, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
for _, container := range gui.DockerCommand.Containers {
_ = container.Stop()
}
return nil
})
}, nil)
}
func (gui *Gui) handleRemoveContainers() error {
return gui.createConfirmationPanel(gui.g, gui.getContainersView(), 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.DockerCommand.Containers {
_ = container.Remove(types.ContainerRemoveOptions{Force: true})
}
return nil
})
}, nil)
}
func (gui *Gui) handleContainersBulkCommand(g *gocui.Gui, v *gocui.View) error {
baseBulkCommands := []config.CustomCommand{
{
Name: gui.Tr.StopAllContainers,
InternalFunction: gui.handleStopContainers,
},
{
Name: gui.Tr.RemoveAllContainers,
InternalFunction: gui.handleRemoveContainers,
},
{
Name: gui.Tr.PruneContainers,
InternalFunction: gui.handlePruneContainers,
},
}
bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Containers...)
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{})
return gui.createBulkCommandMenu(bulkCommands, commandObject)
}

View file

@ -21,7 +21,7 @@ func (r *customCommandOption) GetDisplayStrings(isFocused bool) []string {
return []string{r.name, utils.ColoredString(r.description, color.FgCyan)} return []string{r.name, utils.ColoredString(r.description, color.FgCyan)}
} }
func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject) error { func (gui *Gui) createCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject, title string, waitingStatus string) error {
options := make([]*customCommandOption, len(customCommands)+1) options := make([]*customCommandOption, len(customCommands)+1)
for i, command := range customCommands { for i, command := range customCommands {
resolvedCommand := utils.ApplyTemplate(command.Command, commandObject) resolvedCommand := utils.ApplyTemplate(command.Command, commandObject)
@ -46,6 +46,10 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c
return nil return nil
} }
if option.customCommand.InternalFunction != nil {
return option.customCommand.InternalFunction()
}
// if we have a command for attaching, we attach and return the subprocess error // if we have a command for attaching, we attach and return the subprocess error
if option.customCommand.Attach { if option.customCommand.Attach {
cmd := gui.OSCommand.ExecutableFromString(option.command) cmd := gui.OSCommand.ExecutableFromString(option.command)
@ -53,7 +57,7 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c
return gui.Errors.ErrSubProcess return gui.Errors.ErrSubProcess
} }
return gui.WithWaitingStatus(gui.Tr.RunningCustomCommandStatus, func() error { return gui.WithWaitingStatus(waitingStatus, func() error {
if err := gui.OSCommand.RunCommand(option.command); err != nil { if err := gui.OSCommand.RunCommand(option.command); err != nil {
return gui.createErrorPanel(gui.g, err.Error()) return gui.createErrorPanel(gui.g, err.Error())
} }
@ -61,5 +65,13 @@ func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, c
}) })
} }
return gui.createMenu("", options, len(options), handleMenuPress) return gui.createMenu(title, options, len(options), handleMenuPress)
}
func (gui *Gui) createCustomCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject) error {
return gui.createCommandMenu(customCommands, commandObject, gui.Tr.CustomCommandTitle, gui.Tr.RunningCustomCommandStatus)
}
func (gui *Gui) createBulkCommandMenu(customCommands []config.CustomCommand, commandObject commands.CommandObject) error {
return gui.createCommandMenu(customCommands, commandObject, gui.Tr.BulkCommandTitle, gui.Tr.RunningBulkCommandStatus)
} }

View file

@ -225,6 +225,7 @@ func (gui *Gui) renderGlobalOptions() error {
"PgUp/PgDn": gui.Tr.Scroll, "PgUp/PgDn": gui.Tr.Scroll,
"← → ↑ ↓": gui.Tr.Navigate, "← → ↑ ↓": gui.Tr.Navigate,
"esc/q": gui.Tr.Close, "esc/q": gui.Tr.Close,
"b": gui.Tr.ViewBulkCommands,
"x": gui.Tr.Menu, "x": gui.Tr.Menu,
}) })
} }
@ -283,6 +284,9 @@ func (gui *Gui) Run() error {
go func() { go func() {
for err := range gui.ErrorChan { for err := range gui.ErrorChan {
if err == nil {
continue
}
if strings.Contains(err.Error(), "No such container") { if strings.Contains(err.Error(), "No such container") {
// this happens all the time when e.g. restarting containers so we won't worry about it // this happens all the time when e.g. restarting containers so we won't worry about it
gui.Log.Warn(err) gui.Log.Warn(err)

View file

@ -10,6 +10,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"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/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
@ -23,7 +24,7 @@ func (gui *Gui) getImageContextTitles() []string {
return []string{gui.Tr.ConfigTitle} return []string{gui.Tr.ConfigTitle}
} }
func (gui *Gui) getSelectedImage(g *gocui.Gui) (*commands.Image, error) { func (gui *Gui) getSelectedImage() (*commands.Image, error) {
selectedLine := gui.State.Panels.Images.SelectedLine selectedLine := gui.State.Panels.Images.SelectedLine
if selectedLine == -1 { if selectedLine == -1 {
return &commands.Image{}, gui.Errors.ErrNoImages return &commands.Image{}, gui.Errors.ErrNoImages
@ -41,7 +42,7 @@ func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
Image, err := gui.getSelectedImage(g) Image, err := gui.getSelectedImage()
if err != nil { if err != nil {
if err != gui.Errors.ErrNoImages { if err != gui.Errors.ErrNoImages {
return err return err
@ -207,7 +208,7 @@ func (r *removeImageOption) GetDisplayStrings(isFocused bool) []string {
} }
func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
Image, err := gui.getSelectedImage(g) Image, err := gui.getSelectedImage()
if err != nil { if err != nil {
return nil return nil
} }
@ -248,8 +249,8 @@ func (gui *Gui) handleImagesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createMenu("", options, len(options), handleMenuPress) return gui.createMenu("", options, len(options), handleMenuPress)
} }
func (gui *Gui) handlePruneImages(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneImages() error {
return gui.createConfirmationPanel(gui.g, v, gui.Tr.Confirm, gui.Tr.ConfirmPruneImages, func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.g, gui.getImagesView(), gui.Tr.Confirm, gui.Tr.ConfirmPruneImages, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error {
err := gui.DockerCommand.PruneImages() err := gui.DockerCommand.PruneImages()
if err != nil { if err != nil {
@ -261,17 +262,30 @@ func (gui *Gui) handlePruneImages(g *gocui.Gui, v *gocui.View) error {
} }
func (gui *Gui) handleImagesCustomCommand(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleImagesCustomCommand(g *gocui.Gui, v *gocui.View) error {
image, err := gui.getSelectedImage(g) image, err := gui.getSelectedImage()
if err != nil { if err != nil {
return nil return nil
} }
commandObject := gui.DockerCommand.NewCommandObject( commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{
commands.CommandObject{ Image: image,
Image: image, })
})
customCommands := gui.Config.UserConfig.CustomCommands.Images customCommands := gui.Config.UserConfig.CustomCommands.Images
return gui.createCustomCommandMenu(customCommands, commandObject) return gui.createCustomCommandMenu(customCommands, commandObject)
} }
func (gui *Gui) handleImagesBulkCommand(g *gocui.Gui, v *gocui.View) error {
baseBulkCommands := []config.CustomCommand{
{
Name: gui.Tr.PruneImages,
InternalFunction: gui.handlePruneImages,
},
}
bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Images...)
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{})
return gui.createBulkCommandMenu(bulkCommands, commandObject)
}

View file

@ -239,13 +239,6 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleContainerAttach, Handler: gui.handleContainerAttach,
Description: gui.Tr.Attach, Description: gui.Tr.Attach,
}, },
{
ViewName: "containers",
Key: 'D',
Modifier: gocui.ModNone,
Handler: gui.handlePruneContainers,
Description: gui.Tr.PruneContainers,
},
{ {
ViewName: "containers", ViewName: "containers",
Key: 'm', Key: 'm',
@ -260,6 +253,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleContainersCustomCommand, Handler: gui.handleContainersCustomCommand,
Description: gui.Tr.RunCustomCommand, Description: gui.Tr.RunCustomCommand,
}, },
{
ViewName: "containers",
Key: 'b',
Modifier: gocui.ModNone,
Handler: gui.handleContainersBulkCommand,
Description: gui.Tr.ViewBulkCommands,
},
{ {
ViewName: "services", ViewName: "services",
Key: 'd', Key: 'd',
@ -323,6 +323,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleServicesCustomCommand, Handler: gui.handleServicesCustomCommand,
Description: gui.Tr.RunCustomCommand, Description: gui.Tr.RunCustomCommand,
}, },
{
ViewName: "services",
Key: 'b',
Modifier: gocui.ModNone,
Handler: gui.handleServicesBulkCommand,
Description: gui.Tr.ViewBulkCommands,
},
{ {
ViewName: "images", ViewName: "images",
Key: '[', Key: '[',
@ -353,10 +360,17 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}, },
{ {
ViewName: "images", ViewName: "images",
Key: 'D', Key: 'b',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handlePruneImages, Handler: gui.handleImagesBulkCommand,
Description: gui.Tr.PruneImages, Description: gui.Tr.ViewBulkCommands,
},
{
ViewName: "images",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleImagesCustomCommand,
Description: gui.Tr.RunCustomCommand,
}, },
{ {
ViewName: "volumes", ViewName: "volumes",
@ -388,10 +402,17 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}, },
{ {
ViewName: "volumes", ViewName: "volumes",
Key: 'D', Key: 'b',
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.handlePruneVolumes, Handler: gui.handleVolumesBulkCommand,
Description: gui.Tr.PruneVolumes, Description: gui.Tr.ViewBulkCommands,
},
{
ViewName: "volumes",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleVolumesCustomCommand,
Description: gui.Tr.RunCustomCommand,
}, },
{ {
ViewName: "main", ViewName: "main",

View file

@ -210,7 +210,7 @@ func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error {
}, },
} }
return gui.createCommandMenu(options, gui.Tr.RemovingStatus) return gui.createServiceCommandMenu(options, gui.Tr.RemovingStatus)
} }
func (gui *Gui) handleServiceStop(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleServiceStop(g *gocui.Gui, v *gocui.View) error {
@ -349,7 +349,7 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createMenu("", options, len(options), handleMenuPress) return gui.createMenu("", options, len(options), handleMenuPress)
} }
func (gui *Gui) createCommandMenu(options []*commandOption, status string) error { func (gui *Gui) createServiceCommandMenu(options []*commandOption, status string) error {
handleMenuPress := func(index int) error { handleMenuPress := func(index int) error {
if options[index].command == "" { if options[index].command == "" {
return nil return nil
@ -402,3 +402,10 @@ L:
return gui.createCustomCommandMenu(customCommands, commandObject) return gui.createCustomCommandMenu(customCommands, commandObject)
} }
func (gui *Gui) handleServicesBulkCommand(g *gocui.Gui, v *gocui.View) error {
bulkCommands := gui.Config.UserConfig.BulkCommands.Services
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{})
return gui.createBulkCommandMenu(bulkCommands, commandObject)
}

View file

@ -7,6 +7,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"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/utils" "github.com/jesseduffield/lazydocker/pkg/utils"
) )
@ -239,8 +240,8 @@ func (gui *Gui) handleVolumesRemoveMenu(g *gocui.Gui, v *gocui.View) error {
return gui.createMenu("", options, len(options), handleMenuPress) return gui.createMenu("", options, len(options), handleMenuPress)
} }
func (gui *Gui) handlePruneVolumes(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handlePruneVolumes() error {
return gui.createConfirmationPanel(gui.g, v, gui.Tr.Confirm, gui.Tr.ConfirmPruneVolumes, func(g *gocui.Gui, v *gocui.View) error { return gui.createConfirmationPanel(gui.g, gui.getVolumesView(), gui.Tr.Confirm, gui.Tr.ConfirmPruneVolumes, func(g *gocui.Gui, v *gocui.View) error {
return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error { return gui.WithWaitingStatus(gui.Tr.PruningStatus, func() error {
err := gui.DockerCommand.PruneVolumes() err := gui.DockerCommand.PruneVolumes()
if err != nil { if err != nil {
@ -257,12 +258,25 @@ func (gui *Gui) handleVolumesCustomCommand(g *gocui.Gui, v *gocui.View) error {
return nil return nil
} }
commandObject := gui.DockerCommand.NewCommandObject( commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{
commands.CommandObject{ Volume: volume,
Volume: volume, })
})
customCommands := gui.Config.UserConfig.CustomCommands.Volumes customCommands := gui.Config.UserConfig.CustomCommands.Volumes
return gui.createCustomCommandMenu(customCommands, commandObject) return gui.createCustomCommandMenu(customCommands, commandObject)
} }
func (gui *Gui) handleVolumesBulkCommand(g *gocui.Gui, v *gocui.View) error {
baseBulkCommands := []config.CustomCommand{
{
Name: gui.Tr.PruneVolumes,
InternalFunction: gui.handlePruneVolumes,
},
}
bulkCommands := append(baseBulkCommands, gui.Config.UserConfig.BulkCommands.Volumes...)
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{})
return gui.createBulkCommandMenu(bulkCommands, commandObject)
}

View file

@ -28,6 +28,7 @@ type TranslationSet struct {
Donate string Donate string
Cancel string Cancel string
CustomCommandTitle string CustomCommandTitle string
BulkCommandTitle string
Remove string Remove string
HideStopped string HideStopped string
ForceRemove string ForceRemove string
@ -41,6 +42,7 @@ type TranslationSet struct {
StoppingStatus string StoppingStatus string
RemovingStatus string RemovingStatus string
RunningCustomCommandStatus string RunningCustomCommandStatus string
RunningBulkCommandStatus string
RemoveService string RemoveService string
Stop string Stop string
Restart string Restart string
@ -67,13 +69,18 @@ type TranslationSet struct {
PruneContainers string PruneContainers string
PruneVolumes string PruneVolumes string
ConfirmPruneContainers string ConfirmPruneContainers string
ConfirmStopContainers string
ConfirmRemoveContainers string
ConfirmPruneImages string ConfirmPruneImages string
ConfirmPruneVolumes string ConfirmPruneVolumes string
PruningStatus string PruningStatus string
StopService string StopService string
PressEnterToReturn string PressEnterToReturn string
StopAllContainers string
RemoveAllContainers string
ViewRestartOptions string ViewRestartOptions string
RunCustomCommand string RunCustomCommand string
ViewBulkCommands string
LogsTitle string LogsTitle string
ConfigTitle string ConfigTitle string
@ -93,6 +100,7 @@ func englishSet() TranslationSet {
RestartingStatus: "restarting", RestartingStatus: "restarting",
StoppingStatus: "stopping", StoppingStatus: "stopping",
RunningCustomCommandStatus: "running custom command", RunningCustomCommandStatus: "running custom command",
RunningBulkCommandStatus: "running bulk command",
RunningSubprocess: "running subprocess", RunningSubprocess: "running subprocess",
NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement", NoViewMachingNewLineFocusedSwitchStatement: "No view matching newLineFocused switch statement",
@ -106,37 +114,40 @@ func englishSet() TranslationSet {
Donate: "Donate", Donate: "Donate",
Confirm: "Confirm", Confirm: "Confirm",
Return: "return", Return: "return",
FocusMain: "focus main panel", FocusMain: "focus main panel",
Navigate: "navigate", Navigate: "navigate",
Execute: "execute", Execute: "execute",
Close: "close", Close: "close",
Menu: "menu", Menu: "menu",
Scroll: "scroll", Scroll: "scroll",
OpenConfig: "open lazydocker config", OpenConfig: "open lazydocker config",
EditConfig: "edit lazydocker config", EditConfig: "edit lazydocker config",
Cancel: "cancel", Cancel: "cancel",
Remove: "remove", Remove: "remove",
HideStopped: "Hide/Show stopped containers", HideStopped: "Hide/Show stopped containers",
ForceRemove: "force remove", ForceRemove: "force remove",
RemoveWithVolumes: "remove with volumes", RemoveWithVolumes: "remove with volumes",
RemoveService: "remove containers", RemoveService: "remove containers",
Stop: "stop", Stop: "stop",
Restart: "restart", Restart: "restart",
Rebuild: "rebuild", Rebuild: "rebuild",
Recreate: "recreate", Recreate: "recreate",
PreviousContext: "previous tab", PreviousContext: "previous tab",
NextContext: "next tab", NextContext: "next tab",
Attach: "attach", Attach: "attach",
ViewLogs: "view logs", ViewLogs: "view logs",
RemoveImage: "remove image", RemoveImage: "remove image",
RemoveVolume: "remove volume", RemoveVolume: "remove volume",
RemoveWithoutPrune: "remove without deleting untagged parents", RemoveWithoutPrune: "remove without deleting untagged parents",
PruneContainers: "prune exited containers", PruneContainers: "prune exited containers",
PruneVolumes: "prune unused volumes", PruneVolumes: "prune unused volumes",
PruneImages: "prune unused images", PruneImages: "prune unused images",
ViewRestartOptions: "view restart options", StopAllContainers: "stop all containers",
RunCustomCommand: "run predefined custom command", RemoveAllContainers: "remove all containers (forced)",
ViewRestartOptions: "view restart options",
RunCustomCommand: "run predefined custom command",
ViewBulkCommands: "view bulk commands",
AnonymousReportingTitle: "Help make lazydocker better", AnonymousReportingTitle: "Help make lazydocker better",
AnonymousReportingPrompt: "Would you like to enable anonymous reporting data to help improve lazydocker?", AnonymousReportingPrompt: "Would you like to enable anonymous reporting data to help improve lazydocker?",
@ -150,6 +161,7 @@ func englishSet() TranslationSet {
ImagesTitle: "Images", ImagesTitle: "Images",
VolumesTitle: "Volumes", VolumesTitle: "Volumes",
CustomCommandTitle: "Custom Command:", CustomCommandTitle: "Custom Command:",
BulkCommandTitle: "Bulk Command:",
ErrorTitle: "Error", ErrorTitle: "Error",
LogsTitle: "Logs", LogsTitle: "Logs",
ConfigTitle: "Config", ConfigTitle: "Config",
@ -169,6 +181,8 @@ func englishSet() TranslationSet {
NotEnoughSpace: "Not enough space to render panels", NotEnoughSpace: "Not enough space to render panels",
ConfirmPruneImages: "Are you sure you want to prune all unused images?", ConfirmPruneImages: "Are you sure you want to prune all unused images?",
ConfirmPruneContainers: "Are you sure you want to prune all stopped containers?", ConfirmPruneContainers: "Are you sure you want to prune all stopped containers?",
ConfirmStopContainers: "Are you sure you want to stop all containers?",
ConfirmRemoveContainers: "Are you sure you want to remove all containers?",
ConfirmPruneVolumes: "Are you sure you want to prune all unused volumes?", ConfirmPruneVolumes: "Are you sure you want to prune all unused volumes?",
StopService: "Are you sure you want to stop this service's containers?", StopService: "Are you sure you want to stop this service's containers?",
StopContainer: "Are you sure you want to stop this container?", StopContainer: "Are you sure you want to stop this container?",