diff --git a/pkg/commands/container.go b/pkg/commands/container.go index c4a3a8b6..614bac45 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -418,3 +418,13 @@ func (c *Container) TTYLogsCommand() *exec.Cmd { func (c *Container) Inspect() (types.ContainerJSON, error) { return c.Client.ContainerInspect(context.Background(), c.ID) } + +// RenderTop returns details about the container +func (c *Container) RenderTop() (string, error) { + result, err := c.Client.ContainerTop(context.Background(), c.ID, []string{}) + if err != nil { + return "", err + } + + return utils.RenderTable(append([][]string{result.Titles}, result.Processes...)) +} diff --git a/pkg/commands/service.go b/pkg/commands/service.go index 76e9a61a..8c7dce7e 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -78,3 +78,14 @@ func (s *Service) ViewLogs() (*exec.Cmd, error) { return cmd, nil } + +// RenderTop renders the process list of the service +func (s *Service) RenderTop() (string, error) { + templateString := s.OSCommand.Config.UserConfig.CommandTemplates.ServiceTop + command := utils.ApplyTemplate( + templateString, + s.DockerCommand.NewCommandObject(CommandObject{Service: s}), + ) + + return s.OSCommand.RunCommandWithOutput(command) +} diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index a106408b..def256b8 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -118,6 +118,7 @@ type CommandTemplatesConfig struct { ViewAllLogs string `yaml:"viewAlLogs,omitempty"` DockerComposeConfig string `yaml:"dockerComposeConfig,omitempty"` CheckDockerComposeConfig string `yaml:"checkDockerComposeConfig,omitempty"` + ServiceTop string `yaml:"serviceTop,omitempty"` } type OSConfig struct { @@ -188,6 +189,7 @@ func GetDefaultConfig() UserConfig { ContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .Container.ID }}", ViewContainerLogs: "docker logs --timestamps --follow --tail=100 {{ .Container.ID }}", ContainerTTYLogs: "docker logs --follow --tail=100 {{ .Container.ID }}", + ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}", }, CustomCommands: CustomCommands{ Containers: []CustomCommand{ diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 33d2ef8d..ad1d4608 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -18,11 +18,11 @@ import ( // list panel functions func (gui *Gui) getContainerContexts() []string { - return []string{"logs", "stats", "config"} + return []string{"logs", "stats", "config", "top"} } func (gui *Gui) getContainerContextTitles() []string { - return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.ConfigTitle} + return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.ConfigTitle, gui.Tr.TopTitle} } func (gui *Gui) getSelectedContainer() (*commands.Container, error) { @@ -101,6 +101,10 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { 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") } @@ -142,6 +146,21 @@ func (gui *Gui) renderContainerStats(container *commands.Container) error { }) } +func (gui *Gui) renderContainerTop(container *commands.Container) error { + mainView := gui.getMainView() + mainView.Autoscroll = false + mainView.Wrap = false + + 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 diff --git a/pkg/gui/services_panel.go b/pkg/gui/services_panel.go index 8f4368a3..1e46456e 100644 --- a/pkg/gui/services_panel.go +++ b/pkg/gui/services_panel.go @@ -2,6 +2,7 @@ package gui import ( "fmt" + "time" "github.com/fatih/color" "github.com/go-errors/errors" @@ -13,11 +14,11 @@ import ( // list panel functions func (gui *Gui) getServiceContexts() []string { - return []string{"logs", "stats", "container-config"} + return []string{"logs", "stats", "container-config", "top"} } func (gui *Gui) getServiceContextTitles() []string { - return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.ContainerConfigTitle} + return []string{gui.Tr.LogsTitle, gui.Tr.StatsTitle, gui.Tr.ContainerConfigTitle, gui.Tr.TopTitle} } func (gui *Gui) getSelectedService() (*commands.Service, error) { @@ -95,6 +96,10 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error { if err := gui.renderContainerConfig(service.Container); err != nil { return err } + case "top": + if err := gui.renderServiceTop(service); err != nil { + return err + } default: return errors.New("Unknown context for services panel") } @@ -110,6 +115,21 @@ func (gui *Gui) renderServiceStats(service *commands.Service) error { return gui.renderContainerStats(service.Container) } +func (gui *Gui) renderServiceTop(service *commands.Service) error { + mainView := gui.getMainView() + mainView.Autoscroll = false + mainView.Wrap = false + + return gui.T.NewTickerTask(time.Second, func(stop chan struct{}) { gui.clearMainView() }, func(stop, notifyStopped chan struct{}) { + contents, err := service.RenderTop() + if err != nil { + gui.reRenderString(gui.g, "main", err.Error()) + } + + gui.reRenderString(gui.g, "main", contents) + }) +} + func (gui *Gui) renderServiceLogs(service *commands.Service) error { service, err := gui.getSelectedService() if err != nil { diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 0a56bcf0..f6914e97 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -44,6 +44,7 @@ type TranslationSet struct { ServicesTitle string ContainersTitle string StandaloneContainersTitle string + TopTitle string ImagesTitle string VolumesTitle string NoContainers string @@ -128,6 +129,7 @@ func englishSet() TranslationSet { ErrorTitle: "Error", LogsTitle: "Logs", ConfigTitle: "Config", + TopTitle: "Top", StatsTitle: "Stats", CreditsTitle: "About", ContainerConfigTitle: "Container Config", diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 977175e2..1b3c45f3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -157,6 +157,11 @@ func renderDisplayableList(items []Displayable, config RenderListConfig) (string stringArrays = append([][]string{config.Header}, stringArrays...) } + return RenderTable(stringArrays) +} + +// RenderTable takes an array of string arrays and returns a table containing the values +func RenderTable(stringArrays [][]string) (string, error) { if !displayArraysAligned(stringArrays) { return "", errors.New("Each item must return the same number of strings to display") }