diff --git a/pkg/commands/service.go b/pkg/commands/service.go index 4a6d2685..c611321a 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -2,6 +2,7 @@ package commands import ( "os/exec" + "syscall" "github.com/docker/docker/api/types" "github.com/fatih/color" @@ -59,3 +60,17 @@ func (s *Service) Attach() (*exec.Cmd, error) { func (s *Service) Top() (types.ContainerProcessList, error) { return s.Container.Top() } + +// ViewLogs attaches to a subprocess viewing the service's logs +func (s *Service) ViewLogs() (*exec.Cmd, error) { + templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ViewServiceLogs + command := utils.ApplyTemplate(templateString, s) + + cmd := s.OSCommand.ExecutableFromString(command) + 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 a059acd7..5dfdfee3 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -101,10 +101,11 @@ type GuiConfig struct { } type CommandTemplatesConfig struct { - RestartService string `yaml:"restartService,omitempty"` - DockerCompose string `yaml:"dockerCompose,omitempty"` - StopService string `yaml:"stopService,omitempty"` - ServiceLogs string `yaml:"serviceLogs,omitempty"` + RestartService string `yaml:"restartService,omitempty"` + DockerCompose string `yaml:"dockerCompose,omitempty"` + StopService string `yaml:"stopService,omitempty"` + ServiceLogs string `yaml:"serviceLogs,omitempty"` + ViewServiceLogs string `yaml:"viewServiceLogs,omitempty"` } type OSConfig struct { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index cbac814f..d0ad4e42 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -1,20 +1,15 @@ package gui import ( - "fmt" "math" - "strings" "sync" // "io" // "io/ioutil" - "io/ioutil" - "os" "os/exec" "time" - "github.com/fatih/color" "github.com/go-errors/errors" // "strings" @@ -568,51 +563,6 @@ func (gui *Gui) reRenderMain() error { return nil } -// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration -// if the error returned from a run is a ErrSubProcess, it runs the subprocess -// otherwise it handles the error, possibly by quitting the application -func (gui *Gui) RunWithSubprocesses() error { - for { - if err := gui.Run(); err != nil { - if err == gocui.ErrQuit { - break - } else if err == gui.Errors.ErrSubProcess { - if err := gui.runCommand(); err != nil { - return err - } - } else { - return err - } - } - } - return nil -} - -func (gui *Gui) runCommand() error { - gui.SubProcess.Stdout = os.Stdout - gui.SubProcess.Stderr = os.Stdout - gui.SubProcess.Stdin = os.Stdin - - fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue)) - - if err := gui.SubProcess.Run(); err != nil { - // not handling the error explicitly because usually we're going to see it - // in the output anyway - gui.Log.Error(err) - } - - gui.SubProcess.Stdout = ioutil.Discard - gui.SubProcess.Stderr = ioutil.Discard - gui.SubProcess.Stdin = nil - gui.SubProcess = nil - - // fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.SLocalize("pressEnterToReturn"), color.FgGreen)) - - // fmt.Scanln() // wait for enter press - - return nil -} - func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error { if gui.Config.UserConfig.ConfirmOnQuit { return gui.createConfirmationPanel(g, v, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index f0437046..1664466c 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -64,12 +64,14 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Key: 'q', Modifier: gocui.ModNone, Handler: gui.quit, - }, { - ViewName: "", - Key: gocui.KeyCtrlC, - Modifier: gocui.ModNone, - Handler: gui.quit, - }, { + }, + // { + // ViewName: "", + // Key: gocui.KeyCtrlC, + // Modifier: gocui.ModNone, + // Handler: gui.quit, + // }, + { ViewName: "", Key: gocui.KeyEsc, Modifier: gocui.ModNone, @@ -201,6 +203,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleServiceAttach, Description: gui.Tr.SLocalize("attach"), }, + { + ViewName: "services", + Key: 'm', + Modifier: gocui.ModNone, + Handler: gui.handleServiceViewLogs, + Description: gui.Tr.SLocalize("viewLogs"), + }, { ViewName: "services", Key: '[', diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index d20e3858..0590e2f8 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -278,3 +278,18 @@ func (gui *Gui) handleServiceAttach(g *gocui.Gui, v *gocui.View) error { gui.SubProcess = c return gui.Errors.ErrSubProcess } + +func (gui *Gui) handleServiceViewLogs(g *gocui.Gui, v *gocui.View) error { + service, err := gui.getSelectedService() + if err != nil { + return nil + } + + c, err := service.ViewLogs() + if err != nil { + return gui.createErrorPanel(gui.g, err.Error()) + } + + gui.SubProcess = c + return gui.Errors.ErrSubProcess +} diff --git a/pkg/gui/subprocess.go b/pkg/gui/subprocess.go new file mode 100644 index 00000000..d1c1a9c1 --- /dev/null +++ b/pkg/gui/subprocess.go @@ -0,0 +1,92 @@ +package gui + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "os" + "os/signal" + "strings" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazydocker/pkg/utils" +) + +// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration +// if the error returned from a run is a ErrSubProcess, it runs the subprocess +// otherwise it handles the error, possibly by quitting the application +func (gui *Gui) RunWithSubprocesses() error { + for { + if err := gui.Run(); err != nil { + if err == gocui.ErrQuit { + break + } else if err == gui.Errors.ErrSubProcess { + if err := gui.runCommand(); err != nil { + return err + } + } else { + return err + } + } + } + return nil +} + +func (gui *Gui) runCommand() error { + gui.SubProcess.Stdout = os.Stdout + gui.SubProcess.Stderr = os.Stdout + // gui.SubProcess.Stdin = os.Stdin + + reader := bufio.NewReader(os.Stdin) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + go func() { + <-c + gui.SubProcess.Process.Kill() + }() + + w, err := gui.SubProcess.StdinPipe() + if err != nil { + return err + } + + go func() { + for { + input, err := reader.ReadByte() + gui.Log.Warn("byte received") + gui.Log.Warn(input) + if input == 3 { + gui.SubProcess.Process.Kill() + break + } + if err != nil && err == io.EOF { + gui.SubProcess.Process.Kill() + break + } + w.Write([]byte{input}) + } + }() + + fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue)) + + if err := gui.SubProcess.Run(); err != nil { + // not handling the error explicitly because usually we're going to see it + // in the output anyway + gui.Log.Error(err) + } + + gui.SubProcess.Stdout = ioutil.Discard + gui.SubProcess.Stderr = ioutil.Discard + gui.SubProcess.Stdin = nil + gui.SubProcess = nil + + // fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.SLocalize("pressEnterToReturn"), color.FgGreen)) + + // fmt.Scanln() // wait for enter press + + return nil +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 581e5b72..0c595bbe 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -816,6 +816,10 @@ func addEnglish(i18nObject *i18n.Bundle) error { ID: "attach", Other: "attach", }, + &i18n.Message{ + ID: "viewLogs", + Other: "view logs", + }, &i18n.Message{ ID: "ServicesTitle", Other: "Services",