allow configuring docker log args from within lazydocker

pull/406/head
Jesse Duffield 2 years ago
parent 553b9857cd
commit 78d0ab6d2f

@ -35,6 +35,25 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
infoSectionSize = 1
}
mainBox := &boxlayout.Box{
Direction: boxlayout.ROW,
Weight: mainSectionWeight,
Children: []*boxlayout.Box{
{
Window: "main",
Weight: 1,
},
},
}
showMainInfoView := true
if showMainInfoView {
mainBox.Children = utils.Append(mainBox.Children, &boxlayout.Box{
Window: "mainInfo",
Size: 3,
})
}
root := &boxlayout.Box{
Direction: boxlayout.ROW,
Children: []*boxlayout.Box{
@ -47,10 +66,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
Weight: sideSectionWeight,
ConditionalChildren: gui.sidePanelChildren,
},
{
Window: "main",
Weight: mainSectionWeight,
},
mainBox,
},
},
{

@ -62,14 +62,21 @@ func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, i
height/2 + panelHeight/2
}
func (gui *Gui) createPromptPanel(title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
func (gui *Gui) Prompt(title string, handleConfirm func(string) error) error {
gui.Views.Confirmation.ClearTextArea()
wrappedHandleConfirm := func(g *gocui.Gui, v *gocui.View) error {
input := gui.trimmedContent(v)
return handleConfirm(input)
}
gui.onNewPopupPanel()
err := gui.prepareConfirmationPanel(title, "", false)
if err != nil {
return err
}
gui.Views.Confirmation.Editable = true
return gui.setKeyBindings(gui.g, handleConfirm, nil)
return gui.setKeyBindings(gui.g, wrappedHandleConfirm, nil)
}
func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool) error {

@ -24,7 +24,7 @@ func (gui *Gui) renderContainerLogsToMain(container *commands.Container) tasks.T
Duration: time.Millisecond * 200,
// TODO: see why this isn't working (when switching from Top tab to Logs tab in the services panel, the tops tab's content isn't removed)
Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Wrap: gui.State.LogConfig.Wrap,
Autoscroll: true,
})
}
@ -37,7 +37,7 @@ func (gui *Gui) renderContainerLogsToMainAux(container *commands.Container, ctx
mainView := gui.Views.Main
if err := gui.writeContainerLogs(container, ctx, mainView); err != nil {
if err := gui.writeContainerLogs(container, ctx, mainView, gui.State.LogConfig); err != nil {
gui.Log.Error(err)
}
@ -85,7 +85,7 @@ func (gui *Gui) renderLogsToStdout(container *commands.Container) {
}
}()
if err := gui.writeContainerLogs(container, ctx, os.Stdout); err != nil {
if err := gui.writeContainerLogs(container, ctx, os.Stdout, gui.State.LogConfig); err != nil {
gui.Log.Error(err)
return
}
@ -104,13 +104,13 @@ func (gui *Gui) promptToReturn() {
}
}
func (gui *Gui) writeContainerLogs(container *commands.Container, ctx context.Context, writer io.Writer) error {
func (gui *Gui) writeContainerLogs(container *commands.Container, ctx context.Context, writer io.Writer, logConfig LogConfig) error {
readCloser, err := gui.DockerCommand.Client.ContainerLogs(ctx, container.ID, dockerTypes.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Timestamps: gui.Config.UserConfig.Logs.Timestamps,
Since: gui.Config.UserConfig.Logs.Since,
Tail: gui.Config.UserConfig.Logs.Tail,
Timestamps: logConfig.Timestamps,
Since: logConfig.Since,
Tail: logConfig.Tail,
Follow: true,
})
if err != nil {

@ -69,7 +69,7 @@ func (gui *Gui) getContainersPanel() *panels.SideListPanel[*commands.Container]
// where a container restarts but the new logs don't get read.
// Note that this might be jarring if we have a lot of logs and the container
// restarts a lot, so let's keep an eye on it.
return "containers-" + container.ID + "-" + container.Container.State
return "containers-" + container.ID + "-" + container.Container.State + gui.logArgsKey()
},
},
ListPanel: panels.ListPanel[*commands.Container]{
@ -239,7 +239,7 @@ func (gui *Gui) renderContainerTop(container *commands.Container) tasks.TaskFunc
},
Duration: time.Second,
Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Wrap: gui.State.LogConfig.Wrap,
Autoscroll: false,
})
}

@ -89,6 +89,16 @@ type guiState struct {
// Maintains the state of manual filtering i.e. typing in a substring
// to filter on in the current panel.
Filter filterState
LogConfig LogConfig
}
type LogConfig struct {
Tail string
Since string
Autoscroll bool
Timestamps bool
Wrap bool
}
type filterState struct {
@ -123,7 +133,13 @@ func NewGui(log *logrus.Entry, dockerCommand *commands.DockerCommand, oSCommand
},
},
ViewStack: []string{},
LogConfig: LogConfig{
Tail: config.UserConfig.Logs.Tail,
Since: config.UserConfig.Logs.Since,
Timestamps: config.UserConfig.Logs.Timestamps,
Wrap: config.UserConfig.Gui.WrapMainPanel,
Autoscroll: true,
},
ShowExitedContainers: true,
}
@ -424,9 +440,8 @@ func (gui *Gui) openFile(filename string) error {
}
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
return gui.createPromptPanel(gui.Tr.CustomCommandTitle, func(g *gocui.Gui, v *gocui.View) error {
command := gui.trimmedContent(v)
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(command))
return gui.Prompt(gui.Tr.CustomCommandTitle, func(input string) error {
return gui.runSubprocess(gui.OSCommand.RunCustomCommand(input))
})
}

@ -452,6 +452,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.escapeFilterPrompt),
},
{
ViewName: "",
Key: 'i',
Modifier: gocui.ModNone,
Handler: wrappedHandler(gui.handleOpenLogMenu),
Description: "Set log args (tail, since, etc)",
},
{
ViewName: "",
Key: 'J',

@ -108,6 +108,10 @@ func (gui *Gui) layout(g *gocui.Gui) error {
}
}
if err := gui.renderMainInfoView(); err != nil {
return err
}
// here is a good place log some stuff
// if you download humanlog and do tail -f development.log | humanlog
// this will let you see these branches as prettified json

@ -0,0 +1,102 @@
package gui
import (
"fmt"
"github.com/fatih/color"
"github.com/jesseduffield/lazydocker/pkg/gui/types"
)
func (gui *Gui) renderMainInfoView() error {
str := ""
keyColorSprintFn := color.New(color.FgMagenta).SprintFunc()
valueColorSprintFn := color.New(color.FgGreen).SprintFunc()
keyVal := func(key, value string) string {
return fmt.Sprintf("%s:%s ", keyColorSprintFn(key), valueColorSprintFn(value))
}
str += keyVal("Tail", blankToNil(gui.State.LogConfig.Tail))
str += keyVal("Since", blankToNil(gui.State.LogConfig.Since))
str += keyVal("Timestamps", boolToStr(gui.State.LogConfig.Timestamps))
str += keyVal("Wrap", boolToStr(gui.State.LogConfig.Wrap))
str += keyVal("Autoscroll", boolToStr(gui.Views.Main.Autoscroll))
return gui.renderString(gui.g, "mainInfo", str)
}
func boolToStr(b bool) string {
if b {
return "On"
}
return "Off"
}
func blankToNil(s string) string {
if s == "" {
return "-"
}
return s
}
func (gui *Gui) reselectSideListItem() error {
currentSidePanel, ok := gui.currentSidePanel()
if ok {
if err := currentSidePanel.HandleSelect(); err != nil {
return err
}
}
return nil
}
func (gui *Gui) handleOpenLogMenu() error {
return gui.Menu(CreateMenuOptions{
Title: "Log Options",
Items: []*types.MenuItem{
{
Label: "Set tail",
OnPress: func() error {
return gui.Prompt("Set tail value (e.g. 200). Unset with empty value", func(input string) error {
gui.State.LogConfig.Tail = input
return gui.reselectSideListItem()
})
},
},
{
Label: "Set since",
OnPress: func() error {
return gui.Prompt("Set since value (e.g. 60m). Unset with empty value", func(input string) error {
gui.State.LogConfig.Since = input
return gui.reselectSideListItem()
})
},
},
{
Label: "Toggle timestamps",
OnPress: func() error {
gui.State.LogConfig.Timestamps = !gui.State.LogConfig.Timestamps
return gui.reselectSideListItem()
},
},
{
Label: "Toggle wrap",
OnPress: func() error {
gui.State.LogConfig.Wrap = !gui.State.LogConfig.Wrap
return gui.reselectSideListItem()
},
},
{
Label: "Toggle autoscroll",
OnPress: func() error {
gui.Views.Main.Autoscroll = !gui.Views.Main.Autoscroll
// the view will refresh automatically
return nil
},
},
},
})
}
func (gui *Gui) logArgsKey() string {
// not including autoscroll because that doesn't require refetching the logs
return gui.State.LogConfig.Since + gui.State.LogConfig.Tail + boolToStr(gui.State.LogConfig.Timestamps) + boolToStr(gui.State.LogConfig.Wrap)
}

@ -52,7 +52,7 @@ func (gui *Gui) getProjectPanel() *panels.SideListPanel[*commands.Project] {
}
},
GetItemContextCacheKey: func(project *commands.Project) string {
return "projects-" + project.Name
return "projects-" + project.Name + gui.logArgsKey()
},
},
@ -115,7 +115,7 @@ func (gui *Gui) creditsStr() string {
func (gui *Gui) renderAllLogs(_project *commands.Project) tasks.TaskFunc {
return gui.NewTask(TaskOpts{
Autoscroll: true,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Wrap: gui.State.LogConfig.Wrap,
Func: func(ctx context.Context) {
gui.clearMainView()

@ -50,10 +50,13 @@ func (gui *Gui) getServicesPanel() *panels.SideListPanel[*commands.Service] {
}
},
GetItemContextCacheKey: func(service *commands.Service) string {
key := "services-" + service.ID + "-" + gui.logArgsKey()
if service.Container == nil {
return "services-" + service.ID
return key
}
return "services-" + service.ID + "-" + service.Container.ID + "-" + service.Container.Container.State
return key + service.Container.ID + "-" + service.Container.Container.State
},
},
ListPanel: panels.ListPanel[*commands.Service]{
@ -117,7 +120,7 @@ func (gui *Gui) renderServiceTop(service *commands.Service) tasks.TaskFunc {
},
Duration: time.Second,
Before: func(ctx context.Context) { gui.clearMainView() },
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Wrap: gui.State.LogConfig.Wrap,
Autoscroll: false,
})
}

@ -48,7 +48,7 @@ func (gui *Gui) NewSimpleRenderStringTask(getContent func() string) tasks.TaskFu
return gui.NewRenderStringTask(RenderStringTaskOpts{
GetStrContent: getContent,
Autoscroll: false,
Wrap: gui.Config.UserConfig.Gui.WrapMainPanel,
Wrap: gui.State.LogConfig.Wrap,
})
}

@ -66,7 +66,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) resetMainView() {
gui.State.Panels.Main.ObjectKey = ""
gui.Views.Main.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
gui.Views.Main.Wrap = gui.State.LogConfig.Wrap
}
// if the cursor down past the last item, move it to the last line

@ -36,6 +36,9 @@ type Views struct {
// main panel
Main *gocui.View
// shows information about what's being rendered in the main panel (e.g. `docker logs` tail arg)
MainInfo *gocui.View
// bottom line
Options *gocui.View
Information *gocui.View
@ -72,6 +75,7 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
{viewPtr: &gui.Views.Volumes, name: "volumes", autoPosition: true},
{viewPtr: &gui.Views.Main, name: "main", autoPosition: true},
{viewPtr: &gui.Views.MainInfo, name: "mainInfo", autoPosition: true},
// bottom line
{viewPtr: &gui.Views.Options, name: "options", autoPosition: true},

@ -339,3 +339,11 @@ func IsValidHexValue(v string) bool {
func OpensMenuStyle(str string) string {
return ColoredString(fmt.Sprintf("%s...", str), color.FgMagenta)
}
func Prepend[T any](slice []T, item T) []T {
return append([]T{item}, slice...)
}
func Append[T any](slice []T, item T) []T {
return append(slice, item)
}

Loading…
Cancel
Save