diff --git a/pkg/app/app.go b/pkg/app/app.go index c164211f..90b5292f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -21,13 +21,15 @@ type App struct { DockerCommand *commands.DockerCommand Gui *gui.Gui Tr *i18n.Localizer + ErrorChan chan error } // NewApp bootstrap a new application func NewApp(config *config.AppConfig) (*App, error) { app := &App{ - closers: []io.Closer{}, - Config: config, + closers: []io.Closer{}, + Config: config, + ErrorChan: make(chan error), } var err error app.Log = log.NewLogger(config, "23432119147a4367abf7c0de2aa99a2d") @@ -36,11 +38,11 @@ func NewApp(config *config.AppConfig) (*App, error) { // here is the place to make use of the docker-compose.yml file in the current directory - app.DockerCommand, err = commands.NewDockerCommand(app.Log, app.OSCommand, app.Tr, app.Config) + app.DockerCommand, err = commands.NewDockerCommand(app.Log, app.OSCommand, app.Tr, app.Config, app.ErrorChan) if err != nil { return app, err } - app.Gui, err = gui.NewGui(app.Log, app.DockerCommand, app.OSCommand, app.Tr, config) + app.Gui, err = gui.NewGui(app.Log, app.DockerCommand, app.OSCommand, app.Tr, config, app.ErrorChan) if err != nil { return app, err } diff --git a/pkg/commands/container.go b/pkg/commands/container.go index 6cf112ef..d59abfaf 100644 --- a/pkg/commands/container.go +++ b/pkg/commands/container.go @@ -3,6 +3,7 @@ package commands import ( "context" "os/exec" + "strconv" "strings" "time" @@ -32,6 +33,12 @@ type Container struct { Details Details } +type DetailsCollection struct { + InspectDetails Details + CLIStats ContainerCliStat + ClientStats ContainerStats +} + type Details struct { ID string `json:"Id"` Created time.Time `json:"Created"` @@ -239,7 +246,31 @@ 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.Stats.CPUPerc} + return []string{utils.ColoredString(c.Container.State, c.GetColor()), utils.ColoredString(c.Name, color.FgWhite), c.GetDisplayCPUPerc()} +} + +// GetDisplayCPUPerc colors the cpu percentage based on how extreme it is +func (c *Container) GetDisplayCPUPerc() string { + if c.Stats.CPUPerc == "" { + return "" + } + + percentage, err := strconv.ParseFloat(strings.TrimSuffix(c.Stats.CPUPerc, "%"), 32) + if err != nil { + c.Log.Error(err) + return "" + } + + var clr color.Attribute + if percentage > 90 { + clr = color.FgRed + } else if percentage > 50 { + clr = color.FgYellow + } else { + clr = color.FgWhite + } + + return utils.ColoredString(c.Stats.CPUPerc, clr) } // GetColor Container color @@ -301,7 +332,7 @@ func (c *Container) RestartService() error { // Attach attaches the container func (c *Container) Attach() (*exec.Cmd, error) { // verify that we can in fact attach to this container - if !c.Details.Config.AttachStdin { + if !c.Details.Config.OpenStdin { return nil, errors.New("Container does not support attaching. You must either run the service with the '-it' flag or use `stdin_open: true, tty: true` in the docker-compose.yml file") } diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index cca91d49..b176c6ff 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -24,10 +24,11 @@ type DockerCommand struct { Config *config.AppConfig Client *client.Client InDockerComposeProject bool + ErrorChan chan error } // NewDockerCommand it runs git commands -func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, config *config.AppConfig) (*DockerCommand, error) { +func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { cli, err := client.NewEnvClient() if err != nil { return nil, err @@ -40,17 +41,19 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localize Config: config, Client: cli, InDockerComposeProject: true, // TODO: determine this at startup + ErrorChan: errorChan, }, nil } // UpdateContainerStats takes a slice of containers and returns the same slice but with new stats added // TODO: consider using this for everything stats-related -func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Container, error) { +func (c *DockerCommand) UpdateContainerStats(containers []*Container) { // TODO: consider using a stream rather than polling command := `docker stats --all --no-trunc --no-stream --format '{{json .}}'` output, err := c.OSCommand.RunCommandWithOutput(command) if err != nil { - return nil, err + c.ErrorChan <- err + return } jsonStats := "[" + strings.Join( @@ -63,7 +66,8 @@ func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Contai var stats []ContainerCliStat if err := json.Unmarshal([]byte(jsonStats), &stats); err != nil { - return nil, err + c.ErrorChan <- err + return } for _, stat := range stats { @@ -74,7 +78,9 @@ func (c *DockerCommand) UpdateContainerStats(containers []*Container) ([]*Contai } } - return containers, nil + c.Log.Warn("updated containers") + + return } // GetContainersAndServices returns a slice of docker containers @@ -147,10 +153,7 @@ func (c *DockerCommand) GetContainers() ([]*Container, error) { c.UpdateContainerDetails(ownContainers) - // ownContainers, err = c.UpdateContainerStats(ownContainers) - // if err != nil { - // return nil, err - // } + c.UpdateContainerStats(ownContainers) return ownContainers, nil } diff --git a/pkg/commands/service.go b/pkg/commands/service.go index 4e65c533..4a6d2685 100644 --- a/pkg/commands/service.go +++ b/pkg/commands/service.go @@ -26,7 +26,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.Stats.CPUPerc} + return []string{utils.ColoredString(cont.Container.State, cont.GetColor()), utils.ColoredString(s.Name, color.FgWhite), cont.GetDisplayCPUPerc()} } // Remove removes the service's containers diff --git a/pkg/gui/containers_panel.go b/pkg/gui/containers_panel.go index 5190c5ca..83bbfe2c 100644 --- a/pkg/gui/containers_panel.go +++ b/pkg/gui/containers_panel.go @@ -65,7 +65,7 @@ func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error { if err != gui.Errors.ErrNoContainers { return err } - return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedContainers")) + return nil } key := container.ID + "-" + gui.getContainerContexts()[gui.State.Panels.Containers.ContextIndex] @@ -171,7 +171,7 @@ func (gui *Gui) renderContainerLogs(mainView *gocui.View, container *commands.Co mainView.Autoscroll = true mainView.Title = "Logs" - if container.Details.Config.AttachStdin { + if container.Details.Config.OpenStdin { return gui.renderLogsForTTYContainer(mainView, container) } return gui.renderLogsForRegularContainer(mainView, container) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 361c0364..d1a5d592 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -74,6 +74,7 @@ type Gui struct { statusManager *statusManager waitForIntro sync.WaitGroup T *tasks.TaskManager + ErrorChan chan error } type servicePanelState struct { @@ -118,10 +119,11 @@ type guiState struct { SubProcessOutput string MainProcessMutex sync.Mutex MainProcessChan chan struct{} + Stats map[string]commands.ContainerStats } // NewGui builds a new gui handler -func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config *config.AppConfig) (*Gui, error) { +func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config *config.AppConfig, errorChan chan error) (*Gui, error) { initialState := guiState{ Containers: make([]*commands.Container, 0), @@ -151,11 +153,13 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand Log: log, DockerCommand: dockerCommand, OSCommand: oSCommand, + // TODO: look into this warning State: initialState, Config: config, Tr: tr, statusManager: &statusManager{}, T: tasks.NewTaskManager(log), + ErrorChan: errorChan, } gui.GenerateSentinelErrors() @@ -535,6 +539,12 @@ func (gui *Gui) Run() error { gui.goEvery(time.Millisecond*500, gui.refreshContainersAndServices) }() + go func() { + for err := range gui.ErrorChan { + gui.createErrorPanel(gui.g, err.Error()) + } + }() + g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout())) if err = gui.keybindings(g); err != nil { @@ -546,7 +556,11 @@ func (gui *Gui) Run() error { } func (gui *Gui) reRenderMain() error { - if gui.getMainView().IsTainted() { + mainView := gui.getMainView() + if mainView == nil { + return nil + } + if mainView.IsTainted() { gui.g.Update(func(g *gocui.Gui) error { return nil }) diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 631e0cc6..1b52cf0c 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -856,5 +856,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { ID: "PruningStatus", Other: "pruning", }, + &i18n.Message{ + ID: "StopService", + Other: "Are you sure you want to stop this service's containers? (enter/esc)", + }, ) }