mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-21 23:01:03 +00:00
no more separate code paths for tty container logs
This commit is contained in:
parent
00f05ae012
commit
014dce909c
8 changed files with 61 additions and 52 deletions
|
|
@ -426,3 +426,8 @@ func (c *Container) RenderTop() (string, error) {
|
|||
|
||||
return utils.RenderTable(append([][]string{result.Titles}, result.Processes...))
|
||||
}
|
||||
|
||||
// DetailsLoaded tells us whether we have yet loaded the details for a container. Because this is an asynchronous operation, sometimes we have the container before we have its details. Details is a struct, not a pointer to a struct, so it starts off with heaps of zero values. One of which is the container Image, which starts as a blank string. Given that every container should have an image, this is a good proxy to use
|
||||
func (c *Container) DetailsLoaded() bool {
|
||||
return c.Details.Image != ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type CommandTemplatesConfig struct {
|
|||
ServiceLogs string `yaml:"serviceLogs,omitempty"`
|
||||
ViewServiceLogs string `yaml:"viewServiceLogs,omitempty"`
|
||||
RebuildService string `yaml:"rebuildService,omitempty"`
|
||||
RecreateService string `yaml:"recreateService,omitempty"`
|
||||
|
||||
// ViewContainerLogs is for viewing the container logs in a subprocess. We have this as a separate command in case you want to show all the logs rather than just tail them for the sake of reducing CPU load when in the lazydocker GUI
|
||||
ViewContainerLogs string `yaml:"viewContainerLogs,omitempty"`
|
||||
|
|
@ -179,6 +180,7 @@ func GetDefaultConfig() UserConfig {
|
|||
DockerCompose: "docker-compose",
|
||||
RestartService: "{{ .DockerCompose }} restart {{ .Service.Name }}",
|
||||
RebuildService: "{{ .DockerCompose }} up -d --build {{ .Service.Name }}",
|
||||
RecreateService: "{{ .DockerCompose }} up -d --force-recreate {{ .Service.Name }}",
|
||||
StopService: "{{ .DockerCompose }} stop {{ .Service.Name }}",
|
||||
ServiceLogs: "{{ .DockerCompose }} logs --since=60m --follow {{ .Service.Name }}",
|
||||
ViewServiceLogs: "{{ .DockerCompose }} logs --follow {{ .Service.Name }}",
|
||||
|
|
@ -188,7 +190,6 @@ func GetDefaultConfig() UserConfig {
|
|||
CheckDockerComposeConfig: "{{ .DockerCompose }} config --quiet",
|
||||
ContainerLogs: "docker logs --timestamps --follow --since=60m {{ .Container.ID }}",
|
||||
ViewContainerLogs: "docker logs --timestamps --follow --since=60m {{ .Container.ID }}",
|
||||
ContainerTTYLogs: "docker logs --follow --since=60m {{ .Container.ID }}",
|
||||
ServiceTop: "{{ .DockerCompose }} top {{ .Service.Name }}",
|
||||
},
|
||||
CustomCommands: CustomCommands{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
|
@ -164,51 +162,18 @@ func (gui *Gui) renderContainerLogs(container *commands.Container) error {
|
|||
mainView.Autoscroll = true
|
||||
mainView.Wrap = true
|
||||
|
||||
if container.Details.Config.OpenStdin {
|
||||
return gui.renderLogsForTTYContainer(container)
|
||||
}
|
||||
return gui.renderLogsForRegularContainer(container)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderLogsForRegularContainer(container *commands.Container) error {
|
||||
return gui.renderLogs(container, gui.Config.UserConfig.CommandTemplates.ContainerLogs, func(cmd *exec.Cmd) {
|
||||
mainView := gui.getMainView()
|
||||
cmd.Stdout = mainView
|
||||
cmd.Stderr = mainView
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) renderLogsForTTYContainer(container *commands.Container) error {
|
||||
return gui.renderLogs(container, gui.Config.UserConfig.CommandTemplates.ContainerTTYLogs, func(cmd *exec.Cmd) {
|
||||
// for some reason just saying cmd.Stdout = mainView does not work here as it does for non-tty containers, so we feed it through line by line
|
||||
r, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
gui.ErrorChan <- err
|
||||
}
|
||||
|
||||
go func() {
|
||||
mainView := gui.getMainView()
|
||||
s := bufio.NewScanner(r)
|
||||
s.Split(bufio.ScanLines)
|
||||
for s.Scan() {
|
||||
// I might put a check on the stopped channel here. Would mean more code duplication though
|
||||
mainView.Write(append(s.Bytes(), '\n'))
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) renderLogs(container *commands.Container, template string, setup func(*exec.Cmd)) error {
|
||||
return gui.T.NewTickerTask(time.Millisecond*200, nil, func(stop, notifyStopped chan struct{}) {
|
||||
gui.clearMainView()
|
||||
|
||||
command := utils.ApplyTemplate(
|
||||
template,
|
||||
gui.Config.UserConfig.CommandTemplates.ContainerLogs,
|
||||
gui.DockerCommand.NewCommandObject(commands.CommandObject{Container: container}),
|
||||
)
|
||||
cmd := gui.OSCommand.RunCustomCommand(command)
|
||||
|
||||
setup(cmd)
|
||||
mainView := gui.getMainView()
|
||||
cmd.Stdout = mainView
|
||||
cmd.Stderr = mainView
|
||||
|
||||
cmd.Start()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package gui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
// "io"
|
||||
|
|
@ -267,12 +268,17 @@ func (gui *Gui) Run() error {
|
|||
gui.goEvery(time.Millisecond*100, gui.refreshContainersAndServices)
|
||||
gui.goEvery(time.Millisecond*100, gui.refreshVolumes)
|
||||
gui.goEvery(time.Millisecond*1000, gui.DockerCommand.UpdateContainerDetails)
|
||||
gui.goEvery(time.Millisecond*1000, gui.checkForContextChange)
|
||||
}()
|
||||
|
||||
go gui.DockerCommand.MonitorContainerStats()
|
||||
|
||||
go func() {
|
||||
for err := range gui.ErrorChan {
|
||||
if strings.Contains(err.Error(), "No such container") {
|
||||
// this happens all the time when e.g. restarting containers so we won't worry about it
|
||||
gui.Log.Warn(err)
|
||||
}
|
||||
gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}()
|
||||
|
|
@ -287,6 +293,11 @@ func (gui *Gui) Run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// checkForContextChange runs the currently focused panel's 'select' function, simulating the current item having just been selected. This will then trigger a check to see if anything's changed (e.g. a service has a new container) and if so, the appropriate code will run. For example, if you're reading logs from a service and all of a sudden its container changes, this will trigger the 'select' function, which will work out that the context is not different because of the new container, and then it will re-attempt to get the logs, this time for the correct container. This 'context' is stored in the main panel's ObjectKey. I'm using the term 'context' here more broadly than just the different tabs you can view in a panel.
|
||||
func (gui *Gui) checkForContextChange() error {
|
||||
return gui.newLineFocused(gui.g.CurrentView())
|
||||
}
|
||||
|
||||
func (gui *Gui) reRenderMain() error {
|
||||
mainView := gui.getMainView()
|
||||
if mainView == nil {
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||
}
|
||||
v.Wrap = true
|
||||
v.FgColor = gocui.ColorWhite
|
||||
|
||||
// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
|
||||
v.IgnoreCarriageReturns = true
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
key := "services-" + service.ID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex]
|
||||
containerID := ""
|
||||
if service.Container != nil {
|
||||
containerID = service.Container.ID
|
||||
}
|
||||
key := "services-" + service.ID + "-" + containerID + "-" + gui.getServiceContexts()[gui.State.Panels.Services.ContextIndex]
|
||||
if gui.State.Panels.Main.ObjectKey == key {
|
||||
return nil
|
||||
} else {
|
||||
|
|
@ -305,7 +309,12 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
|
||||
rebuildCommand := utils.ApplyTemplate(
|
||||
gui.Config.UserConfig.CommandTemplates.RebuildService,
|
||||
service,
|
||||
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
|
||||
)
|
||||
|
||||
recreateCommand := utils.ApplyTemplate(
|
||||
gui.Config.UserConfig.CommandTemplates.RecreateService,
|
||||
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
|
||||
)
|
||||
|
||||
options := []*commandOption{
|
||||
|
|
@ -320,8 +329,21 @@ func (gui *Gui) handleServiceRestartMenu(g *gocui.Gui, v *gocui.View) error {
|
|||
if err := service.Restart(); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshContainersAndServices()
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}, {
|
||||
description: gui.Tr.Recreate,
|
||||
command: utils.ApplyTemplate(
|
||||
gui.Config.UserConfig.CommandTemplates.RecreateService,
|
||||
gui.DockerCommand.NewCommandObject(commands.CommandObject{Service: service}),
|
||||
),
|
||||
f: func() error {
|
||||
return gui.WithWaitingStatus(gui.Tr.RestartingStatus, func() error {
|
||||
if err := gui.OSCommand.RunCommand(recreateCommand); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -77,20 +77,20 @@ func (gui *Gui) resetMainView() {
|
|||
gui.getMainView().Wrap = true
|
||||
}
|
||||
|
||||
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) newLineFocused(v *gocui.View) error {
|
||||
switch v.Name() {
|
||||
case "menu":
|
||||
return gui.handleMenuSelect(g, v)
|
||||
return gui.handleMenuSelect(gui.g, v)
|
||||
case "status":
|
||||
return gui.handleStatusSelect(g, v)
|
||||
return gui.handleStatusSelect(gui.g, v)
|
||||
case "services":
|
||||
return gui.handleServiceSelect(g, v)
|
||||
return gui.handleServiceSelect(gui.g, v)
|
||||
case "containers":
|
||||
return gui.handleContainerSelect(g, v)
|
||||
return gui.handleContainerSelect(gui.g, v)
|
||||
case "images":
|
||||
return gui.handleImageSelect(g, v)
|
||||
return gui.handleImageSelect(gui.g, v)
|
||||
case "volumes":
|
||||
return gui.handleVolumeSelect(g, v)
|
||||
return gui.handleVolumeSelect(gui.g, v)
|
||||
case "confirmation":
|
||||
return nil
|
||||
case "main":
|
||||
|
|
@ -137,7 +137,7 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return gui.newLineFocused(g, newView)
|
||||
return gui.newLineFocused(newView)
|
||||
}
|
||||
|
||||
func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type TranslationSet struct {
|
|||
Stop string
|
||||
Restart string
|
||||
Rebuild string
|
||||
Recreate string
|
||||
PreviousContext string
|
||||
NextContext string
|
||||
Attach string
|
||||
|
|
@ -105,6 +106,7 @@ func englishSet() TranslationSet {
|
|||
Stop: "stop",
|
||||
Restart: "restart",
|
||||
Rebuild: "rebuild",
|
||||
Recreate: "recreate",
|
||||
PreviousContext: "previous tab",
|
||||
NextContext: "next tab",
|
||||
Attach: "attach",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue