From c896598e842a40e9d503185b840df7d37ea48037 Mon Sep 17 00:00:00 2001 From: derailed Date: Thu, 27 Feb 2020 21:47:21 -0700 Subject: [PATCH] Fix #583, #584 --- change_logs/release_v0.17.1.md | 24 +++++ internal/config/styles.go | 2 +- internal/dao/log_options.go | 1 + internal/dao/pod.go | 9 +- internal/dao/svc.go | 2 +- internal/keys.go | 1 + internal/model/log.go | 50 +++++++-- internal/model/table.go | 11 +- internal/render/color.go | 4 +- internal/render/color_test.go | 63 +++++++++++ internal/render/header.go | 1 - internal/render/ns.go | 18 ++-- internal/render/ns_test.go | 72 +++++++++---- internal/render/pod.go | 12 ++- internal/render/pod_test.go | 172 ++++++++++++++++++++++-------- internal/render/row_event_test.go | 26 ----- internal/ui/table.go | 14 ++- internal/ui/types.go | 4 +- internal/view/log.go | 36 +++++-- internal/view/log_indicator.go | 13 +++ internal/view/logs_extender.go | 1 - internal/view/table.go | 3 + 22 files changed, 385 insertions(+), 154 deletions(-) create mode 100644 change_logs/release_v0.17.1.md create mode 100644 internal/render/color_test.go diff --git a/change_logs/release_v0.17.1.md b/change_logs/release_v0.17.1.md new file mode 100644 index 00000000..08345dbf --- /dev/null +++ b/change_logs/release_v0.17.1.md @@ -0,0 +1,24 @@ + + +# Release v0.17.1 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated! + +Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +--- + +Maintenance Release! + +## Resolved Bugs/Features/PRs + +- [Issue #584](https://github.com/derailed/k9s/issues/584) +- [Issue #583](https://github.com/derailed/k9s/issues/583) + +--- + + © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/internal/config/styles.go b/internal/config/styles.go index 0d1c1cf7..8504600a 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -251,7 +251,7 @@ func newStatus() Status { ErrorColor: "orangered", HighlightColor: "aqua", KillColor: "mediumpurple", - CompletedColor: "darkseagreen", + CompletedColor: "lightslategray", } } diff --git a/internal/dao/log_options.go b/internal/dao/log_options.go index 67e5e38c..c8a3a70e 100644 --- a/internal/dao/log_options.go +++ b/internal/dao/log_options.go @@ -16,6 +16,7 @@ type LogOptions struct { Previous bool SingleContainer bool MultiPods bool + ShowTimestamp bool } // HasContainer checks if a container is present. diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 983395fb..f1c2271e 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -224,10 +224,11 @@ func (p *Pod) logs(ctx context.Context, c chan<- []byte, opts LogOptions) error func tailLogs(ctx context.Context, logger Logger, c chan<- []byte, opts LogOptions) error { log.Debug().Msgf("Tailing logs for %q -- %q", opts.Path, opts.Container) o := v1.PodLogOptions{ - Container: opts.Container, - Follow: true, - TailLines: &opts.Lines, - Previous: opts.Previous, + Container: opts.Container, + Follow: true, + TailLines: &opts.Lines, + Timestamps: opts.ShowTimestamp, + Previous: opts.Previous, } req, err := logger.Logs(opts.Path, &o) if err != nil { diff --git a/internal/dao/svc.go b/internal/dao/svc.go index 2369870b..f900119d 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -48,7 +48,7 @@ func (s *Service) Pod(fqn string) (string, error) { // GetInstance returns a service instance. func (s *Service) GetInstance(fqn string) (*v1.Service, error) { - o, err := s.Factory.Get(s.gvr.String(), fqn, false, labels.Everything()) + o, err := s.Factory.Get(s.gvr.String(), fqn, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/keys.go b/internal/keys.go index 4b25cc6f..89c60f52 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -25,6 +25,7 @@ const ( KeyApp ContextKey = "app" KeyStyles ContextKey = "styles" KeyMetrics ContextKey = "metrics" + KeyHasMetrics ContextKey = "has-metrics" KeyToast ContextKey = "toast" KeyWithMetrics ContextKey = "withMetrics" KeyViewConfig ContextKey = "viewConfig" diff --git a/internal/model/log.go b/internal/model/log.go index 07cbc4ad..8ffee8d7 100644 --- a/internal/model/log.go +++ b/internal/model/log.go @@ -15,7 +15,7 @@ import ( "github.com/sahilm/fuzzy" ) -const logMaxBufferSize = 50 +const logMaxBufferSize = 100 // LogsListener represents a log model listener. type LogsListener interface { @@ -31,15 +31,16 @@ type LogsListener interface { // Log represents a resource logger. type Log struct { - factory dao.Factory - lines []string - listeners []LogsListener - gvr client.GVR - logOptions dao.LogOptions - cancelFn context.CancelFunc - mx sync.RWMutex - filter string - lastSent int + factory dao.Factory + lines []string + listeners []LogsListener + gvr client.GVR + logOptions dao.LogOptions + cancelFn context.CancelFunc + mx sync.RWMutex + filter string + lastSent int + showTimestamp bool } // NewLog returns a new model. @@ -72,6 +73,16 @@ func (l *Log) Clear() { l.fireLogCleared() } +// ShowTimestamp toggles timestamp on logs. +func (l *Log) ShowTimestamp(b bool) { + l.mx.RLock() + defer l.mx.RUnlock() + + l.showTimestamp = b + l.fireLogCleared() + l.fireLogChanged(l.lines) +} + // Start initialize log tailer. func (l *Log) Start() { if err := l.load(); err != nil { @@ -100,6 +111,7 @@ func (l *Log) Set(lines []string) { // ClearFilter resets the log filter if any. func (l *Log) ClearFilter() { + log.Debug().Msgf("CLEARED!!") l.mx.RLock() defer l.mx.RUnlock() @@ -112,6 +124,7 @@ func (l *Log) Filter(q string) error { l.mx.RLock() defer l.mx.RUnlock() + log.Debug().Msgf("FILTER!") l.filter = q filtered, err := applyFilter(l.filter, l.lines) if err != nil { @@ -262,6 +275,7 @@ func applyFilter(q string, lines []string) ([]string, error) { } func (l *Log) fireLogBuffChanged(lines []string) { + log.Debug().Msgf("FIRE-BUFF-CHNGED") filtered, err := applyFilter(l.filter, lines) if err != nil { l.fireLogError(err) @@ -293,6 +307,22 @@ func (l *Log) fireLogCleared() { // ---------------------------------------------------------------------------- // Helpers... +// BOZO!! Log timestamps. +// func showTimes(lines []string, show bool) []string { +// filtered := make([]string, 0, len(lines)) +// for _, l := range lines { +// tokens := strings.Split(l, " ") +// if show { +// cols := make([]string, 0, len(tokens)) +// cols = append(cols, fmt.Sprintf("%-35s", tokens[0])) +// filtered = append(filtered, strings.Join(append(cols, tokens[1:]...), " ")) +// } else { +// filtered = append(filtered, strings.Join(tokens[1:], " ")) +// } +// } +// return filtered +// } + var fuzzyRx = regexp.MustCompile(`\A\-f`) func isFuzzySelector(s string) bool { diff --git a/internal/model/table.go b/internal/model/table.go index bd4257c8..c3036fee 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -37,7 +37,6 @@ type Table struct { refreshRate time.Duration instance string mx sync.RWMutex - hasMetrics bool } // NewTable returns a new table model. @@ -49,11 +48,6 @@ func NewTable(gvr client.GVR) *Table { } } -// HasMetrics determines if metrics are available on cluster. -func (t *Table) HasMetrics() bool { - return t.hasMetrics -} - // SetInstance sets a single entry table. func (t *Table) SetInstance(path string) { t.instance = path @@ -221,7 +215,6 @@ func (t *Table) list(ctx context.Context, a dao.Accessor) ([]runtime.Object, err } a.Init(factory, t.gvr) - t.hasMetrics = factory.Client().HasMetrics() ns := client.CleanseNamespace(t.namespace) if client.IsClusterScoped(t.namespace) { ns = client.AllNamespaces @@ -275,6 +268,10 @@ func (t *Table) reconcile(ctx context.Context) error { t.data.Update(rows) t.data.SetHeader(t.namespace, meta.Renderer.Header(t.namespace)) + if len(t.data.Header) == 0 { + return fmt.Errorf("fail to list resource %s", t.gvr) + } + return nil } diff --git a/internal/render/color.go b/internal/render/color.go index 16fcdb52..b3a6e0ef 100644 --- a/internal/render/color.go +++ b/internal/render/color.go @@ -1,6 +1,8 @@ package render -import "github.com/gdamore/tcell" +import ( + "github.com/gdamore/tcell" +) var ( // ModColor row modified color. diff --git a/internal/render/color_test.go b/internal/render/color_test.go new file mode 100644 index 00000000..92e947e0 --- /dev/null +++ b/internal/render/color_test.go @@ -0,0 +1,63 @@ +package render_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/render" + "github.com/gdamore/tcell" + "github.com/stretchr/testify/assert" +) + +func TestDefaultColorer(t *testing.T) { + uu := map[string]struct { + re render.RowEvent + e tcell.Color + }{ + "add": { + render.RowEvent{ + Kind: render.EventAdd, + }, + render.AddColor, + }, + "update": { + render.RowEvent{ + Kind: render.EventUpdate, + }, + render.ModColor, + }, + "delete": { + render.RowEvent{ + Kind: render.EventDelete, + }, + render.KillColor, + }, + "no-change": { + render.RowEvent{ + Kind: render.EventUnchanged, + }, + render.StdColor, + }, + "invalid": { + render.RowEvent{ + Kind: render.EventUnchanged, + Row: render.Row{ + Fields: render.Fields{"", "", "blah"}, + }, + }, + render.ErrColor, + }, + } + + h := render.Header{ + render.HeaderColumn{Name: "A"}, + render.HeaderColumn{Name: "B"}, + render.HeaderColumn{Name: "VALID"}, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, render.DefaultColorer("", h, u.re)) + }) + } +} diff --git a/internal/render/header.go b/internal/render/header.go index 3250459c..9a3dffa8 100644 --- a/internal/render/header.go +++ b/internal/render/header.go @@ -72,7 +72,6 @@ func (h Header) Customize(cols []string, wide bool) Header { xx := make(map[int]struct{}, len(h)) for _, c := range cols { idx := h.IndexOf(c, true) - // BOZO!! if idx == -1 { log.Warn().Msgf("Column %s is not available on this resource", c) col := HeaderColumn{ diff --git a/internal/render/ns.go b/internal/render/ns.go index 50c55b45..e7e2c076 100644 --- a/internal/render/ns.go +++ b/internal/render/ns.go @@ -18,20 +18,20 @@ type Namespace struct{} // ColorerFunc colors a resource row. func (n Namespace) ColorerFunc() ColorerFunc { return func(ns string, h Header, re RowEvent) tcell.Color { - if !Happy(ns, h, re.Row) { - return ErrColor - } - if re.Kind == EventAdd { - return DefaultColorer(ns, h, re) - } + c := DefaultColorer(ns, h, re) + if re.Kind == EventUpdate { - return StdColor + c = StdColor } if strings.Contains(strings.TrimSpace(re.Row.Fields[0]), "*") { - return HighlightColor + c = HighlightColor } - return DefaultColorer(ns, h, re) + if !Happy(ns, h, re.Row) { + c = ErrColor + } + + return c } } diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go index ef0b50b8..17c23fdc 100644 --- a/internal/render/ns_test.go +++ b/internal/render/ns_test.go @@ -4,38 +4,64 @@ import ( "testing" "github.com/derailed/k9s/internal/render" + "github.com/gdamore/tcell" "github.com/stretchr/testify/assert" ) func TestNSColorer(t *testing.T) { - var ( - ns = render.Row{Fields: render.Fields{"blee", "Active"}} - term = render.Row{Fields: render.Fields{"blee", render.Terminating}} - dead = render.Row{Fields: render.Fields{"blee", "Inactive"}} - ) - - uu := colorerUCs{ - // Add AllNS - {"", render.RowEvent{Kind: render.EventAdd, Row: ns}, render.AddColor}, - // Mod AllNS - {"", render.RowEvent{Kind: render.EventUpdate, Row: ns}, render.ModColor}, - // MoChange AllNS - {"", render.RowEvent{Kind: render.EventUnchanged, Row: ns}, render.StdColor}, - // Bust NS - {"", render.RowEvent{Kind: render.EventUnchanged, Row: term}, render.ErrColor}, - // Bust NS - {"", render.RowEvent{Kind: render.EventUnchanged, Row: dead}, render.ErrColor}, + uu := map[string]struct { + re render.RowEvent + e tcell.Color + }{ + "add": { + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{ + "blee", + "Active", + }, + }, + }, + e: render.AddColor, + }, + "update": { + re: render.RowEvent{ + Kind: render.EventUpdate, + Row: render.Row{ + Fields: render.Fields{ + "blee", + "Active", + }, + }, + }, + e: render.StdColor, + }, + "decorator": { + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{ + "blee*", + "Active", + }, + }, + }, + e: render.HighlightColor, + }, } h := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, + render.HeaderColumn{Name: "NAME"}, + render.HeaderColumn{Name: "STATUS"}, } - var n render.Namespace - f := n.ColorerFunc() - for _, u := range uu { - assert.Equal(t, u.e, f(u.ns, h, u.r)) + var r render.Namespace + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, r.ColorerFunc()("", h, u.re)) + }) } } diff --git a/internal/render/pod.go b/internal/render/pod.go index 270c86f1..d5dc8202 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -39,6 +39,9 @@ func (p Pod) ColorerFunc() ColorerFunc { c = CompletedColor case Running: c = StdColor + if !Happy(ns, h, re.Row) { + c = ErrColor + } case Terminating: c = KillColor default: @@ -46,7 +49,6 @@ func (p Pod) ColorerFunc() ColorerFunc { c = ErrColor } } - return c } } @@ -116,7 +118,7 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error { } func (p Pod) diagnose(phase string, cr, ct int) error { - if phase == "Completed" { + if phase == Completed { return nil } if cr != ct || ct == 0 { @@ -272,14 +274,14 @@ func (p *Pod) Phase(po *v1.Pod) string { } status, ok = p.containerPhase(po.Status, status) - if ok && status == "Completed" { - status = "Running" + if ok && status == Completed { + status = Running } if po.DeletionTimestamp == nil { return status } - return "Terminating" + return Terminating } func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) { diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index 030314bc..6ba2475d 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -12,58 +12,138 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) -type ( - colorerUC struct { - ns string - r render.RowEvent - e tcell.Color - } - - colorerUCs []colorerUC -) +func init() { + render.AddColor = tcell.ColorBlue + render.HighlightColor = tcell.ColorYellow + render.CompletedColor = tcell.ColorGray + render.StdColor = tcell.ColorWhite + render.ErrColor = tcell.ColorRed + render.KillColor = tcell.ColorGray +} func TestPodColorer(t *testing.T) { - var ( - nsRow = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "0", "Running"}} - toastNS = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "0", "Boom"}} - notReadyNS = render.Row{Fields: render.Fields{"blee", "fred", "0/1", "0", "Boom"}} - row = render.Row{Fields: render.Fields{"fred", "1/1", "0", "Running"}} - toast = render.Row{Fields: render.Fields{"fred", "1/1", "0", "Boom"}} - notReady = render.Row{Fields: render.Fields{"fred", "0/1", "0", "Boom"}} - ) - - uu := colorerUCs{ - // Add allNS - {"", render.RowEvent{Kind: render.EventAdd, Row: nsRow}, render.AddColor}, - // Add Namespaced - {"blee", render.RowEvent{Kind: render.EventAdd, Row: row}, render.AddColor}, - // Mod AllNS - {"", render.RowEvent{Kind: render.EventUpdate, Row: nsRow}, render.ModColor}, - // Mod Namespaced - {"blee", render.RowEvent{Kind: render.EventUpdate, Row: row}, render.ModColor}, - // Mod Busted AllNS - {"", render.RowEvent{Kind: render.EventUpdate, Row: toastNS}, render.ErrColor}, - // Mod Busted Namespaced - {"blee", render.RowEvent{Kind: render.EventUpdate, Row: toast}, render.ErrColor}, - // NotReady AllNS - {"", render.RowEvent{Kind: render.EventUpdate, Row: notReadyNS}, render.ErrColor}, - // NotReady Namespaced - {"blee", render.RowEvent{Kind: render.EventUpdate, Row: notReady}, render.ErrColor}, + stdHeader := render.Header{ + render.HeaderColumn{Name: "NAMESPACE"}, + render.HeaderColumn{Name: "NAME"}, + render.HeaderColumn{Name: "READY"}, + render.HeaderColumn{Name: "RESTART"}, + render.HeaderColumn{Name: "STATUS"}, + render.HeaderColumn{Name: "VALID"}, } - h := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - render.HeaderColumn{Name: "D"}, - render.HeaderColumn{Name: "E"}, - render.HeaderColumn{Name: "F"}, + uu := map[string]struct { + re render.RowEvent + h render.Header + e tcell.Color + }{ + "valid": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.Running, ""}, + }, + }, + e: render.StdColor, + }, + "init": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, ""}, + }, + }, + e: render.AddColor, + }, + "init-err": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.PodInitializing, "blah"}, + }, + }, + e: render.AddColor, + }, + "initialized": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.Initialized, "blah"}, + }, + }, + e: render.HighlightColor, + }, + "completed": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.Completed, "blah"}, + }, + }, + e: render.CompletedColor, + }, + "terminating": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", render.Terminating, "blah"}, + }, + }, + e: render.KillColor, + }, + "invalid": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", "Running", "blah"}, + }, + }, + e: render.ErrColor, + }, + "unknown-cool": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", ""}, + }, + }, + e: render.AddColor, + }, + "unknown-err": { + h: stdHeader, + re: render.RowEvent{ + Kind: render.EventAdd, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", "doh"}, + }, + }, + e: render.ErrColor, + }, + "status": { + h: stdHeader[0:3], + re: render.RowEvent{ + Kind: render.EventDelete, + Row: render.Row{ + Fields: render.Fields{"blee", "fred", "1/1", "0", "blee", ""}, + }, + }, + e: render.KillColor, + }, } - var p render.Pod - f := p.ColorerFunc() - for _, u := range uu { - assert.Equal(t, u.e, f(u.ns, h, u.r)) + var r render.Pod + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, r.ColorerFunc()("", u.h, u.re)) + }) } } diff --git a/internal/render/row_event_test.go b/internal/render/row_event_test.go index 90dd8a0c..8c8aa219 100644 --- a/internal/render/row_event_test.go +++ b/internal/render/row_event_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/derailed/k9s/internal/render" - "github.com/gdamore/tcell" "github.com/stretchr/testify/assert" ) @@ -485,31 +484,6 @@ func TestRowEventsClone(t *testing.T) { } } -func TestDefaultColorer(t *testing.T) { - uu := map[string]struct { - k render.ResEvent - e tcell.Color - }{ - "add": {render.EventAdd, render.AddColor}, - "update": {render.EventUpdate, render.ModColor}, - "delete": {render.EventDelete, render.KillColor}, - "std": {100, render.StdColor}, - } - - h := render.Header{ - render.HeaderColumn{Name: "A"}, - render.HeaderColumn{Name: "B"}, - render.HeaderColumn{Name: "C"}, - } - - for k := range uu { - u := uu[k] - t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, render.DefaultColorer("", h, render.RowEvent{})) - }) - } -} - // Helpers... func makeRowEvents() render.RowEvents { diff --git a/internal/ui/table.go b/internal/ui/table.go index bcb59347..bb515b1c 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -43,6 +43,7 @@ type Table struct { wide bool toast bool header render.Header + hasMetrics bool } // NewTable returns a new table view. @@ -71,6 +72,11 @@ func (t *Table) Init(ctx context.Context) { t.SetSelectionChangedFunc(t.selectionChanged) t.SetBackgroundColor(tcell.ColorDefault) + t.hasMetrics = false + if mx, ok := ctx.Value(internal.KeyHasMetrics).(bool); ok { + t.hasMetrics = mx + } + if cfg, ok := ctx.Value(internal.KeyViewConfig).(*config.CustomView); ok && cfg != nil { cfg.AddListener(t.GVR().String(), t) } @@ -198,7 +204,7 @@ func (t *Table) doUpdate(data render.TableData) { } custData := data.Customize(cols, t.wide) - if t.sortCol.name == "" || custData.Header.IndexOf(t.sortCol.name, false) == -1 { + if (t.sortCol.name == "" || custData.Header.IndexOf(t.sortCol.name, false) == -1) && len(custData.Header) > 0 { t.sortCol.name = custData.Header[0].Name } @@ -206,13 +212,12 @@ func (t *Table) doUpdate(data render.TableData) { fg := t.styles.Table().Header.FgColor.Color() bg := t.styles.Table().Header.BgColor.Color() - hasMX := t.model.HasMetrics() var col int for _, h := range custData.Header { if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() { continue } - if h.MX && !hasMX { + if h.MX && !t.hasMetrics { continue } t.AddHeaderCell(col, h) @@ -239,7 +244,6 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M } marked := t.IsMarked(re.Row.ID) - hasMX := t.model.HasMetrics() var col int for c, field := range re.Row.Fields { if c >= len(h) { @@ -250,7 +254,7 @@ func (t *Table) buildRow(r int, re, ore render.RowEvent, h render.Header, pads M if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() { continue } - if h[c].MX && !hasMX { + if h[c].MX && !t.hasMetrics { continue } diff --git a/internal/ui/types.go b/internal/ui/types.go index 1825a214..32a1e4ed 100644 --- a/internal/ui/types.go +++ b/internal/ui/types.go @@ -52,6 +52,7 @@ type Tabular interface { Namespaceable Lister + // SetInstance sets parent resource path. SetInstance(string) // Empty returns true if model has no data. @@ -60,9 +61,6 @@ type Tabular interface { // Peek returns current model data. Peek() render.TableData - // HasMetrics returns true if metrics are available on cluster. - HasMetrics() bool - // Watch watches a given resource for changes. Watch(context.Context) diff --git a/internal/view/log.go b/internal/view/log.go index 701f8502..82381260 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -26,7 +26,7 @@ const ( logFmt = " Logs([fg:bg:]%s) " // BOZO!! Canned! Need config tail line counts! - tailLineCount = 1000 + tailLineCount = 50 defaultTimeout = 200 * time.Millisecond ) @@ -49,7 +49,7 @@ func NewLog(gvr client.GVR, path, co string, prev bool) *Log { l := Log{ Flex: tview.NewFlex(), cmdBuff: ui.NewCmdBuff('/', ui.FilterBuff), - model: model.NewLog(gvr, buildLogOpts(path, co, prev, tailLineCount), defaultTimeout), + model: model.NewLog(gvr, buildLogOpts(path, co, prev, true, tailLineCount), defaultTimeout), } return &l @@ -167,10 +167,12 @@ func (l *Log) Name() string { return logTitle } func (l *Log) bindKeys() { l.logs.Actions().Set(ui.KeyActions{ - tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false), - tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, true), - ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true), - ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.ToggleAutoScrollCmd, true), + tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false), + tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, true), + ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true), + ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.ToggleAutoScrollCmd, true), + // BOZO!! Log timestamps + // ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.ToggleTimestampCmd, true), ui.KeyF: ui.NewKeyAction("FullScreen", l.fullScreenCmd, true), ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.textWrapCmd, true), tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true), @@ -353,6 +355,17 @@ func (l *Log) textWrapCmd(*tcell.EventKey) *tcell.EventKey { return nil } +// ToggleTimeStampCmd toggles timestamp field. +func (l *Log) ToggleTimestampCmd(evt *tcell.EventKey) *tcell.EventKey { + l.model.Clear() + l.indicator.ToggleTimestamp() + l.model.ShowTimestamp(l.indicator.Timestamp()) + l.model.Stop() + l.model.Start() + + return nil +} + // ToggleAutoScrollCmd toggles autoscroll status. func (l *Log) ToggleAutoScrollCmd(evt *tcell.EventKey) *tcell.EventKey { l.indicator.ToggleAutoScroll() @@ -392,11 +405,12 @@ func extractKey(evt *tcell.EventKey) tcell.Key { return key } -func buildLogOpts(path, co string, prevLogs bool, tailLineCount int) dao.LogOptions { +func buildLogOpts(path, co string, prevLogs, showTime bool, tailLineCount int) dao.LogOptions { return dao.LogOptions{ - Path: path, - Container: co, - Lines: int64(tailLineCount), - Previous: prevLogs, + Path: path, + Container: co, + Lines: int64(tailLineCount), + Previous: prevLogs, + ShowTimestamp: showTime, } } diff --git a/internal/view/log_indicator.go b/internal/view/log_indicator.go index 31a20ec6..ed1dc656 100644 --- a/internal/view/log_indicator.go +++ b/internal/view/log_indicator.go @@ -16,6 +16,7 @@ type LogIndicator struct { scrollStatus int32 fullScreen bool textWrap bool + showTime bool } // NewLogIndicator returns a new indicator. @@ -38,6 +39,11 @@ func (l *LogIndicator) AutoScroll() bool { return atomic.LoadInt32(&l.scrollStatus) == 1 } +// TextWrap reports the current wrap mode. +func (l *LogIndicator) Timestamp() bool { + return l.showTime +} + // TextWrap reports the current wrap mode. func (l *LogIndicator) TextWrap() bool { return l.textWrap @@ -48,6 +54,11 @@ func (l *LogIndicator) FullScreen() bool { return l.fullScreen } +// TextWrap reports the current wrap mode. +func (l *LogIndicator) ToggleTimestamp() { + l.showTime = !l.showTime +} + // ToggleFullScreen toggles the screen mode. func (l *LogIndicator) ToggleFullScreen() { l.fullScreen = !l.fullScreen @@ -75,6 +86,8 @@ func (l *LogIndicator) Refresh() { l.Clear() l.update("Autoscroll: " + l.onOff(l.AutoScroll())) l.update("FullScreen: " + l.onOff(l.fullScreen)) + // BOZO!! log timestamp + // l.update("Timestamp: " + l.onOff(l.showTime)) l.update("Wrap: " + l.onOff(l.textWrap)) } diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 2a588e3d..9a4606f1 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -53,7 +53,6 @@ func isResourcePath(p string) bool { } func (l *LogsExtender) showLogs(path string, prev bool) { - // Need to load and wait for pods ns, _ := client.Namespaced(path) _, err := l.App().factory.CanForResource(ns, "v1/pods", client.MonitorAccess) if err != nil { diff --git a/internal/view/table.go b/internal/view/table.go index bb017e83..d5054b41 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -37,6 +37,9 @@ func (t *Table) Init(ctx context.Context) (err error) { if t.app, err = extractApp(ctx); err != nil { return err } + if t.app.Conn() != nil { + ctx = context.WithValue(ctx, internal.KeyHasMetrics, t.app.Conn().HasMetrics()) + } ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles) ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView) t.Table.Init(ctx)