parent
e936b17fd0
commit
c896598e84
|
|
@ -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)
|
||||
|
|
@ -251,7 +251,7 @@ func newStatus() Status {
|
|||
ErrorColor: "orangered",
|
||||
HighlightColor: "aqua",
|
||||
KillColor: "mediumpurple",
|
||||
CompletedColor: "darkseagreen",
|
||||
CompletedColor: "lightslategray",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type LogOptions struct {
|
|||
Previous bool
|
||||
SingleContainer bool
|
||||
MultiPods bool
|
||||
ShowTimestamp bool
|
||||
}
|
||||
|
||||
// HasContainer checks if a container is present.
|
||||
|
|
|
|||
|
|
@ -227,6 +227,7 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- []byte, opts LogOptio
|
|||
Container: opts.Container,
|
||||
Follow: true,
|
||||
TailLines: &opts.Lines,
|
||||
Timestamps: opts.ShowTimestamp,
|
||||
Previous: opts.Previous,
|
||||
}
|
||||
req, err := logger.Logs(opts.Path, &o)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/sahilm/fuzzy"
|
||||
)
|
||||
|
||||
const logMaxBufferSize = 50
|
||||
const logMaxBufferSize = 100
|
||||
|
||||
// LogsListener represents a log model listener.
|
||||
type LogsListener interface {
|
||||
|
|
@ -40,6 +40,7 @@ type Log struct {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package render
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
import (
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
var (
|
||||
// ModColor row modified color.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -12,58 +12,138 @@ import (
|
|||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
)
|
||||
|
||||
type (
|
||||
colorerUC struct {
|
||||
ns string
|
||||
r render.RowEvent
|
||||
e tcell.Color
|
||||
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
|
||||
}
|
||||
|
||||
colorerUCs []colorerUC
|
||||
)
|
||||
|
||||
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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -171,6 +171,8 @@ func (l *Log) bindKeys() {
|
|||
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,
|
||||
ShowTimestamp: showTime,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue