derailed 2020-02-27 21:47:21 -07:00
parent e936b17fd0
commit c896598e84
22 changed files with 385 additions and 154 deletions

View File

@ -0,0 +1,24 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# 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)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -251,7 +251,7 @@ func newStatus() Status {
ErrorColor: "orangered",
HighlightColor: "aqua",
KillColor: "mediumpurple",
CompletedColor: "darkseagreen",
CompletedColor: "lightslategray",
}
}

View File

@ -16,6 +16,7 @@ type LogOptions struct {
Previous bool
SingleContainer bool
MultiPods bool
ShowTimestamp bool
}
// HasContainer checks if a container is present.

View File

@ -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 {

View File

@ -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
}

View File

@ -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"

View File

@ -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 {

View File

@ -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
}

View File

@ -1,6 +1,8 @@
package render
import "github.com/gdamore/tcell"
import (
"github.com/gdamore/tcell"
)
var (
// ModColor row modified color.

View File

@ -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))
})
}
}

View File

@ -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{

View File

@ -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
}
}

View File

@ -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))
})
}
}

View File

@ -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) {

View File

@ -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))
})
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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,
}
}

View File

@ -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))
}

View File

@ -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 {

View File

@ -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)