diff --git a/internal/model/log.go b/internal/model/log.go index 4dd9afe7..43cbc62c 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -28,25 +28,27 @@ type LogsListener interface { // Log represents a resource logger. type Log struct { - factory dao.Factory - lines dao.LogItems - listeners []LogsListener - gvr client.GVR - logOptions dao.LogOptions - cancelFn context.CancelFunc - mx sync.RWMutex - filter string - lastSent int - flushTimeout time.Duration + factory dao.Factory + lines dao.LogItems + listeners []LogsListener + gvr client.GVR + logOptions dao.LogOptions + cancelFn context.CancelFunc + mx sync.RWMutex + filter string + lastSent int + flushTimeout time.Duration + originalContainer string } // NewLog returns a new model. func NewLog(gvr client.GVR, opts dao.LogOptions, flushTimeout time.Duration) *Log { return &Log{ - gvr: gvr, - logOptions: opts, - lines: nil, - flushTimeout: flushTimeout, + gvr: gvr, + logOptions: opts, + lines: nil, + flushTimeout: flushTimeout, + originalContainer: opts.Container, } } @@ -250,6 +252,16 @@ func (l *Log) Notify() { } } +// ToggleShowTimestamp toggles to show all containers logs. +func (l *Log) ToggleAllContainers() { + if l.logOptions.Container != "" { + l.logOptions.Container = "" + } else { + l.logOptions.Container = l.originalContainer + } + l.Restart() +} + func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) { defer func() { log.Debug().Msgf("updateLogs view bailing out!") diff --git a/internal/model/log_test.go b/internal/model/log_test.go index e963a203..d6bd1f36 100644 --- a/internal/model/log_test.go +++ b/internal/model/log_test.go @@ -205,6 +205,16 @@ func TestLogTimedout(t *testing.T) { assert.Equal(t, e, string(v.data[0])) } +func TestToggleAllContainers(t *testing.T) { + m := model.NewLog(client.NewGVR(""), makeLogOpts(1), 10*time.Millisecond) + m.Init(makeFactory()) + assert.Equal(t, m.GetContainer(), "blee") + m.ToggleAllContainers() + assert.Equal(t, m.GetContainer(), "") + m.ToggleAllContainers() + assert.Equal(t, m.GetContainer(), "blee") +} + // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/view/log.go b/internal/view/log.go index fb5ea216..8c39683a 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -68,7 +68,7 @@ func (l *Log) Init(ctx context.Context) (err error) { l.SetBorder(true) l.SetDirection(tview.FlexRow) - l.indicator = NewLogIndicator(l.app.Config, l.app.Styles) + l.indicator = NewLogIndicator(l.app.Config, l.app.Styles, l.isContainerLogView()) l.AddItem(l.indicator, 1, 1, false) l.indicator.Refresh() @@ -198,6 +198,11 @@ func (l *Log) bindKeys() { tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true), ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, true), }) + if l.isContainerLogView() { + l.logs.Actions().Set(ui.KeyActions{ + ui.KeyA: ui.NewKeyAction("Toggle AllContainers", l.showAllContainers, true), + }) + } } func (l *Log) resetCmd(evt *tcell.EventKey) *tcell.EventKey { @@ -287,6 +292,15 @@ func (l *Log) sinceCmd(a int) func(evt *tcell.EventKey) *tcell.EventKey { } } +func (l *Log) showAllContainers(evt *tcell.EventKey) *tcell.EventKey { + if l.app.InCmdMode() { + return evt + } + l.indicator.ToggleAllContainers() + l.model.ToggleAllContainers() + return nil +} + func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey { if !l.logs.cmdBuff.IsActive() { return evt @@ -416,6 +430,10 @@ func (l *Log) goFullScreen() { } } +func (l *Log) isContainerLogView() bool { + return l.model.GetContainer() != "" +} + // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index 79039aef..9d325f14 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -8,36 +8,40 @@ import ( ) const ( - autoscroll = "Autoscroll" - fullscreen = "FullScreen" - timestamp = "Timestamps" - wrap = "Wrap" - on = "On" - off = "Off" - spacer = " " - bold = "[::b]" + autoscroll = "Autoscroll" + fullscreen = "FullScreen" + timestamp = "Timestamps" + wrap = "Wrap" + allContainers = "AllContainers" + on = "On" + off = "Off" + spacer = " " + bold = "[::b]" ) // LogIndicator represents a log view indicator. type LogIndicator struct { *tview.TextView - styles *config.Styles - scrollStatus int32 - fullScreen bool - textWrap bool - showTime bool + styles *config.Styles + scrollStatus int32 + fullScreen bool + textWrap bool + showTime bool + allContainers bool + shouldDisplayAllContainers bool } // NewLogIndicator returns a new indicator. -func NewLogIndicator(cfg *config.Config, styles *config.Styles) *LogIndicator { +func NewLogIndicator(cfg *config.Config, styles *config.Styles, isContainerLogView bool) *LogIndicator { l := LogIndicator{ - styles: styles, - TextView: tview.NewTextView(), - scrollStatus: 1, - fullScreen: cfg.K9s.Logger.FullScreenLogs, - textWrap: cfg.K9s.Logger.TextWrap, - showTime: cfg.K9s.Logger.ShowTime, + styles: styles, + TextView: tview.NewTextView(), + scrollStatus: 1, + fullScreen: cfg.K9s.Logger.FullScreenLogs, + textWrap: cfg.K9s.Logger.TextWrap, + showTime: cfg.K9s.Logger.ShowTime, + shouldDisplayAllContainers: isContainerLogView, } l.StylesChanged(styles) styles.AddListener(&l) @@ -100,9 +104,18 @@ func (l *LogIndicator) ToggleAutoScroll() { l.Refresh() } +// ToggleTextWrap toggles the wrap mode. +func (l *LogIndicator) ToggleAllContainers() { + l.allContainers = !l.allContainers + l.Refresh() +} + // Refresh updates the view. func (l *LogIndicator) Refresh() { l.Clear() + if l.shouldDisplayAllContainers { + l.update(allContainers, l.allContainers, spacer) + } l.update(autoscroll, l.AutoScroll(), spacer) l.update(fullscreen, l.fullScreen, spacer) l.update(timestamp, l.showTime, spacer) diff --git a/internal/view/log_indicator_test.go b/internal/view/log_indicator_test.go index 1b0d6852..ddd69367 100644 --- a/internal/view/log_indicator_test.go +++ b/internal/view/log_indicator_test.go @@ -10,8 +10,19 @@ import ( func TestLogIndicatorRefresh(t *testing.T) { defaults := config.NewStyles() - v := view.NewLogIndicator(config.NewConfig(nil), defaults) - v.Refresh() + uu := map[string]struct { + li *view.LogIndicator + e string + }{ + "all containers": {view.NewLogIndicator(config.NewConfig(nil), defaults, true), "[::b]AllContainers:Off [::b]Autoscroll:On [::b]FullScreen:Off [::b]Timestamps:Off [::b]Wrap:Off\n"}, + "no all containers": {view.NewLogIndicator(config.NewConfig(nil), defaults, false), "[::b]Autoscroll:On [::b]FullScreen:Off [::b]Timestamps:Off [::b]Wrap:Off\n"}, + } - assert.Equal(t, "[::b]Autoscroll:On [::b]FullScreen:Off [::b]Timestamps:Off [::b]Wrap:Off\n", v.GetText(false)) + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.li.Refresh() + assert.Equal(t, u.li.GetText(false), u.e) + }) + } } diff --git a/internal/view/log_int_test.go b/internal/view/log_int_test.go index 8cf1ab9c..2e8809ba 100644 --- a/internal/view/log_int_test.go +++ b/internal/view/log_int_test.go @@ -16,10 +16,10 @@ func TestLogAutoScroll(t *testing.T) { v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")}) v.GetModel().Notify() - assert.Equal(t, 15, len(v.Hints())) + assert.Equal(t, 16, len(v.Hints())) v.toggleAutoScrollCmd(nil) - assert.Equal(t, "Autoscroll:Off FullScreen:Off Timestamps:Off Wrap:Off", v.Indicator().GetText(true)) + assert.Equal(t, "AllContainers:Off Autoscroll:Off FullScreen:Off Timestamps:Off Wrap:Off", v.Indicator().GetText(true)) } func TestLogViewNav(t *testing.T) { diff --git a/internal/view/log_test.go b/internal/view/log_test.go index b1ea0425..759f0018 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -10,6 +10,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/view" "github.com/derailed/tview" "github.com/stretchr/testify/assert" @@ -73,6 +74,24 @@ func TestLogViewSave(t *testing.T) { assert.Equal(t, len(c2), len(c1)+1) } +func TestAllContainerKeyBinding(t *testing.T) { + uu := map[string]struct { + l *view.Log + e bool + }{ + "all containers": {view.NewLog(client.NewGVR("v1/pods"), "", "container", false), true}, + "no all containers": {view.NewLog(client.NewGVR("v1/pods"), "", "", false), false}, + } + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + u.l.Init(makeContext()) + _, got := u.l.Logs().Actions()[ui.KeyA] + assert.Equal(t, u.e, got) + }) + } +} + // ---------------------------------------------------------------------------- // Helpers...