misc bug fixes

mine
derailed 2019-06-18 17:54:44 -06:00
parent 921a39f897
commit c61365a9c9
19 changed files with 533 additions and 330 deletions

View File

@ -10,6 +10,7 @@ for changes and offers subsequent commands to interact with observed Kubernetes
--- ---
[![Go Report Card](https://goreportcard.com/badge/github.com/derailed/k9s?)](https://goreportcard.com/report/github.com/derailed/k9s) [![Go Report Card](https://goreportcard.com/badge/github.com/derailed/k9s?)](https://goreportcard.com/report/github.com/derailed/k9s)
[![codebeat badge](https://codebeat.co/badges/89e5a80e-dfe8-4426-acf6-6be781e0a12e)](https://codebeat.co/projects/github-com-derailed-k9s-master)
[![Build Status](https://travis-ci.com/derailed/k9s.svg?branch=master)](https://travis-ci.com/derailed/k9s) [![Build Status](https://travis-ci.com/derailed/k9s.svg?branch=master)](https://travis-ci.com/derailed/k9s)
[![release](https://img.shields.io/github/release-pre/derailed/k9s.svg)](https://github.com/derailed/k9s/releases) [![release](https://img.shields.io/github/release-pre/derailed/k9s.svg)](https://github.com/derailed/k9s/releases)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/mum4k/termdash/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/mum4k/termdash/blob/master/LICENSE)

3
go.mod
View File

@ -20,7 +20,6 @@ require (
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/fatih/camelcase v1.0.0 // 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/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell v1.1.1 github.com/gdamore/tcell v1.1.1
github.com/gogo/protobuf v1.2.1 // indirect github.com/gogo/protobuf v1.2.1 // indirect
@ -34,7 +33,6 @@ require (
github.com/imdario/mergo v0.3.7 // indirect github.com/imdario/mergo v0.3.7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.6 // 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/mattn/go-runewidth v0.0.4
github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.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/cobra v0.0.3
github.com/spf13/pflag v1.0.3 // indirect github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.3.0 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 github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect

13
go.sum
View File

@ -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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:QWjK82ccTl3C7Tfyfmv765eRqEt/T3aXp40464cfnlw=
github.com/derailed/tview v0.1.10/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0= 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= 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/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 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= 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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 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 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= 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/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 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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= 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= 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= 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-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-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-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-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 h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -22,19 +22,18 @@ type (
// Style tracks K9s styles. // Style tracks K9s styles.
Style struct { Style struct {
FgColor string `yaml:"fgColor"` FgColor string `yaml:"fgColor"`
BgColor string `yaml:"bgColor"` BgColor string `yaml:"bgColor"`
LogoColor string `yaml:"logoColor"` LogoColor string `yaml:"logoColor"`
Title *Title `yaml:"title"`
Info *Info `yaml:"info"` Border *Border `yaml:"border"`
Border *Border `yaml:"border"` Info *Info `yaml:"info"`
Menu *Menu `yaml:"menu"` Menu *Menu `yaml:"menu"`
Crumb *Crumb `yaml:"crumb"` Crumb *Crumb `yaml:"crumb"`
Table *Table `yaml:"table"` Table *Table `yaml:"table"`
Status *Status `yaml:"status"` Status *Status `yaml:"status"`
Title *Title `yaml:"title"` Yaml *Yaml `yaml:"yaml"`
Yaml *Yaml `yaml:"yaml"` Log *Log `yaml:"logs"`
Log *Log `yaml:"logs"`
} }
// Status tracks resource status styles. // Status tracks resource status styles.
@ -117,13 +116,13 @@ func newStyle() *Style {
FgColor: "cadetblue", FgColor: "cadetblue",
BgColor: "black", BgColor: "black",
LogoColor: "orange", LogoColor: "orange",
Info: newInfo(),
Border: newBorder(), Border: newBorder(),
Title: newTitle(),
Info: newInfo(),
Menu: newMenu(), Menu: newMenu(),
Crumb: newCrumb(), Crumb: newCrumb(),
Table: newTable(), Table: newTable(),
Status: newStatus(), Status: newStatus(),
Title: newTitle(),
Yaml: newYaml(), Yaml: newYaml(),
Log: newLog(), Log: newLog(),
} }

View File

@ -3,12 +3,14 @@ package resource
import ( import (
"bytes" "bytes"
"context" "context"
"fmt"
"path" "path"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions/printers" "k8s.io/cli-runtime/pkg/genericclioptions/printers"
@ -127,14 +129,23 @@ func (b *Base) List(ns string) (Columnars, error) {
// Describe a given resource. // Describe a given resource.
func (b *Base) Describe(kind, pa string) (string, error) { func (b *Base) Describe(kind, pa string) (string, error) {
ns, n := namespaced(pa)
mapping, err := k8s.RestMapping.Find(kind) mapping, err := k8s.RestMapping.Find(kind)
if err != nil { if err != nil {
log.Debug().Msgf("Unable to find mapper for %s %s", kind, pa) g, v, n := b.Resource.(*k8s.Resource).GetInfo()
return "", err 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) d, err := versioned.Describer(b.Connection.Config().Flags(), mapping)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping) log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)

View File

@ -39,32 +39,23 @@ func (v *aliasView) init(context.Context, string) {
v.update(v.hydrate()) v.update(v.hydrate())
v.app.SetFocus(v) v.app.SetFocus(v)
v.resetTitle() v.resetTitle()
v.app.setHints(v.hints())
} }
func (v *aliasView) registerActions() { func (v *aliasView) registerActions() {
delete(v.actions, KeyShiftA)
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true) v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false) v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false) v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true) v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortColCmd(1), true)
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true) v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortColCmd(2), true)
} }
func (v *aliasView) getTitle() string { func (v *aliasView) getTitle() string {
return aliasTitle 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 { func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.cmdBuff.empty() { if !v.cmdBuff.empty() {
v.cmdBuff.reset() v.cmdBuff.reset()
@ -115,7 +106,8 @@ func (v *aliasView) hints() hints {
} }
func (v *aliasView) hydrate() resource.TableData { 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{ data := resource.TableData{
Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"}, Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"},

View File

@ -99,8 +99,10 @@ func (a *appView) registerActions() {
} }
func (a *appView) Init(version string, rate int) { func (a *appView) Init(version string, rate int) {
a.startInformer() if a.conn() != nil {
a.clusterInfo().init(version) a.startInformer()
a.clusterInfo().init(version)
}
a.cmdBuff.addListener(a.cmd()) a.cmdBuff.addListener(a.cmd())
header := tview.NewFlex() header := tview.NewFlex()
@ -333,6 +335,7 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *appView) currentView() igniter { func (a *appView) currentView() igniter {
return a.content.GetPrimitive("main").(igniter) return a.content.GetPrimitive("main").(igniter)
} }
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.inCmdMode() { if a.inCmdMode() {
return evt return evt

View File

@ -1,18 +1,16 @@
package views package views
// import ( import (
// "testing" "testing"
// "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
// "github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
// ) )
// func TestNewApp(t *testing.T) { func TestNewApp(t *testing.T) {
// mk := NewMockKubeSettings() a := NewApp(config.NewConfig(ks{}))
// cfg := config.NewConfig(mk) a.Init("blee", 10)
// a := NewApp(cfg)
// a.Init("blee", 10)
// assert.Equal(t, 10, len(a.actions)) assert.Equal(t, 10, len(a.actions))
// assert.Equal(t, false, a.hasSkins) assert.Equal(t, false, a.hasSkins)
// } }

View File

@ -23,13 +23,11 @@ const (
// K9sBenchDir directory to store K9s benchmark files. // K9sBenchDir directory to store K9s benchmark files.
var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser())) var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser()))
type ( type benchmark struct {
benchmark struct { canceled bool
canceled bool config config.BenchConfig
config config.BenchConfig worker *requester.Work
worker *requester.Work }
}
)
func newBenchmark(base string, cfg config.BenchConfig) (*benchmark, error) { func newBenchmark(base string, cfg config.BenchConfig) (*benchmark, error) {
b := benchmark{config: cfg} b := benchmark{config: cfg}

View File

@ -68,7 +68,9 @@ func (c *command) run(cmd string) bool {
return true return true
} }
default: 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 var r resource.List
if res.listFn != nil { if res.listFn != nil {
r = res.listFn(c.app.conn(), resource.DefaultNamespace) 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 { if !ok {
c.app.flash().warnf("Huh? `%s` command not found", cmd) c.app.flash().warnf("Huh? `%s` command not found", cmd)
return false return false
} }
name := res.Plural name := res.plural
if len(name) == 0 { if name == "" {
name = res.Singular name = res.singular
} }
v = newResourceView( v = newResourceView(
res.Kind, res.title,
c.app, 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) v.setColorerFn(defaultColorer)
c.exec(cmd, v) c.exec(cmd, v)

View File

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

View File

@ -10,8 +10,8 @@ type contextView struct {
*resourceView *resourceView
} }
func newContextView(t string, app *appView, list resource.List) resourceViewer { func newContextView(title string, app *appView, list resource.List) resourceViewer {
v := contextView{newResourceView(t, app, list).(*resourceView)} v := contextView{newResourceView(title, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.useCtx v.enterFn = v.useCtx
v.getTV().cleanseFn = v.cleanser v.getTV().cleanseFn = v.cleanser

View File

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

View File

@ -12,8 +12,8 @@ type deployView struct {
*logResourceView *logResourceView
} }
func newDeployView(ns string, app *appView, list resource.List) resourceViewer { func newDeployView(title string, app *appView, list resource.List) resourceViewer {
v := deployView{newLogResourceView(ns, app, list)} v := deployView{newLogResourceView(title, app, list)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods

16
internal/views/dp_test.go Normal file
View File

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

16
internal/views/ds_test.go Normal file
View File

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

View File

@ -18,6 +18,9 @@ type (
resCmd struct { resCmd struct {
title string title string
api string api string
version string
plural string
singular string
viewFn viewFn viewFn viewFn
listFn listFn listFn listFn
enterFn enterFn enterFn enterFn
@ -26,25 +29,14 @@ type (
} }
) )
func helpCmds(c k8s.Connection) map[string]resCmd { func aliasCmds(c k8s.Connection, m map[string]resCmd) {
cmdMap := resourceViews(c) resourceViews(c, m)
cmds := make(map[string]resCmd, len(cmdMap)) if c != nil {
for k, v := range cmdMap { allCRDs(c, m)
cmds[k] = v
} }
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 { func allCRDs(c k8s.Connection, m map[string]resCmd) {
m := map[string]k8s.APIGroup{}
if c == nil {
return m
}
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces). crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
Resource(). Resource().
List(resource.AllNamespaces) List(resource.AllNamespaces)
@ -58,24 +50,23 @@ func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
Version: ff["version"].(string), Version: ff["version"].(string),
} }
res := resCmd{title: grp.Kind, api: grp.Group, version: grp.Version}
if p, ok := ff["plural"].(string); ok { if p, ok := ff["plural"].(string); ok {
grp.Plural = p res.plural = p
m[p] = grp m[p] = res
} }
if s, ok := ff["singular"].(string); ok { if s, ok := ff["singular"].(string); ok {
grp.Singular = s res.singular = s
m[s] = grp m[s] = res
} }
if aa, ok := ff["aliases"].([]interface{}); ok { if aa, ok := ff["aliases"].([]interface{}); ok {
for _, a := range aa { for _, a := range aa {
m[a.(string)] = grp m[a.(string)] = res
} }
} }
} }
return m
} }
func showRBAC(app *appView, ns, resource, selection string) { 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)) app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
} }
func resourceViews(c k8s.Connection) map[string]resCmd { func resourceViews(c k8s.Connection, m map[string]resCmd) {
cmds := map[string]resCmd{ coreRes(m)
"cm": { rbacRes(m)
title: "ConfigMaps", apiExtRes(m)
api: "", batchRes(m)
viewFn: newResourceView, appsRes(m)
listFn: resource.NewConfigMapList, extRes(m)
}, v1beta1Res(m)
"cr": { custRes(m)
title: "ClusterRoles",
api: "rbac.authorization.k8s.io", if c != nil {
viewFn: newResourceView, hpaRes(c, m)
listFn: resource.NewClusterRoleList, }
enterFn: showRBAC, }
},
"crb": { func coreRes(m map[string]resCmd) {
title: "ClusterRoleBindings", m["cm"] = resCmd{
api: "rbac.authorization.k8s.io", title: "ConfigMaps",
viewFn: newResourceView, api: "",
listFn: resource.NewClusterRoleBindingList, viewFn: newResourceView,
enterFn: showClusterRole, listFn: resource.NewConfigMapList,
}, }
"crd": { m["ctx"] = resCmd{
title: "CustomResourceDefinitions", title: "Contexts",
api: "apiextensions.k8s.io", api: "",
viewFn: newResourceView, viewFn: newContextView,
listFn: resource.NewCustomResourceDefinitionList, listFn: resource.NewContextList,
}, colorerFn: ctxColorer,
"cj": { }
title: "CronJobs", m["ds"] = resCmd{
api: "batch", title: "DaemonSets",
viewFn: newCronJobView, api: "",
listFn: resource.NewCronJobList, viewFn: newDaemonSetView,
}, listFn: resource.NewDaemonSetList,
"ctx": { colorerFn: dpColorer,
title: "Contexts", }
api: "", m["ep"] = resCmd{
viewFn: newContextView, title: "EndPoints",
listFn: resource.NewContextList, api: "",
colorerFn: ctxColorer, viewFn: newResourceView,
}, listFn: resource.NewEndpointsList,
"ds": { }
title: "DaemonSets", m["ev"] = resCmd{
api: "", title: "Events",
viewFn: newDaemonSetView, api: "",
listFn: resource.NewDaemonSetList, viewFn: newResourceView,
colorerFn: dpColorer, listFn: resource.NewEventList,
}, colorerFn: evColorer,
"dp": { }
title: "Deployments", m["no"] = resCmd{
api: "apps", title: "Nodes",
viewFn: newDeployView, api: "",
listFn: resource.NewDeploymentList, viewFn: newNodeView,
colorerFn: dpColorer, listFn: resource.NewNodeList,
}, colorerFn: nsColorer,
"ep": { }
title: "EndPoints", m["ns"] = resCmd{
api: "", title: "Namespaces",
viewFn: newResourceView, api: "",
listFn: resource.NewEndpointsList, viewFn: newNamespaceView,
}, listFn: resource.NewNamespaceList,
"ev": { colorerFn: nsColorer,
title: "Events", }
api: "", m["po"] = resCmd{
viewFn: newResourceView, title: "Pods",
listFn: resource.NewEventList, api: "",
colorerFn: evColorer, viewFn: newPodView,
}, listFn: resource.NewPodList,
"ing": { colorerFn: podColorer,
title: "Ingress", }
api: "extensions", m["pv"] = resCmd{
viewFn: newResourceView, title: "PersistentVolumes",
listFn: resource.NewIngressList, api: "",
}, viewFn: newResourceView,
"jo": { listFn: resource.NewPersistentVolumeList,
title: "Jobs", colorerFn: pvColorer,
api: "batch", }
viewFn: newJobView, m["pvc"] = resCmd{
listFn: resource.NewJobList, title: "PersistentVolumeClaims",
}, api: "",
"no": { viewFn: newResourceView,
title: "Nodes", listFn: resource.NewPersistentVolumeClaimList,
api: "", colorerFn: pvcColorer,
viewFn: newNodeView, }
listFn: resource.NewNodeList, m["rc"] = resCmd{
colorerFn: nsColorer, title: "ReplicationControllers",
}, api: "",
"ns": { viewFn: newResourceView,
title: "Namespaces", listFn: resource.NewReplicationControllerList,
api: "", colorerFn: rsColorer,
viewFn: newNamespaceView, }
listFn: resource.NewNamespaceList, m["sa"] = resCmd{
colorerFn: nsColorer, title: "ServiceAccounts",
}, api: "",
"pdb": { viewFn: newResourceView,
title: "PodDisruptionBudgets", listFn: resource.NewServiceAccountList,
api: "v1.beta1", enterFn: showSAPolicy,
viewFn: newResourceView, }
listFn: resource.NewPDBList, m["sec"] = resCmd{
colorerFn: pdbColorer, title: "Secrets",
}, api: "",
"po": { viewFn: newSecretView,
title: "Pods", listFn: resource.NewSecretList,
api: "", }
viewFn: newPodView, m["svc"] = resCmd{
listFn: resource.NewPodList, title: "Services",
colorerFn: podColorer, api: "",
}, viewFn: newSvcView,
"pv": { listFn: resource.NewServiceList,
title: "PersistentVolumes", }
api: "", }
viewFn: newResourceView,
listFn: resource.NewPersistentVolumeList, func custRes(m map[string]resCmd) {
colorerFn: pvColorer, m["usr"] = resCmd{
}, title: "Users",
"pvc": { api: "",
title: "PersistentVolumeClaims", viewFn: newSubjectView,
api: "", }
viewFn: newResourceView, m["grp"] = resCmd{
listFn: resource.NewPersistentVolumeClaimList, title: "Groups",
colorerFn: pvcColorer, api: "",
}, viewFn: newSubjectView,
"rb": { }
title: "RoleBindings", m["pf"] = resCmd{
api: "rbac.authorization.k8s.io", title: "PortForward",
viewFn: newResourceView, api: "",
listFn: resource.NewRoleBindingList, viewFn: newForwardView,
enterFn: showRole, }
}, m["be"] = resCmd{
"rc": { title: "Benchmark",
title: "ReplicationControllers", api: "",
api: "", viewFn: newBenchView,
viewFn: newResourceView, }
listFn: resource.NewReplicationControllerList, m["sd"] = resCmd{
colorerFn: rsColorer, title: "ScreenDumps",
}, api: "",
"ro": { viewFn: newDumpView,
title: "Roles", }
api: "rbac.authorization.k8s.io", }
viewFn: newResourceView,
listFn: resource.NewRoleList, func rbacRes(m map[string]resCmd) {
enterFn: showRBAC, m["cr"] = resCmd{
}, title: "ClusterRoles",
"rs": { api: "rbac.authorization.k8s.io",
title: "ReplicaSets", viewFn: newResourceView,
api: "apps", listFn: resource.NewClusterRoleList,
viewFn: newReplicaSetView, enterFn: showRBAC,
listFn: resource.NewReplicaSetList, }
colorerFn: rsColorer, m["crb"] = resCmd{
}, title: "ClusterRoleBindings",
"sa": { api: "rbac.authorization.k8s.io",
title: "ServiceAccounts", viewFn: newResourceView,
api: "", listFn: resource.NewClusterRoleBindingList,
viewFn: newResourceView, enterFn: showClusterRole,
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,
},
} }
if c == nil { m["rb"] = resCmd{
return cmds 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"}) rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"})
if err != nil { if err != nil {
log.Error().Err(err).Msg("Checking HPA") log.Error().Err(err).Msg("Checking HPA")
return cmds return
} }
if !ok { if !ok {
log.Error().Msg("HPA are not supported on this cluster") log.Error().Msg("HPA are not supported on this cluster")
return cmds return
} }
switch rev { switch rev {
@ -351,6 +376,4 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
default: default:
log.Panic().Msgf("K9s unsupported HPA version. Exiting!") log.Panic().Msgf("K9s unsupported HPA version. Exiting!")
} }
return cmds
} }

View File

@ -49,14 +49,14 @@ type (
app *appView app *appView
baseTitle string baseTitle string
currentNS string currentNS string
data resource.TableData
actions keyActions actions keyActions
cmdBuff *cmdBuff
colorerFn colorerFn colorerFn colorerFn
sortFn sortFn sortFn sortFn
cleanseFn cleanseFn cleanseFn cleanseFn
data resource.TableData
cmdBuff *cmdBuff
sortCol sortColumn
filterFn func(string) filterFn func(string)
sortCol sortColumn
} }
) )
@ -406,7 +406,15 @@ func (v *tableView) doUpdate(data resource.TableData) {
fgColor = v.colorerFn(data.Namespace, data.Rows[sk]) fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
} }
for col, field := range data.Rows[sk].Fields { 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++ row++
} }
@ -447,7 +455,7 @@ func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string,
v.SetCell(0, col, c) 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" { if header == "AGE" {
dur, err := time.ParseDuration(field) dur, err := time.ParseDuration(field)
if err == nil { if err == nil {
@ -455,21 +463,16 @@ func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col
} }
} }
field += deltas(delta, field) if numerical || cpuRX.MatchString(header) || memRX.MatchString(header) {
align := tview.AlignLeft return field, tview.AlignRight
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {
align = tview.AlignRight
} else if isASCII(field) {
field = pad(field, pads[col])
} }
c := tview.NewTableCell(field) align := tview.AlignLeft
{ if isASCII(field) {
c.SetExpansion(1) return pad(field, padding), align
c.SetAlign(align)
c.SetTextColor(color)
} }
v.SetCell(row, col, c)
return field, align
} }
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) { func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {

View File

@ -2,13 +2,116 @@ package views
import ( import (
"fmt" "fmt"
"io/ioutil"
"path/filepath"
"strings" "strings"
"testing" "testing"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/stretchr/testify/assert" "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) { func TestIsSelector(t *testing.T) {
uu := map[string]struct { uu := map[string]struct {
sel string sel string