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",
|
ErrorColor: "orangered",
|
||||||
HighlightColor: "aqua",
|
HighlightColor: "aqua",
|
||||||
KillColor: "mediumpurple",
|
KillColor: "mediumpurple",
|
||||||
CompletedColor: "darkseagreen",
|
CompletedColor: "lightslategray",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type LogOptions struct {
|
||||||
Previous bool
|
Previous bool
|
||||||
SingleContainer bool
|
SingleContainer bool
|
||||||
MultiPods bool
|
MultiPods bool
|
||||||
|
ShowTimestamp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasContainer checks if a container is present.
|
// 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,
|
Container: opts.Container,
|
||||||
Follow: true,
|
Follow: true,
|
||||||
TailLines: &opts.Lines,
|
TailLines: &opts.Lines,
|
||||||
|
Timestamps: opts.ShowTimestamp,
|
||||||
Previous: opts.Previous,
|
Previous: opts.Previous,
|
||||||
}
|
}
|
||||||
req, err := logger.Logs(opts.Path, &o)
|
req, err := logger.Logs(opts.Path, &o)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ func (s *Service) Pod(fqn string) (string, error) {
|
||||||
|
|
||||||
// GetInstance returns a service instance.
|
// GetInstance returns a service instance.
|
||||||
func (s *Service) GetInstance(fqn string) (*v1.Service, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const (
|
||||||
KeyApp ContextKey = "app"
|
KeyApp ContextKey = "app"
|
||||||
KeyStyles ContextKey = "styles"
|
KeyStyles ContextKey = "styles"
|
||||||
KeyMetrics ContextKey = "metrics"
|
KeyMetrics ContextKey = "metrics"
|
||||||
|
KeyHasMetrics ContextKey = "has-metrics"
|
||||||
KeyToast ContextKey = "toast"
|
KeyToast ContextKey = "toast"
|
||||||
KeyWithMetrics ContextKey = "withMetrics"
|
KeyWithMetrics ContextKey = "withMetrics"
|
||||||
KeyViewConfig ContextKey = "viewConfig"
|
KeyViewConfig ContextKey = "viewConfig"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/sahilm/fuzzy"
|
"github.com/sahilm/fuzzy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const logMaxBufferSize = 50
|
const logMaxBufferSize = 100
|
||||||
|
|
||||||
// LogsListener represents a log model listener.
|
// LogsListener represents a log model listener.
|
||||||
type LogsListener interface {
|
type LogsListener interface {
|
||||||
|
|
@ -40,6 +40,7 @@ type Log struct {
|
||||||
mx sync.RWMutex
|
mx sync.RWMutex
|
||||||
filter string
|
filter string
|
||||||
lastSent int
|
lastSent int
|
||||||
|
showTimestamp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLog returns a new model.
|
// NewLog returns a new model.
|
||||||
|
|
@ -72,6 +73,16 @@ func (l *Log) Clear() {
|
||||||
l.fireLogCleared()
|
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.
|
// Start initialize log tailer.
|
||||||
func (l *Log) Start() {
|
func (l *Log) Start() {
|
||||||
if err := l.load(); err != nil {
|
if err := l.load(); err != nil {
|
||||||
|
|
@ -100,6 +111,7 @@ func (l *Log) Set(lines []string) {
|
||||||
|
|
||||||
// ClearFilter resets the log filter if any.
|
// ClearFilter resets the log filter if any.
|
||||||
func (l *Log) ClearFilter() {
|
func (l *Log) ClearFilter() {
|
||||||
|
log.Debug().Msgf("CLEARED!!")
|
||||||
l.mx.RLock()
|
l.mx.RLock()
|
||||||
defer l.mx.RUnlock()
|
defer l.mx.RUnlock()
|
||||||
|
|
||||||
|
|
@ -112,6 +124,7 @@ func (l *Log) Filter(q string) error {
|
||||||
l.mx.RLock()
|
l.mx.RLock()
|
||||||
defer l.mx.RUnlock()
|
defer l.mx.RUnlock()
|
||||||
|
|
||||||
|
log.Debug().Msgf("FILTER!")
|
||||||
l.filter = q
|
l.filter = q
|
||||||
filtered, err := applyFilter(l.filter, l.lines)
|
filtered, err := applyFilter(l.filter, l.lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -262,6 +275,7 @@ func applyFilter(q string, lines []string) ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) fireLogBuffChanged(lines []string) {
|
func (l *Log) fireLogBuffChanged(lines []string) {
|
||||||
|
log.Debug().Msgf("FIRE-BUFF-CHNGED")
|
||||||
filtered, err := applyFilter(l.filter, lines)
|
filtered, err := applyFilter(l.filter, lines)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.fireLogError(err)
|
l.fireLogError(err)
|
||||||
|
|
@ -293,6 +307,22 @@ func (l *Log) fireLogCleared() {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// 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`)
|
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||||
|
|
||||||
func isFuzzySelector(s string) bool {
|
func isFuzzySelector(s string) bool {
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ type Table struct {
|
||||||
refreshRate time.Duration
|
refreshRate time.Duration
|
||||||
instance string
|
instance string
|
||||||
mx sync.RWMutex
|
mx sync.RWMutex
|
||||||
hasMetrics bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTable returns a new table model.
|
// 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.
|
// SetInstance sets a single entry table.
|
||||||
func (t *Table) SetInstance(path string) {
|
func (t *Table) SetInstance(path string) {
|
||||||
t.instance = path
|
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)
|
a.Init(factory, t.gvr)
|
||||||
|
|
||||||
t.hasMetrics = factory.Client().HasMetrics()
|
|
||||||
ns := client.CleanseNamespace(t.namespace)
|
ns := client.CleanseNamespace(t.namespace)
|
||||||
if client.IsClusterScoped(t.namespace) {
|
if client.IsClusterScoped(t.namespace) {
|
||||||
ns = client.AllNamespaces
|
ns = client.AllNamespaces
|
||||||
|
|
@ -275,6 +268,10 @@ func (t *Table) reconcile(ctx context.Context) error {
|
||||||
t.data.Update(rows)
|
t.data.Update(rows)
|
||||||
t.data.SetHeader(t.namespace, meta.Renderer.Header(t.namespace))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import "github.com/gdamore/tcell"
|
import (
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ModColor row modified color.
|
// 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))
|
xx := make(map[int]struct{}, len(h))
|
||||||
for _, c := range cols {
|
for _, c := range cols {
|
||||||
idx := h.IndexOf(c, true)
|
idx := h.IndexOf(c, true)
|
||||||
// BOZO!!
|
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
log.Warn().Msgf("Column %s is not available on this resource", c)
|
log.Warn().Msgf("Column %s is not available on this resource", c)
|
||||||
col := HeaderColumn{
|
col := HeaderColumn{
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,20 @@ type Namespace struct{}
|
||||||
// ColorerFunc colors a resource row.
|
// ColorerFunc colors a resource row.
|
||||||
func (n Namespace) ColorerFunc() ColorerFunc {
|
func (n Namespace) ColorerFunc() ColorerFunc {
|
||||||
return func(ns string, h Header, re RowEvent) tcell.Color {
|
return func(ns string, h Header, re RowEvent) tcell.Color {
|
||||||
if !Happy(ns, h, re.Row) {
|
c := DefaultColorer(ns, h, re)
|
||||||
return ErrColor
|
|
||||||
}
|
|
||||||
if re.Kind == EventAdd {
|
|
||||||
return DefaultColorer(ns, h, re)
|
|
||||||
}
|
|
||||||
if re.Kind == EventUpdate {
|
if re.Kind == EventUpdate {
|
||||||
return StdColor
|
c = StdColor
|
||||||
}
|
}
|
||||||
if strings.Contains(strings.TrimSpace(re.Row.Fields[0]), "*") {
|
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"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNSColorer(t *testing.T) {
|
func TestNSColorer(t *testing.T) {
|
||||||
var (
|
uu := map[string]struct {
|
||||||
ns = render.Row{Fields: render.Fields{"blee", "Active"}}
|
re render.RowEvent
|
||||||
term = render.Row{Fields: render.Fields{"blee", render.Terminating}}
|
e tcell.Color
|
||||||
dead = render.Row{Fields: render.Fields{"blee", "Inactive"}}
|
}{
|
||||||
)
|
"add": {
|
||||||
|
re: render.RowEvent{
|
||||||
uu := colorerUCs{
|
Kind: render.EventAdd,
|
||||||
// Add AllNS
|
Row: render.Row{
|
||||||
{"", render.RowEvent{Kind: render.EventAdd, Row: ns}, render.AddColor},
|
Fields: render.Fields{
|
||||||
// Mod AllNS
|
"blee",
|
||||||
{"", render.RowEvent{Kind: render.EventUpdate, Row: ns}, render.ModColor},
|
"Active",
|
||||||
// MoChange AllNS
|
},
|
||||||
{"", render.RowEvent{Kind: render.EventUnchanged, Row: ns}, render.StdColor},
|
},
|
||||||
// Bust NS
|
},
|
||||||
{"", render.RowEvent{Kind: render.EventUnchanged, Row: term}, render.ErrColor},
|
e: render.AddColor,
|
||||||
// Bust NS
|
},
|
||||||
{"", render.RowEvent{Kind: render.EventUnchanged, Row: dead}, render.ErrColor},
|
"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{
|
h := render.Header{
|
||||||
render.HeaderColumn{Name: "A"},
|
render.HeaderColumn{Name: "NAME"},
|
||||||
render.HeaderColumn{Name: "B"},
|
render.HeaderColumn{Name: "STATUS"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var n render.Namespace
|
var r render.Namespace
|
||||||
f := n.ColorerFunc()
|
for k := range uu {
|
||||||
for _, u := range uu {
|
u := uu[k]
|
||||||
assert.Equal(t, u.e, f(u.ns, h, u.r))
|
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
|
c = CompletedColor
|
||||||
case Running:
|
case Running:
|
||||||
c = StdColor
|
c = StdColor
|
||||||
|
if !Happy(ns, h, re.Row) {
|
||||||
|
c = ErrColor
|
||||||
|
}
|
||||||
case Terminating:
|
case Terminating:
|
||||||
c = KillColor
|
c = KillColor
|
||||||
default:
|
default:
|
||||||
|
|
@ -46,7 +49,6 @@ func (p Pod) ColorerFunc() ColorerFunc {
|
||||||
c = ErrColor
|
c = ErrColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c
|
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 {
|
func (p Pod) diagnose(phase string, cr, ct int) error {
|
||||||
if phase == "Completed" {
|
if phase == Completed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cr != ct || ct == 0 {
|
if cr != ct || ct == 0 {
|
||||||
|
|
@ -272,14 +274,14 @@ func (p *Pod) Phase(po *v1.Pod) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
status, ok = p.containerPhase(po.Status, status)
|
status, ok = p.containerPhase(po.Status, status)
|
||||||
if ok && status == "Completed" {
|
if ok && status == Completed {
|
||||||
status = "Running"
|
status = Running
|
||||||
}
|
}
|
||||||
if po.DeletionTimestamp == nil {
|
if po.DeletionTimestamp == nil {
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Terminating"
|
return Terminating
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) {
|
func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) {
|
||||||
|
|
|
||||||
|
|
@ -12,58 +12,138 @@ import (
|
||||||
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
func init() {
|
||||||
colorerUC struct {
|
render.AddColor = tcell.ColorBlue
|
||||||
ns string
|
render.HighlightColor = tcell.ColorYellow
|
||||||
r render.RowEvent
|
render.CompletedColor = tcell.ColorGray
|
||||||
e tcell.Color
|
render.StdColor = tcell.ColorWhite
|
||||||
|
render.ErrColor = tcell.ColorRed
|
||||||
|
render.KillColor = tcell.ColorGray
|
||||||
}
|
}
|
||||||
|
|
||||||
colorerUCs []colorerUC
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPodColorer(t *testing.T) {
|
func TestPodColorer(t *testing.T) {
|
||||||
var (
|
stdHeader := render.Header{
|
||||||
nsRow = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "0", "Running"}}
|
render.HeaderColumn{Name: "NAMESPACE"},
|
||||||
toastNS = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "0", "Boom"}}
|
render.HeaderColumn{Name: "NAME"},
|
||||||
notReadyNS = render.Row{Fields: render.Fields{"blee", "fred", "0/1", "0", "Boom"}}
|
render.HeaderColumn{Name: "READY"},
|
||||||
row = render.Row{Fields: render.Fields{"fred", "1/1", "0", "Running"}}
|
render.HeaderColumn{Name: "RESTART"},
|
||||||
toast = render.Row{Fields: render.Fields{"fred", "1/1", "0", "Boom"}}
|
render.HeaderColumn{Name: "STATUS"},
|
||||||
notReady = render.Row{Fields: render.Fields{"fred", "0/1", "0", "Boom"}}
|
render.HeaderColumn{Name: "VALID"},
|
||||||
)
|
|
||||||
|
|
||||||
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},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h := render.Header{
|
uu := map[string]struct {
|
||||||
render.HeaderColumn{Name: "A"},
|
re render.RowEvent
|
||||||
render.HeaderColumn{Name: "B"},
|
h render.Header
|
||||||
render.HeaderColumn{Name: "C"},
|
e tcell.Color
|
||||||
render.HeaderColumn{Name: "D"},
|
}{
|
||||||
render.HeaderColumn{Name: "E"},
|
"valid": {
|
||||||
render.HeaderColumn{Name: "F"},
|
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
|
var r render.Pod
|
||||||
f := p.ColorerFunc()
|
for k := range uu {
|
||||||
for _, u := range uu {
|
u := uu[k]
|
||||||
assert.Equal(t, u.e, f(u.ns, h, u.r))
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, r.ColorerFunc()("", u.h, u.re))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"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...
|
// Helpers...
|
||||||
|
|
||||||
func makeRowEvents() render.RowEvents {
|
func makeRowEvents() render.RowEvents {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ type Table struct {
|
||||||
wide bool
|
wide bool
|
||||||
toast bool
|
toast bool
|
||||||
header render.Header
|
header render.Header
|
||||||
|
hasMetrics bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTable returns a new table view.
|
// NewTable returns a new table view.
|
||||||
|
|
@ -71,6 +72,11 @@ func (t *Table) Init(ctx context.Context) {
|
||||||
t.SetSelectionChangedFunc(t.selectionChanged)
|
t.SetSelectionChangedFunc(t.selectionChanged)
|
||||||
t.SetBackgroundColor(tcell.ColorDefault)
|
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 {
|
if cfg, ok := ctx.Value(internal.KeyViewConfig).(*config.CustomView); ok && cfg != nil {
|
||||||
cfg.AddListener(t.GVR().String(), t)
|
cfg.AddListener(t.GVR().String(), t)
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +204,7 @@ func (t *Table) doUpdate(data render.TableData) {
|
||||||
}
|
}
|
||||||
custData := data.Customize(cols, t.wide)
|
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
|
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()
|
fg := t.styles.Table().Header.FgColor.Color()
|
||||||
bg := t.styles.Table().Header.BgColor.Color()
|
bg := t.styles.Table().Header.BgColor.Color()
|
||||||
|
|
||||||
hasMX := t.model.HasMetrics()
|
|
||||||
var col int
|
var col int
|
||||||
for _, h := range custData.Header {
|
for _, h := range custData.Header {
|
||||||
if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if h.MX && !hasMX {
|
if h.MX && !t.hasMetrics {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.AddHeaderCell(col, h)
|
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)
|
marked := t.IsMarked(re.Row.ID)
|
||||||
hasMX := t.model.HasMetrics()
|
|
||||||
var col int
|
var col int
|
||||||
for c, field := range re.Row.Fields {
|
for c, field := range re.Row.Fields {
|
||||||
if c >= len(h) {
|
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() {
|
if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if h[c].MX && !hasMX {
|
if h[c].MX && !t.hasMetrics {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ type Tabular interface {
|
||||||
Namespaceable
|
Namespaceable
|
||||||
Lister
|
Lister
|
||||||
|
|
||||||
|
// SetInstance sets parent resource path.
|
||||||
SetInstance(string)
|
SetInstance(string)
|
||||||
|
|
||||||
// Empty returns true if model has no data.
|
// Empty returns true if model has no data.
|
||||||
|
|
@ -60,9 +61,6 @@ type Tabular interface {
|
||||||
// Peek returns current model data.
|
// Peek returns current model data.
|
||||||
Peek() render.TableData
|
Peek() render.TableData
|
||||||
|
|
||||||
// HasMetrics returns true if metrics are available on cluster.
|
|
||||||
HasMetrics() bool
|
|
||||||
|
|
||||||
// Watch watches a given resource for changes.
|
// Watch watches a given resource for changes.
|
||||||
Watch(context.Context)
|
Watch(context.Context)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const (
|
||||||
logFmt = " Logs([fg:bg:]%s) "
|
logFmt = " Logs([fg:bg:]%s) "
|
||||||
|
|
||||||
// BOZO!! Canned! Need config tail line counts!
|
// BOZO!! Canned! Need config tail line counts!
|
||||||
tailLineCount = 1000
|
tailLineCount = 50
|
||||||
defaultTimeout = 200 * time.Millisecond
|
defaultTimeout = 200 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ func NewLog(gvr client.GVR, path, co string, prev bool) *Log {
|
||||||
l := Log{
|
l := Log{
|
||||||
Flex: tview.NewFlex(),
|
Flex: tview.NewFlex(),
|
||||||
cmdBuff: ui.NewCmdBuff('/', ui.FilterBuff),
|
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
|
return &l
|
||||||
|
|
@ -171,6 +171,8 @@ func (l *Log) bindKeys() {
|
||||||
tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, true),
|
tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, true),
|
||||||
ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true),
|
ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.ToggleAutoScrollCmd, 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.KeyF: ui.NewKeyAction("FullScreen", l.fullScreenCmd, true),
|
||||||
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.textWrapCmd, true),
|
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.textWrapCmd, true),
|
||||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
||||||
|
|
@ -353,6 +355,17 @@ func (l *Log) textWrapCmd(*tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
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.
|
// ToggleAutoScrollCmd toggles autoscroll status.
|
||||||
func (l *Log) ToggleAutoScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (l *Log) ToggleAutoScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
l.indicator.ToggleAutoScroll()
|
l.indicator.ToggleAutoScroll()
|
||||||
|
|
@ -392,11 +405,12 @@ func extractKey(evt *tcell.EventKey) tcell.Key {
|
||||||
return 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{
|
return dao.LogOptions{
|
||||||
Path: path,
|
Path: path,
|
||||||
Container: co,
|
Container: co,
|
||||||
Lines: int64(tailLineCount),
|
Lines: int64(tailLineCount),
|
||||||
Previous: prevLogs,
|
Previous: prevLogs,
|
||||||
|
ShowTimestamp: showTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type LogIndicator struct {
|
||||||
scrollStatus int32
|
scrollStatus int32
|
||||||
fullScreen bool
|
fullScreen bool
|
||||||
textWrap bool
|
textWrap bool
|
||||||
|
showTime bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLogIndicator returns a new indicator.
|
// NewLogIndicator returns a new indicator.
|
||||||
|
|
@ -38,6 +39,11 @@ func (l *LogIndicator) AutoScroll() bool {
|
||||||
return atomic.LoadInt32(&l.scrollStatus) == 1
|
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.
|
// TextWrap reports the current wrap mode.
|
||||||
func (l *LogIndicator) TextWrap() bool {
|
func (l *LogIndicator) TextWrap() bool {
|
||||||
return l.textWrap
|
return l.textWrap
|
||||||
|
|
@ -48,6 +54,11 @@ func (l *LogIndicator) FullScreen() bool {
|
||||||
return l.fullScreen
|
return l.fullScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextWrap reports the current wrap mode.
|
||||||
|
func (l *LogIndicator) ToggleTimestamp() {
|
||||||
|
l.showTime = !l.showTime
|
||||||
|
}
|
||||||
|
|
||||||
// ToggleFullScreen toggles the screen mode.
|
// ToggleFullScreen toggles the screen mode.
|
||||||
func (l *LogIndicator) ToggleFullScreen() {
|
func (l *LogIndicator) ToggleFullScreen() {
|
||||||
l.fullScreen = !l.fullScreen
|
l.fullScreen = !l.fullScreen
|
||||||
|
|
@ -75,6 +86,8 @@ func (l *LogIndicator) Refresh() {
|
||||||
l.Clear()
|
l.Clear()
|
||||||
l.update("Autoscroll: " + l.onOff(l.AutoScroll()))
|
l.update("Autoscroll: " + l.onOff(l.AutoScroll()))
|
||||||
l.update("FullScreen: " + l.onOff(l.fullScreen))
|
l.update("FullScreen: " + l.onOff(l.fullScreen))
|
||||||
|
// BOZO!! log timestamp
|
||||||
|
// l.update("Timestamp: " + l.onOff(l.showTime))
|
||||||
l.update("Wrap: " + l.onOff(l.textWrap))
|
l.update("Wrap: " + l.onOff(l.textWrap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ func isResourcePath(p string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LogsExtender) showLogs(path string, prev bool) {
|
func (l *LogsExtender) showLogs(path string, prev bool) {
|
||||||
// Need to load and wait for pods
|
|
||||||
ns, _ := client.Namespaced(path)
|
ns, _ := client.Namespaced(path)
|
||||||
_, err := l.App().factory.CanForResource(ns, "v1/pods", client.MonitorAccess)
|
_, err := l.App().factory.CanForResource(ns, "v1/pods", client.MonitorAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ func (t *Table) Init(ctx context.Context) (err error) {
|
||||||
if t.app, err = extractApp(ctx); err != nil {
|
if t.app, err = extractApp(ctx); err != nil {
|
||||||
return err
|
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.KeyStyles, t.app.Styles)
|
||||||
ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView)
|
ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView)
|
||||||
t.Table.Init(ctx)
|
t.Table.Init(ctx)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue