misc bug fixes
parent
921a39f897
commit
c61365a9c9
|
|
@ -10,6 +10,7 @@ for changes and offers subsequent commands to interact with observed Kubernetes
|
|||
---
|
||||
|
||||
[](https://goreportcard.com/report/github.com/derailed/k9s)
|
||||
[](https://codebeat.co/projects/github-com-derailed-k9s-master)
|
||||
[](https://travis-ci.com/derailed/k9s)
|
||||
[](https://github.com/derailed/k9s/releases)
|
||||
[](https://github.com/mum4k/termdash/blob/master/LICENSE)
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -20,7 +20,6 @@ require (
|
|||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell v1.1.1
|
||||
github.com/gogo/protobuf v1.2.1 // indirect
|
||||
|
|
@ -34,7 +33,6 @@ require (
|
|||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4
|
||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||
github.com/onsi/gomega v1.5.0 // indirect
|
||||
|
|
@ -45,7 +43,6 @@ require (
|
|||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.3 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect
|
||||
|
|
|
|||
13
go.sum
13
go.sum
|
|
@ -33,10 +33,6 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/derailed/tview v0.1.8 h1:HjkCCTzgZWkkyUtJMGxhRJtvIvcRLuPCb9Uh11KRoC4=
|
||||
github.com/derailed/tview v0.1.8/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||
github.com/derailed/tview v0.1.9 h1:CYyGBvhJ4VenoRlUE1NDstyv4kayjQVnidSDAwuemdk=
|
||||
github.com/derailed/tview v0.1.9/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||
github.com/derailed/tview v0.1.10 h1:QWjK82ccTl3C7Tfyfmv765eRqEt/T3aXp40464cfnlw=
|
||||
github.com/derailed/tview v0.1.10/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
|
|
@ -55,8 +51,6 @@ github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7Vpz
|
|||
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
|
|
@ -129,10 +123,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
|
|
@ -193,8 +183,6 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719 h1:x9/CGytbUciiGoBHn04xsJJR/Lsg4b3qQlKJSZY5Gzw=
|
||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719/go.mod h1:+72MfLYlS87s4tqq+eVDANQ9GdILz0lkpAFlX/1+WWY=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
|
|
@ -243,7 +231,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
|
||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
|||
|
|
@ -22,19 +22,18 @@ type (
|
|||
|
||||
// Style tracks K9s styles.
|
||||
Style struct {
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
LogoColor string `yaml:"logoColor"`
|
||||
|
||||
Info *Info `yaml:"info"`
|
||||
Border *Border `yaml:"border"`
|
||||
Menu *Menu `yaml:"menu"`
|
||||
Crumb *Crumb `yaml:"crumb"`
|
||||
Table *Table `yaml:"table"`
|
||||
Status *Status `yaml:"status"`
|
||||
Title *Title `yaml:"title"`
|
||||
Yaml *Yaml `yaml:"yaml"`
|
||||
Log *Log `yaml:"logs"`
|
||||
FgColor string `yaml:"fgColor"`
|
||||
BgColor string `yaml:"bgColor"`
|
||||
LogoColor string `yaml:"logoColor"`
|
||||
Title *Title `yaml:"title"`
|
||||
Border *Border `yaml:"border"`
|
||||
Info *Info `yaml:"info"`
|
||||
Menu *Menu `yaml:"menu"`
|
||||
Crumb *Crumb `yaml:"crumb"`
|
||||
Table *Table `yaml:"table"`
|
||||
Status *Status `yaml:"status"`
|
||||
Yaml *Yaml `yaml:"yaml"`
|
||||
Log *Log `yaml:"logs"`
|
||||
}
|
||||
|
||||
// Status tracks resource status styles.
|
||||
|
|
@ -117,13 +116,13 @@ func newStyle() *Style {
|
|||
FgColor: "cadetblue",
|
||||
BgColor: "black",
|
||||
LogoColor: "orange",
|
||||
Info: newInfo(),
|
||||
Border: newBorder(),
|
||||
Title: newTitle(),
|
||||
Info: newInfo(),
|
||||
Menu: newMenu(),
|
||||
Crumb: newCrumb(),
|
||||
Table: newTable(),
|
||||
Status: newStatus(),
|
||||
Title: newTitle(),
|
||||
Yaml: newYaml(),
|
||||
Log: newLog(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package resource
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
|
||||
|
|
@ -127,14 +129,23 @@ func (b *Base) List(ns string) (Columnars, error) {
|
|||
|
||||
// Describe a given resource.
|
||||
func (b *Base) Describe(kind, pa string) (string, error) {
|
||||
ns, n := namespaced(pa)
|
||||
|
||||
mapping, err := k8s.RestMapping.Find(kind)
|
||||
if err != nil {
|
||||
log.Debug().Msgf("Unable to find mapper for %s %s", kind, pa)
|
||||
return "", err
|
||||
g, v, n := b.Resource.(*k8s.Resource).GetInfo()
|
||||
mapper := k8s.RestMapper{b.Connection}
|
||||
var e error
|
||||
mapping, e = mapper.ResourceFor(fmt.Sprintf("%s.%s.%s", n, v, g))
|
||||
if e != nil {
|
||||
log.Debug().Err(err).Msgf("Unable to find mapper for %s %s", kind, pa)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return b.doDescribe(pa, mapping)
|
||||
}
|
||||
|
||||
func (b *Base) doDescribe(pa string, mapping *meta.RESTMapping) (string, error) {
|
||||
ns, n := namespaced(pa)
|
||||
d, err := versioned.Describer(b.Connection.Config().Flags(), mapping)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
||||
|
|
|
|||
|
|
@ -39,32 +39,23 @@ func (v *aliasView) init(context.Context, string) {
|
|||
v.update(v.hydrate())
|
||||
v.app.SetFocus(v)
|
||||
v.resetTitle()
|
||||
v.app.setHints(v.hints())
|
||||
|
||||
}
|
||||
|
||||
func (v *aliasView) registerActions() {
|
||||
delete(v.actions, KeyShiftA)
|
||||
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
|
||||
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
||||
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
||||
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true)
|
||||
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true)
|
||||
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortColCmd(1), true)
|
||||
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortColCmd(2), true)
|
||||
}
|
||||
|
||||
func (v *aliasView) getTitle() string {
|
||||
return aliasTitle
|
||||
}
|
||||
|
||||
func (v *aliasView) sortResourceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
v.sortCol.index, v.sortCol.asc = 1, true
|
||||
v.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *aliasView) sortGroupCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
v.sortCol.index, v.sortCol.asc = 2, true
|
||||
v.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.cmdBuff.empty() {
|
||||
v.cmdBuff.reset()
|
||||
|
|
@ -115,7 +106,8 @@ func (v *aliasView) hints() hints {
|
|||
}
|
||||
|
||||
func (v *aliasView) hydrate() resource.TableData {
|
||||
cmds := helpCmds(v.app.conn())
|
||||
cmds := make(map[string]resCmd, 40)
|
||||
aliasCmds(v.app.conn(), cmds)
|
||||
|
||||
data := resource.TableData{
|
||||
Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"},
|
||||
|
|
|
|||
|
|
@ -99,8 +99,10 @@ func (a *appView) registerActions() {
|
|||
}
|
||||
|
||||
func (a *appView) Init(version string, rate int) {
|
||||
a.startInformer()
|
||||
a.clusterInfo().init(version)
|
||||
if a.conn() != nil {
|
||||
a.startInformer()
|
||||
a.clusterInfo().init(version)
|
||||
}
|
||||
a.cmdBuff.addListener(a.cmd())
|
||||
|
||||
header := tview.NewFlex()
|
||||
|
|
@ -333,6 +335,7 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
func (a *appView) currentView() igniter {
|
||||
return a.content.GetPrimitive("main").(igniter)
|
||||
}
|
||||
|
||||
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
package views
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/config"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// func TestNewApp(t *testing.T) {
|
||||
// mk := NewMockKubeSettings()
|
||||
// cfg := config.NewConfig(mk)
|
||||
// a := NewApp(cfg)
|
||||
// a.Init("blee", 10)
|
||||
func TestNewApp(t *testing.T) {
|
||||
a := NewApp(config.NewConfig(ks{}))
|
||||
a.Init("blee", 10)
|
||||
|
||||
// assert.Equal(t, 10, len(a.actions))
|
||||
// assert.Equal(t, false, a.hasSkins)
|
||||
// }
|
||||
assert.Equal(t, 10, len(a.actions))
|
||||
assert.Equal(t, false, a.hasSkins)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,11 @@ const (
|
|||
// K9sBenchDir directory to store K9s benchmark files.
|
||||
var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser()))
|
||||
|
||||
type (
|
||||
benchmark struct {
|
||||
canceled bool
|
||||
config config.BenchConfig
|
||||
worker *requester.Work
|
||||
}
|
||||
)
|
||||
type benchmark struct {
|
||||
canceled bool
|
||||
config config.BenchConfig
|
||||
worker *requester.Work
|
||||
}
|
||||
|
||||
func newBenchmark(base string, cfg config.BenchConfig) (*benchmark, error) {
|
||||
b := benchmark{config: cfg}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ func (c *command) run(cmd string) bool {
|
|||
return true
|
||||
}
|
||||
default:
|
||||
if res, ok := resourceViews(c.app.conn())[cmd]; ok {
|
||||
cmds := make(map[string]resCmd, 30)
|
||||
resourceViews(c.app.conn(), cmds)
|
||||
if res, ok := cmds[cmd]; ok {
|
||||
var r resource.List
|
||||
if res.listFn != nil {
|
||||
r = res.listFn(c.app.conn(), resource.DefaultNamespace)
|
||||
|
|
@ -91,20 +93,22 @@ func (c *command) run(cmd string) bool {
|
|||
}
|
||||
}
|
||||
|
||||
res, ok := allCRDs(c.app.conn())[cmd]
|
||||
cmds := make(map[string]resCmd, 30)
|
||||
allCRDs(c.app.conn(), cmds)
|
||||
res, ok := cmds[cmd]
|
||||
if !ok {
|
||||
c.app.flash().warnf("Huh? `%s` command not found", cmd)
|
||||
return false
|
||||
}
|
||||
|
||||
name := res.Plural
|
||||
if len(name) == 0 {
|
||||
name = res.Singular
|
||||
name := res.plural
|
||||
if name == "" {
|
||||
name = res.singular
|
||||
}
|
||||
v = newResourceView(
|
||||
res.Kind,
|
||||
res.title,
|
||||
c.app,
|
||||
resource.NewCustomList(c.app.conn(), "", res.Group, res.Version, name),
|
||||
resource.NewCustomList(c.app.conn(), "", res.api, res.version, name),
|
||||
)
|
||||
v.setColorerFn(defaultColorer)
|
||||
c.exec(cmd, v)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommandPush(t *testing.T) {
|
||||
c := newCommand(NewApp(config.NewConfig(ks{})))
|
||||
c.pushCmd("fred")
|
||||
c.pushCmd("blee")
|
||||
p, top := c.previousCmd()
|
||||
|
||||
assert.Equal(t, "fred", p)
|
||||
assert.True(t, top)
|
||||
assert.True(t, c.lastCmd())
|
||||
}
|
||||
|
|
@ -10,8 +10,8 @@ type contextView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newContextView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := contextView{newResourceView(t, app, list).(*resourceView)}
|
||||
func newContextView(title string, app *appView, list resource.List) resourceViewer {
|
||||
v := contextView{newResourceView(title, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.useCtx
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContextView(t *testing.T) {
|
||||
l := resource.NewContextList(nil, "fred")
|
||||
v := newContextView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||
|
||||
assert.Equal(t, "blee", v.getTitle())
|
||||
}
|
||||
|
||||
func TestCleaner(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
s, e string
|
||||
}{
|
||||
"normal": {"fred", "fred"},
|
||||
"default": {"fred*", "fred"},
|
||||
"delta": {"fred(𝜟)", "fred"},
|
||||
}
|
||||
|
||||
v := contextView{}
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, v.cleanser(u.s))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,8 @@ type deployView struct {
|
|||
*logResourceView
|
||||
}
|
||||
|
||||
func newDeployView(ns string, app *appView, list resource.List) resourceViewer {
|
||||
v := deployView{newLogResourceView(ns, app, list)}
|
||||
func newDeployView(title string, app *appView, list resource.List) resourceViewer {
|
||||
v := deployView{newLogResourceView(title, app, list)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeployView(t *testing.T) {
|
||||
l := resource.NewDeploymentList(nil, "fred")
|
||||
v := newDeployView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||
|
||||
assert.Equal(t, "blee", v.getTitle())
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDaemonSetView(t *testing.T) {
|
||||
l := resource.NewDaemonSetList(nil, "fred")
|
||||
v := newDaemonSetView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||
|
||||
assert.Equal(t, "blee", v.getTitle())
|
||||
}
|
||||
|
|
@ -18,6 +18,9 @@ type (
|
|||
resCmd struct {
|
||||
title string
|
||||
api string
|
||||
version string
|
||||
plural string
|
||||
singular string
|
||||
viewFn viewFn
|
||||
listFn listFn
|
||||
enterFn enterFn
|
||||
|
|
@ -26,25 +29,14 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func helpCmds(c k8s.Connection) map[string]resCmd {
|
||||
cmdMap := resourceViews(c)
|
||||
cmds := make(map[string]resCmd, len(cmdMap))
|
||||
for k, v := range cmdMap {
|
||||
cmds[k] = v
|
||||
func aliasCmds(c k8s.Connection, m map[string]resCmd) {
|
||||
resourceViews(c, m)
|
||||
if c != nil {
|
||||
allCRDs(c, m)
|
||||
}
|
||||
for k, v := range allCRDs(c) {
|
||||
cmds[k] = resCmd{title: v.Kind, api: v.Group}
|
||||
}
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
||||
func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
|
||||
m := map[string]k8s.APIGroup{}
|
||||
if c == nil {
|
||||
return m
|
||||
}
|
||||
|
||||
func allCRDs(c k8s.Connection, m map[string]resCmd) {
|
||||
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
|
||||
Resource().
|
||||
List(resource.AllNamespaces)
|
||||
|
|
@ -58,24 +50,23 @@ func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
|
|||
Version: ff["version"].(string),
|
||||
}
|
||||
|
||||
res := resCmd{title: grp.Kind, api: grp.Group, version: grp.Version}
|
||||
if p, ok := ff["plural"].(string); ok {
|
||||
grp.Plural = p
|
||||
m[p] = grp
|
||||
res.plural = p
|
||||
m[p] = res
|
||||
}
|
||||
|
||||
if s, ok := ff["singular"].(string); ok {
|
||||
grp.Singular = s
|
||||
m[s] = grp
|
||||
res.singular = s
|
||||
m[s] = res
|
||||
}
|
||||
|
||||
if aa, ok := ff["aliases"].([]interface{}); ok {
|
||||
for _, a := range aa {
|
||||
m[a.(string)] = grp
|
||||
m[a.(string)] = res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func showRBAC(app *appView, ns, resource, selection string) {
|
||||
|
|
@ -110,220 +101,254 @@ func showSAPolicy(app *appView, _, _, selection string) {
|
|||
app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
|
||||
}
|
||||
|
||||
func resourceViews(c k8s.Connection) map[string]resCmd {
|
||||
cmds := map[string]resCmd{
|
||||
"cm": {
|
||||
title: "ConfigMaps",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewConfigMapList,
|
||||
},
|
||||
"cr": {
|
||||
title: "ClusterRoles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleList,
|
||||
enterFn: showRBAC,
|
||||
},
|
||||
"crb": {
|
||||
title: "ClusterRoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleBindingList,
|
||||
enterFn: showClusterRole,
|
||||
},
|
||||
"crd": {
|
||||
title: "CustomResourceDefinitions",
|
||||
api: "apiextensions.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewCustomResourceDefinitionList,
|
||||
},
|
||||
"cj": {
|
||||
title: "CronJobs",
|
||||
api: "batch",
|
||||
viewFn: newCronJobView,
|
||||
listFn: resource.NewCronJobList,
|
||||
},
|
||||
"ctx": {
|
||||
title: "Contexts",
|
||||
api: "",
|
||||
viewFn: newContextView,
|
||||
listFn: resource.NewContextList,
|
||||
colorerFn: ctxColorer,
|
||||
},
|
||||
"ds": {
|
||||
title: "DaemonSets",
|
||||
api: "",
|
||||
viewFn: newDaemonSetView,
|
||||
listFn: resource.NewDaemonSetList,
|
||||
colorerFn: dpColorer,
|
||||
},
|
||||
"dp": {
|
||||
title: "Deployments",
|
||||
api: "apps",
|
||||
viewFn: newDeployView,
|
||||
listFn: resource.NewDeploymentList,
|
||||
colorerFn: dpColorer,
|
||||
},
|
||||
"ep": {
|
||||
title: "EndPoints",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEndpointsList,
|
||||
},
|
||||
"ev": {
|
||||
title: "Events",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEventList,
|
||||
colorerFn: evColorer,
|
||||
},
|
||||
"ing": {
|
||||
title: "Ingress",
|
||||
api: "extensions",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewIngressList,
|
||||
},
|
||||
"jo": {
|
||||
title: "Jobs",
|
||||
api: "batch",
|
||||
viewFn: newJobView,
|
||||
listFn: resource.NewJobList,
|
||||
},
|
||||
"no": {
|
||||
title: "Nodes",
|
||||
api: "",
|
||||
viewFn: newNodeView,
|
||||
listFn: resource.NewNodeList,
|
||||
colorerFn: nsColorer,
|
||||
},
|
||||
"ns": {
|
||||
title: "Namespaces",
|
||||
api: "",
|
||||
viewFn: newNamespaceView,
|
||||
listFn: resource.NewNamespaceList,
|
||||
colorerFn: nsColorer,
|
||||
},
|
||||
"pdb": {
|
||||
title: "PodDisruptionBudgets",
|
||||
api: "v1.beta1",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPDBList,
|
||||
colorerFn: pdbColorer,
|
||||
},
|
||||
"po": {
|
||||
title: "Pods",
|
||||
api: "",
|
||||
viewFn: newPodView,
|
||||
listFn: resource.NewPodList,
|
||||
colorerFn: podColorer,
|
||||
},
|
||||
"pv": {
|
||||
title: "PersistentVolumes",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPersistentVolumeList,
|
||||
colorerFn: pvColorer,
|
||||
},
|
||||
"pvc": {
|
||||
title: "PersistentVolumeClaims",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPersistentVolumeClaimList,
|
||||
colorerFn: pvcColorer,
|
||||
},
|
||||
"rb": {
|
||||
title: "RoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleBindingList,
|
||||
enterFn: showRole,
|
||||
},
|
||||
"rc": {
|
||||
title: "ReplicationControllers",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewReplicationControllerList,
|
||||
colorerFn: rsColorer,
|
||||
},
|
||||
"ro": {
|
||||
title: "Roles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleList,
|
||||
enterFn: showRBAC,
|
||||
},
|
||||
"rs": {
|
||||
title: "ReplicaSets",
|
||||
api: "apps",
|
||||
viewFn: newReplicaSetView,
|
||||
listFn: resource.NewReplicaSetList,
|
||||
colorerFn: rsColorer,
|
||||
},
|
||||
"sa": {
|
||||
title: "ServiceAccounts",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceAccountList,
|
||||
enterFn: showSAPolicy,
|
||||
},
|
||||
"sec": {
|
||||
title: "Secrets",
|
||||
api: "",
|
||||
viewFn: newSecretView,
|
||||
listFn: resource.NewSecretList,
|
||||
},
|
||||
"sts": {
|
||||
title: "StatefulSets",
|
||||
api: "apps",
|
||||
viewFn: newStatefulSetView,
|
||||
listFn: resource.NewStatefulSetList,
|
||||
colorerFn: stsColorer,
|
||||
},
|
||||
"svc": {
|
||||
title: "Services",
|
||||
api: "",
|
||||
viewFn: newSvcView,
|
||||
listFn: resource.NewServiceList,
|
||||
},
|
||||
"usr": {
|
||||
title: "Users",
|
||||
api: "",
|
||||
viewFn: newSubjectView,
|
||||
},
|
||||
"grp": {
|
||||
title: "Groups",
|
||||
api: "",
|
||||
viewFn: newSubjectView,
|
||||
},
|
||||
"pf": {
|
||||
title: "PortForward",
|
||||
api: "",
|
||||
viewFn: newForwardView,
|
||||
},
|
||||
"be": {
|
||||
title: "Benchmark",
|
||||
api: "",
|
||||
viewFn: newBenchView,
|
||||
},
|
||||
"sd": {
|
||||
title: "ScreenDumps",
|
||||
api: "",
|
||||
viewFn: newDumpView,
|
||||
},
|
||||
func resourceViews(c k8s.Connection, m map[string]resCmd) {
|
||||
coreRes(m)
|
||||
rbacRes(m)
|
||||
apiExtRes(m)
|
||||
batchRes(m)
|
||||
appsRes(m)
|
||||
extRes(m)
|
||||
v1beta1Res(m)
|
||||
custRes(m)
|
||||
|
||||
if c != nil {
|
||||
hpaRes(c, m)
|
||||
}
|
||||
}
|
||||
|
||||
func coreRes(m map[string]resCmd) {
|
||||
m["cm"] = resCmd{
|
||||
title: "ConfigMaps",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewConfigMapList,
|
||||
}
|
||||
m["ctx"] = resCmd{
|
||||
title: "Contexts",
|
||||
api: "",
|
||||
viewFn: newContextView,
|
||||
listFn: resource.NewContextList,
|
||||
colorerFn: ctxColorer,
|
||||
}
|
||||
m["ds"] = resCmd{
|
||||
title: "DaemonSets",
|
||||
api: "",
|
||||
viewFn: newDaemonSetView,
|
||||
listFn: resource.NewDaemonSetList,
|
||||
colorerFn: dpColorer,
|
||||
}
|
||||
m["ep"] = resCmd{
|
||||
title: "EndPoints",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEndpointsList,
|
||||
}
|
||||
m["ev"] = resCmd{
|
||||
title: "Events",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEventList,
|
||||
colorerFn: evColorer,
|
||||
}
|
||||
m["no"] = resCmd{
|
||||
title: "Nodes",
|
||||
api: "",
|
||||
viewFn: newNodeView,
|
||||
listFn: resource.NewNodeList,
|
||||
colorerFn: nsColorer,
|
||||
}
|
||||
m["ns"] = resCmd{
|
||||
title: "Namespaces",
|
||||
api: "",
|
||||
viewFn: newNamespaceView,
|
||||
listFn: resource.NewNamespaceList,
|
||||
colorerFn: nsColorer,
|
||||
}
|
||||
m["po"] = resCmd{
|
||||
title: "Pods",
|
||||
api: "",
|
||||
viewFn: newPodView,
|
||||
listFn: resource.NewPodList,
|
||||
colorerFn: podColorer,
|
||||
}
|
||||
m["pv"] = resCmd{
|
||||
title: "PersistentVolumes",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPersistentVolumeList,
|
||||
colorerFn: pvColorer,
|
||||
}
|
||||
m["pvc"] = resCmd{
|
||||
title: "PersistentVolumeClaims",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPersistentVolumeClaimList,
|
||||
colorerFn: pvcColorer,
|
||||
}
|
||||
m["rc"] = resCmd{
|
||||
title: "ReplicationControllers",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewReplicationControllerList,
|
||||
colorerFn: rsColorer,
|
||||
}
|
||||
m["sa"] = resCmd{
|
||||
title: "ServiceAccounts",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceAccountList,
|
||||
enterFn: showSAPolicy,
|
||||
}
|
||||
m["sec"] = resCmd{
|
||||
title: "Secrets",
|
||||
api: "",
|
||||
viewFn: newSecretView,
|
||||
listFn: resource.NewSecretList,
|
||||
}
|
||||
m["svc"] = resCmd{
|
||||
title: "Services",
|
||||
api: "",
|
||||
viewFn: newSvcView,
|
||||
listFn: resource.NewServiceList,
|
||||
}
|
||||
}
|
||||
|
||||
func custRes(m map[string]resCmd) {
|
||||
m["usr"] = resCmd{
|
||||
title: "Users",
|
||||
api: "",
|
||||
viewFn: newSubjectView,
|
||||
}
|
||||
m["grp"] = resCmd{
|
||||
title: "Groups",
|
||||
api: "",
|
||||
viewFn: newSubjectView,
|
||||
}
|
||||
m["pf"] = resCmd{
|
||||
title: "PortForward",
|
||||
api: "",
|
||||
viewFn: newForwardView,
|
||||
}
|
||||
m["be"] = resCmd{
|
||||
title: "Benchmark",
|
||||
api: "",
|
||||
viewFn: newBenchView,
|
||||
}
|
||||
m["sd"] = resCmd{
|
||||
title: "ScreenDumps",
|
||||
api: "",
|
||||
viewFn: newDumpView,
|
||||
}
|
||||
}
|
||||
|
||||
func rbacRes(m map[string]resCmd) {
|
||||
m["cr"] = resCmd{
|
||||
title: "ClusterRoles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleList,
|
||||
enterFn: showRBAC,
|
||||
}
|
||||
m["crb"] = resCmd{
|
||||
title: "ClusterRoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleBindingList,
|
||||
enterFn: showClusterRole,
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return cmds
|
||||
m["rb"] = resCmd{
|
||||
title: "RoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleBindingList,
|
||||
enterFn: showRole,
|
||||
}
|
||||
m["ro"] = resCmd{
|
||||
title: "Roles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleList,
|
||||
enterFn: showRBAC,
|
||||
}
|
||||
}
|
||||
|
||||
func apiExtRes(m map[string]resCmd) {
|
||||
m["crd"] = resCmd{
|
||||
title: "CustomResourceDefinitions",
|
||||
api: "apiextensions.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewCustomResourceDefinitionList,
|
||||
}
|
||||
}
|
||||
|
||||
func batchRes(m map[string]resCmd) {
|
||||
m["cj"] = resCmd{
|
||||
title: "CronJobs",
|
||||
api: "batch",
|
||||
viewFn: newCronJobView,
|
||||
listFn: resource.NewCronJobList,
|
||||
}
|
||||
m["jo"] = resCmd{
|
||||
title: "Jobs",
|
||||
api: "batch",
|
||||
viewFn: newJobView,
|
||||
listFn: resource.NewJobList,
|
||||
}
|
||||
}
|
||||
|
||||
func appsRes(m map[string]resCmd) {
|
||||
m["dp"] = resCmd{
|
||||
title: "Deployments",
|
||||
api: "apps",
|
||||
viewFn: newDeployView,
|
||||
listFn: resource.NewDeploymentList,
|
||||
colorerFn: dpColorer,
|
||||
}
|
||||
m["rs"] = resCmd{
|
||||
title: "ReplicaSets",
|
||||
api: "apps",
|
||||
viewFn: newReplicaSetView,
|
||||
listFn: resource.NewReplicaSetList,
|
||||
colorerFn: rsColorer,
|
||||
}
|
||||
m["sts"] = resCmd{
|
||||
title: "StatefulSets",
|
||||
api: "apps",
|
||||
viewFn: newStatefulSetView,
|
||||
listFn: resource.NewStatefulSetList,
|
||||
colorerFn: stsColorer,
|
||||
}
|
||||
}
|
||||
|
||||
func extRes(m map[string]resCmd) {
|
||||
m["ing"] = resCmd{
|
||||
title: "Ingress",
|
||||
api: "extensions",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewIngressList,
|
||||
}
|
||||
}
|
||||
|
||||
func v1beta1Res(m map[string]resCmd) {
|
||||
m["pdb"] = resCmd{
|
||||
title: "PodDisruptionBudgets",
|
||||
api: "v1.beta1",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewPDBList,
|
||||
colorerFn: pdbColorer,
|
||||
}
|
||||
}
|
||||
|
||||
func hpaRes(c k8s.Connection, cmds map[string]resCmd) {
|
||||
rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Checking HPA")
|
||||
return cmds
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
log.Error().Msg("HPA are not supported on this cluster")
|
||||
return cmds
|
||||
return
|
||||
}
|
||||
|
||||
switch rev {
|
||||
|
|
@ -351,6 +376,4 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
|
|||
default:
|
||||
log.Panic().Msgf("K9s unsupported HPA version. Exiting!")
|
||||
}
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,14 +49,14 @@ type (
|
|||
app *appView
|
||||
baseTitle string
|
||||
currentNS string
|
||||
data resource.TableData
|
||||
actions keyActions
|
||||
cmdBuff *cmdBuff
|
||||
colorerFn colorerFn
|
||||
sortFn sortFn
|
||||
cleanseFn cleanseFn
|
||||
data resource.TableData
|
||||
cmdBuff *cmdBuff
|
||||
sortCol sortColumn
|
||||
filterFn func(string)
|
||||
sortCol sortColumn
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -406,7 +406,15 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
|||
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
||||
}
|
||||
for col, field := range data.Rows[sk].Fields {
|
||||
v.addBodyCell(data.NumCols, data.Header[col], row, col, field, data.Rows[sk].Deltas[col], fgColor, pads)
|
||||
header := data.Header[col]
|
||||
field, align := v.formatCell(data.NumCols[header], header, field, pads[col])
|
||||
c := tview.NewTableCell(field + deltas(data.Rows[sk].Deltas[col], field))
|
||||
{
|
||||
c.SetExpansion(1)
|
||||
c.SetAlign(align)
|
||||
c.SetTextColor(fgColor)
|
||||
}
|
||||
v.SetCell(row, col, c)
|
||||
}
|
||||
row++
|
||||
}
|
||||
|
|
@ -447,7 +455,7 @@ func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string,
|
|||
v.SetCell(0, col, c)
|
||||
}
|
||||
|
||||
func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col int, field, delta string, color tcell.Color, pads maxyPad) {
|
||||
func (v *tableView) formatCell(numerical bool, header, field string, padding int) (string, int) {
|
||||
if header == "AGE" {
|
||||
dur, err := time.ParseDuration(field)
|
||||
if err == nil {
|
||||
|
|
@ -455,21 +463,16 @@ func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col
|
|||
}
|
||||
}
|
||||
|
||||
field += deltas(delta, field)
|
||||
align := tview.AlignLeft
|
||||
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {
|
||||
align = tview.AlignRight
|
||||
} else if isASCII(field) {
|
||||
field = pad(field, pads[col])
|
||||
if numerical || cpuRX.MatchString(header) || memRX.MatchString(header) {
|
||||
return field, tview.AlignRight
|
||||
}
|
||||
|
||||
c := tview.NewTableCell(field)
|
||||
{
|
||||
c.SetExpansion(1)
|
||||
c.SetAlign(align)
|
||||
c.SetTextColor(color)
|
||||
align := tview.AlignLeft
|
||||
if isASCII(field) {
|
||||
return pad(field, padding), align
|
||||
}
|
||||
v.SetCell(row, col, c)
|
||||
|
||||
return field, align
|
||||
}
|
||||
|
||||
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,116 @@ package views
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
func TestTableViewSave(t *testing.T) {
|
||||
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||
v.baseTitle = "k9s-test"
|
||||
dir := filepath.Join(config.K9sDumpDir, v.app.config.K9s.CurrentCluster)
|
||||
c1, _ := ioutil.ReadDir(dir)
|
||||
v.saveCmd(nil)
|
||||
c2, _ := ioutil.ReadDir(dir)
|
||||
assert.Equal(t, len(c2), len(c1)+1)
|
||||
}
|
||||
|
||||
func TestTableViewNew(t *testing.T) {
|
||||
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||
|
||||
data := resource.TableData{
|
||||
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||
Rows: resource.RowEvents{
|
||||
"ns1/a": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "a", "10", "3m"},
|
||||
Deltas: resource.Row{"", "", "", ""},
|
||||
},
|
||||
"ns1/b": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "b", "15", "1m"},
|
||||
Deltas: resource.Row{"", "", "20", ""},
|
||||
},
|
||||
},
|
||||
NumCols: map[string]bool{
|
||||
"FRED": true,
|
||||
},
|
||||
Namespace: "",
|
||||
}
|
||||
v.update(data)
|
||||
assert.Equal(t, 3, v.GetRowCount())
|
||||
}
|
||||
|
||||
func TestTableViewFilter(t *testing.T) {
|
||||
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||
|
||||
data := resource.TableData{
|
||||
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||
Rows: resource.RowEvents{
|
||||
"ns1/blee": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "blee", "10", "3m"},
|
||||
Deltas: resource.Row{"", "", "", ""},
|
||||
},
|
||||
"ns1/fred": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "fred", "15", "1m"},
|
||||
Deltas: resource.Row{"", "", "20", ""},
|
||||
},
|
||||
},
|
||||
NumCols: map[string]bool{
|
||||
"FRED": true,
|
||||
},
|
||||
Namespace: "",
|
||||
}
|
||||
v.update(data)
|
||||
v.cmdBuff.setActive(true)
|
||||
v.cmdBuff.buff = []rune("blee")
|
||||
v.filterCmd(nil)
|
||||
assert.Equal(t, 2, v.GetRowCount())
|
||||
v.resetCmd(nil)
|
||||
assert.Equal(t, 3, v.GetRowCount())
|
||||
}
|
||||
|
||||
func TestTableViewSort(t *testing.T) {
|
||||
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||
|
||||
data := resource.TableData{
|
||||
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||
Rows: resource.RowEvents{
|
||||
"ns1/blee": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "blee", "10", "3m"},
|
||||
Deltas: resource.Row{"", "", "", ""},
|
||||
},
|
||||
"ns1/fred": &resource.RowEvent{
|
||||
Action: watch.Added,
|
||||
Fields: resource.Row{"ns1", "fred", "15", "1m"},
|
||||
Deltas: resource.Row{"", "", "20", ""},
|
||||
},
|
||||
},
|
||||
NumCols: map[string]bool{
|
||||
"FRED": true,
|
||||
},
|
||||
Namespace: "",
|
||||
}
|
||||
v.update(data)
|
||||
v.sortColCmd(1)(nil)
|
||||
assert.Equal(t, 3, v.GetRowCount())
|
||||
assert.Equal(t, "blee ", v.GetCell(1, 1).Text)
|
||||
|
||||
v.sortInvertCmd(nil)
|
||||
assert.Equal(t, 3, v.GetRowCount())
|
||||
assert.Equal(t, "fred ", v.GetCell(1, 1).Text)
|
||||
}
|
||||
|
||||
func TestIsSelector(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
sel string
|
||||
|
|
|
|||
Loading…
Reference in New Issue