derailed 2020-05-23 11:15:21 -06:00
parent f0ef39b46c
commit 46c2f31249
28 changed files with 482 additions and 194 deletions

View File

@ -0,0 +1,27 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.19.7
## 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, consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or 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 #726](https://github.com/derailed/k9s/issues/726)
* [Issue #724](https://github.com/derailed/k9s/issues/724)
* [Issue #722](https://github.com/derailed/k9s/issues/722)
* [Issue #721](https://github.com/derailed/k9s/issues/721)
* [Issue #720](https://github.com/derailed/k9s/issues/720)
---
<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)

5
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell v1.3.0
github.com/ghodss/yaml v1.0.0
github.com/golang/protobuf v1.4.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.9
github.com/openfaas/faas v0.0.0-20200207215241-6afae214e3ec
@ -23,7 +24,11 @@ require (
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.5.1
golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 // indirect
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 // indirect
golang.org/x/text v0.3.2
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587 // indirect
google.golang.org/grpc v1.29.1 // indirect
gopkg.in/yaml.v2 v2.2.8
helm.sh/helm/v3 v3.2.0
k8s.io/api v0.18.2

31
go.sum
View File

@ -98,6 +98,7 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
@ -178,7 +179,9 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
@ -286,6 +289,16 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
@ -681,6 +694,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 h1:E7ct1C6/33eOdrGZKMoyntcEvs2dwZnDe30crG5vpYU=
golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
@ -719,6 +734,9 @@ golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -767,15 +785,28 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587 h1:1Ym+vvUpq1ZHvxzn34gENJX8U4aKO+vhy2P/2+Xl6qQ=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -68,8 +68,8 @@ func InitConnectionOrDie(config *Config) *APIClient {
}
func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview {
if ns == "-" {
ns = ""
if ns == ClusterScope {
ns = AllNamespaces
}
spec := NewGVR(gvr)
res := spec.GVR()
@ -124,6 +124,7 @@ func (a *APIClient) clearCache() {
// CanI checks if user has access to a certain resource.
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
// log.Debug().Msgf("Check Access %q::%q", ns, gvr)
if IsClusterWide(ns) {
ns = AllNamespaces
}

View File

@ -135,7 +135,7 @@ func (m *MetricsServer) FetchNodesMetrics(ctx context.Context) (*mv1beta1.NodeMe
const msg = "user is not authorized to list node metrics"
mx := new(mv1beta1.NodeMetricsList)
if err := m.checkAccess("", "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
if err := m.checkAccess(ClusterScope, "metrics.k8s.io/v1beta1/nodes", msg); err != nil {
return mx, err
}

View File

@ -110,7 +110,7 @@ func (d *Deployment) Pod(fqn string) (string, error) {
// Load returns a deployment instance.
func (*Deployment) Load(f Factory, fqn string) (*appsv1.Deployment, error) {
o, err := f.Get("apps/v1/deployments", fqn, false, labels.Everything())
o, err := f.Get("apps/v1/deployments", fqn, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -129,7 +129,7 @@ func (d *DaemonSet) Pod(fqn string) (string, error) {
// GetInstance returns a daemonset instance.
func (d *DaemonSet) GetInstance(fqn string) (*appsv1.DaemonSet, error) {
o, err := d.Factory.Get(d.gvr.String(), fqn, false, labels.Everything())
o, err := d.Factory.Get(d.gvr.String(), fqn, true, labels.Everything())
if err != nil {
return nil, err
}

View File

@ -166,7 +166,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) {
// FetchNode retrieves a node.
func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
auth, err := f.Client().CanI("", "v1/nodes", []string{"get"})
auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", []string{"get"})
if err != nil {
return nil, err
}
@ -179,7 +179,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) {
// FetchNodes retrieves all nodes.
func FetchNodes(ctx context.Context, f Factory, labelsSel string) (*v1.NodeList, error) {
auth, err := f.Client().CanI("", "v1/nodes", []string{client.ListVerb})
auth, err := f.Client().CanI(client.ClusterScope, "v1/nodes", []string{client.ListVerb})
if err != nil {
return nil, err
}

View File

@ -287,7 +287,7 @@ func loadPreferred(f Factory, m ResourceMetas) error {
func loadCRDs(f Factory, m ResourceMetas) {
const crdGVR = "apiextensions.k8s.io/v1beta1/customresourcedefinitions"
oo, err := f.List(crdGVR, "", true, labels.Everything())
oo, err := f.List(crdGVR, client.ClusterScope, true, labels.Everything())
if err != nil {
log.Warn().Err(err).Msgf("Fail CRDs load")
return

View File

@ -40,37 +40,6 @@ func NewCmdBuff(key rune, kind BufferKind) *CmdBuff {
}
}
// CurrentSuggestion returns the current suggestion.
func (c *CmdBuff) CurrentSuggestion() (string, bool) {
return "", false
}
// NextSuggestion returns the next suggestion.
func (c *CmdBuff) NextSuggestion() (string, bool) {
return "", false
}
// PrevSuggestion returns the prev suggestion.
func (c *CmdBuff) PrevSuggestion() (string, bool) {
return "", false
}
// ClearSuggestions clear out all suggestions.
func (c *CmdBuff) ClearSuggestions() {}
// AutoSuggests returns true if model implements auto suggestions.
func (c *CmdBuff) AutoSuggests() bool {
return false
}
// Suggestions returns suggestions.
func (c *CmdBuff) Suggestions() []string {
return nil
}
// Notify notifies all listener of current suggestions.
func (c *CmdBuff) Notify() {}
// InCmdMode checks if a command exists and the buffer is active.
func (c *CmdBuff) InCmdMode() bool {
return c.active || len(c.buff) > 0
@ -139,7 +108,7 @@ func (c *CmdBuff) AddListener(w BuffWatcher) {
c.listeners = append(c.listeners, w)
}
// RemoveListener unregisters a listener.
// RemoveListener removes a listener.
func (c *CmdBuff) RemoveListener(l BuffWatcher) {
victim := -1
for i, lis := range c.listeners {

View File

@ -20,7 +20,6 @@ type FishBuff struct {
*CmdBuff
suggestionFn SuggestionFunc
suggestion string
suggestions []string
suggestionIndex int
}
@ -35,43 +34,48 @@ func NewFishBuff(key rune, kind BufferKind) *FishBuff {
// PrevSuggestion returns the prev suggestion.
func (f *FishBuff) PrevSuggestion() (string, bool) {
if f.suggestionIndex < 0 {
if len(f.suggestions) == 0 {
return "", false
}
f.suggestionIndex--
if f.suggestionIndex < 0 {
f.suggestionIndex = 0
} else {
f.suggestionIndex--
}
if f.suggestionIndex < 0 {
f.suggestionIndex = len(f.suggestions) - 1
}
return f.suggestions[f.suggestionIndex], true
}
// NextSuggestion returns the next suggestion.
func (f *FishBuff) NextSuggestion() (string, bool) {
if f.suggestionIndex < 0 {
if len(f.suggestions) == 0 {
return "", false
}
if f.suggestionIndex < 0 {
f.suggestionIndex = 0
} else {
f.suggestionIndex++
}
if f.suggestionIndex >= len(f.suggestions) {
f.suggestionIndex = 0
}
s := f.suggestions[f.suggestionIndex]
f.suggestionIndex++
return s, true
return f.suggestions[f.suggestionIndex], true
}
// ClearSuggestions clear out all suggestions.
func (f *FishBuff) ClearSuggestions() {
f.suggestion, f.suggestionIndex = "", -1
f.suggestionIndex = -1
}
// CurrentSuggestion returns the current suggestion.
func (f *FishBuff) CurrentSuggestion() (string, bool) {
if f.suggestionIndex < 0 {
return "", false
}
if f.suggestionIndex >= len(f.suggestions) {
if len(f.suggestions) == 0 || f.suggestionIndex < 0 || f.suggestionIndex >= len(f.suggestions) {
return "", false
}
@ -101,41 +105,31 @@ func (f *FishBuff) Notify() {
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggestionChanged(cc)
f.fireSuggestionChanged(f.suggestionFn(string(f.buff)))
}
// Add adds a new charater to the buffer.
func (f *FishBuff) Add(r rune) {
f.CmdBuff.Add(r)
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggestionChanged(cc)
f.Notify()
}
// Delete removes the last character from the buffer.
func (f *FishBuff) Delete() {
f.CmdBuff.Delete()
if f.suggestionFn == nil {
return
}
cc := f.suggestionFn(string(f.buff))
f.fireSuggestionChanged(cc)
f.Notify()
}
func (f *FishBuff) fireSuggestionChanged(ss []string) {
f.suggestions, f.suggestionIndex = ss, 0
if len(ss) == 0 {
f.suggestionIndex, f.suggestion = -1, ""
f.suggestionIndex = -1
return
}
f.suggestion = ss[f.suggestionIndex]
for _, l := range f.listeners {
if listener, ok := l.(SuggestionListener); ok {
listener.SuggestionChanged(f.GetText(), f.suggestion)
listener.SuggestionChanged(f.GetText(), ss[f.suggestionIndex])
}
}
}

View File

@ -0,0 +1,89 @@
package model_test
import (
"sort"
"testing"
"github.com/derailed/k9s/internal/model"
"github.com/stretchr/testify/assert"
)
func TestFishAdd(t *testing.T) {
m := mockSuggestionListener{}
f := model.NewFishBuff(' ', model.FilterBuffer)
f.AddListener(&m)
f.SetSuggestionFn(func(text string) sort.StringSlice {
return sort.StringSlice{"blee", "duh"}
})
f.Add('a')
f.SetActive(true)
assert.Equal(t, 1, m.buff)
assert.Equal(t, 1, m.sugg)
assert.True(t, m.active)
assert.Equal(t, "blee", m.suggestion)
c, ok := f.CurrentSuggestion()
assert.True(t, ok)
assert.Equal(t, "blee", c)
c, ok = f.NextSuggestion()
assert.True(t, ok)
assert.Equal(t, "duh", c)
c, ok = f.PrevSuggestion()
assert.True(t, ok)
assert.Equal(t, "blee", c)
}
func TestFishDelete(t *testing.T) {
m := mockSuggestionListener{}
f := model.NewFishBuff(' ', model.FilterBuffer)
f.AddListener(&m)
f.SetSuggestionFn(func(text string) sort.StringSlice {
return sort.StringSlice{"blee", "duh"}
})
f.Add('a')
f.Delete()
f.SetActive(true)
assert.Equal(t, 2, m.buff)
assert.Equal(t, 2, m.sugg)
assert.True(t, m.active)
assert.Equal(t, "blee", m.suggestion)
c, ok := f.CurrentSuggestion()
assert.True(t, ok)
assert.Equal(t, "blee", c)
c, ok = f.NextSuggestion()
assert.True(t, ok)
assert.Equal(t, "duh", c)
c, ok = f.PrevSuggestion()
assert.True(t, ok)
assert.Equal(t, "blee", c)
}
// Helpers...
type mockSuggestionListener struct {
buff, sugg int
suggestion string
active bool
}
func (m *mockSuggestionListener) BufferChanged(s string) {
m.buff++
}
func (m *mockSuggestionListener) BufferActive(state bool, kind model.BufferKind) {
m.active = state
}
func (m *mockSuggestionListener) SuggestionChanged(text, sugg string) {
m.suggestion = sugg
m.sugg++
}

View File

@ -2,6 +2,8 @@ package model
import (
"strings"
"github.com/rs/zerolog/log"
)
// MaxHistory tracks max command history
@ -42,8 +44,9 @@ func (h *History) Push(c string) {
h.commands = append([]string{c}, h.commands[:len(h.commands)-1]...)
}
// Clear clear out the stack using pops.
// Clear clears out the stack.
func (h *History) Clear() {
log.Debug().Msgf("History CLEARED!!!")
h.commands = nil
}

View File

@ -75,6 +75,7 @@ func (t *Table) RemoveListener(l TableListener) {
// Watch initiates model updates.
func (t *Table) Watch(ctx context.Context) {
log.Debug().Msgf("TABLE-WATCH %q", t.gvr)
t.refresh(ctx)
go t.updater(ctx)
}

View File

@ -14,10 +14,10 @@ const (
defaultSpacer = 4
)
var _ PromptModel = (*model.CmdBuff)(nil)
var _ Suggester = (*model.CmdBuff)(nil)
var _ PromptModel = (*model.FishBuff)(nil)
var _ Suggester = (*model.FishBuff)(nil)
var (
_ PromptModel = (*model.FishBuff)(nil)
_ Suggester = (*model.FishBuff)(nil)
)
// Suggester provides suggestions.
type Suggester interface {
@ -142,20 +142,21 @@ func (p *Prompt) keyboard(evt *tcell.EventKey) *tcell.EventKey {
p.model.SetActive(false)
case tcell.KeyCtrlW, tcell.KeyCtrlU:
p.model.ClearText()
case tcell.KeyDown:
if next, ok := m.NextSuggestion(); ok {
p.suggest(p.model.GetText(), next)
}
case tcell.KeyUp:
if prev, ok := m.PrevSuggestion(); ok {
p.suggest(p.model.GetText(), prev)
if s, ok := m.NextSuggestion(); ok {
p.suggest(p.model.GetText(), s)
}
case tcell.KeyDown:
if s, ok := m.PrevSuggestion(); ok {
p.suggest(p.model.GetText(), s)
}
case tcell.KeyTab, tcell.KeyRight, tcell.KeyCtrlF:
if curr, ok := m.CurrentSuggestion(); ok {
p.model.SetText(p.model.GetText() + curr)
if s, ok := m.CurrentSuggestion(); ok {
p.model.SetText(p.model.GetText() + s)
m.ClearSuggestions()
}
}
return evt
}

View File

@ -36,7 +36,7 @@ func TestTableUpdate(t *testing.T) {
func TestTableSelection(t *testing.T) {
v := ui.NewTable(client.NewGVR("fred"))
v.Init(makeContext())
m := &testModel{}
m := &mockModel{}
v.SetModel(m)
v.Update(m.Peek())
v.SelectRow(1, true)
@ -57,34 +57,36 @@ func TestTableSelection(t *testing.T) {
// ----------------------------------------------------------------------------
// Helpers...
type testModel struct{}
type mockModel struct{}
var _ ui.Tabular = &testModel{}
var _ ui.Tabular = &mockModel{}
func (t *testModel) SetInstance(string) {}
func (t *testModel) Empty() bool { return false }
func (t *testModel) HasMetrics() bool { return true }
func (t *testModel) Peek() render.TableData { return makeTableData() }
func (t *testModel) ClusterWide() bool { return false }
func (t *testModel) GetNamespace() string { return "blee" }
func (t *testModel) SetNamespace(string) {}
func (t *testModel) ToggleToast() {}
func (t *testModel) AddListener(model.TableListener) {}
func (t *testModel) Watch(context.Context) {}
func (t *testModel) Get(ctx context.Context, path string) (runtime.Object, error) {
func (t *mockModel) SetInstance(string) {}
func (t *mockModel) Empty() bool { return false }
func (t *mockModel) HasMetrics() bool { return true }
func (t *mockModel) Peek() render.TableData { return makeTableData() }
func (t *mockModel) Refresh(context.Context) {}
func (t *mockModel) ClusterWide() bool { return false }
func (t *mockModel) GetNamespace() string { return "blee" }
func (t *mockModel) SetNamespace(string) {}
func (t *mockModel) ToggleToast() {}
func (t *mockModel) AddListener(model.TableListener) {}
func (t *mockModel) RemoveListener(model.TableListener) {}
func (t *mockModel) Watch(context.Context) {}
func (t *mockModel) Get(ctx context.Context, path string) (runtime.Object, error) {
return nil, nil
}
func (t *testModel) Delete(ctx context.Context, path string, c, f bool) error {
func (t *mockModel) Delete(ctx context.Context, path string, c, f bool) error {
return nil
}
func (t *testModel) Describe(context.Context, string) (string, error) {
func (t *mockModel) Describe(context.Context, string) (string, error) {
return "", nil
}
func (t *testModel) ToYAML(ctx context.Context, path string) (string, error) {
func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) {
return "", nil
}
func (t *testModel) InNamespace(string) bool { return true }
func (t *testModel) SetRefreshRate(time.Duration) {}
func (t *mockModel) InNamespace(string) bool { return true }
func (t *mockModel) SetRefreshRate(time.Duration) {}
func makeTableData() render.TableData {
t := render.NewTableData()

View File

@ -17,7 +17,7 @@ type Tree struct {
actions KeyActions
selectedItem string
cmdBuff *model.CmdBuff
cmdBuff *model.FishBuff
expandNodes bool
Count int
keyListener KeyListenerFunc
@ -29,7 +29,7 @@ func NewTree() *Tree {
TreeView: tview.NewTreeView(),
expandNodes: true,
actions: make(KeyActions),
cmdBuff: model.NewCmdBuff('/', model.FilterBuffer),
cmdBuff: model.NewFishBuff('/', model.FilterBuffer),
}
}
@ -62,7 +62,7 @@ func (t *Tree) ExpandNodes() bool {
}
// CmdBuff returns the filter command.
func (t *Tree) CmdBuff() *model.CmdBuff {
func (t *Tree) CmdBuff() *model.FishBuff {
return t.cmdBuff
}

View File

@ -64,12 +64,18 @@ type Tabular interface {
// Watch watches a given resource for changes.
Watch(context.Context)
// Refresh forces a new refresh.
Refresh(context.Context)
// SetRefreshRate sets the model watch loop rate.
SetRefreshRate(time.Duration)
// AddListener registers a model listener.
AddListener(model.TableListener)
// RemoveListener unregister a model listener.
RemoveListener(model.TableListener)
// Delete a resource.
Delete(ctx context.Context, path string, cascade, force bool) error
}

View File

@ -29,7 +29,7 @@ func TestAliasNew(t *testing.T) {
func TestAliasSearch(t *testing.T) {
v := view.NewAlias(client.NewGVR("aliases"))
assert.Nil(t, v.Init(makeContext()))
v.GetTable().SetModel(&testModel{})
v.GetTable().SetModel(&mockModel{})
v.GetTable().Refresh()
v.App().Prompt().SetModel(v.GetTable().CmdBuff())
v.App().Prompt().SendStrokes("blee")
@ -94,41 +94,44 @@ func (k ks) NamespaceNames(nn []v1.Namespace) []string {
return []string{"test"}
}
type testModel struct{}
type mockModel struct{}
var _ ui.Tabular = (*testModel)(nil)
var _ ui.Suggester = (*testModel)(nil)
var _ ui.Tabular = (*mockModel)(nil)
var _ ui.Suggester = (*mockModel)(nil)
func (t *testModel) CurrentSuggestion() (string, bool) { return "", false }
func (t *testModel) NextSuggestion() (string, bool) { return "", false }
func (t *testModel) PrevSuggestion() (string, bool) { return "", false }
func (t *testModel) ClearSuggestions() {}
func (t *mockModel) CurrentSuggestion() (string, bool) { return "", false }
func (t *mockModel) NextSuggestion() (string, bool) { return "", false }
func (t *mockModel) PrevSuggestion() (string, bool) { return "", false }
func (t *mockModel) ClearSuggestions() {}
func (t *mockModel) SetInstance(string) {}
func (t *mockModel) Empty() bool { return false }
func (t *mockModel) HasMetrics() bool { return true }
func (t *mockModel) Peek() render.TableData { return makeTableData() }
func (t *mockModel) ClusterWide() bool { return false }
func (t *mockModel) GetNamespace() string { return "blee" }
func (t *mockModel) SetNamespace(string) {}
func (t *mockModel) ToggleToast() {}
func (t *mockModel) AddListener(model.TableListener) {}
func (t *mockModel) RemoveListener(model.TableListener) {}
func (t *mockModel) Watch(context.Context) {}
func (t *mockModel) Refresh(context.Context) {}
func (t *mockModel) Get(context.Context, string) (runtime.Object, error) {
func (t *testModel) SetInstance(string) {}
func (t *testModel) Empty() bool { return false }
func (t *testModel) HasMetrics() bool { return true }
func (t *testModel) Peek() render.TableData { return makeTableData() }
func (t *testModel) ClusterWide() bool { return false }
func (t *testModel) GetNamespace() string { return "blee" }
func (t *testModel) SetNamespace(string) {}
func (t *testModel) ToggleToast() {}
func (t *testModel) AddListener(model.TableListener) {}
func (t *testModel) Watch(context.Context) {}
func (t *testModel) Get(context.Context, string) (runtime.Object, error) {
return nil, nil
}
func (t *testModel) Delete(context.Context, string, bool, bool) error {
func (t *mockModel) Delete(context.Context, string, bool, bool) error {
return nil
}
func (t *testModel) Describe(context.Context, string) (string, error) {
func (t *mockModel) Describe(context.Context, string) (string, error) {
return "", nil
}
func (t *testModel) ToYAML(ctx context.Context, path string) (string, error) {
func (t *mockModel) ToYAML(ctx context.Context, path string) (string, error) {
return "", nil
}
func (t *testModel) InNamespace(string) bool { return true }
func (t *testModel) SetRefreshRate(time.Duration) {}
func (t *mockModel) InNamespace(string) bool { return true }
func (t *mockModel) SetRefreshRate(time.Duration) {}
func makeTableData() render.TableData {
return render.TableData{

View File

@ -330,14 +330,15 @@ func (a *App) isValidNS(ns string) bool {
if ns == client.AllNamespaces || ns == client.NamespaceAll {
return true
}
ctx, cancel := context.WithTimeout(context.Background(), client.CallTimeout)
defer cancel()
_, err := a.Conn().DialOrDie().CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{})
if err != nil {
log.Warn().Err(err).Msgf("Unable to find namespace %q", ns)
log.Warn().Err(err).Msgf("Validation failed for namespace: %q", ns)
}
return err == nil
return true
}
func (a *App) switchCtx(name string, loadPods bool) error {
@ -502,6 +503,12 @@ func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
func (a *App) meowCmd(msg string) {
if err := a.inject(NewMeow(a, msg)); err != nil {
a.Flash().Err(err)
}
}
func (a *App) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.CmdBuff().InCmdMode() {
return evt
@ -537,13 +544,25 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
}
func (a *App) gotoResource(cmd, path string, clearStack bool) error {
return a.command.run(cmd, path, clearStack)
err := a.command.run(cmd, path, clearStack)
if err == nil {
return err
}
c := NewMeow(a, err.Error())
_ = c.Init(context.Background())
a.Content.Stack.Clear()
a.Content.Push(c)
return nil
}
func (a *App) inject(c model.Component) error {
ctx := context.WithValue(context.Background(), internal.KeyApp, a)
if err := c.Init(ctx); err != nil {
return fmt.Errorf("component init failed for %q %v", c.Name(), err)
log.Error().Err(err).Msgf("component init failed for %q %v", c.Name(), err)
c = NewMeow(a, err.Error())
_ = c.Init(ctx)
}
a.Content.Push(c)

View File

@ -73,7 +73,6 @@ func (b *Browser) Init(ctx context.Context) error {
if row == 0 && b.GetRowCount() > 0 {
b.Select(1, 0)
}
b.GetModel().AddListener(b)
b.GetModel().SetRefreshRate(time.Duration(b.App().Config.K9s.GetRefreshRate()) * time.Second)
b.CmdBuff().SetSuggestionFn(b.suggestFilter())
@ -92,16 +91,13 @@ func (b *Browser) suggestFilter() model.SuggestionFunc {
s = strings.ToLower(s)
for _, h := range b.App().filterHistory.List() {
if h == s {
if s == h {
continue
}
if strings.HasPrefix(h, s) {
entries = append(entries, strings.Replace(h, s, "", 1))
}
}
if len(entries) == 0 {
return nil
}
return
}
}
@ -126,6 +122,7 @@ func (b *Browser) Start() {
}
b.Stop()
b.GetModel().AddListener(b)
b.Table.Start()
b.CmdBuff().AddListener(b)
b.GetModel().Watch(b.prepareContext())
@ -133,6 +130,7 @@ func (b *Browser) Start() {
// Stop terminates browser updates.
func (b *Browser) Stop() {
b.GetModel().RemoveListener(b)
b.CmdBuff().RemoveListener(b)
b.Table.Stop()
if b.cancelFn != nil {
@ -146,14 +144,12 @@ func (b *Browser) BufferChanged(s string) {}
// BufferActive indicates the buff activity changed.
func (b *Browser) BufferActive(state bool, k model.BufferKind) {
if b.cancelFn != nil {
b.cancelFn()
}
b.GetModel().Watch(b.prepareContext())
if !state && b.GetRowCount() > 1 {
b.App().filterHistory.Push(b.CmdBuff().GetText())
}
b.app.QueueUpdateDraw(func() {
b.Update(b.GetModel().Peek())
if b.GetRowCount() > 1 {
b.App().filterHistory.Push(b.CmdBuff().GetText())
}
})
}
func (b *Browser) prepareContext() context.Context {
@ -239,11 +235,10 @@ func (b *Browser) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return b.App().PrevCmd(evt)
}
b.CmdBuff().Reset()
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
b.CmdBuff().Reset()
b.Start()
}
b.CmdBuff().Reset()
b.Refresh()
return nil

View File

@ -106,23 +106,24 @@ func (c *Command) xrayCmd(cmd string) error {
return c.exec(cmd, "xrays", x, true)
}
func (c *Command) checkAccess(gvr string) error {
m, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
if err != nil {
return err
}
ns := client.CleanseNamespace(c.app.Config.ActiveNamespace())
if dao.IsK8sMeta(m) && c.app.ConOK() {
if _, e := c.app.factory.CanForResource(ns, gvr, client.MonitorAccess); e != nil {
return e
}
}
return nil
}
// BOZO!!
// func (c *Command) checkAccess(gvr string) error {
// m, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
// if err != nil {
// return err
// }
// ns := client.CleanseNamespace(c.app.Config.ActiveNamespace())
// if dao.IsK8sMeta(m) && c.app.ConOK() {
// if _, e := c.app.factory.CanForResource(ns, gvr, client.MonitorAccess); e != nil {
// return e
// }
// }
// return nil
// }
// Exec the Command by showing associated display.
func (c *Command) run(cmd, path string, clearStack bool) error {
if c.specialCmd(cmd) {
if c.specialCmd(cmd, path) {
return nil
}
cmds := strings.Split(cmd, " ")
@ -130,17 +131,16 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
if err != nil {
return err
}
if err := c.checkAccess(gvr); err != nil {
return err
}
//if err := c.checkAccess(gvr); err != nil {
// return err
//}
switch cmds[0] {
case "ctx", "context", "contexts":
if len(cmds) == 2 {
return useContext(c.app, cmds[1])
}
view := c.componentFor(gvr, path, v)
return c.exec(cmd, gvr, view, clearStack)
return c.exec(cmd, gvr, c.componentFor(gvr, path, v), clearStack)
default:
// checks if Command includes a namespace
ns := c.app.Config.ActiveNamespace()
@ -171,14 +171,17 @@ func (c *Command) defaultCmd() error {
if err := c.run(cmd, "", true); err != nil {
log.Error().Err(err).Msgf("Saved command load failed. Loading default view")
return c.run("pod", "", true)
return c.run("meow", err.Error(), true)
}
return nil
}
func (c *Command) specialCmd(cmd string) bool {
func (c *Command) specialCmd(cmd, path string) bool {
cmds := strings.Split(cmd, " ")
switch cmds[0] {
case "meow":
c.app.meowCmd(path)
return true
case "q", "Q", "quit":
c.app.BailOut()
return true

View File

@ -23,7 +23,7 @@ type Details struct {
actions ui.KeyActions
app *App
title, subject string
cmdBuff *model.CmdBuff
cmdBuff *model.FishBuff
model *model.Text
currentRegion, maxRegions int
searchable bool
@ -37,7 +37,7 @@ func NewDetails(app *App, title, subject string, searchable bool) *Details {
title: title,
subject: subject,
actions: make(ui.KeyActions),
cmdBuff: model.NewCmdBuff('/', model.FilterBuffer),
cmdBuff: model.NewFishBuff('/', model.FilterBuffer),
model: model.NewText(),
searchable: searchable,
}

View File

@ -45,6 +45,7 @@ func (d *DaemonSet) showPods(app *App, model ui.Tabular, _, path string) {
ds, err := res.GetInstance(path)
if err != nil {
d.App().Flash().Err(err)
return
}
showPodsFromSelector(app, path, ds.Spec.Selector)

135
internal/view/meow.go Normal file
View File

@ -0,0 +1,135 @@
package view
import (
"context"
"fmt"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"strings"
)
// Meow represents a bomb viewer
type Meow struct {
*tview.TextView
actions ui.KeyActions
app *App
says string
}
// NewMeow returns a details viewer.
func NewMeow(app *App, says string) *Meow {
return &Meow{
TextView: tview.NewTextView(),
app: app,
actions: make(ui.KeyActions),
says: says,
}
}
// Init initializes the viewer.
func (m *Meow) Init(_ context.Context) error {
m.SetBorder(true)
m.SetScrollable(true).SetWrap(true).SetRegions(true)
m.SetDynamicColors(true)
m.SetHighlightColor(tcell.ColorOrange)
m.SetTitleColor(tcell.ColorAqua)
m.SetInputCapture(m.keyboard)
m.SetBorderPadding(0, 0, 1, 1)
m.SetChangedFunc(func() {
m.app.Draw()
})
m.updateTitle()
m.SetTextAlign(tview.AlignCenter)
m.app.Styles.AddListener(m)
m.StylesChanged(m.app.Styles)
m.bindKeys()
m.SetInputCapture(m.keyboard)
m.talk()
return nil
}
func (m *Meow) talk() {
says := m.says
if len(says) == 0 {
says = "Nothing to report here. Please move along..."
}
buff := make([]string, 0, len(cow)+3)
buff = append(buff, " "+strings.Repeat("─", len(says)+8))
buff = append(buff, fmt.Sprintf("< [red::b]MEOW! %s [-::-] >", says))
buff = append(buff, " "+strings.Repeat("─", len(says)+8))
spacer := strings.Repeat(" ", len(says)/2-8)
for _, s := range cow {
buff = append(buff, spacer+s)
}
m.SetText(strings.Join(buff, "\n"))
}
func (m *Meow) bindKeys() {
m.actions.Set(ui.KeyActions{
tcell.KeyEscape: ui.NewKeyAction("Back", m.resetCmd, false),
})
}
func (m *Meow) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := m.actions[ui.AsKey(evt)]; ok {
return a.Action(evt)
}
return evt
}
// StylesChanged notifies the skin changes.
func (m *Meow) StylesChanged(s *config.Styles) {
m.SetBackgroundColor(m.app.Styles.BgColor())
m.SetTextColor(m.app.Styles.FgColor())
m.SetBorderFocusColor(m.app.Styles.Frame().Border.FocusColor.Color())
}
func (m *Meow) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
return m.app.PrevCmd(evt)
}
// Actions returns menu actions
func (m *Meow) Actions() ui.KeyActions {
return m.actions
}
// Name returns the component name.
func (m *Meow) Name() string { return "cow" }
// Start starts the view updater.
func (m *Meow) Start() {}
// Stop terminates the updater.
func (m *Meow) Stop() {
m.app.Styles.RemoveListener(m)
}
// Hints returns menu hints.
func (m *Meow) Hints() model.MenuHints {
return m.actions.Hints()
}
// ExtraHints returns additional hints.
func (m *Meow) ExtraHints() map[string]string {
return nil
}
func (m *Meow) updateTitle() {
m.SetTitle(" Meow! ")
}
var cow = []string{
`\ ^__^ `,
` \ (oo)\_______ `,
` (__)\ )\/\`,
` ||----w | `,
` || || `,
}

View File

@ -16,6 +16,7 @@ import (
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// Grapheable represents a graphic component.
@ -285,6 +286,10 @@ func (p *Pulse) Actions() ui.KeyActions {
// Hints returns the view hints.
func (p *Pulse) Hints() model.MenuHints {
log.Debug().Msgf("PULSES -- HINTS!!")
for k := range p.actions {
log.Debug().Msgf("KEY %v", p.actions[k].Description)
}
return p.actions.Hints()
}

View File

@ -64,7 +64,7 @@ func TestTableNew(t *testing.T) {
func TestTableViewFilter(t *testing.T) {
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetModel(&testTableModel{})
v.SetModel(&mockTableModel{})
v.Refresh()
v.CmdBuff().SetActive(true)
v.CmdBuff().SetText("blee")
@ -75,7 +75,7 @@ func TestTableViewFilter(t *testing.T) {
func TestTableViewSort(t *testing.T) {
v := NewTable(client.NewGVR("test"))
v.Init(makeContext())
v.SetModel(&testTableModel{})
v.SetModel(&mockTableModel{})
v.SortColCmd("NAME", true)(nil)
assert.Equal(t, 3, v.GetRowCount())
assert.Equal(t, "blee", v.GetCell(1, 0).Text)
@ -88,35 +88,37 @@ func TestTableViewSort(t *testing.T) {
// ----------------------------------------------------------------------------
// Helpers...
type testTableModel struct{}
type mockTableModel struct{}
var _ ui.Tabular = (*testTableModel)(nil)
var _ ui.Tabular = (*mockTableModel)(nil)
func (t *testTableModel) SetInstance(string) {}
func (t *testTableModel) Empty() bool { return false }
func (t *testTableModel) HasMetrics() bool { return true }
func (t *testTableModel) Peek() render.TableData { return makeTableData() }
func (t *testTableModel) ClusterWide() bool { return false }
func (t *testTableModel) GetNamespace() string { return "blee" }
func (t *testTableModel) SetNamespace(string) {}
func (t *testTableModel) ToggleToast() {}
func (t *testTableModel) AddListener(model.TableListener) {}
func (t *testTableModel) Watch(context.Context) {}
func (t *testTableModel) Get(context.Context, string) (runtime.Object, error) {
func (t *mockTableModel) SetInstance(string) {}
func (t *mockTableModel) Empty() bool { return false }
func (t *mockTableModel) HasMetrics() bool { return true }
func (t *mockTableModel) Peek() render.TableData { return makeTableData() }
func (t *mockTableModel) Refresh(context.Context) {}
func (t *mockTableModel) ClusterWide() bool { return false }
func (t *mockTableModel) GetNamespace() string { return "blee" }
func (t *mockTableModel) SetNamespace(string) {}
func (t *mockTableModel) ToggleToast() {}
func (t *mockTableModel) AddListener(model.TableListener) {}
func (t *mockTableModel) RemoveListener(model.TableListener) {}
func (t *mockTableModel) Watch(context.Context) {}
func (t *mockTableModel) Get(context.Context, string) (runtime.Object, error) {
return nil, nil
}
func (t *testTableModel) Delete(context.Context, string, bool, bool) error {
func (t *mockTableModel) Delete(context.Context, string, bool, bool) error {
return nil
}
func (t *testTableModel) Describe(context.Context, string) (string, error) {
func (t *mockTableModel) Describe(context.Context, string) (string, error) {
return "", nil
}
func (t *testTableModel) ToYAML(ctx context.Context, path string) (string, error) {
func (t *mockTableModel) ToYAML(ctx context.Context, path string) (string, error) {
return "", nil
}
func (t *testTableModel) InNamespace(string) bool { return true }
func (t *testTableModel) SetRefreshRate(time.Duration) {}
func (t *mockTableModel) InNamespace(string) bool { return true }
func (t *mockTableModel) SetRefreshRate(time.Duration) {}
func makeTableData() render.TableData {
t := render.NewTableData()

View File

@ -106,10 +106,6 @@ func (f *Factory) waitForCacheSync(ns string) {
ns = client.AllNamespaces
}
if f.isClusterWide() {
ns = client.AllNamespaces
}
f.mx.RLock()
defer f.mx.RUnlock()
fac, ok := f.factories[ns]
@ -156,8 +152,8 @@ func (f *Factory) SetActiveNS(ns string) {
func (f *Factory) isClusterWide() bool {
f.mx.RLock()
defer f.mx.RUnlock()
_, ok := f.factories[client.AllNamespaces]
return ok
}