From 1f5df32141bb5c158e2d5cc53386f14d02c111c9 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Jun 2019 23:20:42 +1000 Subject: [PATCH] WIP streaming commands --- pkg/config/app_config.go | 1 + pkg/gui/containers_panel.go | 15 +++--- pkg/gui/images_panel.go | 14 ++---- pkg/gui/keybindings.go | 7 +++ pkg/gui/services_panel.go | 92 +++++++++++++++++++++++++++---------- pkg/gui/view_helpers.go | 7 +++ pkg/i18n/english.go | 4 ++ 7 files changed, 100 insertions(+), 40 deletions(-) diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 5dfdfee3..c6c7bdac 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -106,6 +106,7 @@ type CommandTemplatesConfig struct { StopService string `yaml:"stopService,omitempty"` ServiceLogs string `yaml:"serviceLogs,omitempty"` ViewServiceLogs string `yaml:"viewServiceLogs,omitempty"` + RebuildService string `yaml:"rebuildService,omitempty"` } type OSConfig struct { diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index a37cdfcb..4aae8e9c 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -81,10 +81,6 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { mainView := gui.getMainView() - mainView.Clear() - mainView.SetOrigin(0, 0) - mainView.SetCursor(0, 0) - switch gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] { case "logs": if err := gui.renderContainerLogs(mainView, container); err != nil { @@ -114,9 +110,11 @@ func (gui *Gui) renderContainerConfig(mainView *gocui.View, container *commands. return err } - return gui.T.NewTask(func(stop chan struct{}) { + go gui.T.NewTask(func(stop chan struct{}) { gui.renderString(gui.g, "main", string(data)) }) + + return nil } func (gui *Gui) renderContainerStats(mainView *gocui.View, container *commands.Container) error { @@ -153,13 +151,14 @@ func (gui *Gui) renderLogsForRegularContainer(mainView *gocui.View, container *c cmd.Stdout = mainView cmd.Stderr = mainView - go gui.runProcessWithLock(cmd) + gui.runProcessWithLock(cmd) return nil } func (gui *Gui) runProcessWithLock(cmd *exec.Cmd) { - gui.T.NewTask(func(stop chan struct{}) { + go gui.T.NewTask(func(stop chan struct{}) { + gui.clearMainView() cmd.Start() go func() { @@ -189,7 +188,7 @@ func (gui *Gui) renderLogsForTTYContainer(mainView *gocui.View, container *comma } }() - go gui.runProcessWithLock(cmd) + gui.runProcessWithLock(cmd) return nil } diff --git a/pkg/gui/images_panel.go b/pkg/gui/images_panel.go index 3cb88fb4..9142d759 100644 --- a/pkg/gui/images_panel.go +++ b/pkg/gui/images_panel.go @@ -78,10 +78,6 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error { mainView := gui.getMainView() - mainView.Clear() - mainView.SetOrigin(0, 0) - mainView.SetCursor(0, 0) - switch gui.getImageContexts()[gui.State.Panels.Images.ContextIndex] { case "config": if err := gui.renderImageConfig(mainView, Image); err != nil { @@ -95,11 +91,7 @@ func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) renderImageConfig(mainView *gocui.View, image *commands.Image) error { - mainView.Autoscroll = false - mainView.Wrap = false - mainView.Title = "Config" - - gui.T.NewTask(func(stop chan struct{}) { + go gui.T.NewTask(func(stop chan struct{}) { output := "" output += utils.WithPadding("ID: ", 10) + image.Image.ID + "\n" output += utils.WithPadding("Tags: ", 10) + utils.ColoredString(strings.Join(image.Image.RepoTags, ", "), color.FgGreen) + "\n" @@ -113,6 +105,10 @@ func (gui *Gui) renderImageConfig(mainView *gocui.View, image *commands.Image) e output += "\n\n" + history + mainView.Autoscroll = false + mainView.Wrap = false + mainView.Title = "Config" + gui.renderString(gui.g, "main", output) }) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c636863a..f7fa50c4 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -242,6 +242,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding { Handler: gui.handleServicesNextContext, Description: gui.Tr.NextContext, }, + { + ViewName: "services", + Key: 'R', + Modifier: gocui.ModNone, + Handler: gui.handleServiceRestartMenu, + Description: gui.Tr.ViewRestartOptions, + }, { ViewName: "images", Key: '[', diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index f711dfff..9f87569a 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -1,8 +1,10 @@ package gui import ( + "bytes" "encoding/json" "fmt" + "io" "github.com/fatih/color" "github.com/go-errors/errors" @@ -73,10 +75,6 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error { mainView := gui.getMainView() - mainView.Clear() - mainView.SetOrigin(0, 0) - mainView.SetCursor(0, 0) - switch gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex] { case "logs": if err := gui.renderServiceLogs(mainView, service); err != nil { @@ -101,7 +99,7 @@ func (gui *Gui) renderServiceConfig(mainView *gocui.View, service *commands.Serv mainView.Autoscroll = false mainView.Title = "Config" - gui.T.NewTask(func(stop chan struct{}) { + go gui.T.NewTask(func(stop chan struct{}) { // TODO: actually show service config data, err := json.MarshalIndent(&service.Container.Container, "", " ") if err != nil { @@ -129,6 +127,9 @@ func (gui *Gui) renderServiceLogs(mainView *gocui.View, service *commands.Servic } if service.Container == nil { + go gui.T.NewTask(func(stop chan struct{}) { + gui.clearMainView() + }) return nil } @@ -183,14 +184,14 @@ func (gui *Gui) handleServicesNextContext(g *gocui.Gui, v *gocui.View) error { return nil } -type removeServiceOption struct { +type commandOption struct { description string command string } // GetDisplayStrings is a function. -func (r *removeServiceOption) GetDisplayStrings(isFocused bool) []string { - return []string{r.description, color.New(color.FgRed).Sprint(r.command)} +func (r *commandOption) GetDisplayStrings(isFocused bool) []string { + return []string{r.description, color.New(color.FgCyan).Sprint(r.command)} } func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error { @@ -201,7 +202,7 @@ func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error { composeCommand := gui.Config.UserConfig.CommandTemplates.DockerCompose - options := []*removeServiceOption{ + options := []*commandOption{ { description: gui.Tr.Remove, command: fmt.Sprintf("%s rm --stop --force %s", composeCommand, service.Name), @@ -215,20 +216,7 @@ func (gui *Gui) handleServiceRemoveMenu(g *gocui.Gui, v *gocui.View) error { }, } - handleMenuPress := func(index int) error { - if options[index].command == "" { - return nil - } - return gui.WithWaitingStatus(gui.Tr.RemovingStatus, func() error { - if err := gui.OSCommand.RunCommand(options[index].command); err != nil { - return gui.createErrorPanel(gui.g, err.Error()) - } - - return gui.refreshContainersAndServices() - }) - } - - return gui.createMenu("", options, len(options), handleMenuPress) + return gui.createCommandMenu(options, gui.Tr.RemovingStatus) } func (gui *Gui) handleServiceStop(g *gocui.Gui, v *gocui.View) error { @@ -293,3 +281,61 @@ func (gui *Gui) handleServiceViewLogs(g *gocui.Gui, v *gocui.View) error { gui.SubProcess = c return gui.Errors.ErrSubProcess } + +func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error { + service, err := gui.getSelectedService() + if err != nil { + return nil + } + + options := []*commandOption{ + { + description: gui.Tr.Restart, + command: utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.RestartService, service.Name), + }, + { + description: gui.Tr.Rebuild, + command: utils.ApplyTemplate(gui.Config.UserConfig.CommandTemplates.RebuildService, service.Name), + }, + { + description: gui.Tr.Cancel, + }, + } + + return gui.createCommandMenu(options, gui.Tr.RestartingStatus) +} + +func (gui *Gui) createCommandMenu(options []*commandOption, status string) error { + + handleMenuPress := func(index int) error { + if options[index].command == "" { + return nil + } + gui.getMainView().Clear() + return gui.WithWaitingStatus(status, func() error { + done := make(chan struct{}) + go gui.T.NewTask(func(stop chan struct{}) { + cmd := gui.OSCommand.RunCustomCommand(options[index].command) + + cmd.Stdout = gui.getMainView() + var stderrBuf bytes.Buffer + cmd.Stderr = io.MultiWriter(gui.getMainView(), &stderrBuf) + + cmd.Run() + + done <- struct{}{} + + errorMessage := stderrBuf.String() + if errorMessage != "" { + gui.createErrorPanel(gui.g, errorMessage) + } + }) + + <-done + + return nil + }) + } + + return gui.createMenu("", options, len(options), handleMenuPress) +} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 2aff7449..8b89a144 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -359,3 +359,10 @@ func (gui *Gui) isPopupPanel(viewName string) bool { func (gui *Gui) popupPanelFocused() bool { return gui.isPopupPanel(gui.currentViewName()) } + +func (gui *Gui) clearMainView() { + mainView := gui.getMainView() + mainView.Clear() + mainView.SetOrigin(0, 0) + mainView.SetCursor(0, 0) +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 8baac9d4..6fa266b4 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -34,6 +34,7 @@ type TranslationSet struct { RemoveService string Stop string Restart string + Rebuild string PreviousContext string NextContext string Attach string @@ -50,6 +51,7 @@ type TranslationSet struct { PruningStatus string StopService string PressEnterToReturn string + ViewRestartOptions string } func englishSet() TranslationSet { @@ -79,6 +81,7 @@ func englishSet() TranslationSet { RemoveService: "remove containers", Stop: "stop", Restart: "restart", + Rebuild: "rebuild", PreviousContext: "previous context", NextContext: "next context", Attach: "attach", @@ -86,6 +89,7 @@ func englishSet() TranslationSet { RemoveImage: "remove image", RemoveWithoutPrune: "remove without deleting untagged parents", PruneImages: "prune unused images", + ViewRestartOptions: "view restart options", AnonymousReportingTitle: "Help make lazydocker better", AnonymousReportingPrompt: "Would you like to enable anonymous reporting data to help improve lazydocker? (enter/esc)",