You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
652 lines
19 KiB
Go
652 lines
19 KiB
Go
package gui
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/fatih/color"
|
|
"github.com/go-errors/errors"
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazydocker/pkg/commands"
|
|
"github.com/jesseduffield/lazydocker/pkg/config"
|
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
|
)
|
|
|
|
// list panel functions
|
|
|
|
func (gui *Gui) getContainerContexts() []string {
|
|
return []string{"logs", "stats", "env", "config", "top"}
|
|
}
|
|
|
|
func (gui *Gui) getContainerContextTitles() []string {
|
|
return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.EnvTitle, gui.Tr.ConfigTitle, gui.Tr.TopTitle}
|
|
}
|
|
|
|
func (gui *Gui) getSelectedContainer() (*commands.Container, error) {
|
|
selectedLine := gui.State.Panels.Containers.SelectedLine
|
|
if selectedLine == -1 {
|
|
return &commands.Container{}, gui.Errors.ErrNoContainers
|
|
}
|
|
|
|
return gui.DockerCommand.DisplayContainers[selectedLine], nil
|
|
}
|
|
|
|
func (gui *Gui) handleContainersClick(g *gocui.Gui, v *gocui.View) error {
|
|
itemCount := len(gui.DockerCommand.DisplayContainers)
|
|
handleSelect := gui.handleContainerSelect
|
|
selectedLine := &gui.State.Panels.Containers.SelectedLine
|
|
|
|
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
|
}
|
|
|
|
func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
if err != gui.Errors.ErrNoContainers {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.DisplayContainers), v)
|
|
|
|
key := "containers-" + container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex]
|
|
if !gui.shouldRefresh(key) {
|
|
return nil
|
|
}
|
|
|
|
mainView := gui.getMainView()
|
|
mainView.Tabs = gui.getContainerContextTitles()
|
|
mainView.TabIndex = gui.State.Panels.Containers.ContextIndex
|
|
|
|
gui.clearMainView()
|
|
|
|
switch gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] {
|
|
case "logs":
|
|
if err := gui.renderContainerLogs(container); err != nil {
|
|
return err
|
|
}
|
|
case "config":
|
|
if err := gui.renderContainerConfig(container); err != nil {
|
|
return err
|
|
}
|
|
case "env":
|
|
if err := gui.renderContainerEnv(container); err != nil {
|
|
return err
|
|
}
|
|
case "stats":
|
|
if err := gui.renderContainerStats(container); err != nil {
|
|
return err
|
|
}
|
|
case "top":
|
|
if err := gui.renderContainerTop(container); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return errors.New("Unknown context for containers panel")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) renderContainerEnv(container *commands.Container) error {
|
|
mainView := gui.getMainView()
|
|
mainView.Autoscroll = false
|
|
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
envVariablesList := [][]string{}
|
|
renderedTable := gui.Tr.NothingToDisplay
|
|
if len(container.Details.Config.Env) > 0 {
|
|
var err error
|
|
for _, env := range container.Details.Config.Env {
|
|
splitEnv := strings.SplitN(env, "=", 2)
|
|
key := splitEnv[0]
|
|
value := ""
|
|
if len(splitEnv) > 1 {
|
|
value = splitEnv[1]
|
|
}
|
|
envVariablesList = append(envVariablesList,
|
|
[]string{
|
|
utils.ColoredString(key+":", color.FgGreen),
|
|
utils.ColoredString(value, color.FgYellow),
|
|
})
|
|
}
|
|
renderedTable, err = utils.RenderTable(envVariablesList)
|
|
if err != nil {
|
|
gui.Log.Error(err)
|
|
renderedTable = gui.Tr.CannotDisplayEnvVariables
|
|
}
|
|
}
|
|
return gui.T.NewTask(func(stop chan struct{}) {
|
|
_ = gui.renderString(gui.g, "main", renderedTable)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderContainerConfig(container *commands.Container) error {
|
|
mainView := gui.getMainView()
|
|
mainView.Autoscroll = false
|
|
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
|
|
padding := 10
|
|
output := ""
|
|
output += utils.WithPadding("ID: ", padding) + container.ID + "\n"
|
|
output += utils.WithPadding("Name: ", padding) + container.Name + "\n"
|
|
output += utils.WithPadding("Command: ", padding) + strings.Join(append([]string{container.Details.Path}, container.Details.Args...), " ") + "\n"
|
|
output += utils.WithPadding("Labels: ", padding) + utils.FormatMap(padding, container.Details.Config.Labels)
|
|
output += "\n"
|
|
|
|
output += utils.WithPadding("Mounts: ", padding)
|
|
if len(container.Details.Mounts) > 0 {
|
|
output += "\n"
|
|
for _, mount := range container.Details.Mounts {
|
|
if mount.Type == "volume" {
|
|
output += fmt.Sprintf("%s%s %s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Name)
|
|
} else {
|
|
output += fmt.Sprintf("%s%s %s:%s\n", strings.Repeat(" ", padding), utils.ColoredString(mount.Type+":", color.FgYellow), mount.Source, mount.Destination)
|
|
}
|
|
}
|
|
} else {
|
|
output += "none\n"
|
|
}
|
|
|
|
output += utils.WithPadding("Ports: ", padding)
|
|
if len(container.Details.NetworkSettings.Ports) > 0 {
|
|
output += "\n"
|
|
for k, v := range container.Details.NetworkSettings.Ports {
|
|
for _, host := range v {
|
|
output += fmt.Sprintf("%s%s %s\n", strings.Repeat(" ", padding), utils.ColoredString(host.HostPort+":", color.FgYellow), k)
|
|
}
|
|
}
|
|
} else {
|
|
output += "none\n"
|
|
}
|
|
|
|
data, err := json.MarshalIndent(&container.Details, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
output += fmt.Sprintf("\nFull details:\n\n%s", string(data))
|
|
|
|
return gui.T.NewTask(func(stop chan struct{}) {
|
|
_ = gui.renderString(gui.g, "main", output)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderContainerStats(container *commands.Container) error {
|
|
mainView := gui.getMainView()
|
|
mainView.Autoscroll = false
|
|
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
|
|
return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) {
|
|
width, _ := mainView.Size()
|
|
|
|
contents, err := container.RenderStats(width)
|
|
if err != nil {
|
|
_ = gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
|
|
_ = gui.reRenderString(gui.g, "main", contents)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderContainerTop(container *commands.Container) error {
|
|
mainView := gui.getMainView()
|
|
mainView.Autoscroll = false
|
|
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
|
|
return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) {
|
|
contents, err := container.RenderTop()
|
|
if err != nil {
|
|
_ = gui.reRenderString(gui.g, "main", err.Error())
|
|
}
|
|
|
|
_ = gui.reRenderString(gui.g, "main", contents)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderContainerLogs(container *commands.Container) error {
|
|
mainView := gui.getMainView()
|
|
mainView.Autoscroll = true
|
|
mainView.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
|
|
return gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) {
|
|
gui.renderContainerLogsAux(container, stop, notifyStopped)
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) renderContainerLogsAux(container *commands.Container, stop, notifyStopped chan struct{}) {
|
|
gui.clearMainView()
|
|
|
|
command := utils.ApplyTemplate(
|
|
gui.Config.UserConfig.CommandTemplates.ContainerLogs,
|
|
gui.DockerCommand.NewCommandObject(commands.CommandObject{Container: container}),
|
|
)
|
|
cmd := gui.OSCommand.RunCustomCommand(command)
|
|
|
|
// Ensure the child process is treated as a group, as the child process spawns
|
|
// its own children. Termination requires sending the signal to the group
|
|
// process ID.
|
|
gui.OSCommand.PrepareForChildren(cmd)
|
|
|
|
mainView := gui.getMainView()
|
|
cmd.Stdout = mainView
|
|
cmd.Stderr = mainView
|
|
|
|
_ = cmd.Start()
|
|
|
|
go func() {
|
|
<-stop
|
|
if err := gui.OSCommand.Kill(cmd); err != nil {
|
|
gui.Log.Warn(err)
|
|
}
|
|
gui.Log.Info("killed container logs process")
|
|
}()
|
|
|
|
_ = cmd.Wait()
|
|
|
|
// if we are here because the task has been stopped, we should return
|
|
// if we are here then the container must have exited, meaning we should wait until it's back again before
|
|
ticker := time.NewTicker(time.Millisecond * 100)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
return
|
|
case <-ticker.C:
|
|
result, err := container.Inspect()
|
|
if err != nil {
|
|
// if we get an error, then the container has probably been removed so we'll get out of here
|
|
gui.Log.Error(err)
|
|
notifyStopped <- struct{}{}
|
|
return
|
|
}
|
|
if result.State.Running {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) refreshContainersAndServices() error {
|
|
containersView := gui.getContainersView()
|
|
if containersView == nil {
|
|
// if the containersView hasn't been instantiated yet we just return
|
|
return nil
|
|
}
|
|
|
|
// keep track of current service selected so that we can reposition our cursor if it moves position in the list
|
|
sl := gui.State.Panels.Services.SelectedLine
|
|
var selectedService *commands.Service
|
|
if len(gui.DockerCommand.Services) > 0 {
|
|
selectedService = gui.DockerCommand.Services[sl]
|
|
}
|
|
|
|
if err := gui.DockerCommand.RefreshContainersAndServices(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// see if our selected service has moved
|
|
if selectedService != nil {
|
|
for i, service := range gui.DockerCommand.Services {
|
|
if service.ID == selectedService.ID {
|
|
if i == sl {
|
|
break
|
|
}
|
|
gui.State.Panels.Services.SelectedLine = i
|
|
gui.focusPoint(0, i, len(gui.DockerCommand.Services), gui.getServicesView())
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(gui.DockerCommand.DisplayContainers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 {
|
|
gui.State.Panels.Containers.SelectedLine = 0
|
|
}
|
|
if len(gui.DockerCommand.DisplayContainers)-1 < gui.State.Panels.Containers.SelectedLine {
|
|
gui.State.Panels.Containers.SelectedLine = len(gui.DockerCommand.DisplayContainers) - 1
|
|
}
|
|
|
|
// doing the exact same thing for services
|
|
if len(gui.DockerCommand.Services) > 0 && gui.State.Panels.Services.SelectedLine == -1 {
|
|
gui.State.Panels.Services.SelectedLine = 0
|
|
}
|
|
if len(gui.DockerCommand.Services)-1 < gui.State.Panels.Services.SelectedLine {
|
|
gui.State.Panels.Services.SelectedLine = len(gui.DockerCommand.Services) - 1
|
|
}
|
|
|
|
gui.renderContainersAndServices()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) renderContainersAndServices() {
|
|
gui.g.Update(func(g *gocui.Gui) error {
|
|
containersView := gui.getContainersView()
|
|
containersView.Clear()
|
|
isFocused := gui.g.CurrentView().Name() == "containers"
|
|
|
|
list, err := utils.RenderList(gui.DockerCommand.DisplayContainers, utils.IsFocused(isFocused))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprint(containersView, list)
|
|
|
|
if containersView == g.CurrentView() {
|
|
if err := gui.handleContainerSelect(g, containersView); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// doing the exact same thing for services
|
|
if !gui.DockerCommand.InDockerComposeProject {
|
|
return nil
|
|
}
|
|
servicesView := gui.getServicesView()
|
|
servicesView.Clear()
|
|
isFocused = gui.g.CurrentView().Name() == "services"
|
|
list, err = utils.RenderList(gui.DockerCommand.Services, utils.IsFocused(isFocused))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprint(servicesView, list)
|
|
|
|
if servicesView == g.CurrentView() {
|
|
return gui.handleServiceSelect(g, servicesView)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
|
|
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
|
|
return nil
|
|
}
|
|
|
|
panelState := gui.State.Panels.Containers
|
|
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), false)
|
|
|
|
return gui.handleContainerSelect(gui.g, v)
|
|
}
|
|
|
|
func (gui *Gui) handleContainersPrevLine(g *gocui.Gui, v *gocui.View) error {
|
|
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
|
|
return nil
|
|
}
|
|
|
|
panelState := gui.State.Panels.Containers
|
|
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), true)
|
|
|
|
return gui.handleContainerSelect(gui.g, v)
|
|
}
|
|
|
|
func (gui *Gui) handleContainersNextContext(g *gocui.Gui, v *gocui.View) error {
|
|
contexts := gui.getContainerContexts()
|
|
if gui.State.Panels.Containers.ContextIndex >= len(contexts)-1 {
|
|
gui.State.Panels.Containers.ContextIndex = 0
|
|
} else {
|
|
gui.State.Panels.Containers.ContextIndex++
|
|
}
|
|
|
|
_ = gui.handleContainerSelect(gui.g, v)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) handleContainersPrevContext(g *gocui.Gui, v *gocui.View) error {
|
|
contexts := gui.getContainerContexts()
|
|
if gui.State.Panels.Containers.ContextIndex <= 0 {
|
|
gui.State.Panels.Containers.ContextIndex = len(contexts) - 1
|
|
} else {
|
|
gui.State.Panels.Containers.ContextIndex--
|
|
}
|
|
|
|
_ = gui.handleContainerSelect(gui.g, v)
|
|
|
|
return nil
|
|
}
|
|
|
|
type removeContainerOption struct {
|
|
description string
|
|
command string
|
|
configOptions types.ContainerRemoveOptions
|
|
}
|
|
|
|
// GetDisplayStrings is a function.
|
|
func (r *removeContainerOption) GetDisplayStrings(isFocused bool) []string {
|
|
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
|
}
|
|
|
|
func (gui *Gui) handleHideStoppedContainers(g *gocui.Gui, v *gocui.View) error {
|
|
gui.DockerCommand.ShowExited = !gui.DockerCommand.ShowExited
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) handleContainersRemoveMenu(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
options := []*removeContainerOption{
|
|
{
|
|
description: gui.Tr.Remove,
|
|
command: "docker rm " + container.ID[1:10],
|
|
configOptions: types.ContainerRemoveOptions{},
|
|
},
|
|
{
|
|
description: gui.Tr.RemoveWithVolumes,
|
|
command: "docker rm --volumes " + container.ID[1:10],
|
|
configOptions: types.ContainerRemoveOptions{RemoveVolumes: true},
|
|
},
|
|
{
|
|
description: gui.Tr.Cancel,
|
|
},
|
|
}
|
|
|
|
handleMenuPress := func(index int) error {
|
|
if options[index].command == "" {
|
|
return nil
|
|
}
|
|
configOptions := options[index].configOptions
|
|
|
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
|
if err := container.Remove(configOptions); err != nil {
|
|
if commands.HasErrorCode(err, commands.MustStopContainer) {
|
|
return gui.createConfirmationPanel(gui.g, v, gui.Tr.Confirm, gui.Tr.MustForceToRemoveContainer, func(g *gocui.Gui, v *gocui.View) error {
|
|
return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error {
|
|
configOptions.Force = true
|
|
if err := container.Remove(configOptions); err != nil {
|
|
return err
|
|
}
|
|
return gui.refreshContainersAndServices()
|
|
})
|
|
}, nil)
|
|
}
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
return gui.refreshContainersAndServices()
|
|
})
|
|
}
|
|
|
|
return gui.createMenu("", options, len(options), handleMenuPress)
|
|
}
|
|
|
|
func (gui *Gui) handleContainerStop(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return gui.createConfirmationPanel(gui.g, v, gui.Tr.Confirm, gui.Tr.StopContainer, func(g *gocui.Gui, v *gocui.View) error {
|
|
return gui.WithWaitingStatus(gui.Tr.StoppingStatus, func() error {
|
|
if err := container.Stop(); err != nil {
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
|
|
return gui.refreshContainersAndServices()
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (gui *Gui) handleContainerRestart(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
|
|
if err := container.Restart(); err != nil {
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
|
|
return gui.refreshContainersAndServices()
|
|
})
|
|
}
|
|
|
|
func (gui *Gui) handleContainerAttach(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
c, err := container.Attach()
|
|
if err != nil {
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
|
|
gui.SubProcess = c
|
|
return gui.Errors.ErrSubProcess
|
|
}
|
|
|
|
func (gui *Gui) handlePruneContainers() 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 {
|
|
err := gui.DockerCommand.PruneContainers()
|
|
if err != nil {
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
return nil
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (gui *Gui) handleContainerViewLogs(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
c, err := container.ViewLogs()
|
|
if err != nil {
|
|
return gui.createErrorPanel(gui.g, err.Error())
|
|
}
|
|
|
|
gui.SubProcess = c
|
|
return gui.Errors.ErrSubProcess
|
|
}
|
|
|
|
func (gui *Gui) handleContainersExecShell(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{
|
|
Container: container,
|
|
})
|
|
resolvedCommand := utils.ApplyTemplate("docker exec -it {{ .Container.ID }} /bin/sh -c 'eval $(grep ^$(id -un): /etc/passwd | cut -d : -f 7-)'", commandObject)
|
|
// attach and return the subprocess error
|
|
cmd := gui.OSCommand.ExecutableFromString(resolvedCommand)
|
|
gui.SubProcess = cmd
|
|
return gui.Errors.ErrSubProcess
|
|
}
|
|
|
|
func (gui *Gui) handleContainersCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
commandObject := gui.DockerCommand.NewCommandObject(commands.CommandObject{
|
|
Container: container,
|
|
})
|
|
|
|
customCommands := gui.Config.UserConfig.CustomCommands.Containers
|
|
|
|
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)
|
|
}
|
|
|
|
// Open first port in browser
|
|
func (gui *Gui) handleContainersOpenInBrowserCommand(g *gocui.Gui, v *gocui.View) error {
|
|
container, err := gui.getSelectedContainer()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return gui.openContainerInBrowser(container)
|
|
}
|
|
|
|
func (gui *Gui) openContainerInBrowser(container *commands.Container) error {
|
|
// skip if no any ports
|
|
if len(container.Container.Ports) == 0 {
|
|
return nil
|
|
}
|
|
// skip if the first port is not published
|
|
port := container.Container.Ports[0]
|
|
if port.IP == "" {
|
|
return nil
|
|
}
|
|
ip := port.IP
|
|
if ip == "0.0.0.0" {
|
|
ip = "localhost"
|
|
}
|
|
link := fmt.Sprintf("http://%s:%d/", ip, port.PublicPort)
|
|
return gui.OSCommand.OpenLink(link)
|
|
}
|