From b65df625f4450a4a6fc1c288ac44bfa646541d41 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 9 Jun 2019 00:08:22 +1000 Subject: [PATCH] much more stuff --- pkg/commands/container.go | 15 +++++- pkg/commands/os.go | 11 +++++ pkg/commands/service.go | 3 +- pkg/config/app_config.go | 2 + pkg/gui/gui.go | 6 +++ pkg/gui/keybindings.go | 14 ++++++ pkg/gui/status_panel.go | 97 ++++++++++++++++++++++++++++++++++++++- pkg/gui/subprocess.go | 10 ++-- 8 files changed, 151 insertions(+), 7 deletions(-) diff --git a/pkg/commands/container.go b/pkg/commands/container.go index a7926839..ce0d44c2 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -254,7 +254,17 @@ type ContainerCliStat struct { // GetDisplayStrings returns the dispaly string of Container func (c *Container) GetDisplayStrings(isFocused bool) []string { - return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite), c.GetDisplayCPUPerc()} + return []string{c.GetDisplayStatus(), utils.ColoredString(c.Name, color.FgWhite), c.GetDisplayCPUPerc()} +} + +// GetDisplayStatus returns the colored status of the container +func (c *Container) GetDisplayStatus() string { + state := c.Container.State + if c.Container.State == "exited" { + state += " (" + strconv.Itoa(c.Details.State.ExitCode) + ")" + } + + return utils.ColoredString(state, c.GetColor()) } // GetDisplayCPUPerc colors the cpu percentage based on how extreme it is @@ -292,6 +302,9 @@ func (c *Container) ProducingLogs() bool { func (c *Container) GetColor() color.Attribute { switch c.Container.State { case "exited": + if c.Details.State.ExitCode == 0 { + return color.FgBlue + } return color.FgRed case "created": return color.FgCyan diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 24976e88..0e79fe84 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" "sync" + "syscall" "time" "github.com/go-errors/errors" @@ -366,3 +367,13 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error { } return nil } + +// Kill kills a process. If the process has Setpgid == true, then we have anticipated that it might spawn its own child processes, so we've given it a process group ID (PGID) equal to its process id (PID) and given its child processes will inherit the PGID, we can kill that group, rather than killing the process itself. +func (c *OSCommand) Kill(cmd *exec.Cmd) error { + if cmd.SysProcAttr != nil && cmd.SysProcAttr.Setpgid == true { + // minus sign means we're talking about a PGID as opposed to a PID + return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + } + + return cmd.Process.Kill() +} diff --git a/pkg/commands/service.go b/pkg/commands/service.go index 3ffbf740..f04a20d9 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -27,7 +27,7 @@ func (s *Service) GetDisplayStrings(isFocused bool) []string { } cont := s.Container - return []string{utils.ColoredString(cont.Container.State, cont.GetColor()), utils.ColoredString(s.Name, color.FgWhite), cont.GetDisplayCPUPerc()} + return []string{cont.GetDisplayStatus(), utils.ColoredString(s.Name, color.FgWhite), cont.GetDisplayCPUPerc()} } // Remove removes the service's containers @@ -68,7 +68,6 @@ func (s *Service) ViewLogs() (*exec.Cmd, error) { // so long as this is commented in, the child process does not receive the interrupt cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, - Pgid: 0, } return cmd, nil diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 46ae8256..629e42a2 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -110,6 +110,7 @@ type CommandTemplatesConfig struct { RebuildService string `yaml:"rebuildService,omitempty"` ContainerLogs string `yaml:"containerLogs,omitempty"` ContainerTTYLogs string `yaml:"containerTTYLogs,omitempty"` + AllLogs string `yaml:"allLogs,omitempty"` } type OSConfig struct { @@ -164,6 +165,7 @@ func GetDefaultConfig() UserConfig { ServiceLogs: "apdev logs {{ .Name }}", ContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .ID }}", ContainerTTYLogs: "docker logs --follow --tail=100 {{ .ID }}", + AllLogs: "apdev logs --tail=100", }, OS: GetPlatformDefaultConfig(), Update: UpdateConfig{ diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index d96c3585..23c6d862 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -77,6 +77,10 @@ type containerPanelState struct { ContextIndex int // for specifying if you are looking at logs/stats/config/etc } +type statusState struct { + ContextIndex int // for specifying if you are looking at credits/logs +} + type menuPanelState struct { SelectedLine int } @@ -96,6 +100,7 @@ type panelStates struct { Menu *menuPanelState Main *mainPanelState Images *imagePanelState + Status *statusState } type guiState struct { @@ -121,6 +126,7 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand Main: &mainPanelState{ ObjectKey: "", }, + Status: &statusState{ContextIndex: 0}, }, } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index f3552ade..2718effe 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -133,6 +133,20 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleOpenConfig, Description: gui.Tr.OpenConfig, }, + { + ViewName: "status", + Key: '[', + Modifier: gocui.ModNone, + Handler: gui.handleStatusPrevContext, + Description: gui.Tr.PreviousContext, + }, + { + ViewName: "status", + Key: ']', + Modifier: gocui.ModNone, + Handler: gui.handleStatusNextContext, + Description: gui.Tr.NextContext, + }, { ViewName: "menu", Key: gocui.KeyEsc, diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 3cb37f61..418e3576 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -3,10 +3,16 @@ package gui import ( "fmt" "strings" + "syscall" + "github.com/go-errors/errors" "github.com/jesseduffield/gocui" ) +func (gui *Gui) getStatusContexts() []string { + return []string{"logs", "credits"} +} + func (gui *Gui) refreshStatus() error { v := gui.getStatusView() @@ -28,6 +34,36 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { return err } + key := gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex] + if gui.State.Panels.Main.ObjectKey == key { + return nil + } else { + gui.State.Panels.Main.ObjectKey = key + } + + gui.clearMainView() + + switch gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex] { + case "credits": + if err := gui.renderCredits(); err != nil { + return err + } + case "logs": + if err := gui.renderAllLogs(); err != nil { + return err + } + default: + return errors.New("Unknown context for status panel") + } + + return nil +} + +func (gui *Gui) renderCredits() error { + mainView := gui.getMainView() + mainView.Autoscroll = false + mainView.Title = "about" + dashboardString := strings.Join( []string{ lazydockerTitle(), @@ -37,7 +73,40 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { "Raise an Issue: https://github.com/jesseduffield/lazydocker/issues", }, "\n\n") - return gui.renderString(g, "main", dashboardString) + go gui.T.NewTask(func(stop chan struct{}) { + gui.renderString(gui.g, "main", dashboardString) + }) + + return nil +} + +func (gui *Gui) renderAllLogs() error { + mainView := gui.getMainView() + mainView.Autoscroll = true + mainView.Title = "logs" + + go gui.T.NewTask(func(stop chan struct{}) { + gui.clearMainView() + + cmd := gui.OSCommand.RunCustomCommand(gui.Config.UserConfig.CommandTemplates.AllLogs) + + cmd.Stdout = mainView + cmd.Stderr = mainView + + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + cmd.Start() + + go func() { + <-stop + if err := gui.OSCommand.Kill(cmd); err != nil { + gui.Log.Error(err) + } + }() + + cmd.Wait() + }) + + return nil } func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error { @@ -60,3 +129,29 @@ func lazydockerTitle() string { |___/ ` } + +func (gui *Gui) handleStatusPrevContext(g *gocui.Gui, v *gocui.View) error { + contexts := gui.getStatusContexts() + if gui.State.Panels.Status.ContextIndex >= len(contexts)-1 { + gui.State.Panels.Status.ContextIndex = 0 + } else { + gui.State.Panels.Status.ContextIndex++ + } + + gui.handleStatusSelect(gui.g, v) + + return nil +} + +func (gui *Gui) handleStatusNextContext(g *gocui.Gui, v *gocui.View) error { + contexts := gui.getStatusContexts() + if gui.State.Panels.Status.ContextIndex <= 0 { + gui.State.Panels.Status.ContextIndex = len(contexts) - 1 + } else { + gui.State.Panels.Status.ContextIndex-- + } + + gui.handleStatusSelect(gui.g, v) + + return nil +} diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go index df4655c2..c1545cd7 100644 --- a/pkg/gui/subprocess.go +++ b/pkg/gui/subprocess.go @@ -21,6 +21,10 @@ func (gui *Gui) RunWithSubprocesses() error { if err == gocui.ErrQuit { break } else if err == gui.Errors.ErrSubProcess { + // preparing the state for when we return + gui.State.PreviousView = gui.currentViewName() + gui.State.Panels.Main.ObjectKey = "" + if err := gui.runCommand(); err != nil { return err } @@ -33,8 +37,6 @@ func (gui *Gui) RunWithSubprocesses() error { } func (gui *Gui) runCommand() error { - gui.State.PreviousView = gui.currentViewName() - gui.SubProcess.Stdout = os.Stdout gui.SubProcess.Stderr = os.Stdout gui.SubProcess.Stdin = os.Stdin @@ -46,7 +48,9 @@ func (gui *Gui) runCommand() error { <-c signal.Stop(c) - gui.SubProcess.Process.Kill() + if err := gui.OSCommand.Kill(gui.SubProcess); err != nil { + gui.Log.Error(err) + } }() fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue))