From 81820b6459b2b93162ee5ddb2bd4322e75985aa4 Mon Sep 17 00:00:00 2001 From: derailed Date: Mon, 26 Oct 2020 21:05:25 -0600 Subject: [PATCH] checkpoint --- README.md | 4 +- internal/ui/table.go | 2 +- internal/view/app.go | 2 - internal/view/details.go | 53 ++++++++---- internal/view/log.go | 7 +- internal/view/logger.go | 173 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 214 insertions(+), 27 deletions(-) create mode 100644 internal/view/logger.go diff --git a/README.md b/README.md index 59450dac..3e67af46 100644 --- a/README.md +++ b/README.md @@ -276,8 +276,8 @@ K9s uses aliases to navigate most K8s resources. tail: 200 # Defines the total number of log lines to allow in the view. Default 1000 buffer: 500 - # Represents how far to go back in the log timeline in seconds. Default is 1min - sinceSeconds: 60 + # Represents how far to go back in the log timeline in seconds. Setting to -1 will show all available logs. Default is 5min. + sinceSeconds: 300 # Go full screen while displaying logs. Default false fullScreenLogs: false # Toggles log line wrap. Default false diff --git a/internal/ui/table.go b/internal/ui/table.go index 4de4adf4..a6705dcc 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -380,7 +380,7 @@ func (t *Table) filtered(data render.TableData) render.TableData { return fuzzyFilter(q[2:], filtered) } - filtered, err := rxFilter(t.cmdBuff.GetText(), filtered) + filtered, err := rxFilter(q, filtered) if err != nil { log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp") t.cmdBuff.ClearText(true) diff --git a/internal/view/app.go b/internal/view/app.go index ff8e256a..b670d6ab 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -113,8 +113,6 @@ func (a *App) Init(version string, rate int) error { return err } a.CmdBuff().SetSuggestionFn(a.suggestCommand()) - // BOZO!! - // a.CmdBuff().AddListener(a) a.layout(ctx, version) a.initSignals() diff --git a/internal/view/details.go b/internal/view/details.go index 65307cbd..3aa0b02d 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -18,8 +18,9 @@ const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " // Details represents a generic text viewer. type Details struct { - *tview.TextView + *tview.Flex + text *tview.TextView actions ui.KeyActions app *App title, subject string @@ -27,12 +28,14 @@ type Details struct { model *model.Text currentRegion, maxRegions int searchable bool + fullScreen bool } // NewDetails returns a details viewer. func NewDetails(app *App, title, subject string, searchable bool) *Details { d := Details{ - TextView: tview.NewTextView(), + Flex: tview.NewFlex(), + text: tview.NewTextView(), app: app, title: title, subject: subject, @@ -41,6 +44,7 @@ func NewDetails(app *App, title, subject string, searchable bool) *Details { model: model.NewText(), searchable: searchable, } + d.AddItem(d.text, 0, 1, true) return &d } @@ -50,9 +54,9 @@ func (d *Details) Init(_ context.Context) error { if d.title != "" { d.SetBorder(true) } - d.SetScrollable(true).SetWrap(true).SetRegions(true) - d.SetDynamicColors(true) - d.SetHighlightColor(tcell.ColorOrange) + d.text.SetScrollable(true).SetWrap(true).SetRegions(true) + d.text.SetDynamicColors(true) + d.text.SetHighlightColor(tcell.ColorOrange) d.SetTitleColor(tcell.ColorAqua) d.SetInputCapture(d.keyboard) d.SetBorderPadding(0, 0, 1, 1) @@ -73,8 +77,8 @@ func (d *Details) Init(_ context.Context) error { // TextChanged notifies the model changed. func (d *Details) TextChanged(lines []string) { - d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) - d.ScrollToBeginning() + d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(lines, "\n"))) + d.text.ScrollToBeginning() } // TextFiltered notifies when the filter changed. @@ -89,11 +93,11 @@ func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) { d.maxRegions++ } - d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n"))) - d.Highlight() + d.text.SetText(colorizeYAML(d.app.Styles.Views().Yaml, strings.Join(ll, "\n"))) + d.text.Highlight() if d.maxRegions > 0 { - d.Highlight("search_0") - d.ScrollToHighlight() + d.text.Highlight("search_0") + d.text.ScrollToHighlight() } } @@ -117,6 +121,7 @@ func (d *Details) bindKeys() { tcell.KeyEscape: ui.NewKeyAction("Back", d.resetCmd, false), tcell.KeyCtrlS: ui.NewKeyAction("Save", d.saveCmd, false), ui.KeyC: ui.NewKeyAction("Copy", d.cpCmd, true), + ui.KeyF: ui.NewKeyAction("Toggle FullScreen", d.toggleFullScreenCmd, true), ui.KeyN: ui.NewKeyAction("Next Match", d.nextCmd, true), ui.KeyShiftN: ui.NewKeyAction("Prev Match", d.prevCmd, true), ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", d.activateCmd, false), @@ -139,7 +144,7 @@ func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey { // StylesChanged notifies the skin changed. func (d *Details) StylesChanged(s *config.Styles) { d.SetBackgroundColor(d.app.Styles.BgColor()) - d.SetTextColor(d.app.Styles.FgColor()) + d.text.SetTextColor(d.app.Styles.FgColor()) d.SetBorderFocusColor(d.app.Styles.Frame().Border.FocusColor.Color()) d.TextChanged(d.model.Peek()) } @@ -190,13 +195,25 @@ func (d *Details) nextCmd(evt *tcell.EventKey) *tcell.EventKey { if d.currentRegion >= d.maxRegions { d.currentRegion = 0 } - d.Highlight(fmt.Sprintf("search_%d", d.currentRegion)) - d.ScrollToHighlight() + d.text.Highlight(fmt.Sprintf("search_%d", d.currentRegion)) + d.text.ScrollToHighlight() d.updateTitle() return nil } +func (d *Details) toggleFullScreenCmd(evt *tcell.EventKey) *tcell.EventKey { + if d.app.InCmdMode() { + return evt + } + + d.fullScreen = !d.fullScreen + d.SetFullScreen(d.fullScreen) + d.Box.SetBorder(!d.fullScreen) + + return nil +} + func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey { if d.cmdBuff.Empty() { return evt @@ -206,8 +223,8 @@ func (d *Details) prevCmd(evt *tcell.EventKey) *tcell.EventKey { if d.currentRegion < 0 { d.currentRegion = d.maxRegions - 1 } - d.Highlight(fmt.Sprintf("search_%d", d.currentRegion)) - d.ScrollToHighlight() + d.text.Highlight(fmt.Sprintf("search_%d", d.currentRegion)) + d.text.ScrollToHighlight() d.updateTitle() return nil @@ -256,7 +273,7 @@ func (d *Details) resetCmd(evt *tcell.EventKey) *tcell.EventKey { } func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey { - if path, err := saveYAML(d.app.Config.K9s.CurrentCluster, d.title, d.GetText(true)); err != nil { + if path, err := saveYAML(d.app.Config.K9s.CurrentCluster, d.title, d.text.GetText(true)); err != nil { d.app.Flash().Err(err) } else { d.app.Flash().Infof("Log %s saved successfully!", path) @@ -267,7 +284,7 @@ func (d *Details) saveCmd(evt *tcell.EventKey) *tcell.EventKey { func (d *Details) cpCmd(evt *tcell.EventKey) *tcell.EventKey { d.app.Flash().Info("Content copied to clipboard...") - if err := clipboard.WriteAll(d.GetText(true)); err != nil { + if err := clipboard.WriteAll(d.text.GetText(true)); err != nil { d.app.Flash().Err(err) } diff --git a/internal/view/log.go b/internal/view/log.go index 3ed7ef38..f9e88f67 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -40,7 +40,7 @@ type Log struct { *tview.Flex app *App - logs *Details + logs *Logger indicator *LogIndicator ansiWriter io.Writer model *model.Log @@ -76,7 +76,7 @@ func (l *Log) Init(ctx context.Context) (err error) { l.AddItem(l.indicator, 1, 1, false) l.indicator.Refresh() - l.logs = NewDetails(l.app, "", "", false) + l.logs = NewLogger(l.app) if err = l.logs.Init(ctx); err != nil { return err } @@ -105,7 +105,6 @@ func (l *Log) Init(ctx context.Context) (err error) { func (l *Log) LogCleared() { l.app.QueueUpdateDraw(func() { l.logs.Clear() - // l.logs.ScrollTo(0, 0) }) } @@ -259,7 +258,7 @@ func (l *Log) updateTitle() { } // Logs returns the log viewer. -func (l *Log) Logs() *Details { +func (l *Log) Logs() *Logger { return l.logs } diff --git a/internal/view/logger.go b/internal/view/logger.go new file mode 100644 index 00000000..8105f8c0 --- /dev/null +++ b/internal/view/logger.go @@ -0,0 +1,173 @@ +package view + +import ( + "context" + "github.com/atotto/clipboard" + "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/model" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/tview" + "github.com/gdamore/tcell" +) + +const loggerTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] " + +// Logger represents a generic log viewer. +type Logger struct { + *tview.TextView + + actions ui.KeyActions + app *App + title, subject string + cmdBuff *model.FishBuff +} + +// NewLogger returns a logger viewer. +func NewLogger(app *App) *Logger { + return &Logger{ + TextView: tview.NewTextView(), + app: app, + actions: make(ui.KeyActions), + cmdBuff: model.NewFishBuff('/', model.FilterBuffer), + } +} + +// Init initializes the viewer. +func (l *Logger) Init(_ context.Context) error { + if l.title != "" { + l.SetBorder(true) + } + l.SetScrollable(true).SetWrap(true).SetRegions(true) + l.SetDynamicColors(true) + l.SetHighlightColor(tcell.ColorOrange) + l.SetTitleColor(tcell.ColorAqua) + l.SetInputCapture(l.keyboard) + l.SetBorderPadding(0, 0, 1, 1) + + l.app.Styles.AddListener(l) + l.StylesChanged(l.app.Styles) + + l.app.Prompt().SetModel(l.cmdBuff) + l.cmdBuff.AddListener(l) + + l.bindKeys() + l.SetInputCapture(l.keyboard) + + return nil +} + +// BufferChanged indicates the buffer was changel. +func (l *Logger) BufferChanged(s string) {} + +// BufferCompleted indicates input was acceptel. +func (l *Logger) BufferCompleted(s string) { +} + +// BufferActive indicates the buff activity changel. +func (l *Logger) BufferActive(state bool, k model.BufferKind) { + l.app.BufferActive(state, k) +} + +func (l *Logger) bindKeys() { + l.actions.Set(ui.KeyActions{ + tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, false), + tcell.KeyCtrlS: ui.NewKeyAction("Save", l.saveCmd, false), + ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, true), + ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", l.activateCmd, false), + tcell.KeyDelete: ui.NewSharedKeyAction("Erase", l.eraseCmd, false), + }) +} + +func (l *Logger) keyboard(evt *tcell.EventKey) *tcell.EventKey { + if a, ok := l.actions[ui.AsKey(evt)]; ok { + return a.Action(evt) + } + + return evt +} + +// StylesChanged notifies the skin changel. +func (l *Logger) StylesChanged(s *config.Styles) { + l.SetBackgroundColor(l.app.Styles.BgColor()) + l.SetTextColor(l.app.Styles.FgColor()) + l.SetBorderFocusColor(l.app.Styles.Frame().Border.FocusColor.Color()) +} + +// SetSubject updates the subject. +func (l *Logger) SetSubject(s string) { + l.subject = s +} + +// Actions returns menu actions +func (l *Logger) Actions() ui.KeyActions { + return l.actions +} + +// Name returns the component name. +func (l *Logger) Name() string { return l.title } + +// Start starts the view updater. +func (l *Logger) Start() {} + +// Stop terminates the updater. +func (l *Logger) Stop() { + l.app.Styles.RemoveListener(l) +} + +// Hints returns menu hints. +func (l *Logger) Hints() model.MenuHints { + return l.actions.Hints() +} + +// ExtraHints returns additional hints. +func (l *Logger) ExtraHints() map[string]string { + return nil +} + +func (l *Logger) activateCmd(evt *tcell.EventKey) *tcell.EventKey { + if l.app.InCmdMode() { + return evt + } + l.app.ResetPrompt(l.cmdBuff) + + return nil +} + +func (l *Logger) eraseCmd(evt *tcell.EventKey) *tcell.EventKey { + if !l.cmdBuff.IsActive() { + return nil + } + l.cmdBuff.Delete() + + return nil +} + +func (l *Logger) resetCmd(evt *tcell.EventKey) *tcell.EventKey { + if !l.cmdBuff.InCmdMode() { + l.cmdBuff.Reset() + return l.app.PrevCmd(evt) + } + l.cmdBuff.SetActive(false) + l.cmdBuff.Reset() + + return nil +} + +func (l *Logger) saveCmd(evt *tcell.EventKey) *tcell.EventKey { + if path, err := saveYAML(l.app.Config.K9s.CurrentCluster, l.title, l.GetText(true)); err != nil { + l.app.Flash().Err(err) + } else { + l.app.Flash().Infof("Log %s saved successfully!", path) + } + + return nil +} + +func (l *Logger) cpCmd(evt *tcell.EventKey) *tcell.EventKey { + l.app.Flash().Info("Content copied to clipboard...") + if err := clipboard.WriteAll(l.GetText(true)); err != nil { + l.app.Flash().Err(err) + } + + return nil +} \ No newline at end of file