added toggle header + alias command fixes

mine
derailed 2019-10-01 15:31:17 -06:00
parent 7d05e4401e
commit 5baa447e46
62 changed files with 841 additions and 698 deletions

1
go.sum
View File

@ -386,6 +386,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

122
internal/config/alias.go Normal file
View File

@ -0,0 +1,122 @@
package config
import (
"io/ioutil"
"path/filepath"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v2"
)
// K9sAlias stores K9s command aliases.
var K9sAlias = filepath.Join(K9sHome, "alias.yml")
type Alias map[string]string
// Aliases represents a collection of aliases.
type Aliases struct {
Alias Alias `yaml:"alias"`
}
// NewAliases return a new alias.
func NewAliases() Aliases {
aa := Aliases{Alias: make(Alias, 50)}
aa.loadDefaults()
return aa
}
func (a Aliases) loadDefaults() {
a.Alias["dp"] = "apps/v1/deployments"
a.Alias["sec"] = "v1/secrets"
a.Alias["jo"] = "batch/v1/jobs"
a.Alias["cr"] = "rbac.authorization.k8s.io/v1/clusterroles"
a.Alias["crb"] = "rbac.authorization.k8s.io/v1/clusterrolebindings"
a.Alias["ro"] = "rbac.authorization.k8s.io/v1/roles"
a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings"
a.Alias["np"] = "networking.k8s.io/v1beta1/rolebindings"
{
a.Alias["ctx"] = "contexts"
a.Alias["contexts"] = "contexts"
a.Alias["context"] = "contexts"
}
{
a.Alias["usr"] = "users"
a.Alias["users"] = "users"
a.Alias["user"] = "user"
}
{
a.Alias["grp"] = "groups"
a.Alias["group"] = "groups"
a.Alias["groups"] = "groups"
}
{
a.Alias["pf"] = "portforwards"
a.Alias["portforwards"] = "portforwards"
a.Alias["portforward"] = "portforwards"
}
{
a.Alias["be"] = "benchmarks"
a.Alias["benchmark"] = "benchmarks"
a.Alias["benchmarks"] = "benchmarks"
}
{
a.Alias["sd"] = "screendumps"
a.Alias["screendump"] = "screendumps"
a.Alias["screendumps"] = "screendumps"
}
}
// Load K9s aliases.
func (a Aliases) Load() error {
return a.LoadAliases(K9sAlias)
}
// Get retrieves an alias.
func (a Aliases) Get(k string) (string, bool) {
v, ok := a.Alias[k]
return v, ok
}
// Define declares a new alias.
func (a Aliases) Define(args ...string) {
if len(args)%2 != 0 {
panic("Invalid alias definition. You must specify pairs")
}
for i := 0; i < len(args); i += 2 {
a.Alias[args[i]] = args[i+1]
}
}
// LoadAliases K9s alias from a given file.
func (a Aliases) LoadAliases(path string) error {
f, err := ioutil.ReadFile(path)
if err != nil {
return err
}
var aa Aliases
if err := yaml.Unmarshal(f, &aa); err != nil {
return err
}
for k, v := range aa.Alias {
a.Alias[k] = v
}
return nil
}
// Save alias to disk.
func (a Aliases) Save() error {
log.Debug().Msg("[Config] Saving Aliases...")
return a.SaveAliases(K9sAlias)
}
// SaveAliases saves aliases to a given file.
func (a Aliases) SaveAliases(path string) error {
EnsurePath(path, DefaultDirMod)
cfg, err := yaml.Marshal(a)
if err != nil {
return err
}
return ioutil.WriteFile(path, cfg, 0644)
}

View File

@ -0,0 +1,26 @@
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestAliasesLoad(t *testing.T) {
aa := config.NewAliases()
assert.Nil(t, aa.LoadAliases("test_assets/alias.yml"))
assert.Equal(t, 27, len(aa.Alias))
}
func TestAliasesSave(t *testing.T) {
aa := config.NewAliases()
aa.Alias["test"] = "fred"
aa.Alias["blee"] = "duh"
aa.SaveAliases("/tmp/a.yml")
assert.Nil(t, aa.LoadAliases("/tmp/a.yml"))
assert.Equal(t, 28, len(aa.Alias))
}

View File

@ -30,15 +30,24 @@ var (
) )
type ( type (
// Connection present a kubernetes api server connection. // Connection represents a kubernetes api server connection.
Connection k8s.Connection Connection k8s.Connection
// KubeSettings exposes kubeconfig context informations. // KubeSettings exposes kubeconfig context information.
KubeSettings interface { KubeSettings interface {
// CurrentContextName returns the name of the current context.
CurrentContextName() (string, error) CurrentContextName() (string, error)
// CurrentClusterName returns the name of the current cluster.
CurrentClusterName() (string, error) CurrentClusterName() (string, error)
// CurrentNamespace returns the name of the current namespace.
CurrentNamespaceName() (string, error) CurrentNamespaceName() (string, error)
// ClusterNames() returns all available cluster names.
ClusterNames() ([]string, error) ClusterNames() ([]string, error)
// NamespaceNames returns all available namespace names.
NamespaceNames(nn []v1.Namespace) []string NamespaceNames(nn []v1.Namespace) []string
} }
@ -180,7 +189,6 @@ func (c *Config) Load(path string) error {
if cfg.K9s != nil { if cfg.K9s != nil {
c.K9s = cfg.K9s c.K9s = cfg.K9s
} }
log.Debug().Msgf("Headless ? %t", c.K9s.Headless)
return nil return nil
} }

View File

@ -8,7 +8,7 @@ const (
DefaultLogLevel = "info" DefaultLogLevel = "info"
// DefaultCommand represents the default command to run. // DefaultCommand represents the default command to run.
DefaultCommand = "po" DefaultCommand = ""
) )
// Flags represents K9s configuration flags. // Flags represents K9s configuration flags.

View File

@ -80,7 +80,6 @@ func (k *K9s) ActiveCluster() *Cluster {
if c, ok := k.Clusters[k.CurrentCluster]; ok { if c, ok := k.Clusters[k.CurrentCluster]; ok {
return c return c
} }
k.Clusters[k.CurrentCluster] = NewCluster() k.Clusters[k.CurrentCluster] = NewCluster()
return k.Clusters[k.CurrentCluster] return k.Clusters[k.CurrentCluster]

View File

@ -0,0 +1,3 @@
alias:
dp: "apps.v1.deployments"
pe: ".v1.pods"

62
internal/k8s/gvr.go Normal file
View File

@ -0,0 +1,62 @@
package k8s
import (
"path"
"strings"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GVR represents a fully qualified kubernetes resource.
type GVR string
// NewGVR returns a new gvr.
func NewGVR(g, v, r string) GVR {
return GVR(path.Join(g, v, r))
}
// ToGVR returns a new gvr from a string.
func ToGVR(gv, r string) GVR {
return GVR(path.Join(gv, r))
}
// ResName returns a full res name ie dp.v1.apps.
func (g GVR) ResName() string {
return g.ToR() + "." + g.ToV() + "." + g.ToG()
}
// AsGR returns the group version.
func (g GVR) AsGR() schema.GroupVersion {
return schema.GroupVersion{
Group: g.ToG(),
Version: g.ToV(),
}
}
// String returns a GVR as a string.
func (g GVR) String() string {
return string(g)
}
// ToV returns the resource version.
func (g GVR) ToV() string {
tokens := strings.Split(string(g), "/")
return tokens[len(tokens)-2]
}
// ToR returns the resource name.
func (g GVR) ToR() string {
tokens := strings.Split(string(g), "/")
return tokens[len(tokens)-1]
}
// ToG returns the resource group name.
func (g GVR) ToG() string {
tokens := strings.Split(string(g), "/")
switch len(tokens) {
case 3:
return tokens[0]
default:
return ""
}
}

View File

@ -26,14 +26,6 @@ type RestMapper struct {
Connection Connection
} }
// Find a mapping given a resource kind.
func (*RestMapper) Find(kind string) (*meta.RESTMapping, error) {
if m, ok := kindToMapper[kind]; ok {
return m, nil
}
return nil, fmt.Errorf("no mapping for kind %s", kind)
}
// ToRESTMapper map resources to kind, and map kind and version to interfaces for manipulating K8s objects. // ToRESTMapper map resources to kind, and map kind and version to interfaces for manipulating K8s objects.
func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) { func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) {
rc := r.RestConfigOrDie() rc := r.RestConfigOrDie()
@ -115,154 +107,3 @@ func (*RestMapper) toRESTMapping(gvr schema.GroupVersionResource, res string) *m
func (*RestMapper) Name() meta.RESTScopeName { func (*RestMapper) Name() meta.RESTScopeName {
return meta.RESTScopeNameNamespace return meta.RESTScopeNameNamespace
} }
var kindToMapper = map[string]*meta.RESTMapping{
"ConfigMap": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmap"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ConfigMap"},
Scope: RestMapping,
},
"Pod": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"},
Scope: RestMapping,
},
"Service": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "service"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"},
Scope: RestMapping,
},
"EndPoints": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpoints"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Endpoints"},
Scope: RestMapping,
},
"Namespace": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespace"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
Scope: RestMapping,
},
"Node": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "node"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Node"},
Scope: RestMapping,
},
"PersistentVolume": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolume"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PersistentVolume"},
Scope: RestMapping,
},
"PersistentVolumeClaim": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaim"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "PersistentVolumeClaim"},
Scope: RestMapping,
},
"ReplicationController": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "replicationcontroller"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ReplicationController"},
Scope: RestMapping,
},
"Secret": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secret"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Secret"},
Scope: RestMapping,
},
"StorageClasse": {
Resource: schema.GroupVersionResource{Group: "storage.k8s.io", Version: "v1", Resource: "storageclass"},
GroupVersionKind: schema.GroupVersionKind{Group: "storage.k8s.io", Version: "v1", Kind: "StorageClass"},
Scope: RestMapping,
},
"ServiceAccount": {
Resource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "serviceaccount"},
GroupVersionKind: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "ServiceAccount"},
Scope: RestMapping,
},
"Deployment": {
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
Scope: RestMapping,
},
"ReplicaSet": {
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicaset"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "ReplicaSet"},
Scope: RestMapping,
},
"StatefulSet": {
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
Scope: RestMapping,
},
"HorizontalPodAutoscaler": {
Resource: schema.GroupVersionResource{Group: "autoscaling", Version: "v1", Resource: "horizontalpodautoscaler"},
GroupVersionKind: schema.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "HorizontalPodAutoscaler"},
Scope: RestMapping,
},
"Job": {
Resource: schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "job"},
GroupVersionKind: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
Scope: RestMapping,
},
"CronJob": {
Resource: schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "cronjob"},
GroupVersionKind: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "CronJob"},
Scope: RestMapping,
},
"DaemonSet": {
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "daemonset"},
GroupVersionKind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "DaemonSet"},
Scope: RestMapping,
},
"Ingress": {
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingress"},
GroupVersionKind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Ingress"},
Scope: RestMapping,
},
"ClusterRole": {
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrole"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole"},
Scope: RestMapping,
},
"ClusterRoleBinding": {
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebinding"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRoleBinding"},
Scope: RestMapping,
},
"Role": {
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "role"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "Role"},
Scope: RestMapping,
},
"RoleBinding": {
Resource: schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebinding"},
GroupVersionKind: schema.GroupVersionKind{Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "RoleBinding"},
Scope: RestMapping,
},
"CustomResourceDefinition": {
Resource: schema.GroupVersionResource{Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions"},
GroupVersionKind: schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition"},
Scope: RestMapping,
},
"NetworkPolicy": {
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "networkpolicies"},
GroupVersionKind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "NetworkPolicy"},
Scope: RestMapping,
},
"Event": {
Resource: schema.GroupVersionResource{Group: "events.k8s.io", Version: "v1beta1", Resource: "events"},
GroupVersionKind: schema.GroupVersionKind{Group: "events.k8s.io", Version: "v1beta1", Kind: "Event"},
Scope: RestMapping,
},
"PodDisruptionBudget": {
Resource: schema.GroupVersionResource{Group: "policy", Version: "v1beta1", Resource: "poddisruptionbudgets"},
GroupVersionKind: schema.GroupVersionKind{Group: "policy", Version: "v1beta1", Kind: "PodDisruptionBudget"},
Scope: RestMapping,
},
}

View File

@ -17,24 +17,24 @@ type Resource struct {
*base *base
Connection Connection
group, version, name string gvr GVR
} }
// NewResource returns a new Resource. // NewResource returns a new Resource.
func NewResource(c Connection, group, version, name string) *Resource { func NewResource(c Connection, gvr GVR) *Resource {
return &Resource{base: &base{}, Connection: c, group: group, version: version, name: name} return &Resource{base: &base{}, Connection: c, gvr: gvr}
} }
// GetInfo returns info about apigroup. // GetInfo returns info about apigroup.
func (r *Resource) GetInfo() (string, string, string) { func (r *Resource) GetInfo() GVR {
return r.group, r.version, r.name return r.gvr
} }
func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface { func (r *Resource) nsRes() dynamic.NamespaceableResourceInterface {
g := schema.GroupVersionResource{ g := schema.GroupVersionResource{
Group: r.group, Group: r.gvr.ToG(),
Version: r.version, Version: r.gvr.ToV(),
Resource: r.name, Resource: r.gvr.ToR(),
} }
return r.DynDialOrDie().Resource(g) return r.DynDialOrDie().Resource(g)
} }
@ -46,7 +46,7 @@ func (r *Resource) Get(ns, n string) (interface{}, error) {
// List all Resources in a given namespace. // List all Resources in a given namespace.
func (r *Resource) List(ns string) (Collection, error) { func (r *Resource) List(ns string) (Collection, error) {
obj, err := r.listAll(ns, r.name) obj, err := r.listAll(ns, r.gvr.ToR())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,9 +82,10 @@ func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
func (r *Resource) getClient() (*rest.RESTClient, error) { func (r *Resource) getClient() (*rest.RESTClient, error) {
crConfig := r.RestConfigOrDie() crConfig := r.RestConfigOrDie()
crConfig.GroupVersion = &schema.GroupVersion{Group: r.group, Version: r.version} gv := r.gvr.AsGR()
crConfig.GroupVersion = &gv
crConfig.APIPath = "/apis" crConfig.APIPath = "/apis"
if len(r.group) == 0 { if len(r.gvr.ToG()) == 0 {
crConfig.APIPath = "/api" crConfig.APIPath = "/api"
} }
codec, _ := r.codec() codec, _ := r.codec()
@ -99,7 +100,7 @@ func (r *Resource) getClient() (*rest.RESTClient, error) {
func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) { func (r *Resource) codec() (serializer.CodecFactory, runtime.ParameterCodec) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
gv := schema.GroupVersion{Group: r.group, Version: r.version} gv := r.gvr.AsGR()
metav1.AddToGroupVersion(scheme, gv) metav1.AddToGroupVersion(scheme, gv)
scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{})

View File

@ -3,14 +3,12 @@ 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"
genericprinters "k8s.io/cli-runtime/pkg/printers" genericprinters "k8s.io/cli-runtime/pkg/printers"
@ -102,9 +100,7 @@ func (*Base) NumCols(n string) map[string]bool {
} }
// ExtFields returns extended fields in relation to headers. // ExtFields returns extended fields in relation to headers.
func (*Base) ExtFields() Properties { func (*Base) ExtFields(*TypeMeta) {}
return Properties{}
}
// Get a resource by name // Get a resource by name
func (b *Base) Get(path string) (Columnar, error) { func (b *Base) Get(path string) (Columnar, error) {
@ -133,30 +129,13 @@ 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(gvr, pa string) (string, error) {
mapping, err := k8s.RestMapping.Find(kind)
if err == nil {
return b.doDescribe(pa, mapping)
}
resource, ok := b.Resource.(*k8s.Resource)
if !ok {
log.Debug().Msgf("resource not a (*k8s.Resource) and %s", err)
return "", fmt.Errorf("resource not a (*k8s.Resource) and %s", err)
}
g, v, n := resource.GetInfo()
mapper := k8s.RestMapper{Connection: b.Connection} mapper := k8s.RestMapper{Connection: b.Connection}
var e error mapping, err := mapper.ResourceFor(k8s.GVR(gvr).ResName())
mapping, e = mapper.ResourceFor(fmt.Sprintf("%s.%s.%s", n, v, g)) if err != nil {
if e != nil { log.Debug().Err(err).Msgf("Unable to find mapper for %s %s", gvr, pa)
log.Debug().Err(e).Msgf("Unable to find mapper for %s %s", kind, pa) return "", err
return "", e
} }
return b.doDescribe(pa, mapping)
}
func (b *Base) doDescribe(pa string, mapping *meta.RESTMapping) (string, error) {
ns, n := Namespaced(pa) 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 {

View File

@ -89,24 +89,33 @@ func (r *CustomResourceDefinition) Fields(ns string) Row {
} }
// ExtFields returns extended fields. // ExtFields returns extended fields.
func (r *CustomResourceDefinition) ExtFields() Properties { func (r *CustomResourceDefinition) ExtFields(m *TypeMeta) {
var ( i := r.instance
pp = Properties{} spec, ok := i.Object["spec"].(map[string]interface{})
i = r.instance if !ok {
) return
if spec, ok := i.Object["spec"].(map[string]interface{}); ok {
if meta, ok := i.Object["metadata"].(map[string]interface{}); ok {
pp["name"] = meta["name"]
}
pp["group"], pp["version"] = spec["group"], spec["version"]
if names, ok := spec["names"].(map[string]interface{}); ok {
pp["kind"] = names["kind"]
pp["singular"], pp["plural"] = names["singular"], names["plural"]
pp["aliases"] = names["shortNames"]
}
} }
return pp if meta, ok := i.Object["metadata"].(map[string]interface{}); ok {
m.Name = meta["name"].(string)
}
m.Group, m.Version = spec["group"].(string), spec["version"].(string)
m.Namespaced = isNamespaced(spec["scope"].(string))
names, ok := spec["names"].(map[string]interface{})
if !ok {
return
}
m.Kind = names["kind"].(string)
m.Singular, m.Plural = names["singular"].(string), names["plural"].(string)
if names["shortNames"] != nil {
for _, s := range names["shortNames"].([]interface{}) {
m.ShortNames = append(m.ShortNames, s.(string))
}
} else {
m.ShortNames = nil
}
}
func isNamespaced(scope string) bool {
return scope == "Namespaced"
} }

View File

@ -43,12 +43,6 @@ func TestCRDFields(t *testing.T) {
assert.Equal(t, "fred", r[0]) assert.Equal(t, "fred", r[0])
} }
func TestCRDExtFields(t *testing.T) {
p := newCRDFull().ExtFields()
assert.Equal(t, 7, len(p))
}
func TestCRDFieldsAllNS(t *testing.T) { func TestCRDFieldsAllNS(t *testing.T) {
r := newCRD().Fields(resource.AllNamespaces) r := newCRD().Fields(resource.AllNamespaces)

View File

@ -17,35 +17,36 @@ import (
type Custom struct { type Custom struct {
*Base *Base
instance *metav1beta1.TableRow instance *metav1beta1.TableRow
group, version, name string gvr k8s.GVR
headers Row headers Row
} }
// NewCustomList returns a new resource list. // NewCustomList returns a new resource list.
func NewCustomList(c k8s.Connection, ns, group, version, name string) List { func NewCustomList(c k8s.Connection, namespaced bool, ns, gvr string) List {
if !c.IsNamespaced(name) { if !namespaced {
ns = NotNamespaced ns = NotNamespaced
} }
g := k8s.GVR(gvr)
return NewList( return NewList(
ns, ns,
name, g.ToR(),
NewCustom(c, group, version, name), AllVerbsAccess|DescribeAccess, NewCustom(c, g), AllVerbsAccess|DescribeAccess,
) )
} }
// NewCustom instantiates a new Kubernetes Resource. // NewCustom instantiates a new Kubernetes Resource.
func NewCustom(c k8s.Connection, group, version, name string) *Custom { func NewCustom(c k8s.Connection, gvr k8s.GVR) *Custom {
cr := &Custom{Base: &Base{Connection: c, Resource: k8s.NewResource(c, group, version, name)}} cr := &Custom{Base: &Base{Connection: c, Resource: k8s.NewResource(c, gvr)}}
cr.Factory = cr cr.Factory = cr
cr.group, cr.version, cr.name = group, version, name cr.gvr = gvr
return cr return cr
} }
// New builds a new Custom instance from a k8s resource. // New builds a new Custom instance from a k8s resource.
func (r *Custom) New(i interface{}) Columnar { func (r *Custom) New(i interface{}) Columnar {
cr := NewCustom(r.Connection, "", "", "") cr := NewCustom(r.Connection, "")
switch instance := i.(type) { switch instance := i.(type) {
case *metav1beta1.TableRow: case *metav1beta1.TableRow:
cr.instance = instance cr.instance = instance
@ -66,7 +67,7 @@ func (r *Custom) New(i interface{}) Columnar {
} }
name := meta["name"].(string) name := meta["name"].(string)
cr.path = path.Join(ns, name) cr.path = path.Join(ns, name)
cr.group, cr.version, cr.name = obj["kind"].(string), obj["apiVersion"].(string), name cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), name)
return cr return cr
} }

View File

@ -130,7 +130,7 @@ func k8sCustomRow() *metav1beta1.TableRow {
func newCustom() resource.Columnar { func newCustom() resource.Columnar {
mc := NewMockConnection() mc := NewMockConnection()
return resource.NewCustom(mc, "g", "v1", "fred").New(k8sCustomRow()) return resource.NewCustom(mc, "g/v1/fred").New(k8sCustomRow())
} }
func customYaml() string { func customYaml() string {

View File

@ -48,8 +48,17 @@ type (
// RowEvents tracks resource update events. // RowEvents tracks resource update events.
RowEvents map[string]*RowEvent RowEvents map[string]*RowEvent
// Properties a collection of extra properties on a K8s resource. // TypeMeta represents resource type meta data.
Properties map[string]interface{} TypeMeta struct {
Name string
Namespaced bool
Group string
Version string
Kind string
Singular string
Plural string
ShortNames []string
}
// TableData tracks a K8s resource for tabular display. // TableData tracks a K8s resource for tabular display.
TableData struct { TableData struct {
@ -81,7 +90,7 @@ type (
Columnar interface { Columnar interface {
Header(ns string) Row Header(ns string) Row
Fields(ns string) Row Fields(ns string) Row
ExtFields() Properties ExtFields(*TypeMeta)
Name() string Name() string
SetPodMetrics(*mv1beta1.PodMetrics) SetPodMetrics(*mv1beta1.PodMetrics)
SetNodeMetrics(*mv1beta1.NodeMetrics) SetNodeMetrics(*mv1beta1.NodeMetrics)
@ -102,7 +111,7 @@ type (
Get(path string) (Columnar, error) Get(path string) (Columnar, error)
List(ns string) (Columnars, error) List(ns string) (Columnars, error)
Delete(path string, cascade, force bool) error Delete(path string, cascade, force bool) error
Describe(kind, pa string) (string, error) Describe(gvr, pa string) (string, error)
Marshal(pa string) (string, error) Marshal(pa string) (string, error)
Header(ns string) Row Header(ns string) Row
NumCols(ns string) map[string]bool NumCols(ns string) map[string]bool

View File

@ -123,7 +123,7 @@ func stsHeader() resource.Row {
} }
func stsYaml() string { func stsYaml() string {
return `apiVersion: v1 return `apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
metadata: metadata:
creationTimestamp: "2018-12-14T17:36:43Z" creationTimestamp: "2018-12-14T17:36:43Z"

View File

@ -30,19 +30,21 @@ func NewKeyAction(d string, a ActionHandler, display bool) KeyAction {
// Hints returns a collection of hints. // Hints returns a collection of hints.
func (a KeyActions) Hints() Hints { func (a KeyActions) Hints() Hints {
kk := make([]int, 0, len(a)) kk := make([]int, 0, len(a))
for k, v := range a { for k := range a {
if v.Visible { kk = append(kk, int(k))
kk = append(kk, int(k))
}
} }
sort.Ints(kk) sort.Ints(kk)
hh := make(Hints, 0, len(kk)) hh := make(Hints, 0, len(kk))
for _, k := range kk { for _, k := range kk {
if name, ok := tcell.KeyNames[tcell.Key(k)]; ok { if name, ok := tcell.KeyNames[tcell.Key(k)]; ok {
hh = append(hh, Hint{ hh = append(hh,
Mnemonic: name, Hint{
Description: a[tcell.Key(k)].Description}) Mnemonic: name,
Description: a[tcell.Key(k)].Description,
Visible: a[tcell.Key(k)].Visible,
},
)
} else { } else {
log.Error().Msgf("Unable to locate KeyName for %#v", string(k)) log.Error().Msgf("Unable to locate KeyName for %#v", string(k))
} }

View File

@ -7,7 +7,6 @@ import (
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
) )
// Igniter represents an initializable view. // Igniter represents an initializable view.
@ -125,8 +124,8 @@ func (a *App) GetCmd() string {
return a.cmdBuff.String() return a.cmdBuff.String()
} }
// GetCmdBuff returns a cmd buffer. // CmdBuff returns a cmd buffer.
func (a *App) GetCmdBuff() *CmdBuff { func (a *App) CmdBuff() *CmdBuff {
return a.cmdBuff return a.cmdBuff
} }
@ -177,7 +176,6 @@ func (a *App) keyboard(evt *tcell.EventKey) *tcell.EventKey {
} }
if a, ok := a.actions[key]; ok { if a, ok := a.actions[key]; ok {
log.Debug().Msgf(">> App handled key: %s", tcell.KeyNames[key])
return a.Action(evt) return a.Action(evt)
} }

View File

@ -61,11 +61,13 @@ func (v *CmdView) write(s string) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Event Listener protocol... // Event Listener protocol...
func (v *CmdView) changed(s string) { // BufferChanged indicates the buffer was changed.
func (v *CmdView) BufferChanged(s string) {
v.update(s) v.update(s)
} }
func (v *CmdView) active(f bool) { // BufferActive indicates the buff activity changed.
func (v *CmdView) BufferActive(f bool) {
v.activated = f v.activated = f
if f { if f {
v.SetBorder(true) v.SetBorder(true)

View File

@ -3,9 +3,13 @@ package ui
const maxBuff = 10 const maxBuff = 10
type ( type (
buffWatcher interface { // BuffWatcher represents a command buffer listener.
changed(s string) BuffWatcher interface {
active(state bool) // Changed indicates the buffer was changed.
BufferChanged(s string)
// Active indicates the buff activity changed.
BufferActive(state bool)
} }
// CmdBuff represents user command input. // CmdBuff represents user command input.
@ -13,7 +17,7 @@ type (
buff []rune buff []rune
hotKey rune hotKey rune
active bool active bool
listeners []buffWatcher listeners []BuffWatcher
} }
) )
@ -22,7 +26,7 @@ func NewCmdBuff(key rune) *CmdBuff {
return &CmdBuff{ return &CmdBuff{
hotKey: key, hotKey: key,
buff: make([]rune, 0, maxBuff), buff: make([]rune, 0, maxBuff),
listeners: []buffWatcher{}, listeners: []BuffWatcher{},
} }
} }
@ -88,18 +92,18 @@ func (c *CmdBuff) Empty() bool {
// Event Listeners... // Event Listeners...
// AddListener registers a cmd buffer listener. // AddListener registers a cmd buffer listener.
func (c *CmdBuff) AddListener(w ...buffWatcher) { func (c *CmdBuff) AddListener(w ...BuffWatcher) {
c.listeners = append(c.listeners, w...) c.listeners = append(c.listeners, w...)
} }
func (c *CmdBuff) fireChanged() { func (c *CmdBuff) fireChanged() {
for _, l := range c.listeners { for _, l := range c.listeners {
l.changed(c.String()) l.BufferChanged(c.String())
} }
} }
func (c *CmdBuff) fireActive(b bool) { func (c *CmdBuff) fireActive(b bool) {
for _, l := range c.listeners { for _, l := range c.listeners {
l.active(b) l.BufferActive(b)
} }
} }

View File

@ -12,11 +12,11 @@ type testListener struct {
inact int inact int
} }
func (l *testListener) changed(s string) { func (l *testListener) BufferChanged(s string) {
l.text = s l.text = s
} }
func (l *testListener) active(s bool) { func (l *testListener) BufferActive(s bool) {
if s { if s {
l.act++ l.act++
return return

View File

@ -23,6 +23,6 @@ func TestCmdInCmdMode(t *testing.T) {
assert.Equal(t, "T> blee!\n", v.GetText(false)) assert.Equal(t, "T> blee!\n", v.GetText(false))
assert.False(t, v.InCmdMode()) assert.False(t, v.InCmdMode())
v.active(true) v.BufferActive(true)
assert.True(t, v.InCmdMode()) assert.True(t, v.InCmdMode())
} }

View File

@ -8,7 +8,9 @@ import (
type ( type (
// Hint represents keyboard mnemonic. // Hint represents keyboard mnemonic.
Hint struct { Hint struct {
Mnemonic, Description string Mnemonic string
Description string
Visible bool
} }
// Hints a collection of keyboard mnemonics. // Hints a collection of keyboard mnemonics.
Hints []Hint Hints []Hint

View File

@ -13,14 +13,14 @@ import (
// IndicatorView represents a status indicator. // IndicatorView represents a status indicator.
type IndicatorView struct { type IndicatorView struct {
*tview.TextView *tview.TextView
app *App app *App
styles *config.Styles styles *config.Styles
permanent string permanent string
cancel context.CancelFunc
cancel context.CancelFunc
} }
// NewIndicatorView returns a new logo. // NewIndicatorView returns a new status indicator.
func NewIndicatorView(app *App, styles *config.Styles) *IndicatorView { func NewIndicatorView(app *App, styles *config.Styles) *IndicatorView {
v := IndicatorView{ v := IndicatorView{
TextView: tview.NewTextView(), TextView: tview.NewTextView(),

View File

@ -69,6 +69,9 @@ func (v *MenuView) buildMenuTable(hh Hints) [][]string {
firstCmd := true firstCmd := true
maxKeys := make([]int, colCount+1) maxKeys := make([]int, colCount+1)
for _, h := range hh { for _, h := range hh {
if !h.Visible {
continue
}
isDigit := menuRX.MatchString(h.Mnemonic) isDigit := menuRX.MatchString(h.Mnemonic)
if !isDigit && firstCmd { if !isDigit && firstCmd {
row, col, firstCmd = 0, col+1, false row, col, firstCmd = 0, col+1, false

View File

@ -12,9 +12,9 @@ func TestNewMenuView(t *testing.T) {
defaults, _ := config.NewStyles("") defaults, _ := config.NewStyles("")
v := NewMenuView(defaults) v := NewMenuView(defaults)
v.HydrateMenu(Hints{ v.HydrateMenu(Hints{
{"a", "bleeA"}, {"a", "bleeA", true},
{"b", "bleeB"}, {"b", "bleeB", true},
{"0", "zero"}, {"0", "zero", true},
}) })
assert.Equal(t, " [fuchsia:black:b]<0> [white:black:d]zero ", v.GetCell(0, 0).Text) assert.Equal(t, " [fuchsia:black:b]<0> [white:black:d]zero ", v.GetCell(0, 0).Text)
@ -35,9 +35,10 @@ func TestKeyActions(t *testing.T) {
tcell.Key(Key1): NewKeyAction("one", nil, false), tcell.Key(Key1): NewKeyAction("one", nil, false),
}, },
e: Hints{ e: Hints{
{"0", "zero"}, {"0", "zero", true},
{"a", "bleeA"}, {"1", "one", false},
{"b", "bleeB"}, {"a", "bleeA", true},
{"b", "bleeB", true},
}, },
}, },
} }

View File

@ -174,8 +174,8 @@ func (v *Table) GetSelectedItem() string {
func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key() key := evt.Key()
if key == tcell.KeyRune { if key == tcell.KeyRune {
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Add(evt.Rune()) v.SearchBuff().Add(evt.Rune())
v.ClearSelection() v.ClearSelection()
v.doUpdate(v.filtered()) v.doUpdate(v.filtered())
v.SelectFirstRow() v.SelectFirstRow()
@ -185,7 +185,6 @@ func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
} }
if a, ok := v.actions[key]; ok { if a, ok := v.actions[key]; ok {
log.Debug().Msgf(">> TableView handled %s", tcell.KeyNames[key])
return a.Action(evt) return a.Action(evt)
} }
@ -242,7 +241,7 @@ func (v *Table) Update(data resource.TableData) {
func (v *Table) doUpdate(data resource.TableData) { func (v *Table) doUpdate(data resource.TableData) {
v.activeNS = data.Namespace v.activeNS = data.Namespace
if v.activeNS == resource.AllNamespaces && v.activeNS != "*" { if v.activeNS == resource.AllNamespaces && v.activeNS != "*" {
v.actions[KeyShiftP] = NewKeyAction("Sort Namespace", v.SortColCmd(-2), true) v.actions[KeyShiftP] = NewKeyAction("Sort Namespace", v.SortColCmd(-2), false)
} else { } else {
delete(v.actions, KeyShiftP) delete(v.actions, KeyShiftP)
} }
@ -438,8 +437,8 @@ func (v *Table) KeyBindings() KeyActions {
return v.actions return v.actions
} }
// Cmd returns the associated command buffer. // SearchBuff returns the associated command buffer.
func (v *Table) Cmd() *CmdBuff { func (v *Table) SearchBuff() *CmdBuff {
return v.cmdBuff return v.cmdBuff
} }

View File

@ -3,7 +3,9 @@ package views
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -11,7 +13,7 @@ import (
const ( const (
aliasTitle = "Aliases" aliasTitle = "Aliases"
aliasTitleFmt = " [aqua::b]%s([fuchsia::b]%d[fuchsia::-])[aqua::-] " aliasTitleFmt = " [aqua::b]%s([fuchsia::b]%d[fuchsia::-][aqua::-]) "
) )
type aliasView struct { type aliasView struct {
@ -52,8 +54,8 @@ func (v *aliasView) registerActions() {
tcell.KeyEnter: ui.NewKeyAction("Goto", v.gotoCmd, true), tcell.KeyEnter: ui.NewKeyAction("Goto", v.gotoCmd, true),
tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false), tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false), ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false),
ui.KeyShiftR: ui.NewKeyAction("Sort Resources", v.SortColCmd(1), true), ui.KeyShiftR: ui.NewKeyAction("Sort Resources", v.SortColCmd(1), false),
ui.KeyShiftO: ui.NewKeyAction("Sort Groups", v.SortColCmd(2), true), ui.KeyShiftO: ui.NewKeyAction("Sort Groups", v.SortColCmd(2), false),
}) })
} }
@ -62,8 +64,8 @@ func (v *aliasView) getTitle() string {
} }
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().Empty() { if !v.SearchBuff().Empty() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }
@ -73,13 +75,15 @@ func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
func (v *aliasView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *aliasView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
r, _ := v.GetSelection() r, _ := v.GetSelection()
if r != 0 { if r != 0 {
return v.runCmd(evt) s := ui.TrimCell(v.Table, r, 1)
tokens := strings.Split(s, ",")
v.app.gotoResource(tokens[0], true)
return nil
} }
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
return v.activateCmd(evt) return v.activateCmd(evt)
} }
return evt return evt
} }
@ -88,8 +92,8 @@ func (v *aliasView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cancel() v.cancel()
} }
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Reset() v.SearchBuff().Reset()
} else { } else {
v.app.inject(v.current) v.app.inject(v.current)
} }
@ -97,32 +101,30 @@ func (v *aliasView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
func (v *aliasView) runCmd(evt *tcell.EventKey) *tcell.EventKey {
r, _ := v.GetSelection()
if r > 0 {
v.app.gotoResource(ui.TrimCell(v.Table, r, 0), true)
}
return nil
}
func (v *aliasView) hydrate() resource.TableData { func (v *aliasView) hydrate() resource.TableData {
cmds := make(map[string]*resCmd, 40)
aliasCmds(v.app.Conn(), cmds)
data := resource.TableData{ data := resource.TableData{
Header: resource.Row{"ALIAS", "RESOURCE", "APIGROUP"}, Header: resource.Row{"RESOURCE", "COMMAND", "APIGROUP"},
Rows: make(resource.RowEvents, len(cmds)), Rows: make(resource.RowEvents, len(aliases.Alias)),
Namespace: resource.NotNamespaced, Namespace: resource.NotNamespaced,
} }
for k := range cmds { aa := make(map[string][]string, len(aliases.Alias))
fields := resource.Row{ for alias, gvr := range aliases.Alias {
ui.Pad(k, 30), if _, ok := aa[gvr]; ok {
ui.Pad(cmds[k].gvr, 30), aa[gvr] = append(aa[gvr], alias)
ui.Pad(cmds[k].api, 30), } else {
aa[gvr] = []string{alias}
} }
data.Rows[k] = &resource.RowEvent{ }
for gvr, aliases := range aa {
g := k8s.GVR(gvr)
fields := resource.Row{
ui.Pad(g.ToR(), 30),
ui.Pad(strings.Join(aliases, ","), 70),
ui.Pad(g.ToG(), 30),
}
data.Rows[string(gvr)] = &resource.RowEvent{
Action: resource.New, Action: resource.New,
Fields: fields, Fields: fields,
Deltas: fields, Deltas: fields,

View File

@ -13,6 +13,6 @@ func TestAliasView(t *testing.T) {
v.Init(nil, "") v.Init(nil, "")
assert.Equal(t, 3, len(td.Header)) assert.Equal(t, 3, len(td.Header))
assert.Equal(t, 41, len(td.Rows)) assert.Equal(t, 15, len(td.Rows))
assert.Equal(t, "Aliases", v.getTitle()) assert.Equal(t, "Aliases", v.getTitle())
} }

View File

@ -54,6 +54,7 @@ type (
stopCh chan struct{} stopCh chan struct{}
forwarders map[string]forwarder forwarders map[string]forwarder
version string version string
showHeader bool
} }
) )
@ -76,11 +77,13 @@ func NewApp(cfg *config.Config) *appView {
func (a *appView) Init(version string, rate int) { func (a *appView) Init(version string, rate int) {
a.version = version a.version = version
a.CmdBuff().AddListener(a)
a.App.Init() a.App.Init()
a.AddActions(ui.KeyActions{ a.AddActions(ui.KeyActions{
tcell.KeyCtrlH: ui.NewKeyAction("ToggleHeader", a.toggleHeaderCmd, false),
ui.KeyHelp: ui.NewKeyAction("Help", a.helpCmd, false), ui.KeyHelp: ui.NewKeyAction("Help", a.helpCmd, false),
tcell.KeyCtrlA: ui.NewKeyAction("Aliases", a.aliasCmd, true), tcell.KeyCtrlA: ui.NewKeyAction("Aliases", a.aliasCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, false), tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, false),
}) })
@ -96,29 +99,60 @@ func (a *appView) Init(version string, rate int) {
} }
} }
header := tview.NewFlex()
{
header.SetDirection(tview.FlexColumn)
header.AddItem(a.clusterInfo(), 35, 1, false)
header.AddItem(a.Menu(), 0, 1, false)
header.AddItem(a.Logo(), 26, 1, false)
}
main := tview.NewFlex() main := tview.NewFlex()
main.SetDirection(tview.FlexRow) main.SetDirection(tview.FlexRow)
a.Main().AddPage("main", main, true, false)
a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
if !a.Config.K9s.GetHeadless() { main.AddItem(a.indicator(), 1, 1, false)
main.AddItem(header, 7, 1, false) // main.AddItem(a.Cmd(), 3, 1, false)
} else {
main.AddItem(a.indicator(), 1, 1, false)
}
main.AddItem(a.Cmd(), 3, 1, false)
main.AddItem(a.Frame(), 0, 10, true) main.AddItem(a.Frame(), 0, 10, true)
main.AddItem(a.Crumbs(), 2, 1, false) main.AddItem(a.Crumbs(), 2, 1, false)
main.AddItem(a.Flash(), 1, 1, false) main.AddItem(a.Flash(), 1, 1, false)
a.toggleHeader(!a.Config.K9s.GetHeadless())
}
a.Main().AddPage("main", main, true, false) // Changed indicates the buffer was changed.
a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true) func (a *appView) BufferChanged(s string) {}
// Active indicates the buff activity changed.
func (a *appView) BufferActive(state bool) {
flex, ok := a.Main().GetPrimitive("main").(*tview.Flex)
if !ok {
return
}
if state {
flex.AddItemAtIndex(1, a.Cmd(), 3, 1, false)
} else if flex.ItemAt(1) == a.Cmd() {
flex.RemoveItemAtIndex(1)
}
a.Draw()
}
func (a *appView) toggleHeader(flag bool) {
a.showHeader = flag
flex := a.Main().GetPrimitive("main").(*tview.Flex)
if a.showHeader {
flex.RemoveItemAtIndex(0)
flex.AddItemAtIndex(0, a.buildHeader(), 7, 1, false)
} else {
flex.RemoveItemAtIndex(0)
flex.AddItemAtIndex(0, a.indicator(), 1, 1, false)
a.refreshIndicator()
}
}
func (a *appView) buildHeader() tview.Primitive {
header := tview.NewFlex()
header.SetDirection(tview.FlexColumn)
if !a.showHeader {
return header
}
header.AddItem(a.clusterInfo(), 35, 1, false)
header.AddItem(a.Menu(), 0, 1, false)
header.AddItem(a.Logo(), 26, 1, false)
return header
} }
func (a *appView) clusterUpdater(ctx context.Context) { func (a *appView) clusterUpdater(ctx context.Context) {
@ -129,10 +163,11 @@ func (a *appView) clusterUpdater(ctx context.Context) {
return return
case <-time.After(clusterRefresh): case <-time.After(clusterRefresh):
a.QueueUpdateDraw(func() { a.QueueUpdateDraw(func() {
if a.Config.K9s.GetHeadless() { if !a.showHeader {
a.refreshIndicator() a.refreshIndicator()
} else {
a.clusterInfo().refresh()
} }
a.clusterInfo().refresh()
}) })
} }
} }
@ -273,6 +308,14 @@ func (a *appView) setIndicator(l ui.FlashLevel, msg string) {
a.Draw() a.Draw()
} }
func (a *appView) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey {
a.showHeader = !a.showHeader
a.toggleHeader(a.showHeader)
a.Draw()
return nil
}
func (a *appView) prevCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
if top, ok := a.command.previousCmd(); ok { if top, ok := a.command.previousCmd(); ok {
log.Debug().Msgf("Previous command %s", top) log.Debug().Msgf("Previous command %s", top)
@ -283,7 +326,7 @@ func (a *appView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
func (a *appView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.GetCmdBuff().IsActive() && !a.GetCmdBuff().Empty() { if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() {
a.gotoResource(a.GetCmd(), true) a.gotoResource(a.GetCmd(), true)
a.ResetCmd() a.ResetCmd()
return nil return nil
@ -294,9 +337,6 @@ func (a *appView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.InCmdMode() {
return evt
}
if _, ok := a.Frame().GetPrimitive("main").(*helpView); ok { if _, ok := a.Frame().GetPrimitive("main").(*helpView); ok {
return evt return evt
} }
@ -307,9 +347,6 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.InCmdMode() {
return evt
}
if _, ok := a.Frame().GetPrimitive("main").(*aliasView); ok { if _, ok := a.Frame().GetPrimitive("main").(*aliasView); ok {
return evt return evt
} }
@ -337,10 +374,8 @@ func (a *appView) inject(i ui.Igniter) {
} }
a.Frame().RemovePage("main") a.Frame().RemovePage("main")
var ctx context.Context var ctx context.Context
{ ctx, a.cancel = context.WithCancel(context.Background())
ctx, a.cancel = context.WithCancel(context.Background()) i.Init(ctx, a.Config.ActiveNamespace())
i.Init(ctx, a.Config.ActiveNamespace())
}
a.Frame().AddPage("main", i, true, true) a.Frame().AddPage("main", i, true, true)
a.SetFocus(i) a.SetFocus(i)
} }

View File

@ -40,7 +40,7 @@ type benchView struct {
app *appView app *appView
} }
func newBenchView(title string, app *appView, _ resource.List) resourceViewer { func newBenchView(title, gvr string, app *appView, _ resource.List) resourceViewer {
v := benchView{app: app} v := benchView{app: app}
v.masterDetail = newMasterDetail(benchTitle, "", app, v.backCmd) v.masterDetail = newMasterDetail(benchTitle, "", app, v.backCmd)
v.keyBindings() v.keyBindings()
@ -108,7 +108,7 @@ func (v *benchView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tce
} }
func (v *benchView) enterCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *benchView) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.masterPage().Cmd().IsActive() { if v.masterPage().SearchBuff().IsActive() {
return v.masterPage().filterCmd(evt) return v.masterPage().filterCmd(evt)
} }

View File

@ -1,8 +1,11 @@
package views package views
import ( import (
"fmt"
"regexp" "regexp"
"time"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -41,15 +44,18 @@ func (c *command) previousCmd() (string, bool) {
// DefaultCmd reset default command ie show pods. // DefaultCmd reset default command ie show pods.
func (c *command) defaultCmd() { func (c *command) defaultCmd() {
c.pushCmd(c.app.Config.ActiveView()) cmd := c.app.Config.ActiveView()
c.run(c.app.Config.ActiveView()) c.pushCmd(cmd)
if !c.run(cmd) {
log.Error().Err(fmt.Errorf("Unable to load command %s", cmd)).Msg("Command failed")
}
} }
// Helpers... // Helpers...
var policyMatcher = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`) var policyMatcher = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`)
func (c *command) isStdCmd(cmd string) bool { func (c *command) isCustCmd(cmd string) bool {
switch { switch {
case cmd == "q", cmd == "quit": case cmd == "q", cmd == "quit":
c.app.BailOut() c.app.BailOut()
@ -70,87 +76,72 @@ func (c *command) isStdCmd(cmd string) bool {
return false return false
} }
func (c *command) isAliasCmd(alias string, cmds map[string]resCmd) bool {
res, ok := cmds[alias]
if !ok {
return false
}
var r resource.List
if res.listFn != nil {
r = res.listFn(c.app.Conn(), resource.DefaultNamespace)
}
v := newResourceView(res.gvr, c.app, r)
if res.viewFn != nil {
v = res.viewFn(res.gvr, c.app, r)
}
if res.colorerFn != nil {
v.setColorerFn(res.colorerFn)
}
if res.enterFn != nil {
v.setEnterFn(res.enterFn)
}
if res.decorateFn != nil {
v.setDecorateFn(res.decorateFn)
}
const fmat = "Viewing resource %s..."
c.app.Flash().Infof(fmat, res.gvr)
log.Debug().Msgf("Running command %s", alias)
c.exec(alias, v)
return true
}
func (c *command) isCRDCmd(cmd string) bool {
crds := map[string]resCmd{}
res, ok := crds[cmd]
if !ok {
return false
}
name := res.plural
if name == "" {
name = res.singular
}
v := newResourceView(
res.gvr,
c.app,
resource.NewCustomList(c.app.Conn(), "", res.api, res.version, name),
)
v.setColorerFn(ui.DefaultColorer)
c.exec(cmd, v)
return true
}
// Exec the command by showing associated display. // Exec the command by showing associated display.
func (c *command) run(cmd string) bool { func (c *command) run(cmd string) bool {
if c.isStdCmd(cmd) { defer func(t time.Time) {
log.Debug().Msgf("RUN CMD Elapsed %v", time.Since(t))
}(time.Now())
if c.isCustCmd(cmd) {
return true return true
} }
cmds := make(map[string]resCmd, 30) vv := make(viewers, 200)
resourceViews(c.app.Conn(), cmds) resourceViews(c.app.Conn(), vv)
allCRDs(c.app.Conn(), cmds) allCRDs(c.app.Conn(), vv)
gvr, ok := aliases.Get(cmd)
a, ok := aliases[cmd]
if !ok { if !ok {
log.Error().Err(fmt.Errorf("Huh? `%s` command not found", cmd)).Msg("Command Failed")
c.app.Flash().Warnf("Huh? `%s` command not found", cmd) c.app.Flash().Warnf("Huh? `%s` command not found", cmd)
return false return false
} }
v, ok := vv[gvr]
return c.isAliasCmd(a, cmds) if !ok {
log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", cmd)).Msg("Viewer Failed")
c.app.Flash().Warnf("Huh? `%s` viewer not found", gvr)
return false
}
return c.execCmd(gvr, v)
} }
func (c *command) exec(cmd string, v ui.Igniter) { func (c *command) execCmd(gvr string, v viewer) bool {
if v == nil { log.Debug().Msgf("ExecCmd gvr %s", gvr)
return var r resource.List
if v.listFn != nil {
r = v.listFn(c.app.Conn(), resource.DefaultNamespace)
} }
c.app.Config.SetActiveView(cmd)
var view resourceViewer
if v.viewFn != nil {
view = v.viewFn(v.kind, gvr, c.app, r)
} else {
view = newResourceView(v.kind, gvr, c.app, r)
}
if v.colorerFn != nil {
view.setColorerFn(v.colorerFn)
}
if v.enterFn != nil {
view.setEnterFn(v.enterFn)
}
if v.decorateFn != nil {
view.setDecorateFn(v.decorateFn)
}
return c.exec(gvr, view)
}
func (c *command) exec(gvr string, v ui.Igniter) bool {
if v == nil {
log.Error().Err(fmt.Errorf("No igniter given for %s", gvr))
return false
}
g := k8s.GVR(gvr)
c.app.Flash().Infof("Viewing resource %s...", g.ToR())
log.Debug().Msgf("Running command %s", gvr)
c.app.Config.SetActiveView(g.ToR())
c.app.Config.Save() c.app.Config.Save()
c.app.inject(v) c.app.inject(v)
return true
} }

View File

@ -22,8 +22,8 @@ type containerView struct {
exitFn func() exitFn func()
} }
func newContainerView(ns string, app *appView, list resource.List, path string, exitFn func()) resourceViewer { func newContainerView(title string, app *appView, list resource.List, path string, exitFn func()) resourceViewer {
v := containerView{logResourceView: newLogResourceView(ns, app, list)} v := containerView{logResourceView: newLogResourceView(title, "", app, list)}
v.path = &path v.path = &path
v.envFn = v.k9sEnv v.envFn = v.k9sEnv
v.containerFn = v.selectedContainer v.containerFn = v.selectedContainer
@ -48,10 +48,10 @@ func (v *containerView) extraActions(aa ui.KeyActions) {
aa[ui.KeyS] = ui.NewKeyAction("Shell", v.shellCmd, true) aa[ui.KeyS] = ui.NewKeyAction("Shell", v.shellCmd, true)
aa[tcell.KeyEscape] = ui.NewKeyAction("Back", v.backCmd, false) aa[tcell.KeyEscape] = ui.NewKeyAction("Back", v.backCmd, false)
aa[ui.KeyP] = ui.NewKeyAction("Previous", v.backCmd, false) aa[ui.KeyP] = ui.NewKeyAction("Previous", v.backCmd, false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(6, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(6, false), false)
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(7, false), true) aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(7, false), false)
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(8, false), true) aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(8, false), false)
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(9, false), true) aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(9, false), false)
} }
func (v *containerView) k9sEnv() K9sEnv { func (v *containerView) k9sEnv() K9sEnv {

View File

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

View File

@ -12,7 +12,7 @@ func TestContextView(t *testing.T) {
l := resource.NewContextList(nil, "fred") l := resource.NewContextList(nil, "fred")
v := newContextView("blee", NewApp(config.NewConfig(ks{})), l).(*contextView) v := newContextView("blee", NewApp(config.NewConfig(ks{})), l).(*contextView)
assert.Equal(t, 3, len(v.hints())) assert.Equal(t, 10, len(v.hints()))
} }
func TestCleaner(t *testing.T) { func TestCleaner(t *testing.T) {

View File

@ -10,8 +10,8 @@ type cronJobView struct {
*resourceView *resourceView
} }
func newCronJobView(t string, app *appView, list resource.List) resourceViewer { func newCronJobView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := cronJobView{resourceView: newResourceView(t, app, list).(*resourceView)} v := cronJobView{resourceView: newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
return &v return &v

View File

@ -15,8 +15,8 @@ type deployView struct {
const scaleDialogKey = "scale" const scaleDialogKey = "scale"
func newDeployView(title string, app *appView, list resource.List) resourceViewer { func newDeployView(title, gvr string, app *appView, list resource.List) resourceViewer {
logResourceView := newLogResourceView(title, app, list) logResourceView := newLogResourceView(title, gvr, app, list)
v := deployView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)} v := deployView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -27,8 +27,8 @@ func newDeployView(title string, app *appView, list resource.List) resourceViewe
func (v *deployView) extraActions(aa ui.KeyActions) { func (v *deployView) extraActions(aa ui.KeyActions) {
v.logResourceView.extraActions(aa) v.logResourceView.extraActions(aa)
v.scalableResourceView.extraActions(aa) v.scalableResourceView.extraActions(aa)
aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), false)
} }
func (v *deployView) showPods(app *appView, _, res, sel string) { func (v *deployView) showPods(app *appView, _, res, sel string) {

View File

@ -12,5 +12,5 @@ func TestDeployView(t *testing.T) {
l := resource.NewDeploymentList(nil, "fred") l := resource.NewDeploymentList(nil, "fred")
v := newDeployView("blee", NewApp(config.NewConfig(ks{})), l).(*deployView) v := newDeployView("blee", NewApp(config.NewConfig(ks{})), l).(*deployView)
assert.Equal(t, 3, len(v.hints())) assert.Equal(t, 10, len(v.hints()))
} }

View File

@ -12,8 +12,8 @@ type daemonSetView struct {
*logResourceView *logResourceView
} }
func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer { func newDaemonSetView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := daemonSetView{newLogResourceView(t, app, list)} v := daemonSetView{newLogResourceView(title, gvr, app, list)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -22,8 +22,8 @@ func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer
func (v *daemonSetView) extraActions(aa ui.KeyActions) { func (v *daemonSetView) extraActions(aa ui.KeyActions) {
v.logResourceView.extraActions(aa) v.logResourceView.extraActions(aa)
aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), false)
} }
func (v *daemonSetView) showPods(app *appView, _, res, sel string) { func (v *daemonSetView) showPods(app *appView, _, res, sel string) {

View File

@ -12,5 +12,5 @@ func TestDaemonSetView(t *testing.T) {
l := resource.NewDaemonSetList(nil, "fred") l := resource.NewDaemonSetList(nil, "fred")
v := newDaemonSetView("blee", NewApp(config.NewConfig(ks{})), l).(*daemonSetView) v := newDaemonSetView("blee", NewApp(config.NewConfig(ks{})), l).(*daemonSetView)
assert.Equal(t, 3, len(v.hints())) assert.Equal(t, 10, len(v.hints()))
} }

View File

@ -34,7 +34,7 @@ type dumpView struct {
cancel context.CancelFunc cancel context.CancelFunc
} }
func newDumpView(_ string, app *appView, _ resource.List) resourceViewer { func newDumpView(_, _ string, app *appView, _ resource.List) resourceViewer {
v := dumpView{ v := dumpView{
Pages: tview.NewPages(), Pages: tview.NewPages(),
app: app, app: app,
@ -112,7 +112,7 @@ func (v *dumpView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcel
func (v *dumpView) enterCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *dumpView) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msg("Dump enter!") log.Debug().Msg("Dump enter!")
tv := v.getTV() tv := v.getTV()
if tv.Cmd().IsActive() { if tv.SearchBuff().IsActive() {
return tv.filterCmd(evt) return tv.filterCmd(evt)
} }
sel := tv.GetSelectedItem() sel := tv.GetSelectedItem()

View File

@ -33,7 +33,7 @@ type forwardView struct {
var _ resourceViewer = &forwardView{} var _ resourceViewer = &forwardView{}
func newForwardView(ns string, app *appView, list resource.List) resourceViewer { func newForwardView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := forwardView{ v := forwardView{
Pages: tview.NewPages(), Pages: tview.NewPages(),
app: app, app: app,
@ -107,8 +107,8 @@ func (v *forwardView) registerActions() {
tcell.KeyCtrlD: ui.NewKeyAction("Delete", v.deleteCmd, true), tcell.KeyCtrlD: ui.NewKeyAction("Delete", v.deleteCmd, true),
ui.KeySlash: ui.NewKeyAction("Filter", tv.activateCmd, false), ui.KeySlash: ui.NewKeyAction("Filter", tv.activateCmd, false),
ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false), ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false),
ui.KeyShiftP: ui.NewKeyAction("Sort Ports", v.sortColCmd(2, true), true), ui.KeyShiftP: ui.NewKeyAction("Sort Ports", v.sortColCmd(2, true), false),
ui.KeyShiftU: ui.NewKeyAction("Sort URL", v.sortColCmd(4, true), true), ui.KeyShiftU: ui.NewKeyAction("Sort URL", v.sortColCmd(4, true), false),
}) })
} }
@ -210,8 +210,8 @@ func (v *forwardView) getSelectedItem() string {
func (v *forwardView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *forwardView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
tv := v.getTV() tv := v.getTV()
if !tv.Cmd().Empty() { if !tv.SearchBuff().Empty() {
tv.Cmd().Reset() tv.SearchBuff().Reset()
return nil return nil
} }
@ -243,8 +243,8 @@ func (v *forwardView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
tv := v.getTV() tv := v.getTV()
if tv.Cmd().IsActive() { if tv.SearchBuff().IsActive() {
tv.Cmd().Reset() tv.SearchBuff().Reset()
} else { } else {
v.app.inject(v.app.Frame().GetPrimitive("main").(ui.Igniter)) v.app.inject(v.app.Frame().GetPrimitive("main").(ui.Igniter))
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"runtime" "runtime"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
@ -80,35 +81,95 @@ func (v *helpView) Init(_ context.Context, _ string) {
func (v *helpView) showHelp() ui.Hints { func (v *helpView) showHelp() ui.Hints {
return ui.Hints{ return ui.Hints{
{"?", "Help"}, {
{"Ctrl-a", "Aliases view"}, Mnemonic: "?",
Description: "Help",
},
{
Mnemonic: "Ctrl-a",
Description: "Aliases",
},
} }
} }
func (v *helpView) showNav() ui.Hints { func (v *helpView) showNav() ui.Hints {
return ui.Hints{ return ui.Hints{
{"g", "Goto Top"}, {
{"Shift-g", "Goto Bottom"}, Mnemonic: "g",
{"Ctrl-b", "Page Down"}, Description: "Goto Top",
{"Ctrl-f", "Page Up"}, },
{"h", "Left"}, {
{"l", "Right"}, Mnemonic: "Shift-g",
{"k", "Up"}, Description: "Goto Bottom",
{"j", "Down"}, },
{
Mnemonic: "Ctrl-b",
Description: "Page Down"},
{
Mnemonic: "Ctrl-f",
Description: "Page Up",
},
{
Mnemonic: "h",
Description: "Left",
},
{
Mnemonic: "l",
Description: "Right",
},
{
Mnemonic: "k",
Description: "Up",
},
{
Mnemonic: "j",
Description: "Down",
},
} }
} }
func (v *helpView) showGeneral() ui.Hints { func (v *helpView) showGeneral() ui.Hints {
return ui.Hints{ return ui.Hints{
{":cmd", "Command mode"}, {
{"/term", "Filter mode"}, Mnemonic: ":cmd",
{"esc", "Clear filter"}, Description: "Command mode",
{"tab", "Next term match"}, },
{"backtab", "Previous term match"}, {
{"Ctrl-r", "Refresh"}, Mnemonic: "/term",
{"Shift-i", "Invert Sort"}, Description: "Filter mode",
{"p", "Previous resource view"}, },
{":q", "Quit"}, {
Mnemonic: "esc",
Description: "Clear filter",
},
{
Mnemonic: "tab",
Description: "Next Field",
},
{
Mnemonic: "backtab",
Description: "Previous Field",
},
{
Mnemonic: "Ctrl-r",
Description: "Refresh",
},
{
Mnemonic: "Ctrl-h",
Description: "Toggle Header",
},
{
Mnemonic: "Shift-i",
Description: "Invert Sort",
},
{
Mnemonic: "p",
Description: "Previous View",
},
{
Mnemonic: ":q",
Description: "Quit",
},
} }
} }
@ -127,24 +188,30 @@ func (v *helpView) resetTitle() {
func (v *helpView) build(hh ui.Hints) { func (v *helpView) build(hh ui.Hints) {
v.Clear() v.Clear()
sort.Sort(hh) sort.Sort(hh)
v.addSection(0, 0, "Resource", hh) v.addSection(0, 0, "RESOURCE", hh)
v.addSection(0, 4, "General", v.showGeneral()) v.addSection(0, 4, "GENERAL", v.showGeneral())
v.addSection(0, 6, "Navigation", v.showNav()) v.addSection(0, 6, "NAVIGATION", v.showNav())
v.addSection(0, 8, "Help", v.showHelp()) v.addSection(0, 8, "HELP", v.showHelp())
} }
func (v *helpView) addSection(r, c int, title string, hh ui.Hints) { func (v *helpView) addSection(r, c int, title string, hh ui.Hints) {
row := r row := r
cell := tview.NewTableCell(title) cell := tview.NewTableCell(title)
cell.SetTextColor(tcell.ColorWhite) cell.SetTextColor(tcell.ColorGreen)
cell.SetAttributes(tcell.AttrBold) cell.SetAttributes(tcell.AttrBold)
v.SetCell(r, c, cell) cell.SetExpansion(2)
cell.SetAlign(tview.AlignLeft)
v.SetCell(r, c+1, cell)
row++ row++
for _, h := range hh { for _, h := range hh {
col := c col := c
cell := tview.NewTableCell(toMnemonic(h.Mnemonic)) cell := tview.NewTableCell(toMnemonic(h.Mnemonic))
cell.SetTextColor(tcell.ColorDodgerBlue) if _, err := strconv.Atoi(h.Mnemonic); err != nil {
cell.SetTextColor(tcell.ColorDodgerBlue)
} else {
cell.SetTextColor(tcell.ColorFuchsia)
}
cell.SetAttributes(tcell.AttrBold) cell.SetAttributes(tcell.AttrBold)
cell.SetAlign(tview.AlignRight) cell.SetAlign(tview.AlignRight)
v.SetCell(row, col, cell) v.SetCell(row, col, cell)

View File

@ -42,7 +42,7 @@ func TestNewHelpView(t *testing.T) {
cfg := config.NewConfig(ks{}) cfg := config.NewConfig(ks{})
a := NewApp(cfg) a := NewApp(cfg)
v := newHelpView(a, nil, ui.Hints{{"blee", "duh"}}) v := newHelpView(a, nil, ui.Hints{{Mnemonic: "blee", Description: "duh"}})
v.Init(nil, "") v.Init(nil, "")
assert.Equal(t, "<blee>", v.GetCell(1, 0).Text) assert.Equal(t, "<blee>", v.GetCell(1, 0).Text)

View File

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

View File

@ -16,9 +16,9 @@ type (
} }
) )
func newLogResourceView(ns string, app *appView, list resource.List) *logResourceView { func newLogResourceView(title, gvr string, app *appView, list resource.List) *logResourceView {
v := logResourceView{ v := logResourceView{
resourceView: newResourceView(ns, app, list).(*resourceView), resourceView: newResourceView(title, gvr, app, list).(*resourceView),
} }
v.AddPage("logs", newLogsView(list.GetName(), app, &v), true, false) v.AddPage("logs", newLogsView(list.GetName(), app, &v), true, false)

View File

@ -10,8 +10,8 @@ type nodeView struct {
*resourceView *resourceView
} }
func newNodeView(t string, app *appView, list resource.List) resourceViewer { func newNodeView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := nodeView{newResourceView(t, app, list).(*resourceView)} v := nodeView{newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -19,10 +19,10 @@ func newNodeView(t string, app *appView, list resource.List) resourceViewer {
} }
func (v *nodeView) extraActions(aa ui.KeyActions) { func (v *nodeView) extraActions(aa ui.KeyActions) {
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(7, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(7, false), false)
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(8, false), true) aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(8, false), false)
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(9, false), true) aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(9, false), false)
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(10, false), true) aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(10, false), false)
} }
func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -50,7 +50,7 @@ func showPods(app *appView, ns, labelSel, fieldSel string, a ui.ActionHandler) {
list.SetLabelSelector(labelSel) list.SetLabelSelector(labelSel)
list.SetFieldSelector(fieldSel) list.SetFieldSelector(fieldSel)
pv := newPodView("Pods", app, list) pv := newPodView("Pods", "v1/pods", app, list)
pv.setColorerFn(podColorer) pv.setColorerFn(podColorer)
// pv.setExtraActionsFn(func(aa ui.KeyActions) { // pv.setExtraActionsFn(func(aa ui.KeyActions) {
pv.masterPage().SetActions(ui.KeyActions{ pv.masterPage().SetActions(ui.KeyActions{

View File

@ -21,8 +21,8 @@ type namespaceView struct {
*resourceView *resourceView
} }
func newNamespaceView(t string, app *appView, list resource.List) resourceViewer { func newNamespaceView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := namespaceView{newResourceView(t, app, list).(*resourceView)} v := namespaceView{newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.masterPage().SetSelectedFn(v.cleanser) v.masterPage().SetSelectedFn(v.cleanser)
v.decorateFn = v.decorate v.decorateFn = v.decorate

View File

@ -32,8 +32,8 @@ type loggable interface {
switchPage(n string) switchPage(n string)
} }
func newPodView(t string, app *appView, list resource.List) resourceViewer { func newPodView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := podView{resourceView: newResourceView(t, app, list).(*resourceView)} v := podView{resourceView: newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.listContainers v.enterFn = v.listContainers
@ -56,15 +56,15 @@ func (v *podView) extraActions(aa ui.KeyActions) {
aa[ui.KeyL] = ui.NewKeyAction("Logs", v.logsCmd, true) aa[ui.KeyL] = ui.NewKeyAction("Logs", v.logsCmd, true)
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", v.prevLogsCmd, true) aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", v.prevLogsCmd, true)
aa[ui.KeyShiftR] = ui.NewKeyAction("Sort Ready", v.sortColCmd(1, false), true) aa[ui.KeyShiftR] = ui.NewKeyAction("Sort Ready", v.sortColCmd(1, false), false)
aa[ui.KeyShiftS] = ui.NewKeyAction("Sort Status", v.sortColCmd(2, true), true) aa[ui.KeyShiftS] = ui.NewKeyAction("Sort Status", v.sortColCmd(2, true), false)
aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Restart", v.sortColCmd(3, false), true) aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Restart", v.sortColCmd(3, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(4, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", v.sortColCmd(4, false), false)
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(5, false), true) aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", v.sortColCmd(5, false), false)
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(6, false), true) aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", v.sortColCmd(6, false), false)
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(7, false), true) aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", v.sortColCmd(7, false), false)
aa[ui.KeyShiftD] = ui.NewKeyAction("Sort IP", v.sortColCmd(8, true), true) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort IP", v.sortColCmd(8, true), false)
aa[ui.KeyShiftO] = ui.NewKeyAction("Sort Node", v.sortColCmd(9, true), true) aa[ui.KeyShiftO] = ui.NewKeyAction("Sort Node", v.sortColCmd(9, true), false)
} }
func (v *podView) listContainers(app *appView, _, res, sel string) { func (v *podView) listContainers(app *appView, _, res, sel string) {

View File

@ -76,10 +76,10 @@ func (v *policyView) bindKeys() {
tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false), tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false), ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false),
ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false), ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false),
ui.KeyShiftS: ui.NewKeyAction("Sort Namespace", v.SortColCmd(0), true), ui.KeyShiftS: ui.NewKeyAction("Sort Namespace", v.SortColCmd(0), false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", v.SortColCmd(1), true), ui.KeyShiftN: ui.NewKeyAction("Sort Name", v.SortColCmd(1), false),
ui.KeyShiftO: ui.NewKeyAction("Sort Group", v.SortColCmd(2), true), ui.KeyShiftO: ui.NewKeyAction("Sort Group", v.SortColCmd(2), false),
ui.KeyShiftB: ui.NewKeyAction("Sort Binding", v.SortColCmd(3), true), ui.KeyShiftB: ui.NewKeyAction("Sort Binding", v.SortColCmd(3), false),
}) })
} }
@ -96,8 +96,8 @@ func (v *policyView) refresh() {
} }
func (v *policyView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *policyView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().Empty() { if !v.SearchBuff().Empty() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }
@ -109,8 +109,8 @@ func (v *policyView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cancel() v.cancel()
} }
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }

View File

@ -125,7 +125,7 @@ func (v *rbacView) bindKeys() {
tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false), tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false), ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false),
ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false), ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false),
ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", v.SortColCmd(1), true), ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", v.SortColCmd(1), false),
}) })
} }
@ -142,8 +142,8 @@ func (v *rbacView) refresh() {
} }
func (v *rbacView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *rbacView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().Empty() { if !v.SearchBuff().Empty() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }
@ -155,8 +155,8 @@ func (v *rbacView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cancel() v.cancel()
} }
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }

View File

@ -1,10 +1,10 @@
package views package views
import ( import (
"fmt"
"path"
"strings" "strings"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
@ -13,22 +13,14 @@ import (
) )
type ( type (
viewFn func(ns string, app *appView, list resource.List) resourceViewer viewFn func(title, gvr string, app *appView, list resource.List) resourceViewer
listFn func(c resource.Connection, ns string) resource.List listFn func(c resource.Connection, ns string) resource.List
enterFn func(app *appView, ns, resource, selection string) enterFn func(app *appView, ns, resource, selection string)
decorateFn func(resource.TableData) resource.TableData decorateFn func(resource.TableData) resource.TableData
crdCmd struct { viewer struct {
api string
version string
plural string
singular string
}
resCmd struct {
crdCmd
gvr string gvr string
kind string
namespaced bool namespaced bool
verbs metav1.Verbs verbs metav1.Verbs
viewFn viewFn viewFn viewFn
@ -38,51 +30,25 @@ type (
decorateFn decorateFn decorateFn decorateFn
} }
AliasConfig struct { viewers map[string]viewer
Aliases map[string]string `yaml:"aliases"`
}
) )
var DefaultAliasConfig = AliasConfig{ func aliasCmds(c k8s.Connection, vv viewers) {
Aliases: map[string]string{ resourceViews(c, vv)
"Deployment": "dp",
"Secret": "sec",
"Jobs": "jo",
"ClusterRoles": "cr",
"ClusterRoleBindings": "crb",
"RoleBindings": "rb",
"Roles": "ro",
"NetworkPolicies": "np",
"Contexts": "ctx",
"Users": "usr",
"Groups": "grp",
"PortForward": "pf",
"Benchmark": "be",
"ScreenDumps": "sd",
},
}
func aliasCmds(c k8s.Connection, m map[string]resCmd) {
resourceViews(c, m)
if c != nil { if c != nil {
allCRDs(c, m) allCRDs(c, vv)
} }
} }
func listFunc(l resource.List) viewFn { func listFunc(l resource.List) viewFn {
return func(ns string, app *appView, list resource.List) resourceViewer { return func(title, gvr string, app *appView, list resource.List) resourceViewer {
return newResourceView( return newResourceView(title, gvr, app, l)
ns,
app,
l,
)
} }
} }
func allCRDs(c k8s.Connection, m map[string]resCmd) { var aliases = config.NewAliases()
func allCRDs(c k8s.Connection, vv viewers) {
crds, err := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces). crds, err := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
Resource(). Resource().
List(resource.AllNamespaces) List(resource.AllNamespaces)
@ -91,30 +57,31 @@ func allCRDs(c k8s.Connection, m map[string]resCmd) {
return return
} }
t := time.Now()
var meta resource.TypeMeta
for _, crd := range crds { for _, crd := range crds {
ff := crd.ExtFields() crd.ExtFields(&meta)
gvr := path.Join(ff["group"].(string), ff["version"].(string), ff["kind"].(string)) gvr := k8s.NewGVR(meta.Group, meta.Version, meta.Plural)
var name string gvrs := gvr.String()
if p, ok := ff["plural"].(string); ok { if meta.Plural != "" {
aliases[p] = gvr aliases.Define(meta.Plural, gvrs)
name = p
} }
if s, ok := ff["singular"].(string); ok { if meta.Singular != "" {
aliases[s] = gvr aliases.Define(meta.Singular, gvrs)
name = s
} }
if aa, ok := ff["aliases"].([]interface{}); ok { for _, a := range meta.ShortNames {
for _, a := range aa { aliases.Define(a, gvrs)
aliases[a.(string)] = gvr
}
} }
m[gvr] = resCmd{
gvr: gvr, vv[gvrs] = viewer{
viewFn: listFunc(resource.NewCustomList(c, "", ff["group"].(string), ff["version"].(string), name)), gvr: gvrs,
kind: meta.Kind,
viewFn: listFunc(resource.NewCustomList(c, meta.Namespaced, "", gvrs)),
colorerFn: ui.DefaultColorer, colorerFn: ui.DefaultColorer,
} }
} }
log.Debug().Msgf("Loading CRDS %v", time.Since(t))
} }
func showRBAC(app *appView, ns, resource, selection string) { func showRBAC(app *appView, ns, resource, selection string) {
@ -156,40 +123,44 @@ func showSAPolicy(app *appView, _, _, selection string) {
app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n)) app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
} }
type Aliases map[string]string func load(c k8s.Connection, vv viewers) {
if err := aliases.Load(); err != nil {
var aliases Aliases log.Error().Err(err).Msg("No custom aliases defined in config")
}
func load(c k8s.Connection, viewRes map[string]resCmd) {
// cc := map[string]resCmd{}
aliases = make(Aliases, len(viewRes))
rr, _ := c.DialOrDie().Discovery().ServerPreferredResources() rr, _ := c.DialOrDie().Discovery().ServerPreferredResources()
for _, r := range rr { for _, r := range rr {
log.Debug().Msgf("Group %#v", r.GroupVersion)
for _, res := range r.APIResources { for _, res := range r.APIResources {
log.Debug().Msgf("\tRes %s -- %q:%q -- %+v", res.Name, res.Group, res.Version, res.ShortNames) gvr := k8s.ToGVR(r.GroupVersion, res.Name)
gvr := path.Join(r.GroupVersion, res.Name) cmd, ok := vv[gvr.String()]
// Get singular, plural, shortname and to alias under gvr name if !ok {
if cmd, ok := viewRes[gvr]; !ok { // log.Debug().Msgf(fmt.Sprintf(">> No viewer defined for `%s`", gvr))
log.Error().Msgf(fmt.Sprintf(">> Missed %s", gvr)) continue
} else { }
log.Debug().Msgf("Res %#v", res) cmd.namespaced = res.Namespaced
cmd.namespaced = res.Namespaced cmd.kind = res.Kind
cmd.verbs = res.Verbs cmd.verbs = res.Verbs
cmd.gvr = gvr cmd.gvr = gvr.String()
viewRes[gvr] = cmd vv[gvr.String()] = cmd
aliases[strings.ToLower(res.Kind)] = gvr gvrStr := gvr.String()
aliases[res.Name] = gvr aliases.Define(
aliases[res.SingularName] = gvr strings.ToLower(res.Kind), gvrStr,
for _, s := range res.ShortNames { res.Name, gvrStr,
aliases[s] = gvr )
} if len(res.SingularName) > 0 {
aliases.Define(res.SingularName, gvrStr)
}
for _, s := range res.ShortNames {
aliases.Define(s, gvrStr)
} }
} }
} }
} }
func resourceViews(c k8s.Connection, m map[string]resCmd) { func resourceViews(c k8s.Connection, m viewers) {
defer func(t time.Time) {
log.Debug().Msgf("Loading Views Elapsed %v", time.Since(t))
}(time.Now())
coreRes(m) coreRes(m)
miscRes(m) miscRes(m)
appsRes(m) appsRes(m)
@ -203,171 +174,170 @@ func resourceViews(c k8s.Connection, m map[string]resCmd) {
load(c, m) load(c, m)
} }
func coreRes(m map[string]resCmd) { func coreRes(vv viewers) {
m["v1/nodes"] = resCmd{ vv["v1/nodes"] = viewer{
viewFn: newNodeView, viewFn: newNodeView,
listFn: resource.NewNodeList, listFn: resource.NewNodeList,
colorerFn: nsColorer, colorerFn: nsColorer,
} }
m["v1/namespaces"] = resCmd{ vv["v1/namespaces"] = viewer{
viewFn: newNamespaceView, viewFn: newNamespaceView,
listFn: resource.NewNamespaceList, listFn: resource.NewNamespaceList,
colorerFn: nsColorer, colorerFn: nsColorer,
} }
m["v1/pods"] = resCmd{ vv["v1/pods"] = viewer{
viewFn: newPodView, viewFn: newPodView,
listFn: resource.NewPodList, listFn: resource.NewPodList,
colorerFn: podColorer, colorerFn: podColorer,
} }
m["v1/serviceaccounts"] = resCmd{ vv["v1/serviceaccounts"] = viewer{
viewFn: newResourceView,
listFn: resource.NewServiceAccountList, listFn: resource.NewServiceAccountList,
enterFn: showSAPolicy, enterFn: showSAPolicy,
} }
m["v1/services"] = resCmd{ vv["v1/services"] = viewer{
viewFn: newSvcView, viewFn: newSvcView,
listFn: resource.NewServiceList, listFn: resource.NewServiceList,
} }
m["v1/configmaps"] = resCmd{ vv["v1/configmaps"] = viewer{
listFn: resource.NewConfigMapList, listFn: resource.NewConfigMapList,
} }
m["v1/persistentvolumes"] = resCmd{ vv["v1/persistentvolumes"] = viewer{
listFn: resource.NewPersistentVolumeList, listFn: resource.NewPersistentVolumeList,
colorerFn: pvColorer, colorerFn: pvColorer,
} }
m["v1/persistentvolumeclaims"] = resCmd{ vv["v1/persistentvolumeclaims"] = viewer{
listFn: resource.NewPersistentVolumeClaimList, listFn: resource.NewPersistentVolumeClaimList,
colorerFn: pvcColorer, colorerFn: pvcColorer,
} }
m["v1/secrets"] = resCmd{ vv["v1/secrets"] = viewer{
viewFn: newSecretView, viewFn: newSecretView,
listFn: resource.NewSecretList, listFn: resource.NewSecretList,
} }
m["v1/endpoints"] = resCmd{ vv["v1/endpoints"] = viewer{
listFn: resource.NewEndpointsList, listFn: resource.NewEndpointsList,
} }
m["v1/events"] = resCmd{ vv["v1/events"] = viewer{
listFn: resource.NewEventList, listFn: resource.NewEventList,
colorerFn: evColorer, colorerFn: evColorer,
} }
m["v1/replicationcontrollers"] = resCmd{ vv["v1/replicationcontrollers"] = viewer{
viewFn: newScalableResourceView, viewFn: newScalableResourceView,
listFn: resource.NewReplicationControllerList, listFn: resource.NewReplicationControllerList,
colorerFn: rsColorer, colorerFn: rsColorer,
} }
} }
func miscRes(m map[string]resCmd) { func miscRes(vv viewers) {
m["storage.k8s.io/storageclasses"] = resCmd{ vv["storage.k8s.io/v1/storageclasses"] = viewer{
listFn: resource.NewStorageClassList, listFn: resource.NewStorageClassList,
} }
m["ctx"] = resCmd{ vv["contexts"] = viewer{
gvr: "Contexts", gvr: "contexts",
kind: "Contexts",
viewFn: newContextView, viewFn: newContextView,
listFn: resource.NewContextList, listFn: resource.NewContextList,
colorerFn: ctxColorer, colorerFn: ctxColorer,
} }
m["usr"] = resCmd{ vv["users"] = viewer{
gvr: "users",
viewFn: newSubjectView, viewFn: newSubjectView,
} }
m["grp"] = resCmd{ vv["groups"] = viewer{
gvr: "groups",
viewFn: newSubjectView, viewFn: newSubjectView,
} }
m["pf"] = resCmd{ vv["portforwards"] = viewer{
gvr: "PortForward", gvr: "portforwards",
viewFn: newForwardView, viewFn: newForwardView,
} }
m["be"] = resCmd{ vv["benchmarks"] = viewer{
gvr: "Benchmark", gvr: "benchmarks",
viewFn: newBenchView, viewFn: newBenchView,
} }
m["sd"] = resCmd{ vv["screendumps"] = viewer{
gvr: "ScreenDumps", gvr: "screendumps",
viewFn: newDumpView, viewFn: newDumpView,
} }
} }
func appsRes(m map[string]resCmd) { func appsRes(vv viewers) {
m["apps/v1/deployments"] = resCmd{ vv["apps/v1/deployments"] = viewer{
viewFn: newDeployView, viewFn: newDeployView,
listFn: resource.NewDeploymentList, listFn: resource.NewDeploymentList,
colorerFn: dpColorer, colorerFn: dpColorer,
} }
m["apps/v1/replicasets"] = resCmd{ vv["apps/v1/replicasets"] = viewer{
viewFn: newReplicaSetView, viewFn: newReplicaSetView,
listFn: resource.NewReplicaSetList, listFn: resource.NewReplicaSetList,
colorerFn: rsColorer, colorerFn: rsColorer,
} }
m["apps/v1/statefulsets"] = resCmd{ vv["apps/v1/statefulsets"] = viewer{
viewFn: newStatefulSetView, viewFn: newStatefulSetView,
listFn: resource.NewStatefulSetList, listFn: resource.NewStatefulSetList,
colorerFn: stsColorer, colorerFn: stsColorer,
} }
m["apps/v1/daemonsets"] = resCmd{ vv["apps/v1/daemonsets"] = viewer{
viewFn: newDaemonSetView, viewFn: newDaemonSetView,
listFn: resource.NewDaemonSetList, listFn: resource.NewDaemonSetList,
colorerFn: dpColorer, colorerFn: dpColorer,
} }
} }
func authRes(m map[string]resCmd) { func authRes(vv viewers) {
m["rbac.authorization.k8s.io/v1/clusterroles"] = resCmd{ vv["rbac.authorization.k8s.io/v1/clusterroles"] = viewer{
listFn: resource.NewClusterRoleList, listFn: resource.NewClusterRoleList,
enterFn: showRBAC, enterFn: showRBAC,
} }
m["rbac.authorization.k8s.io/v1/clusterrolebindings"] = resCmd{ vv["rbac.authorization.k8s.io/v1/clusterrolebindings"] = viewer{
listFn: resource.NewClusterRoleBindingList, listFn: resource.NewClusterRoleBindingList,
enterFn: showClusterRole, enterFn: showClusterRole,
} }
m["rbac.authorization.k8s.io/v1/rolebindings"] = resCmd{ vv["rbac.authorization.k8s.io/v1/rolebindings"] = viewer{
listFn: resource.NewRoleBindingList, listFn: resource.NewRoleBindingList,
enterFn: showRole, enterFn: showRole,
} }
m["rbac.authorization.k8s.io/v1/roles"] = resCmd{ vv["rbac.authorization.k8s.io/v1/roles"] = viewer{
listFn: resource.NewRoleList, listFn: resource.NewRoleList,
enterFn: showRBAC, enterFn: showRBAC,
} }
} }
func extRes(m map[string]resCmd) { func extRes(vv viewers) {
m["apiextensions.k8s.io/v1/customresourcedefinitions"] = resCmd{ vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = viewer{
listFn: resource.NewCustomResourceDefinitionList, listFn: resource.NewCustomResourceDefinitionList,
enterFn: showCRD, enterFn: showCRD,
} }
} }
func netRes(m map[string]resCmd) { func netRes(vv viewers) {
m["networking.k8s.io/v1/networkpolicies"] = resCmd{ vv["networking.k8s.io/v1/networkpolicies"] = viewer{
gvr: "apiextensions.k8s.io/NetworkPolicies",
listFn: resource.NewNetworkPolicyList, listFn: resource.NewNetworkPolicyList,
} }
m["networking.k8s.io/v1beta1/ingresses"] = resCmd{ vv["extensions/v1beta1/ingresses"] = viewer{
listFn: resource.NewIngressList, listFn: resource.NewIngressList,
} }
} }
func batchRes(m map[string]resCmd) { func batchRes(vv viewers) {
m["batch/v1/cronjobs"] = resCmd{ vv["batch/v1beta1/cronjobs"] = viewer{
viewFn: newCronJobView, viewFn: newCronJobView,
listFn: resource.NewCronJobList, listFn: resource.NewCronJobList,
} }
m["batch/v1/jobs"] = resCmd{ vv["batch/v1/jobs"] = viewer{
viewFn: newJobView, viewFn: newJobView,
listFn: resource.NewJobList, listFn: resource.NewJobList,
} }
} }
func policyRes(m map[string]resCmd) { func policyRes(vv viewers) {
m["policy/v1beta1/poddisruptionbudgets"] = resCmd{ vv["policy/v1beta1/poddisruptionbudgets"] = viewer{
viewFn: newResourceView,
listFn: resource.NewPDBList, listFn: resource.NewPDBList,
colorerFn: pdbColorer, colorerFn: pdbColorer,
} }
} }
func hpaRes(m map[string]resCmd) { func hpaRes(vv viewers) {
m["autoscaling/v1/horizontalpodautoscalers"] = resCmd{ vv["autoscaling/v1/horizontalpodautoscalers"] = viewer{
listFn: resource.NewHorizontalPodAutoscalerV1List, listFn: resource.NewHorizontalPodAutoscalerV1List,
} }
} }

View File

@ -37,12 +37,14 @@ type (
colorerFn ui.ColorerFunc colorerFn ui.ColorerFunc
decorateFn decorateFn decorateFn decorateFn
envFn envFn envFn envFn
gvr string
} }
) )
func newResourceView(title string, app *appView, list resource.List) resourceViewer { func newResourceView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := resourceView{ v := resourceView{
list: list, list: list,
gvr: gvr,
} }
v.masterDetail = newMasterDetail(title, list.GetNamespace(), app, v.backCmd) v.masterDetail = newMasterDetail(title, list.GetNamespace(), app, v.backCmd)
v.envFn = v.defaultK9sEnv v.envFn = v.defaultK9sEnv
@ -223,7 +225,7 @@ func (v *resourceView) defaultEnter(ns, _, selection string) {
return return
} }
yaml, err := v.list.Resource().Describe(v.masterPage().GetBaseTitle(), selection) yaml, err := v.list.Resource().Describe(v.gvr, selection)
if err != nil { if err != nil {
v.app.Flash().Errf("Describe command failed: %s", err) v.app.Flash().Errf("Describe command failed: %s", err)
return return
@ -316,7 +318,7 @@ func (v *resourceView) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
v.refresh() v.refresh()
v.masterPage().UpdateTitle() v.masterPage().UpdateTitle()
v.masterPage().SelectRow(1, true) v.masterPage().SelectRow(1, true)
v.app.GetCmdBuff().Reset() v.app.CmdBuff().Reset()
v.app.Config.SetActiveNamespace(v.currentNS) v.app.Config.SetActiveNamespace(v.currentNS)
v.app.Config.Save() v.app.Config.Save()

View File

@ -23,8 +23,8 @@ type replicaSetView struct {
*resourceView *resourceView
} }
func newReplicaSetView(t string, app *appView, list resource.List) resourceViewer { func newReplicaSetView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := replicaSetView{newResourceView(t, app, list).(*resourceView)} v := replicaSetView{newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -32,8 +32,8 @@ func newReplicaSetView(t string, app *appView, list resource.List) resourceViewe
} }
func (v *replicaSetView) extraActions(aa ui.KeyActions) { func (v *replicaSetView) extraActions(aa ui.KeyActions) {
aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), true) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), false)
aa[tcell.KeyCtrlB] = ui.NewKeyAction("Rollback", v.rollbackCmd, true) aa[tcell.KeyCtrlB] = ui.NewKeyAction("Rollback", v.rollbackCmd, true)
} }

View File

@ -17,8 +17,8 @@ type (
} }
) )
func newScalableResourceView(title string, app *appView, list resource.List) resourceViewer { func newScalableResourceView(title, gvr string, app *appView, list resource.List) resourceViewer {
return *newScalableResourceViewForParent(newResourceView(title, app, list).(*resourceView)) return *newScalableResourceViewForParent(newResourceView(title, gvr, app, list).(*resourceView))
} }
func newScalableResourceViewForParent(parent *resourceView) *scalableResourceView { func newScalableResourceViewForParent(parent *resourceView) *scalableResourceView {

View File

@ -13,8 +13,8 @@ type secretView struct {
*resourceView *resourceView
} }
func newSecretView(t string, app *appView, list resource.List) resourceViewer { func newSecretView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := secretView{newResourceView(t, app, list).(*resourceView)} v := secretView{newResourceView(title, gvr, app, list).(*resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
return &v return &v

View File

@ -14,8 +14,8 @@ type statefulSetView struct {
scalableResourceView *scalableResourceView scalableResourceView *scalableResourceView
} }
func newStatefulSetView(t string, app *appView, list resource.List) resourceViewer { func newStatefulSetView(title, gvr string, app *appView, list resource.List) resourceViewer {
logResourceView := newLogResourceView(t, app, list) logResourceView := newLogResourceView(title, gvr, app, list)
v := statefulSetView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)} v := statefulSetView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)}
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -26,8 +26,8 @@ func newStatefulSetView(t string, app *appView, list resource.List) resourceView
func (v *statefulSetView) extraActions(aa ui.KeyActions) { func (v *statefulSetView) extraActions(aa ui.KeyActions) {
v.logResourceView.extraActions(aa) v.logResourceView.extraActions(aa)
v.scalableResourceView.extraActions(aa) v.scalableResourceView.extraActions(aa)
aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(1, false), true) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(1, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(2, false), true) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(2, false), false)
} }
func (v *statefulSetView) showPods(app *appView, ns, res, sel string) { func (v *statefulSetView) showPods(app *appView, ns, res, sel string) {

View File

@ -33,7 +33,7 @@ type (
} }
) )
func newSubjectView(ns string, app *appView, list resource.List) resourceViewer { func newSubjectView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := subjectView{} v := subjectView{}
v.tableView = newTableView(app, "Subject") v.tableView = newTableView(app, "Subject")
v.SetActiveNS("*") v.SetActiveNS("*")
@ -95,7 +95,7 @@ func (v *subjectView) bindKeys() {
tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false), tcell.KeyEscape: ui.NewKeyAction("Reset", v.resetCmd, false),
ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false), ui.KeySlash: ui.NewKeyAction("Filter", v.activateCmd, false),
ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false), ui.KeyP: ui.NewKeyAction("Previous", v.app.prevCmd, false),
ui.KeyShiftK: ui.NewKeyAction("Sort Kind", v.SortColCmd(1), true), ui.KeyShiftK: ui.NewKeyAction("Sort Kind", v.SortColCmd(1), false),
}) })
} }
@ -136,8 +136,8 @@ func (v *subjectView) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
func (v *subjectView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *subjectView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().Empty() { if !v.SearchBuff().Empty() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }
@ -149,8 +149,8 @@ func (v *subjectView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cancel() v.cancel()
} }
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Reset() v.SearchBuff().Reset()
return nil return nil
} }
@ -285,8 +285,9 @@ func (v *subjectView) namespacedSubjects() (resource.RowEvents, error) {
} }
func mapCmdSubject(subject string) string { func mapCmdSubject(subject string) string {
log.Debug().Msgf("!!!!!!Subject %q", subject)
switch subject { switch subject {
case "grp": case "groups":
return "Group" return "Group"
case "sas": case "sas":
return "ServiceAccount" return "ServiceAccount"

View File

@ -22,9 +22,9 @@ type svcView struct {
bench *perf.Benchmark bench *perf.Benchmark
} }
func newSvcView(t string, app *appView, list resource.List) resourceViewer { func newSvcView(title, gvr string, app *appView, list resource.List) resourceViewer {
v := svcView{ v := svcView{
resourceView: newResourceView(t, app, list).(*resourceView), resourceView: newResourceView(title, gvr, app, list).(*resourceView),
} }
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.enterFn = v.showPods v.enterFn = v.showPods
@ -47,7 +47,7 @@ func (v *svcView) extraActions(aa ui.KeyActions) {
aa[ui.KeyL] = ui.NewKeyAction("Logs", v.logsCmd, true) aa[ui.KeyL] = ui.NewKeyAction("Logs", v.logsCmd, true)
aa[tcell.KeyCtrlB] = ui.NewKeyAction("Bench", v.benchCmd, true) aa[tcell.KeyCtrlB] = ui.NewKeyAction("Bench", v.benchCmd, true)
aa[tcell.KeyCtrlK] = ui.NewKeyAction("Bench Stop", v.benchStopCmd, true) aa[tcell.KeyCtrlK] = ui.NewKeyAction("Bench Stop", v.benchStopCmd, true)
aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Type", v.sortColCmd(1, false), true) aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Type", v.sortColCmd(1, false), false)
} }
func (v *svcView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { func (v *svcView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
@ -217,7 +217,7 @@ func (v *svcView) showSvcPods(ns string, sel map[string]string, b ui.ActionHandl
list := resource.NewPodList(v.app.Conn(), ns) list := resource.NewPodList(v.app.Conn(), ns)
list.SetLabelSelector(strings.Join(s, ",")) list.SetLabelSelector(strings.Join(s, ","))
pv := newPodView("Pods", v.app, list) pv := newPodView("Pods", "v1/pods", v.app, list)
pv.setColorerFn(podColorer) pv.setColorerFn(podColorer)
pv.setExtraActionsFn(func(aa ui.KeyActions) { pv.setExtraActionsFn(func(aa ui.KeyActions) {
aa[tcell.KeyEsc] = ui.NewKeyAction("Back", b, true) aa[tcell.KeyEsc] = ui.NewKeyAction("Back", b, true)

View File

@ -17,14 +17,23 @@ func newTableView(app *appView, title string) *tableView {
Table: ui.NewTable(title, app.Styles), Table: ui.NewTable(title, app.Styles),
app: app, app: app,
} }
v.Cmd().AddListener(app.Cmd()) v.SearchBuff().AddListener(app.Cmd())
v.Cmd().Reset() v.SearchBuff().AddListener(&v)
v.SearchBuff().Reset()
v.bindKeys() v.bindKeys()
return &v return &v
} }
// BufferChanged indicates the buffer was changed.
func (v *tableView) BufferChanged(s string) {}
// BufferActive indicates the buff activity changed.
func (v *tableView) BufferActive(state bool) {
v.app.BufferActive(state)
}
func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
if path, err := saveTable(v.app.Config.K9s.CurrentCluster, v.GetBaseTitle(), v.GetData()); err != nil { if path, err := saveTable(v.app.Config.K9s.CurrentCluster, v.GetBaseTitle(), v.GetData()); err != nil {
v.app.Flash().Err(err) v.app.Flash().Err(err)
@ -49,18 +58,18 @@ func (v *tableView) bindKeys() {
tcell.KeyBackspace: ui.NewKeyAction("Erase", v.eraseCmd, false), tcell.KeyBackspace: ui.NewKeyAction("Erase", v.eraseCmd, false),
tcell.KeyDelete: ui.NewKeyAction("Erase", v.eraseCmd, false), tcell.KeyDelete: ui.NewKeyAction("Erase", v.eraseCmd, false),
ui.KeyShiftI: ui.NewKeyAction("Invert", v.SortInvertCmd, false), ui.KeyShiftI: ui.NewKeyAction("Invert", v.SortInvertCmd, false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", v.SortColCmd(0), true), ui.KeyShiftN: ui.NewKeyAction("Sort Name", v.SortColCmd(0), false),
ui.KeyShiftA: ui.NewKeyAction("Sort Age", v.SortColCmd(-1), true), ui.KeyShiftA: ui.NewKeyAction("Sort Age", v.SortColCmd(-1), false),
}) })
} }
func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().IsActive() { if !v.SearchBuff().IsActive() {
return evt return evt
} }
v.Cmd().SetActive(false) v.SearchBuff().SetActive(false)
cmd := v.Cmd().String() cmd := v.SearchBuff().String()
if isLabelSelector(cmd) && v.filterFn != nil { if isLabelSelector(cmd) && v.filterFn != nil {
v.filterFn(trimLabelSelector(cmd)) v.filterFn(trimLabelSelector(cmd))
return nil return nil
@ -71,21 +80,21 @@ func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
func (v *tableView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.Cmd().IsActive() { if v.SearchBuff().IsActive() {
v.Cmd().Delete() v.SearchBuff().Delete()
} }
return nil return nil
} }
func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.Cmd().Empty() { if !v.SearchBuff().Empty() {
v.app.Flash().Info("Clearing filter...") v.app.Flash().Info("Clearing filter...")
} }
if isLabelSelector(v.Cmd().String()) { if isLabelSelector(v.SearchBuff().String()) {
v.filterFn("") v.filterFn("")
} }
v.Cmd().Reset() v.SearchBuff().Reset()
v.Refresh() v.Refresh()
return nil return nil
@ -97,11 +106,11 @@ func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
v.app.Flash().Info("Filter mode activated.") v.app.Flash().Info("Filter mode activated.")
if isLabelSelector(v.Cmd().String()) { if isLabelSelector(v.SearchBuff().String()) {
return nil return nil
} }
v.Cmd().Reset() v.SearchBuff().Reset()
v.Cmd().SetActive(true) v.SearchBuff().SetActive(true)
return nil return nil
} }

View File

@ -72,8 +72,8 @@ func TestTableViewFilter(t *testing.T) {
Namespace: "", Namespace: "",
} }
v.Update(data) v.Update(data)
v.Cmd().SetActive(true) v.SearchBuff().SetActive(true)
v.Cmd().Set([]rune("blee")) v.SearchBuff().Set([]rune("blee"))
v.filterCmd(nil) v.filterCmd(nil)
assert.Equal(t, 2, v.GetRowCount()) assert.Equal(t, 2, v.GetRowCount())
v.resetCmd(nil) v.resetCmd(nil)