parent
f0ef39b46c
commit
46c2f31249
|
|
@ -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
5
go.mod
|
|
@ -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
31
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 | `,
|
||||
` || || `,
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue