diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index f499e265..c90c4c02 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -33,7 +33,9 @@ type DockerCommand struct { ServiceMutex sync.Mutex Services []*Service Containers []*Container - Images []*Image + // DisplayContainers is the array of containers we will display in the containers panel. If Gui.ShowAllContainers is false, this will only be those containers which aren't based on a service. This reduces clutter and duplication in the UI + DisplayContainers []*Container + Images []*Image } // NewDockerCommand it runs git commands @@ -166,19 +168,13 @@ func (c *DockerCommand) GetContainersAndServices() error { } } - // find out which services have corresponding containers and assign them - for _, service := range services { - foundMatch := false - for _, container := range containers { - if container.ServiceID != "" && container.ServiceID == service.ID { - foundMatch = true - service.Container = container - break - } - } - if !foundMatch { - service.Container = nil - } + c.assignContainersToServices(containers, services) + + var displayContainers []*Container + if c.Config.UserConfig.Gui.ShowAllContainers { + displayContainers = containers + } else { + displayContainers = c.obtainStandaloneContainers(containers, services) } // sort services first by whether they have a linked container, and second by alphabetical order @@ -196,10 +192,39 @@ func (c *DockerCommand) GetContainersAndServices() error { c.Containers = containers c.Services = services + c.DisplayContainers = displayContainers return nil } +func (c *DockerCommand) assignContainersToServices(containers []*Container, services []*Service) { +L: + for _, service := range services { + for _, container := range containers { + if container.ServiceID != "" && container.ServiceID == service.ID { + service.Container = container + continue L + } + } + service.Container = nil + } +} + +func (c *DockerCommand) obtainStandaloneContainers(containers []*Container, services []*Service) []*Container { + standaloneContainers := []*Container{} +L: + for _, container := range containers { + for _, service := range services { + if container.ServiceID != "" && container.ServiceID == service.ID { + continue L + } + } + standaloneContainers = append(standaloneContainers, container) + } + + return standaloneContainers +} + // GetContainers gets the docker containers func (c *DockerCommand) GetContainers() ([]*Container, error) { c.ContainerMutex.Lock() diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index c77b187d..46ae8256 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -94,10 +94,11 @@ type ThemeConfig struct { } type GuiConfig struct { - ScrollHeight int `yaml:"scrollHeight,omitempty"` - ScrollPastBottom bool `yaml:"scrollPastBottom,omitempty"` - MouseEvents bool `yaml:"mouseEvents,omitempty"` - Theme ThemeConfig `yaml:"theme,omitempty"` + ScrollHeight int `yaml:"scrollHeight,omitempty"` + ScrollPastBottom bool `yaml:"scrollPastBottom,omitempty"` + MouseEvents bool `yaml:"mouseEvents,omitempty"` + Theme ThemeConfig `yaml:"theme,omitempty"` + ShowAllContainers bool `yaml:"showAllContainers,omitempty"` } type CommandTemplatesConfig struct { @@ -139,6 +140,7 @@ type StatsConfig struct { } // GetDefaultConfig returns the application default configuration +// NOTE: do not default a boolean to true, because false is the boolean zero value and this will be ignored when parsing the user's config func GetDefaultConfig() UserConfig { return UserConfig{ Gui: GuiConfig{ @@ -150,6 +152,7 @@ func GetDefaultConfig() UserConfig { InactiveBorderColor: []string{"white"}, OptionsTextColor: []string{"blue"}, }, + ShowAllContainers: false, }, Reporting: "undetermined", ConfirmOnQuit: false, diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index f008ddeb..793bc02b 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -28,7 +28,7 @@ func (gui *Gui) getSelectedContainer(g *gocui.Gui) (*commands.Container, error) return &commands.Container{}, gui.Errors.ErrNoContainers } - return gui.DockerCommand.Containers[selectedLine], nil + return gui.DockerCommand.DisplayContainers[selectedLine], nil } func (gui *Gui) handleContainersFocus(g *gocui.Gui, v *gocui.View) error { @@ -42,7 +42,7 @@ func (gui *Gui) handleContainersFocus(g *gocui.Gui, v *gocui.View) error { prevSelectedLine := gui.State.Panels.Containers.SelectedLine newSelectedLine := cy - oy - if newSelectedLine > len(gui.DockerCommand.Containers)-1 || len(utils.Decolorise(gui.DockerCommand.Containers[newSelectedLine].Name)) < cx { + if newSelectedLine > len(gui.DockerCommand.DisplayContainers)-1 || len(utils.Decolorise(gui.DockerCommand.DisplayContainers[newSelectedLine].Name)) < cx { return gui.handleContainerSelect(gui.g, v) } @@ -75,7 +75,7 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { gui.State.Panels.Main.ObjectKey = key } - if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.Containers), v); err != nil { + if err := gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.DisplayContainers), v); err != nil { return err } @@ -147,15 +147,45 @@ func (gui *Gui) renderContainerLogs(mainView *gocui.View, container *commands.Co } func (gui *Gui) renderLogsForRegularContainer(container *commands.Container) error { - mainView := gui.getMainView() - go gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { - - gui.clearMainView() - - cmd := gui.OSCommand.RunCustomCommand(utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.ContainerLogs, container)) - + gui.renderLogs(container, gui.Config.UserConfig.CommandTemplates.ContainerLogs, func(cmd *exec.Cmd) { + mainView := gui.getMainView() cmd.Stdout = mainView cmd.Stderr = mainView + }) + + return nil +} + +func (gui *Gui) renderLogsForTTYContainer(container *commands.Container) error { + gui.renderLogs(container, gui.Config.UserConfig.CommandTemplates.ContainerTTYLogs, func(cmd *exec.Cmd) { + // for some reason just saying cmd.Stdout = mainView does not work here as it does for non-tty containers, so we feed it through line by line + r, err := cmd.StdoutPipe() + if err != nil { + gui.ErrorChan <- err + } + + go func() { + mainView := gui.getMainView() + s := bufio.NewScanner(r) + s.Split(bufio.ScanLines) + for s.Scan() { + // I might put a check on the stopped channel here. Would mean more code duplication though + mainView.Write(append(s.Bytes(), '\n')) + } + }() + }) + + return nil +} + +func (gui *Gui) renderLogs(container *commands.Container, template string, setup func(*exec.Cmd)) { + gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { + gui.clearMainView() + + command := utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.ContainerTTYLogs, container) + cmd := gui.OSCommand.RunCustomCommand(command) + + setup(cmd) cmd.Start() @@ -187,59 +217,6 @@ func (gui *Gui) renderLogsForRegularContainer(container *commands.Container) err } } }) - - return nil -} - -func (gui *Gui) runProcessWithLock(cmd *exec.Cmd) { - go gui.T.NewTask(func(stop chan struct{}) { - gui.clearMainView() - cmd.Start() - - go func() { - <-stop - cmd.Process.Kill() - }() - - cmd.Wait() - }) -} - -func (gui *Gui) renderLogsForTTYContainer(container *commands.Container) error { - mainView := gui.getMainView() - gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) { - gui.clearMainView() - - command := utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.ContainerTTYLogs, container) - cmd := gui.OSCommand.RunCustomCommand(command) - - // for some reason just saying cmd.Stdout = mainView does not work here as it does for non-tty containers, so we feed it through line by line - r, err := cmd.StdoutPipe() - if err != nil { - gui.ErrorChan <- err - } - - go func() { - s := bufio.NewScanner(r) - s.Split(bufio.ScanLines) - for s.Scan() { - // I might put a check on the stopped channel here. Would mean more code duplication though - mainView.Write(append(s.Bytes(), '\n')) - } - }() - - cmd.Start() - - go func() { - <-stop - cmd.Process.Kill() - return - }() - - cmd.Wait() - }) - - return nil } func (gui *Gui) refreshContainersAndServices() error { @@ -275,11 +252,11 @@ func (gui *Gui) refreshContainersAndServices() error { } } - if len(gui.DockerCommand.Containers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 { + if len(gui.DockerCommand.DisplayContainers) > 0 && gui.State.Panels.Containers.SelectedLine == -1 { gui.State.Panels.Containers.SelectedLine = 0 } - if len(gui.DockerCommand.Containers)-1 < gui.State.Panels.Containers.SelectedLine { - gui.State.Panels.Containers.SelectedLine = len(gui.DockerCommand.Containers) - 1 + 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 @@ -293,7 +270,7 @@ func (gui *Gui) refreshContainersAndServices() error { gui.g.Update(func(g *gocui.Gui) error { containersView.Clear() isFocused := gui.g.CurrentView().Name() == "containers" - list, err := utils.RenderList(gui.DockerCommand.Containers, utils.IsFocused(isFocused)) + list, err := utils.RenderList(gui.DockerCommand.DisplayContainers, utils.IsFocused(isFocused)) if err != nil { return err } @@ -337,7 +314,7 @@ func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error { } panelState := gui.State.Panels.Containers - gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Containers), false) + gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), false) return gui.handleContainerSelect(gui.g, v) } @@ -348,7 +325,7 @@ func (gui *Gui) handleContainersPrevLine(g *gocui.Gui, v *gocui.View) error { } panelState := gui.State.Panels.Containers - gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.Containers), true) + gui.changeSelectedLine(&panelState.SelectedLine, len(gui.DockerCommand.DisplayContainers), true) return gui.handleContainerSelect(gui.g, v) } diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index eb2126d6..a03216d7 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -164,7 +164,11 @@ func (gui *Gui) layout(g *gocui.Gui) error { return err } containersView.Highlight = true - containersView.Title = gui.Tr.ContainersTitle + if gui.Config.UserConfig.Gui.ShowAllContainers { + containersView.Title = gui.Tr.ContainersTitle + } else { + containersView.Title = gui.Tr.StandaloneContainersTitle + } containersView.FgColor = gocui.ColorWhite gui.focusPoint(0, gui.State.Panels.Containers.SelectedLine, len(gui.DockerCommand.Containers), containersView) diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index 5098e8ea..df4655c2 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -57,16 +57,16 @@ func (gui *Gui) runCommand() error { gui.Log.Error(err) } + gui.SubProcess.Stdin = nil + gui.SubProcess.Stdout = ioutil.Discard + gui.SubProcess.Stderr = ioutil.Discard + gui.SubProcess = nil + signal.Stop(c) fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.PressEnterToReturn, color.FgGreen)) fmt.Scanln() // wait for enter press - gui.SubProcess.Stdout = ioutil.Discard - gui.SubProcess.Stderr = ioutil.Discard - gui.SubProcess.Stdin = nil - gui.SubProcess = nil - return nil } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 73ca5459..c99131fe 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -41,6 +41,7 @@ type TranslationSet struct { ViewLogs string ServicesTitle string ContainersTitle string + StandaloneContainersTitle string ImagesTitle string NoContainers string NoImages string @@ -97,12 +98,13 @@ func englishSet() TranslationSet { AnonymousReportingTitle: "Help make lazydocker better", AnonymousReportingPrompt: "Would you like to enable anonymous reporting data to help improve lazydocker? (enter/esc)", - StatusTitle: "Status", - ServicesTitle: "Services", - ContainersTitle: "Containers", - ImagesTitle: "Images", - CustomCommandTitle: "Custom Command:", - ErrorTitle: "Error", + StatusTitle: "Status", + ServicesTitle: "Services", + ContainersTitle: "Containers", + StandaloneContainersTitle: "Standalone Containers", + ImagesTitle: "Images", + CustomCommandTitle: "Custom Command:", + ErrorTitle: "Error", NoContainers: "No containers", NoImages: "No images",