mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-22 07:11:01 +00:00
allow configuring docker log args from within lazydocker
This commit is contained in:
parent
553b9857cd
commit
78d0ab6d2f
14 changed files with 192 additions and 26 deletions
|
|
@ -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
|
||||
|
|
|
|||
102
pkg/gui/main_info_view.go
Normal file
102
pkg/gui/main_info_view.go
Normal file
|
|
@ -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…
Add table
Reference in a new issue