feat(log): Add all containers toggle (#1141)

Closes #1138
mine
Raul Cabello Martin 2021-06-04 16:25:48 +02:00 committed by GitHub
parent c9783fac61
commit a631712c05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 40 deletions

View File

@ -28,25 +28,27 @@ type LogsListener interface {
// Log represents a resource logger. // Log represents a resource logger.
type Log struct { type Log struct {
factory dao.Factory factory dao.Factory
lines dao.LogItems lines dao.LogItems
listeners []LogsListener listeners []LogsListener
gvr client.GVR gvr client.GVR
logOptions dao.LogOptions logOptions dao.LogOptions
cancelFn context.CancelFunc cancelFn context.CancelFunc
mx sync.RWMutex mx sync.RWMutex
filter string filter string
lastSent int lastSent int
flushTimeout time.Duration flushTimeout time.Duration
originalContainer string
} }
// NewLog returns a new model. // NewLog returns a new model.
func NewLog(gvr client.GVR, opts dao.LogOptions, flushTimeout time.Duration) *Log { func NewLog(gvr client.GVR, opts dao.LogOptions, flushTimeout time.Duration) *Log {
return &Log{ return &Log{
gvr: gvr, gvr: gvr,
logOptions: opts, logOptions: opts,
lines: nil, lines: nil,
flushTimeout: flushTimeout, 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) { func (l *Log) updateLogs(ctx context.Context, c dao.LogChan) {
defer func() { defer func() {
log.Debug().Msgf("updateLogs view bailing out!") log.Debug().Msgf("updateLogs view bailing out!")

View File

@ -205,6 +205,16 @@ func TestLogTimedout(t *testing.T) {
assert.Equal(t, e, string(v.data[0])) 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... // Helpers...

View File

@ -68,7 +68,7 @@ func (l *Log) Init(ctx context.Context) (err error) {
l.SetBorder(true) l.SetBorder(true)
l.SetDirection(tview.FlexRow) 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.AddItem(l.indicator, 1, 1, false)
l.indicator.Refresh() l.indicator.Refresh()
@ -198,6 +198,11 @@ func (l *Log) bindKeys() {
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true), tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, 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 { 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 { func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !l.logs.cmdBuff.IsActive() { if !l.logs.cmdBuff.IsActive() {
return evt return evt
@ -416,6 +430,10 @@ func (l *Log) goFullScreen() {
} }
} }
func (l *Log) isContainerLogView() bool {
return l.model.GetContainer() != ""
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...

View File

@ -8,36 +8,40 @@ import (
) )
const ( const (
autoscroll = "Autoscroll" autoscroll = "Autoscroll"
fullscreen = "FullScreen" fullscreen = "FullScreen"
timestamp = "Timestamps" timestamp = "Timestamps"
wrap = "Wrap" wrap = "Wrap"
on = "On" allContainers = "AllContainers"
off = "Off" on = "On"
spacer = " " off = "Off"
bold = "[::b]" spacer = " "
bold = "[::b]"
) )
// LogIndicator represents a log view indicator. // LogIndicator represents a log view indicator.
type LogIndicator struct { type LogIndicator struct {
*tview.TextView *tview.TextView
styles *config.Styles styles *config.Styles
scrollStatus int32 scrollStatus int32
fullScreen bool fullScreen bool
textWrap bool textWrap bool
showTime bool showTime bool
allContainers bool
shouldDisplayAllContainers bool
} }
// NewLogIndicator returns a new indicator. // 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{ l := LogIndicator{
styles: styles, styles: styles,
TextView: tview.NewTextView(), TextView: tview.NewTextView(),
scrollStatus: 1, scrollStatus: 1,
fullScreen: cfg.K9s.Logger.FullScreenLogs, fullScreen: cfg.K9s.Logger.FullScreenLogs,
textWrap: cfg.K9s.Logger.TextWrap, textWrap: cfg.K9s.Logger.TextWrap,
showTime: cfg.K9s.Logger.ShowTime, showTime: cfg.K9s.Logger.ShowTime,
shouldDisplayAllContainers: isContainerLogView,
} }
l.StylesChanged(styles) l.StylesChanged(styles)
styles.AddListener(&l) styles.AddListener(&l)
@ -100,9 +104,18 @@ func (l *LogIndicator) ToggleAutoScroll() {
l.Refresh() l.Refresh()
} }
// ToggleTextWrap toggles the wrap mode.
func (l *LogIndicator) ToggleAllContainers() {
l.allContainers = !l.allContainers
l.Refresh()
}
// Refresh updates the view. // Refresh updates the view.
func (l *LogIndicator) Refresh() { func (l *LogIndicator) Refresh() {
l.Clear() l.Clear()
if l.shouldDisplayAllContainers {
l.update(allContainers, l.allContainers, spacer)
}
l.update(autoscroll, l.AutoScroll(), spacer) l.update(autoscroll, l.AutoScroll(), spacer)
l.update(fullscreen, l.fullScreen, spacer) l.update(fullscreen, l.fullScreen, spacer)
l.update(timestamp, l.showTime, spacer) l.update(timestamp, l.showTime, spacer)

View File

@ -10,8 +10,19 @@ import (
func TestLogIndicatorRefresh(t *testing.T) { func TestLogIndicatorRefresh(t *testing.T) {
defaults := config.NewStyles() defaults := config.NewStyles()
v := view.NewLogIndicator(config.NewConfig(nil), defaults) uu := map[string]struct {
v.Refresh() 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)
})
}
} }

View File

@ -16,10 +16,10 @@ func TestLogAutoScroll(t *testing.T) {
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")}) v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
v.GetModel().Notify() v.GetModel().Notify()
assert.Equal(t, 15, len(v.Hints())) assert.Equal(t, 16, len(v.Hints()))
v.toggleAutoScrollCmd(nil) 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) { func TestLogViewNav(t *testing.T) {

View File

@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view" "github.com/derailed/k9s/internal/view"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -73,6 +74,24 @@ func TestLogViewSave(t *testing.T) {
assert.Equal(t, len(c2), len(c1)+1) 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... // Helpers...